mirror of https://github.com/open-mmlab/mmocr.git
[Docs] Useful Tools (#1349)
* init useful tools * apply comments Co-authored-by: Tong Gao <gaotongxiao@gmail.com> * update link Co-authored-by: Tong Gao <gaotongxiao@gmail.com> Co-authored-by: liukuikun <liukuikun@sensetime.com>pull/1357/head
parent
19b19cc404
commit
e72edd6dcb
|
@ -1,142 +1,52 @@
|
|||
# Useful Tools
|
||||
|
||||
We provide some useful tools under `mmocr/tools` directory.
|
||||
## Analysis Tools
|
||||
|
||||
## Publish a Model
|
||||
### Dataset Visualization Tool
|
||||
|
||||
Before you upload a model to AWS, you may want to
|
||||
(1) convert the model weights to CPU tensors, (2) delete the optimizer states and
|
||||
(3) compute the hash of the checkpoint file and append the hash id to the filename. These functionalities could be achieved by `tools/publish_model.py`.
|
||||
MMOCR provides a dataset visualization tool `tools/analysis_tools/browse_datasets.py` to help users troubleshoot possible dataset-related problems. You just need to specify the path to the training config and the tool will automatically plots the images transformed by corresponding data pipelines with the GT labels. The following example demonstrates how to use the tool to visualize the training data used by the "DBNet_R50_icdar2015" model.
|
||||
|
||||
```shell
|
||||
python tools/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
|
||||
```Bash
|
||||
# Example: Visualizing the training data used by dbnet_r50dcn_v2_fpnc_1200e_icadr2015
|
||||
python tools/analysis_tools/browse_dataset.py configs/textdet/dbnet/dbnet_r50dcnv2_fpnc_1200e_icdar2015.py
|
||||
```
|
||||
|
||||
For example,
|
||||
The visualization results will be like:
|
||||
|
||||
```shell
|
||||
python tools/publish_model.py work_dirs/psenet/latest.pth psenet_r50_fpnf_sbn_1x_20190801.pth
|
||||
<center class="half">
|
||||
<img src="https://user-images.githubusercontent.com/24622904/187611542-01e9aa94-fc12-4756-964b-a0e472522a3a.jpg" width="250"/><img src="https://user-images.githubusercontent.com/24622904/187611555-3f5ea616-863d-4538-884f-bccbebc2f7e7.jpg" width="250"/><img src="https://user-images.githubusercontent.com/24622904/187611581-88be3970-fbfe-4f62-8cdf-7a8a7786af29.jpg" width="250"/>
|
||||
</center>
|
||||
|
||||
Based on this tool, users can easily verify if the annotation of a custom dataset is correct. Also, you can verify if the data augmentation strategies are running as you expected by modifying `train_pipeline` in the configuration file. The optional parameters of `browse_dataset.py` are as follows.
|
||||
|
||||
| ARGS | Type | Description |
|
||||
| --------------- | ----- | ------------------------------------------------------------------------------------- |
|
||||
| config | str | (required) Path to the config. |
|
||||
| --output-dir | str | If GUI is not available, specifying an output path to save the visualization results. |
|
||||
| --show-interval | float | Interval of visualization (s), defaults to 2. |
|
||||
|
||||
### Offline Evaluation Tool
|
||||
|
||||
For saved prediction results, we provide an offline evaluation script `tools/analysis_tools/offline_eval.py`. The following example demonstrates how to use this tool to evaluate the output of the "PSENet" model offline.
|
||||
|
||||
```Bash
|
||||
# When running the test script for the first time, you can save the output of the model by specifying the --save-preds parameter
|
||||
python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --save-preds
|
||||
# Example: Testing on PSENet
|
||||
python tools/test.py configs/textdet/psenet/psenet_r50_fpnf_600e_icdar2015.py epoch_600.pth --save-preds
|
||||
|
||||
# Then, using the saved outputs for offline evaluation
|
||||
python tools/analysis_tool/offline_eval.py ${CONFIG_FILE} ${PRED_FILE}
|
||||
# Example: Offline evaluation of saved PSENet results
|
||||
python tools/analysis_tools/offline_eval.py configs/textdet/psenet/psenet_r50_fpnf_600e_icdar2015.py work_dirs/psenet_r50_fpnf_600e_icdar2015/epoch_600.pth_predictions.pkl
|
||||
```
|
||||
|
||||
The final output filename will be `psenet_r50_fpnf_sbn_1x_20190801-{hash id}.pth`.
|
||||
`-save-preds` saves the output to `work_dir/CONFIG_NAME/MODEL_NAME_predictions.pkl` by default
|
||||
|
||||
## Convert text recognition dataset to lmdb format
|
||||
In addition, based on this tool, users can also convert predictions obtained from other libraries into MMOCR-supported formats, then use MMOCR's built-in metrics to evaluate them.
|
||||
|
||||
Reading images or labels from files can be slow when data are excessive, e.g. on a scale of millions. Besides, in academia, most of the scene text recognition datasets are stored in lmdb format, including images and labels. To get closer to the mainstream practice and enhance the data storage efficiency, MMOCR now provides `tools/data/utils/lmdb_converter.py` to convert text recognition datasets to lmdb format.
|
||||
|
||||
| Arguments | Type | Description |
|
||||
| ----------------- | ---- | ------------------------------------------------------------------------- |
|
||||
| `label_path` | str | Path to label file. |
|
||||
| `output` | str | Output lmdb path. |
|
||||
| `--img-root` | str | Input imglist path. |
|
||||
| `--label-only` | bool | Only converter label to lmdb |
|
||||
| `--label-format` | str | The format of the label file, either txt or jsonl. |
|
||||
| `--batch-size` | int | Processing batch size, defaults to 1000 |
|
||||
| `--encoding` | str | Bytes coding scheme, defaults to utf8. |
|
||||
| `--lmdb-map-size` | int | Maximum size database may grow to , defaults to 1099511627776 bytes (1TB) |
|
||||
|
||||
### Examples
|
||||
|
||||
Generate a mixed lmdb file with label.txt and images in `imgs/`:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.txt imgs.lmdb -i imgs
|
||||
```
|
||||
|
||||
Generate a mixed lmdb file with label.jsonl and images in `imgs/`:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.json imgs.lmdb -i imgs -f jsonl
|
||||
```
|
||||
|
||||
Generate a label-only lmdb file with label.txt:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.txt label.lmdb --label-only
|
||||
```
|
||||
|
||||
Generate a label-only lmdb file with label.jsonl:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.json label.lmdb --label-only -f jsonl
|
||||
```
|
||||
|
||||
## Convert annotations from Labelme
|
||||
|
||||
[Labelme](https://github.com/wkentaro/labelme) is a popular graphical image annotation tool. You can convert the labels generated by labelme to the MMOCR data format using `tools/data/common/labelme_converter.py`. Both detection and recognition tasks are supported.
|
||||
|
||||
```bash
|
||||
# tasks can be "det" or both "det", "recog"
|
||||
python tools/data/common/labelme_converter.py <json_dir> <image_dir> <out_dir> --tasks <tasks>
|
||||
```
|
||||
|
||||
For example, converting the labelme format annotation in `tests/data/toy_dataset/labelme` to MMOCR detection labels `instances_training.txt` and cropping the image patches for recognition task to `tests/data/toy_dataset/crops` with the labels `train_label.jsonl`:
|
||||
|
||||
```bash
|
||||
python tools/data/common/labelme_converter.py tests/data/toy_dataset/labelme tests/data/toy_dataset/imgs tests/data/toy_dataset --tasks det recog
|
||||
```
|
||||
|
||||
## Log Analysis
|
||||
|
||||
You can use `tools/analyze_logs.py` to plot loss/hmean curves given a training log file. Run `pip install seaborn` first to install the dependency.
|
||||
|
||||

|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}]
|
||||
```
|
||||
|
||||
| Arguments | Type | Description |
|
||||
| ----------- | ---- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `--keys` | str | The metric that you want to plot. Defaults to `loss`. |
|
||||
| `--title` | str | Title of figure. |
|
||||
| `--legend` | str | Legend of each plot. |
|
||||
| `--backend` | str | Backend of the plot. [more info](https://matplotlib.org/stable/users/explain/backends.html) |
|
||||
| `--style` | str | Style of the plot. Defaults to `dark`. [more info](https://seaborn.pydata.org/generated/seaborn.set_style.html) |
|
||||
| `--out` | str | Path of output figure. |
|
||||
|
||||
**Examples:**
|
||||
|
||||
Download the following DBNet and CRNN training logs to run demos.
|
||||
|
||||
```shell
|
||||
wget https://download.openmmlab.com/mmocr/textdet/dbnet/dbnet_r18_fpnc_sbn_1200e_icdar2015_20210329-ba3ab597.log.json -O DBNet_log.json
|
||||
|
||||
wget https://download.openmmlab.com/mmocr/textrecog/crnn/20210326_111035.log.json -O CRNN_log.json
|
||||
```
|
||||
|
||||
Please specify an output path if you are running the codes on systems without a GUI.
|
||||
|
||||
- Plot loss metric.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve DBNet_log.json --keys loss --legend loss
|
||||
```
|
||||
|
||||
- Plot hmean-iou:hmean metric of text detection.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve DBNet_log.json --keys hmean-iou:hmean --legend hmean-iou:hmean
|
||||
```
|
||||
|
||||
- Plot 0_1-N.E.D metric of text recognition.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve CRNN_log.json --keys 0_1-N.E.D --legend 0_1-N.E.D
|
||||
```
|
||||
|
||||
- Compute the average training speed.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py cal_train_time CRNN_log.json --include-outliers
|
||||
```
|
||||
|
||||
The output is expected to be like the following.
|
||||
|
||||
```text
|
||||
-----Analyze train time of CRNN_log.json-----
|
||||
slowest epoch 4, average time is 0.3464
|
||||
fastest epoch 5, average time is 0.2365
|
||||
time std over epochs is 0.0356
|
||||
average iter time: 0.2906 s/iter
|
||||
```
|
||||
| ARGS | Type | Description |
|
||||
| ------------- | ----- | --------------------------------- |
|
||||
| config | str | (required) Path to the config. |
|
||||
| pkl_results | str | (required) The saved predictions. |
|
||||
| --cfg-options | float | Override configs. [Example](<>) |
|
||||
|
|
|
@ -1,142 +1,52 @@
|
|||
# 常用工具
|
||||
|
||||
We provide some useful tools under `mmocr/tools` directory.
|
||||
## 分析工具
|
||||
|
||||
## Publish a Model
|
||||
### 数据集可视化工具
|
||||
|
||||
Before you upload a model to AWS, you may want to
|
||||
(1) convert the model weights to CPU tensors, (2) delete the optimizer states and
|
||||
(3) compute the hash of the checkpoint file and append the hash id to the filename. These functionalities could be achieved by `tools/publish_model.py`.
|
||||
MMOCR 提供了数据集可视化工具 `tools/analysis_tools/browse_datasets.py` 以辅助用户排查可能遇到的数据集相关的问题。用户只需要指定所使用的训练配置文件路径,该工具即可自动将经过数据流水线(data pipeline)处理过的图像及其对应的真实标签绘制出来。例如,以下命令演示了如何使用该工具对 "DBNet_R50_icdar2015" 模型使用的训练数据进行可视化操作:
|
||||
|
||||
```shell
|
||||
python tools/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
|
||||
```Bash
|
||||
# 示例:可视化 dbnet_r50dcn_v2_fpnc_1200e_icadr2015 使用的训练数据
|
||||
python tools/analysis_tools/browse_dataset.py configs/textdet/dbnet/dbnet_r50dcnv2_fpnc_1200e_icdar2015.py
|
||||
```
|
||||
|
||||
For example,
|
||||
效果如下图所示:
|
||||
|
||||
```shell
|
||||
python tools/publish_model.py work_dirs/psenet/latest.pth psenet_r50_fpnf_sbn_1x_20190801.pth
|
||||
<center class="half">
|
||||
<img src="https://user-images.githubusercontent.com/24622904/187611542-01e9aa94-fc12-4756-964b-a0e472522a3a.jpg" width="250"/><img src="https://user-images.githubusercontent.com/24622904/187611555-3f5ea616-863d-4538-884f-bccbebc2f7e7.jpg" width="250"/><img src="https://user-images.githubusercontent.com/24622904/187611581-88be3970-fbfe-4f62-8cdf-7a8a7786af29.jpg" width="250"/>
|
||||
</center>
|
||||
|
||||
基于此工具,用户可以方便地验证自定义数据集的标注格式是否正确;也可以通过修改配置文件中的 `train_pipeline` 来验证不同的数据增强策略组合是否符合自己的预期。`browse_dataset.py` 的可选参数如下:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --------------- | ----- | -------------------------------------------------------------------------------------------------------- |
|
||||
| config | str | (必须)配置文件路径。 |
|
||||
| --output-dir | str | 可视化结果保存路径。对于不存在图形界面的设备,如服务器集群等,用户可以通过指定输出路径来保存可视化结果。 |
|
||||
| --show-interval | float | 可视化图像间隔秒数,默认为 2。 |
|
||||
|
||||
### 离线评测工具
|
||||
|
||||
对于已保存的预测结果,我们提供了离线评测脚本 `tools/analysis_tools/offline_eval.py`。例如,以下代码演示了如何使用该工具对 "PSENet" 模型的输出结果进行离线评估:
|
||||
|
||||
```Bash
|
||||
# 初次运行测试脚本时,用户可以通过指定 --save-preds 参数来保存模型的输出结果
|
||||
python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --save-preds
|
||||
# 示例:对 PSENet 进行测试
|
||||
python tools/test.py configs/textdet/psenet/psenet_r50_fpnf_600e_icdar2015.py epoch_600.pth --save-preds
|
||||
|
||||
# 之后即可使用已保存的输出文件进行离线评估
|
||||
python tools/analysis_tool/offline_eval.py ${CONFIG_FILE} ${PRED_FILE}
|
||||
# 示例:对已保存的 PSENet 结果进行离线评估
|
||||
python tools/analysis_tools/offline_eval.py configs/textdet/psenet/psenet_r50_fpnf_600e_icdar2015.py work_dirs/psenet_r50_fpnf_600e_icdar2015/epoch_600.pth_predictions.pkl
|
||||
```
|
||||
|
||||
The final output filename will be `psenet_r50_fpnf_sbn_1x_20190801-{hash id}.pth`.
|
||||
`--save-preds` 默认将输出结果保存至 `work_dir/CONFIG_NAME/MODEL_NAME_predictions.pkl`
|
||||
|
||||
## Convert text recognition dataset to lmdb format
|
||||
此外,基于此工具,用户也可以将其他算法库获取的预测结果转换成 MMOCR 支持的格式,从而使用 MMOCR 内置的评估指标来对其他算法库的模型进行评测。
|
||||
|
||||
Reading images or labels from files can be slow when data are excessive, e.g. on a scale of millions. Besides, in academia, most of the scene text recognition datasets are stored in lmdb format, including images and labels. To get closer to the mainstream practice and enhance the data storage efficiency, MMOCR now provides `tools/data/utils/lmdb_converter.py` to convert text recognition datasets to lmdb format.
|
||||
|
||||
| Arguments | Type | Description |
|
||||
| ----------------- | ---- | ------------------------------------------------------------------------- |
|
||||
| `label_path` | str | Path to label file. |
|
||||
| `output` | str | Output lmdb path. |
|
||||
| `--img-root` | str | Input imglist path. |
|
||||
| `--label-only` | bool | Only converter label to lmdb |
|
||||
| `--label-format` | str | The format of the label file, either txt or jsonl. |
|
||||
| `--batch-size` | int | Processing batch size, defaults to 1000 |
|
||||
| `--encoding` | str | Bytes coding scheme, defaults to utf8. |
|
||||
| `--lmdb-map-size` | int | Maximum size database may grow to , defaults to 1099511627776 bytes (1TB) |
|
||||
|
||||
### Examples
|
||||
|
||||
Generate a mixed lmdb file with label.txt and images in `imgs/`:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.txt imgs.lmdb -i imgs
|
||||
```
|
||||
|
||||
Generate a mixed lmdb file with label.jsonl and images in `imgs/`:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.json imgs.lmdb -i imgs -f jsonl
|
||||
```
|
||||
|
||||
Generate a label-only lmdb file with label.txt:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.txt label.lmdb --label-only
|
||||
```
|
||||
|
||||
Generate a label-only lmdb file with label.jsonl:
|
||||
|
||||
```bash
|
||||
python tools/data/utils/lmdb_converter.py label.json label.lmdb --label-only -f jsonl
|
||||
```
|
||||
|
||||
## Convert annotations from Labelme
|
||||
|
||||
[Labelme](https://github.com/wkentaro/labelme) is a popular graphical image annotation tool. You can convert the labels generated by labelme to the MMOCR data format using `tools/data/common/labelme_converter.py`. Both detection and recognition tasks are supported.
|
||||
|
||||
```bash
|
||||
# tasks can be "det" or both "det", "recog"
|
||||
python tools/data/common/labelme_converter.py <json_dir> <image_dir> <out_dir> --tasks <tasks>
|
||||
```
|
||||
|
||||
For example, converting the labelme format annotation in `tests/data/toy_dataset/labelme` to MMOCR detection labels `instances_training.txt` and cropping the image patches for recognition task to `tests/data/toy_dataset/crops` with the labels `train_label.jsonl`:
|
||||
|
||||
```bash
|
||||
python tools/data/common/labelme_converter.py tests/data/toy_dataset/labelme tests/data/toy_dataset/imgs tests/data/toy_dataset --tasks det recog
|
||||
```
|
||||
|
||||
## Log Analysis
|
||||
|
||||
You can use `tools/analyze_logs.py` to plot loss/hmean curves given a training log file. Run `pip install seaborn` first to install the dependency.
|
||||
|
||||

|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}]
|
||||
```
|
||||
|
||||
| Arguments | Type | Description |
|
||||
| ----------- | ---- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `--keys` | str | The metric that you want to plot. Defaults to `loss`. |
|
||||
| `--title` | str | Title of figure. |
|
||||
| `--legend` | str | Legend of each plot. |
|
||||
| `--backend` | str | Backend of the plot. [more info](https://matplotlib.org/stable/users/explain/backends.html) |
|
||||
| `--style` | str | Style of the plot. Defaults to `dark`. [more info](https://seaborn.pydata.org/generated/seaborn.set_style.html) |
|
||||
| `--out` | str | Path of output figure. |
|
||||
|
||||
**Examples:**
|
||||
|
||||
Download the following DBNet and CRNN training logs to run demos.
|
||||
|
||||
```shell
|
||||
wget https://download.openmmlab.com/mmocr/textdet/dbnet/dbnet_r18_fpnc_sbn_1200e_icdar2015_20210329-ba3ab597.log.json -O DBNet_log.json
|
||||
|
||||
wget https://download.openmmlab.com/mmocr/textrecog/crnn/20210326_111035.log.json -O CRNN_log.json
|
||||
```
|
||||
|
||||
Please specify an output path if you are running the codes on systems without a GUI.
|
||||
|
||||
- Plot loss metric.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve DBNet_log.json --keys loss --legend loss
|
||||
```
|
||||
|
||||
- Plot hmean-iou:hmean metric of text detection.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve DBNet_log.json --keys hmean-iou:hmean --legend hmean-iou:hmean
|
||||
```
|
||||
|
||||
- Plot 0_1-N.E.D metric of text recognition.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py plot_curve CRNN_log.json --keys 0_1-N.E.D --legend 0_1-N.E.D
|
||||
```
|
||||
|
||||
- Compute the average training speed.
|
||||
|
||||
```shell
|
||||
python tools/analyze_logs.py cal_train_time CRNN_log.json --include-outliers
|
||||
```
|
||||
|
||||
The output is expected to be like the following.
|
||||
|
||||
```text
|
||||
-----Analyze train time of CRNN_log.json-----
|
||||
slowest epoch 4, average time is 0.3464
|
||||
fastest epoch 5, average time is 0.2365
|
||||
time std over epochs is 0.0356
|
||||
average iter time: 0.2906 s/iter
|
||||
```
|
||||
| 参数 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------------------------------------- |
|
||||
| config | str | (必须)配置文件路径。 |
|
||||
| pkl_results | str | (必须)预先保存的预测结果文件。 |
|
||||
| --cfg-options | float | 用于覆写配置文件中的指定参数。[示例](<>) |
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
"""Modified from https://github.com/open-
|
||||
mmlab/mmdetection/blob/master/tools/analysis_tools/analyze_logs.py."""
|
||||
import argparse
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import seaborn as sns
|
||||
|
||||
|
||||
def cal_train_time(log_dicts, args):
|
||||
for i, log_dict in enumerate(log_dicts):
|
||||
print(f'{"-" * 5}Analyze train time of {args.json_logs[i]}{"-" * 5}')
|
||||
all_times = []
|
||||
for epoch in log_dict.keys():
|
||||
if args.include_outliers:
|
||||
all_times.append(log_dict[epoch]['time'])
|
||||
else:
|
||||
all_times.append(log_dict[epoch]['time'][1:])
|
||||
all_times = np.array(all_times)
|
||||
epoch_ave_time = all_times.mean(-1)
|
||||
slowest_epoch = epoch_ave_time.argmax()
|
||||
fastest_epoch = epoch_ave_time.argmin()
|
||||
std_over_epoch = epoch_ave_time.std()
|
||||
print(f'slowest epoch {slowest_epoch + 1}, '
|
||||
f'average time is {epoch_ave_time[slowest_epoch]:.4f}')
|
||||
print(f'fastest epoch {fastest_epoch + 1}, '
|
||||
f'average time is {epoch_ave_time[fastest_epoch]:.4f}')
|
||||
print(f'time std over epochs is {std_over_epoch:.4f}')
|
||||
print(f'average iter time: {np.mean(all_times):.4f} s/iter')
|
||||
print()
|
||||
|
||||
|
||||
def plot_curve(log_dicts, args):
|
||||
if args.backend is not None:
|
||||
plt.switch_backend(args.backend)
|
||||
sns.set_style(args.style)
|
||||
# if legend is None, use {filename}_{key} as legend
|
||||
legend = args.legend
|
||||
if legend is None:
|
||||
legend = []
|
||||
for json_log in args.json_logs:
|
||||
for metric in args.keys:
|
||||
legend.append(f'{json_log}_{metric}')
|
||||
assert len(legend) == (len(args.json_logs) * len(args.keys))
|
||||
metrics = args.keys
|
||||
|
||||
num_metrics = len(metrics)
|
||||
for i, log_dict in enumerate(log_dicts):
|
||||
epochs = list(log_dict.keys())
|
||||
for j, metric in enumerate(metrics):
|
||||
print(f'Plot curve of {args.json_logs[i]}, metric is {metric}')
|
||||
|
||||
epoch_based_metrics = [
|
||||
'hmean', 'word_acc', 'word_acc_ignore_case',
|
||||
'word_acc_ignore_case_symbol', 'char_recall', 'char_precision',
|
||||
'1-N.E.D', 'macro_f1'
|
||||
]
|
||||
if any(metric in m for m in epoch_based_metrics):
|
||||
# determine whether it is a epoch-plotted metric
|
||||
# e.g. hmean-iou:hmean, 0_word_acc
|
||||
xs = []
|
||||
ys = []
|
||||
for epoch in epochs:
|
||||
ys += log_dict[epoch][metric]
|
||||
if 'val' in log_dict[epoch]['mode']:
|
||||
xs.append(epoch)
|
||||
plt.xlabel('epoch')
|
||||
plt.plot(xs, ys, label=legend[i * num_metrics + j], marker='o')
|
||||
else:
|
||||
xs = []
|
||||
ys = []
|
||||
if log_dict[epochs[0]]['mode'][-1] == 'val':
|
||||
num_iters_per_epoch = log_dict[epochs[0]]['iter'][-2]
|
||||
else:
|
||||
num_iters_per_epoch = log_dict[epochs[0]]['iter'][-1]
|
||||
for epoch in epochs:
|
||||
iters = log_dict[epoch]['iter']
|
||||
if log_dict[epoch]['mode'][-1] == 'val':
|
||||
iters = iters[:-1]
|
||||
xs.append(
|
||||
np.array(iters) + (epoch - 1) * num_iters_per_epoch)
|
||||
ys.append(np.array(log_dict[epoch][metric][:len(iters)]))
|
||||
xs = np.concatenate(xs)
|
||||
ys = np.concatenate(ys)
|
||||
plt.xlabel('iter')
|
||||
plt.plot(
|
||||
xs, ys, label=legend[i * num_metrics + j], linewidth=0.5)
|
||||
plt.grid()
|
||||
plt.legend()
|
||||
if args.title is not None:
|
||||
plt.title(args.title)
|
||||
if args.out is None:
|
||||
plt.show()
|
||||
else:
|
||||
print(f'Save curve to: {args.out}')
|
||||
plt.savefig(args.out)
|
||||
plt.cla()
|
||||
|
||||
|
||||
def add_plot_parser(subparsers):
|
||||
parser_plt = subparsers.add_parser(
|
||||
'plot_curve', help='Parser for plotting curves')
|
||||
parser_plt.add_argument(
|
||||
'json_logs',
|
||||
type=str,
|
||||
nargs='+',
|
||||
help='Path of train log in json format')
|
||||
parser_plt.add_argument(
|
||||
'--keys',
|
||||
type=str,
|
||||
nargs='+',
|
||||
default=['loss'],
|
||||
help='The metric that you want to plot')
|
||||
parser_plt.add_argument('--title', type=str, help='Title of figure')
|
||||
parser_plt.add_argument(
|
||||
'--legend',
|
||||
type=str,
|
||||
nargs='+',
|
||||
default=None,
|
||||
help='Legend of each plot')
|
||||
parser_plt.add_argument(
|
||||
'--backend', type=str, default=None, help='Backend of plt')
|
||||
parser_plt.add_argument(
|
||||
'--style', type=str, default='dark', help='Style of plt')
|
||||
parser_plt.add_argument('--out', type=str, default=None)
|
||||
|
||||
|
||||
def add_time_parser(subparsers):
|
||||
parser_time = subparsers.add_parser(
|
||||
'cal_train_time',
|
||||
help='Parser for computing the average time per training iteration')
|
||||
parser_time.add_argument(
|
||||
'json_logs',
|
||||
type=str,
|
||||
nargs='+',
|
||||
help='Path of train log in json format')
|
||||
parser_time.add_argument(
|
||||
'--include-outliers',
|
||||
action='store_true',
|
||||
help='Include the first value of every epoch when computing '
|
||||
'the average time')
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Analyze Json Log')
|
||||
# currently only support plot curve and calculate average train time
|
||||
subparsers = parser.add_subparsers(dest='task', help='task parser')
|
||||
add_plot_parser(subparsers)
|
||||
add_time_parser(subparsers)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def load_json_logs(json_logs):
|
||||
# load and convert json_logs to log_dict, key is epoch, value is a sub dict
|
||||
# keys of sub dict is different metrics, e.g. memory, loss
|
||||
# value of sub dict is a list of corresponding values of all iterations
|
||||
log_dicts = [dict() for _ in json_logs]
|
||||
for json_log, log_dict in zip(json_logs, log_dicts):
|
||||
with open(json_log) as log_file:
|
||||
for line in log_file:
|
||||
log = json.loads(line.strip())
|
||||
# skip lines without `epoch` field
|
||||
if 'epoch' not in log:
|
||||
continue
|
||||
epoch = log.pop('epoch')
|
||||
if epoch not in log_dict:
|
||||
log_dict[epoch] = defaultdict(list)
|
||||
for k, v in log.items():
|
||||
log_dict[epoch][k].append(v)
|
||||
return log_dicts
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
json_logs = args.json_logs
|
||||
for json_log in json_logs:
|
||||
assert json_log.endswith('.json')
|
||||
|
||||
log_dicts = load_json_logs(json_logs)
|
||||
|
||||
eval(args.task)(log_dicts, args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -33,7 +33,10 @@ def parse_args():
|
|||
'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
|
||||
'Note that the quotation marks are necessary and that no white space '
|
||||
'is allowed.')
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args([
|
||||
'configs/textdet/dbnet/dbnet_resnet50-dcnv2_fpnc_1200e_icdar2015.py',
|
||||
'--output-dir', 'tools/analysis_tools/save', '--not-show'
|
||||
])
|
||||
return args
|
||||
|
||||
|
||||
|
@ -54,8 +57,8 @@ def main():
|
|||
progress_bar = mmengine.ProgressBar(len(dataset))
|
||||
for item in dataset:
|
||||
img = item['inputs'].permute(1, 2, 0).numpy()
|
||||
data_sample = item['data_sample'].numpy()
|
||||
img_path = osp.basename(item['data_sample'].img_path)
|
||||
data_sample = item['data_samples'].numpy()
|
||||
img_path = osp.basename(item['data_samples'].img_path)
|
||||
out_file = osp.join(args.output_dir,
|
||||
img_path) if args.output_dir is not None else None
|
||||
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os.path as osp
|
||||
import warnings
|
||||
from functools import partial
|
||||
|
||||
import mmcv
|
||||
import mmengine
|
||||
|
||||
from mmocr.utils import list_to_file
|
||||
from mmocr.utils.img_utils import crop_img, warp_img
|
||||
|
||||
|
||||
def parse_labelme_json(json_file,
|
||||
img_dir,
|
||||
out_dir,
|
||||
tasks,
|
||||
ignore_marker='###',
|
||||
recog_format='jsonl',
|
||||
warp_flag=False):
|
||||
invalid_res = [[], [], []]
|
||||
|
||||
json_obj = mmengine.load(json_file)
|
||||
|
||||
img_file = osp.basename(json_obj['imagePath'])
|
||||
img_full_path = osp.join(img_dir, img_file)
|
||||
|
||||
img_width = json_obj['imageWidth']
|
||||
img_height = json_obj['imageHeight']
|
||||
if 'recog' in tasks:
|
||||
src_img = mmcv.imread(img_full_path)
|
||||
img_basename = osp.splitext(img_file)[0]
|
||||
sub_dir = osp.join(out_dir, 'crops', img_basename)
|
||||
mmengine.mkdir_or_exist(sub_dir)
|
||||
|
||||
det_line_json_list = []
|
||||
recog_crop_line_str_list = []
|
||||
recog_warp_line_str_list = []
|
||||
|
||||
shape_info = json_obj['shapes']
|
||||
idx = 0
|
||||
annos = []
|
||||
for box_info in shape_info:
|
||||
shape = box_info['shape_type']
|
||||
if shape not in ['rectangle', 'polygon']:
|
||||
msg = 'Only \'rectangle\' and \'polygon\' boxes are supported. '
|
||||
msg += f'Boxes with {shape} will be discarded.'
|
||||
warnings.warn(msg)
|
||||
return invalid_res
|
||||
poly = []
|
||||
box_points = box_info['points']
|
||||
for point in box_points:
|
||||
poly.extend([int(x) for x in point])
|
||||
x_list = poly[0::2]
|
||||
y_list = poly[1::2]
|
||||
quad = []
|
||||
if shape == 'rectangle':
|
||||
warp_flag = False
|
||||
quad = [
|
||||
poly[0], poly[1], poly[2], poly[1], poly[2], poly[3], poly[0],
|
||||
poly[3]
|
||||
]
|
||||
else:
|
||||
if len(poly) < 8 or len(poly) % 2 != 0:
|
||||
msg = f'Invalid polygon {poly}. '
|
||||
msg += 'The polygon is expected to have 8 or more than 8 '
|
||||
msg += 'even number of coordinates in MMOCR.'
|
||||
warnings.warn(msg)
|
||||
return invalid_res
|
||||
if len(poly) == 8:
|
||||
quad = poly
|
||||
else:
|
||||
warp_flag = False
|
||||
x_min, x_max, y_min, y_max = min(x_list), max(x_list), min(
|
||||
y_list), max(y_list)
|
||||
quad = [x_min, y_min, x_max, y_min, x_max, y_max, x_min, y_max]
|
||||
text_label = box_info['label']
|
||||
# for textdet
|
||||
anno = {}
|
||||
anno['iscrowd'] = 0 if text_label != ignore_marker else 1
|
||||
anno['category_id'] = 1
|
||||
w = max(x_list) - min(x_list)
|
||||
h = max(y_list) - min(y_list)
|
||||
anno['bbox'] = [min(x_list), min(y_list), w, h]
|
||||
if shape == 'rectangle':
|
||||
anno['segmentation'] = [quad]
|
||||
else:
|
||||
anno['segmentation'] = [poly]
|
||||
anno['text'] = text_label
|
||||
annos.append(anno)
|
||||
# for textrecog
|
||||
if 'recog' in tasks:
|
||||
if text_label == ignore_marker or len(text_label) == 0:
|
||||
continue
|
||||
cropped_img = crop_img(src_img, quad)
|
||||
img_path_cropped_img = osp.join(sub_dir, f'crop_{idx}.jpg')
|
||||
mmcv.imwrite(cropped_img, img_path_cropped_img)
|
||||
if recog_format == 'txt':
|
||||
recog_crop_line_str_list.append(
|
||||
f'{img_path_cropped_img} {text_label}')
|
||||
elif recog_format == 'jsonl':
|
||||
recog_crop_line_str_list.append(
|
||||
json.dumps({
|
||||
'filename': img_path_cropped_img,
|
||||
'text': text_label
|
||||
}))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
if warp_flag:
|
||||
warpped_img = warp_img(src_img, quad)
|
||||
img_path_warpped_img = osp.join(sub_dir, f'warp_{idx}.jpg')
|
||||
mmcv.imwrite(warpped_img, img_path_warpped_img)
|
||||
if recog_format == 'txt':
|
||||
recog_warp_line_str_list.append(
|
||||
f'{img_path_warpped_img} {text_label}')
|
||||
elif recog_format == 'jsonl':
|
||||
recog_warp_line_str_list.append(
|
||||
json.dumps({
|
||||
'filename': img_path_warpped_img,
|
||||
'text': text_label
|
||||
}))
|
||||
idx += 1
|
||||
|
||||
line_json = {
|
||||
'file_name': img_file,
|
||||
'height': img_height,
|
||||
'width': img_width,
|
||||
'annotations': annos
|
||||
}
|
||||
det_line_json_list.append(json.dumps(line_json, ensure_ascii=False))
|
||||
|
||||
return [
|
||||
det_line_json_list, recog_crop_line_str_list, recog_warp_line_str_list
|
||||
]
|
||||
|
||||
|
||||
def process(json_dir,
|
||||
img_dir,
|
||||
out_dir,
|
||||
tasks=['det'],
|
||||
nproc=1,
|
||||
recog_format='jsonl',
|
||||
warp=False):
|
||||
mmengine.mkdir_or_exist(out_dir)
|
||||
|
||||
json_file_list = glob.glob(osp.join(json_dir, '*.json'))
|
||||
|
||||
parse_labelme_json_func = partial(
|
||||
parse_labelme_json,
|
||||
img_dir=img_dir,
|
||||
out_dir=out_dir,
|
||||
tasks=tasks,
|
||||
recog_format=recog_format,
|
||||
warp_flag=warp)
|
||||
|
||||
if nproc <= 1:
|
||||
total_results = mmengine.track_progress(parse_labelme_json_func,
|
||||
json_file_list)
|
||||
else:
|
||||
total_results = mmengine.track_parallel_progress(
|
||||
parse_labelme_json_func,
|
||||
json_file_list,
|
||||
keep_order=True,
|
||||
nproc=nproc)
|
||||
|
||||
total_det_line_json_list = []
|
||||
total_recog_crop_line_str = []
|
||||
total_recog_warp_line_str = []
|
||||
for res in total_results:
|
||||
total_det_line_json_list.extend(res[0])
|
||||
if 'recog' in tasks:
|
||||
total_recog_crop_line_str.extend(res[1])
|
||||
total_recog_warp_line_str.extend(res[2])
|
||||
|
||||
mmengine.mkdir_or_exist(out_dir)
|
||||
det_out_file = osp.join(out_dir, 'instances_training.txt')
|
||||
list_to_file(det_out_file, total_det_line_json_list)
|
||||
|
||||
if 'recog' in tasks:
|
||||
recog_out_file_crop = osp.join(out_dir, f'train_label.{recog_format}')
|
||||
list_to_file(recog_out_file_crop, total_recog_crop_line_str)
|
||||
if warp:
|
||||
recog_out_file_warp = osp.join(out_dir,
|
||||
f'warp_train_label.{recog_format}')
|
||||
list_to_file(recog_out_file_warp, total_recog_warp_line_str)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('json_dir', help='Root dir for labelme json file.')
|
||||
parser.add_argument('image_dir', help='Root dir for image file.')
|
||||
parser.add_argument(
|
||||
'out_dir', help='Dir to save annotations in mmocr format.')
|
||||
parser.add_argument(
|
||||
'--tasks',
|
||||
nargs='+',
|
||||
help='Tasks to be processed, can be only "det" or both: "det recog"')
|
||||
parser.add_argument(
|
||||
'--nproc', type=int, default=1, help='Number of process.')
|
||||
parser.add_argument(
|
||||
'--format',
|
||||
default='jsonl',
|
||||
help='Use jsonl or string to format recognition annotations',
|
||||
choices=['jsonl', 'txt'])
|
||||
parser.add_argument(
|
||||
'--warp',
|
||||
help='Store warpped img for recognition task',
|
||||
action='store_true')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
process(args.json_dir, args.image_dir, args.out_dir, args.tasks,
|
||||
args.nproc, args.format, args.warp)
|
||||
|
||||
print('finish')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue