diff --git a/AdelaiDet/.gitignore b/AdelaiDet/.gitignore
new file mode 100755
index 0000000..4948818
--- /dev/null
+++ b/AdelaiDet/.gitignore
@@ -0,0 +1,57 @@
+# output dir
+output
+instant_test_output
+inference_test_output
+
+
+*.jpg
+*.png
+*.txt
+
+# compilation and distribution
+__pycache__
+_ext
+*.pyc
+*.so
+AdelaiDet.egg-info/
+build/
+dist/
+
+# pytorch/python/numpy formats
+*.pth
+*.pkl
+*.npy
+
+# ipython/jupyter notebooks
+*.ipynb
+**/.ipynb_checkpoints/
+
+# Editor temporaries
+*.swn
+*.swo
+*.swp
+*~
+
+# Pycharm editor settings
+.idea
+.vscode
+.python-version
+
+# project dirs
+/datasets/coco
+/datasets/lvis
+/datasets/pic
+/datasets/ytvos
+/models
+/demo_outputs
+/example_inputs
+/debug
+/weights
+/export
+eval.sh
+
+demo/performance.py
+demo/demo2.py
+train.sh
+benchmark.sh
+script
\ No newline at end of file
diff --git a/AdelaiDet/LICENSE b/AdelaiDet/LICENSE
new file mode 100755
index 0000000..ce9f162
--- /dev/null
+++ b/AdelaiDet/LICENSE
@@ -0,0 +1,26 @@
+AdelaiDet for non-commercial purposes
+(For commercial use, contact chhshen@gmail.com for obtaining a commerical license.)
+
+Copyright (c) 2019 the authors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/AdelaiDet/MODEL_ZOO.md b/AdelaiDet/MODEL_ZOO.md
new file mode 100755
index 0000000..66c542f
--- /dev/null
+++ b/AdelaiDet/MODEL_ZOO.md
@@ -0,0 +1,44 @@
+# AdelaiDet Model Zoo and Baselines
+
+## Introduction
+This file documents a collection of models trained with AdelaiDet in Nov, 2019.
+
+## Models
+
+The inference time is measured on one 1080Ti based on the most recent commit on Detectron2 ([ffff8ac](https://github.com/facebookresearch/detectron2/commit/ffff8acc35ea88ad1cb1806ab0f00b4c1c5dbfd9)).
+
+More models will be released soon. Stay tuned.
+
+### COCO Object Detecton Baselines with FCOS
+
+Name | box AP | download
+--- |:---:|:---:
+[FCOS_R_50_1x](configs/FCOS-Detection/R_50_1x.yaml) | 38.7 | [model](https://cloudstor.aarnet.edu.au/plus/s/glqFc13cCoEyHYy/download)
+
+### COCO Instance Segmentation Baselines with [BlendMask](https://arxiv.org/abs/2001.00309)
+
+Model | Name |inference time (ms/im) | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:|:---:
+Mask R-CNN | [550_R_50_3x](configs/RCNN/550_R_50_FPN_3x.yaml) | 63 | 39.1 | 35.3 |
+BlendMask | [550_R_50_3x](configs/BlendMask/550_R_50_3x.yaml) | 36 | 38.7 | 34.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/R3Qintf7N8UCiIt/download)
+Mask R-CNN | [R_50_1x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml) | 80 | 38.6 | 35.2 |
+BlendMask | [R_50_1x](configs/BlendMask/R_50_1x.yaml) | 73 | 39.9 | 35.8 | [model](https://cloudstor.aarnet.edu.au/plus/s/zoxXPnr6Hw3OJgK/download)
+Mask R-CNN | [R_50_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml) | 80 | 41.0 | 37.2 |
+BlendMask | [R_50_3x](configs/BlendMask/R_50_3x.yaml) | 74 | 42.7 | 37.8 | [model](https://cloudstor.aarnet.edu.au/plus/s/ZnaInHFEKst6mvg/download)
+Mask R-CNN | [R_101_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_FPN_3x.yaml) | 100 | 42.9 | 38.6 |
+BlendMask | [R_101_3x](configs/BlendMask/R_101_3x.yaml) | 94 | 44.8 | 39.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/e4fXrliAcMtyEBy/download)
+BlendMask | [R_101_dcni3_5x](configs/BlendMask/R_101_dcni3_5x.yaml) | 105 | 46.8 | 41.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/vbnKnQtaGlw8TKv/download)
+
+### COCO Panoptic Segmentation Baselines with BlendMask
+Model | Name | PQ | PQTh | PQSt | download
+--- |:---:|:---:|:---:|:---:|:---:
+Panoptic FPN | [R_50_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_3x.yaml) | 41.5 | 48.3 | 31.2 |
+BlendMask | [R_50_3x](configs/BlendMask/Panoptic/R_50_3x.yaml) | 42.5 | 49.5 | 32.0 | [model](https://cloudstor.aarnet.edu.au/plus/s/oDgi0826JOJXCr5/download)
+Panoptic FPN | [R_101_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/panoptic_fpn_R_101_3x.yaml) | 43.0 | 49.7 | 32.9 |
+BlendMask | [R_101_3x](configs/BlendMask/Panoptic/R_101_3x.yaml) | 44.3 | 51.6 | 33.2 | [model](https://cloudstor.aarnet.edu.au/plus/s/u6gZwj06MWDEkYe/download)
+BlendMask | [R_101_dcni3_5x](configs/BlendMask/Panoptic/R_101_dcni3_5x.yaml) | 46.0 | 52.9 | 35.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/Jwp41WEzDdrhWsN/download)
+
+### Person in Context with BlendMask
+Model | Name | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:
+BlendMask | [R_50_1x](configs/BlendMask/Person/R_50_1x.yaml) | 70.6 | 66.7 | [model](https://cloudstor.aarnet.edu.au/plus/s/nvpcKTFA5fsagc0/download)
\ No newline at end of file
diff --git a/AdelaiDet/README.md b/AdelaiDet/README.md
new file mode 100755
index 0000000..5125704
--- /dev/null
+++ b/AdelaiDet/README.md
@@ -0,0 +1,281 @@
+
+
+
+
+# AdelaiDet
+
+AdelaiDet is an open source toolbox for multiple instance-level recognition tasks on top of [Detectron2](https://github.com/facebookresearch/detectron2).
+All instance-level recognition works from our group are open-sourced here.
+
+To date, AdelaiDet implements the following algorithms:
+
+* [FCOS](configs/FCOS-Detection/README.md)
+* [BlendMask](configs/BlendMask/README.md)
+* [MEInst](configs/MEInst-InstanceSegmentation/README.md)
+* [ABCNet](configs/BAText/README.md)
+* [ABCNetv2](configs/BAText#quick-start-abcnetv2)
+* [CondInst](configs/CondInst/README.md)
+* [SOLO](https://arxiv.org/abs/1912.04488) ([mmdet version](https://github.com/WXinlong/SOLO))
+* [SOLOv2](configs/SOLOv2/README.md)
+* [BoxInst](configs/BoxInst/README.md) ([video demo](https://www.youtube.com/watch?v=NuF8NAYf5L8))
+* [DenseCL](configs/DenseCL/README.md)
+* [FCPose](configs/FCPose/README.md)
+* [DirectPose](https://arxiv.org/abs/1911.07451) _to be released_
+
+
+
+## Models
+### COCO Object Detecton Baselines with [FCOS](https://arxiv.org/abs/1904.01355)
+Name | inf. time | box AP | download
+--- |:---:|:---:|:---
+[FCOS_R_50_1x](configs/FCOS-Detection/R_50_1x.yaml) | 16 FPS | 38.7 | [model](https://cloudstor.aarnet.edu.au/plus/s/glqFc13cCoEyHYy/download)
+[FCOS_MS_R_101_2x](configs/FCOS-Detection/MS_R_101_2x.yaml) | 12 FPS | 43.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/M3UOT6JcyHy2QW1/download)
+[FCOS_MS_X_101_32x8d_2x](configs/FCOS-Detection/MS_X_101_32x8d_2x.yaml) | 6.6 FPS | 43.9 | [model](https://cloudstor.aarnet.edu.au/plus/s/R7H00WeWKZG45pP/download)
+[FCOS_MS_X_101_32x8d_dcnv2_2x](configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2.yaml) | 4.6 FPS | 46.6 | [model](https://cloudstor.aarnet.edu.au/plus/s/TDsnYK8OXDTrafF/download)
+[FCOS_RT_MS_DLA_34_4x_shtw](configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers.yaml) | 52 FPS | 39.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/4vc3XwQezyhNvnB/download)
+
+More models can be found in FCOS [README.md](configs/FCOS-Detection/README.md).
+
+### COCO Instance Segmentation Baselines with [BlendMask](https://arxiv.org/abs/2001.00309)
+
+Model | Name |inf. time | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:|:---:
+Mask R-CNN | [R_101_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_FPN_3x.yaml) | 10 FPS | 42.9 | 38.6 |
+BlendMask | [R_101_3x](configs/BlendMask/R_101_3x.yaml) | 11 FPS | 44.8 | 39.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/e4fXrliAcMtyEBy/download)
+BlendMask | [R_101_dcni3_5x](configs/BlendMask/R_101_dcni3_5x.yaml) | 10 FPS | 46.8 | 41.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/vbnKnQtaGlw8TKv/download)
+
+For more models and information, please refer to BlendMask [README.md](configs/BlendMask/README.md).
+
+### COCO Instance Segmentation Baselines with [MEInst](https://arxiv.org/abs/2003.11712)
+
+Name | inf. time | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:
+[MEInst_R_50_3x](https://github.com/aim-uofa/AdelaiDet/configs/MEInst-InstanceSegmentation/MEInst_R_50_3x.yaml) | 12 FPS | 43.6 | 34.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/1ID0DeuI9JsFQoG/download)
+
+For more models and information, please refer to MEInst [README.md](configs/MEInst-InstanceSegmentation/README.md).
+
+### Total_Text results with [ABCNet](configs/BAText/README.md)
+
+Name | inf. time | e2e-hmean | det-hmean | download
+--- |:---------:|:---------:|:---------:|:---:
+[v1-totaltext](configs/BAText/TotalText/attn_R_50.yaml) | 11 FPS | 67.1 | 86.0 | [model](https://cloudstor.aarnet.edu.au/plus/s/t2EFYGxNpKPUqhc/download)
+[v2-totaltext](configs/BAText/TotalText/v2_attn_R_50.yaml) | 7.7 FPS | 71.8 | 87.2 | [model](https://drive.google.com/file/d/1jR5-A-7ITvjdSx3kWVE9bMgh_biMsqcR/view?usp=sharing)
+
+For more models and information, please refer to ABCNet [README.md](configs/BAText/README.md).
+
+### COCO Instance Segmentation Baselines with [CondInst](https://arxiv.org/abs/2003.05664)
+
+Name | inf. time | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:
+[CondInst_MS_R_50_1x](configs/CondInst/MS_R_50_1x.yaml) | 14 FPS | 39.7 | 35.7 | [model](https://cloudstor.aarnet.edu.au/plus/s/Trx1r4tLJja7sLT/download)
+[CondInst_MS_R_50_BiFPN_3x_sem](configs/CondInst/MS_R_50_BiFPN_3x_sem.yaml) | 13 FPS | 44.7 | 39.4 | [model](https://cloudstor.aarnet.edu.au/plus/s/9cAHjZtdaAGnb2Q/download)
+[CondInst_MS_R_101_3x](configs/CondInst/MS_R_101_3x.yaml) | 11 FPS | 43.3 | 38.6 | [model](https://cloudstor.aarnet.edu.au/plus/s/vWLiYm8OnrTSUD2/download)
+[CondInst_MS_R_101_BiFPN_3x_sem](configs/CondInst/MS_R_101_BiFPN_3x_sem.yaml) | 10 FPS | 45.7 | 40.2 | [model](https://cloudstor.aarnet.edu.au/plus/s/2p1ashxl54Su8vv/download)
+
+For more models and information, please refer to CondInst [README.md](configs/CondInst/README.md).
+
+Note that:
+- Inference time for all projects is measured on a NVIDIA 1080Ti with batch size 1.
+- APs are evaluated on COCO2017 val split unless specified.
+
+
+## Installation
+
+First install Detectron2 following the official guide: [INSTALL.md](https://github.com/facebookresearch/detectron2/blob/master/INSTALL.md).
+
+*Please use Detectron2 with commit id [9eb4831](https://github.com/facebookresearch/detectron2/commit/9eb4831f742ae6a13b8edb61d07b619392fb6543) if you have any issues related to Detectron2.*
+
+Then build AdelaiDet with:
+
+```
+git clone https://github.com/aim-uofa/AdelaiDet.git
+cd AdelaiDet
+python setup.py build develop
+```
+
+If you are using docker, a pre-built image can be pulled with:
+
+```
+docker pull tianzhi0549/adet:latest
+```
+
+Some projects may require special setup, please follow their own `README.md` in [configs](configs).
+
+## Quick Start
+
+### Inference with Pre-trained Models
+
+1. Pick a model and its config file, for example, `fcos_R_50_1x.yaml`.
+2. Download the model `wget https://cloudstor.aarnet.edu.au/plus/s/glqFc13cCoEyHYy/download -O fcos_R_50_1x.pth`
+3. Run the demo with
+```
+python demo/demo.py \
+ --config-file configs/FCOS-Detection/R_50_1x.yaml \
+ --input input1.jpg input2.jpg \
+ --opts MODEL.WEIGHTS fcos_R_50_1x.pth
+```
+
+### Train Your Own Models
+
+To train a model with "train_net.py", first
+setup the corresponding datasets following
+[datasets/README.md](https://github.com/facebookresearch/detectron2/blob/master/datasets/README.md),
+then run:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/FCOS-Detection/R_50_1x.yaml \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/fcos_R_50_1x
+```
+To evaluate the model after training, run:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/FCOS-Detection/R_50_1x.yaml \
+ --eval-only \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/fcos_R_50_1x \
+ MODEL.WEIGHTS training_dir/fcos_R_50_1x/model_final.pth
+```
+Note that:
+- The configs are made for 8-GPU training. To train on another number of GPUs, change the `--num-gpus`.
+- If you want to measure the inference time, please change `--num-gpus` to 1.
+- We set `OMP_NUM_THREADS=1` by default, which achieves the best speed on our machines, please change it as needed.
+- This quick start is made for FCOS. If you are using other projects, please check the projects' own `README.md` in [configs](configs).
+
+
+## Acknowledgements
+
+The authors are grateful to
+Nvidia, Huawei Noah's Ark Lab, ByteDance, Adobe who generously donated GPU computing in the past a few years.
+
+## Citing AdelaiDet
+
+If you use this toolbox in your research or wish to refer to the baseline results published here, please use the following BibTeX entries:
+
+```BibTeX
+
+@misc{tian2019adelaidet,
+ author = {Tian, Zhi and Chen, Hao and Wang, Xinlong and Liu, Yuliang and Shen, Chunhua},
+ title = {{AdelaiDet}: A Toolbox for Instance-level Recognition Tasks},
+ howpublished = {\url{https://git.io/adelaidet}},
+ year = {2019}
+}
+```
+and relevant publications:
+```BibTeX
+
+@inproceedings{tian2019fcos,
+ title = {{FCOS}: Fully Convolutional One-Stage Object Detection},
+ author = {Tian, Zhi and Shen, Chunhua and Chen, Hao and He, Tong},
+ booktitle = {Proc. Int. Conf. Computer Vision (ICCV)},
+ year = {2019}
+}
+
+@article{tian2021fcos,
+ title = {{FCOS}: A Simple and Strong Anchor-free Object Detector},
+ author = {Tian, Zhi and Shen, Chunhua and Chen, Hao and He, Tong},
+ journal = {IEEE T. Pattern Analysis and Machine Intelligence (TPAMI)},
+ year = {2021}
+}
+
+@inproceedings{chen2020blendmask,
+ title = {{BlendMask}: Top-Down Meets Bottom-Up for Instance Segmentation},
+ author = {Chen, Hao and Sun, Kunyang and Tian, Zhi and Shen, Chunhua and Huang, Yongming and Yan, Youliang},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2020}
+}
+
+@inproceedings{zhang2020MEInst,
+ title = {Mask Encoding for Single Shot Instance Segmentation},
+ author = {Zhang, Rufeng and Tian, Zhi and Shen, Chunhua and You, Mingyu and Yan, Youliang},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2020}
+}
+
+@inproceedings{liu2020abcnet,
+ title = {{ABCNet}: Real-time Scene Text Spotting with Adaptive {B}ezier-Curve Network},
+ author = {Liu, Yuliang and Chen, Hao and Shen, Chunhua and He, Tong and Jin, Lianwen and Wang, Liangwei},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2020}
+}
+
+@ARTICLE{9525302,
+ author={Liu, Yuliang and Shen, Chunhua and Jin, Lianwen and He, Tong and Chen, Peng and Liu, Chongyu and Chen, Hao},
+ journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
+ title={ABCNet v2: Adaptive Bezier-Curve Network for Real-time End-to-end Text Spotting},
+ year={2021},
+ volume={},
+ number={},
+ pages={1-1},
+ doi={10.1109/TPAMI.2021.3107437}
+}
+
+@inproceedings{wang2020solo,
+ title = {{SOLO}: Segmenting Objects by Locations},
+ author = {Wang, Xinlong and Kong, Tao and Shen, Chunhua and Jiang, Yuning and Li, Lei},
+ booktitle = {Proc. Eur. Conf. Computer Vision (ECCV)},
+ year = {2020}
+}
+
+@inproceedings{wang2020solov2,
+ title = {{SOLOv2}: Dynamic and Fast Instance Segmentation},
+ author = {Wang, Xinlong and Zhang, Rufeng and Kong, Tao and Li, Lei and Shen, Chunhua},
+ booktitle = {Proc. Advances in Neural Information Processing Systems (NeurIPS)},
+ year = {2020}
+}
+
+@article{wang2021solo,
+ title = {{SOLO}: A Simple Framework for Instance Segmentation},
+ author = {Wang, Xinlong and Zhang, Rufeng and Shen, Chunhua and Kong, Tao and Li, Lei},
+ journal = {IEEE T. Pattern Analysis and Machine Intelligence (TPAMI)},
+ year = {2021}
+}
+
+@article{tian2019directpose,
+ title = {{DirectPose}: Direct End-to-End Multi-Person Pose Estimation},
+ author = {Tian, Zhi and Chen, Hao and Shen, Chunhua},
+ journal = {arXiv preprint arXiv:1911.07451},
+ year = {2019}
+}
+
+@inproceedings{tian2020conditional,
+ title = {Conditional Convolutions for Instance Segmentation},
+ author = {Tian, Zhi and Shen, Chunhua and Chen, Hao},
+ booktitle = {Proc. Eur. Conf. Computer Vision (ECCV)},
+ year = {2020}
+}
+
+@article{CondInst2022Tian,
+ title = {Instance and Panoptic Segmentation Using Conditional Convolutions},
+ author = {Tian, Zhi and Zhang, Bowen and Chen, Hao and Shen, Chunhua},
+ journal = {IEEE T. Pattern Analysis and Machine Intelligence (TPAMI)},
+ year = {2022}
+}
+
+@inproceedings{tian2021boxinst,
+ title = {{BoxInst}: High-Performance Instance Segmentation with Box Annotations},
+ author = {Tian, Zhi and Shen, Chunhua and Wang, Xinlong and Chen, Hao},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2021}
+}
+
+@inproceedings{wang2021densecl,
+ title = {Dense Contrastive Learning for Self-Supervised Visual Pre-Training},
+ author = {Wang, Xinlong and Zhang, Rufeng and Shen, Chunhua and Kong, Tao and Li, Lei},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2021}
+}
+
+@inproceedings{Mao2021pose,
+ title = {{FCPose}: Fully Convolutional Multi-Person Pose Estimation With Dynamic Instance-Aware Convolutions},
+ author = {Mao, Weian and Tian, Zhi and Wang, Xinlong and Shen, Chunhua},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2021}
+}
+```
+
+## License
+
+For academic use, this project is licensed under the 2-clause BSD License - see the LICENSE file for details. For commercial use, please contact [Chunhua Shen](mailto:chhshen@gmail.com).
diff --git a/AdelaiDet/adet/__init__.py b/AdelaiDet/adet/__init__.py
new file mode 100755
index 0000000..8969464
--- /dev/null
+++ b/AdelaiDet/adet/__init__.py
@@ -0,0 +1,3 @@
+from adet import modeling
+
+__version__ = "0.1.1"
diff --git a/AdelaiDet/adet/checkpoint/__init__.py b/AdelaiDet/adet/checkpoint/__init__.py
new file mode 100755
index 0000000..77c2142
--- /dev/null
+++ b/AdelaiDet/adet/checkpoint/__init__.py
@@ -0,0 +1,3 @@
+from .adet_checkpoint import AdetCheckpointer
+
+__all__ = ["AdetCheckpointer"]
diff --git a/AdelaiDet/adet/checkpoint/adet_checkpoint.py b/AdelaiDet/adet/checkpoint/adet_checkpoint.py
new file mode 100755
index 0000000..6d03b20
--- /dev/null
+++ b/AdelaiDet/adet/checkpoint/adet_checkpoint.py
@@ -0,0 +1,36 @@
+import pickle, os
+from fvcore.common.file_io import PathManager
+from detectron2.checkpoint import DetectionCheckpointer
+
+
+class AdetCheckpointer(DetectionCheckpointer):
+ """
+ Same as :class:`DetectronCheckpointer`, but is able to convert models
+ in AdelaiDet, such as LPF backbone.
+ """
+ def _load_file(self, filename):
+ if filename.endswith(".pkl"):
+ with PathManager.open(filename, "rb") as f:
+ data = pickle.load(f, encoding="latin1")
+ if "model" in data and "__author__" in data:
+ # file is in Detectron2 model zoo format
+ self.logger.info("Reading a file from '{}'".format(data["__author__"]))
+ return data
+ else:
+ # assume file is from Caffe2 / Detectron1 model zoo
+ if "blobs" in data:
+ # Detection models have "blobs", but ImageNet models don't
+ data = data["blobs"]
+ data = {k: v for k, v in data.items() if not k.endswith("_momentum")}
+ if "weight_order" in data:
+ del data["weight_order"]
+ return {"model": data, "__author__": "Caffe2", "matching_heuristics": True}
+
+ loaded = super()._load_file(filename) # load native pth checkpoint
+ if "model" not in loaded:
+ loaded = {"model": loaded}
+
+ basename = os.path.basename(filename).lower()
+ if "lpf" in basename or "dla" in basename:
+ loaded["matching_heuristics"] = True
+ return loaded
diff --git a/AdelaiDet/adet/config/__init__.py b/AdelaiDet/adet/config/__init__.py
new file mode 100755
index 0000000..be94463
--- /dev/null
+++ b/AdelaiDet/adet/config/__init__.py
@@ -0,0 +1,5 @@
+from .config import get_cfg
+
+__all__ = [
+ "get_cfg",
+]
diff --git a/AdelaiDet/adet/config/config.py b/AdelaiDet/adet/config/config.py
new file mode 100755
index 0000000..3501d32
--- /dev/null
+++ b/AdelaiDet/adet/config/config.py
@@ -0,0 +1,13 @@
+from detectron2.config import CfgNode
+
+
+def get_cfg() -> CfgNode:
+ """
+ Get a copy of the default config.
+
+ Returns:
+ a detectron2 CfgNode instance.
+ """
+ from .defaults import _C
+
+ return _C.clone()
diff --git a/AdelaiDet/adet/config/defaults.py b/AdelaiDet/adet/config/defaults.py
new file mode 100755
index 0000000..d262079
--- /dev/null
+++ b/AdelaiDet/adet/config/defaults.py
@@ -0,0 +1,367 @@
+from detectron2.config.defaults import _C
+from detectron2.config import CfgNode as CN
+
+
+# ---------------------------------------------------------------------------- #
+# Additional Configs
+# ---------------------------------------------------------------------------- #
+_C.MODEL.MOBILENET = False
+_C.MODEL.BACKBONE.ANTI_ALIAS = False
+_C.MODEL.RESNETS.DEFORM_INTERVAL = 1
+_C.INPUT.HFLIP_TRAIN = True
+_C.INPUT.CROP.CROP_INSTANCE = True
+_C.INPUT.IS_ROTATE = False
+
+# ---------------------------------------------------------------------------- #
+# FCOS Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.FCOS = CN()
+
+# This is the number of foreground classes.
+_C.MODEL.FCOS.NUM_CLASSES = 80
+_C.MODEL.FCOS.IN_FEATURES = ["p3", "p4", "p5", "p6", "p7"]
+_C.MODEL.FCOS.FPN_STRIDES = [8, 16, 32, 64, 128]
+_C.MODEL.FCOS.PRIOR_PROB = 0.01
+_C.MODEL.FCOS.INFERENCE_TH_TRAIN = 0.05
+_C.MODEL.FCOS.INFERENCE_TH_TEST = 0.05
+_C.MODEL.FCOS.NMS_TH = 0.6
+_C.MODEL.FCOS.PRE_NMS_TOPK_TRAIN = 1000
+_C.MODEL.FCOS.PRE_NMS_TOPK_TEST = 1000
+_C.MODEL.FCOS.POST_NMS_TOPK_TRAIN = 100
+_C.MODEL.FCOS.POST_NMS_TOPK_TEST = 100
+_C.MODEL.FCOS.TOP_LEVELS = 2
+_C.MODEL.FCOS.NORM = "GN" # Support GN or none
+_C.MODEL.FCOS.USE_SCALE = True
+
+# The options for the quality of box prediction
+# It can be "ctrness" (as described in FCOS paper) or "iou"
+# Using "iou" here generally has ~0.4 better AP on COCO
+# Note that for compatibility, we still use the term "ctrness" in the code
+_C.MODEL.FCOS.BOX_QUALITY = "ctrness"
+
+# Multiply centerness before threshold
+# This will affect the final performance by about 0.05 AP but save some time
+_C.MODEL.FCOS.THRESH_WITH_CTR = False
+
+# Focal loss parameters
+_C.MODEL.FCOS.LOSS_ALPHA = 0.25
+_C.MODEL.FCOS.LOSS_GAMMA = 2.0
+
+# The normalizer of the classification loss
+# The normalizer can be "fg" (normalized by the number of the foreground samples),
+# "moving_fg" (normalized by the MOVING number of the foreground samples),
+# or "all" (normalized by the number of all samples)
+_C.MODEL.FCOS.LOSS_NORMALIZER_CLS = "fg"
+_C.MODEL.FCOS.LOSS_WEIGHT_CLS = 1.0
+
+_C.MODEL.FCOS.SIZES_OF_INTEREST = [64, 128, 256, 512]
+_C.MODEL.FCOS.USE_RELU = True
+_C.MODEL.FCOS.USE_DEFORMABLE = False
+
+# the number of convolutions used in the cls and bbox tower
+_C.MODEL.FCOS.NUM_CLS_CONVS = 4
+_C.MODEL.FCOS.NUM_BOX_CONVS = 4
+_C.MODEL.FCOS.NUM_SHARE_CONVS = 0
+_C.MODEL.FCOS.CENTER_SAMPLE = True
+_C.MODEL.FCOS.POS_RADIUS = 1.5
+_C.MODEL.FCOS.LOC_LOSS_TYPE = 'giou'
+_C.MODEL.FCOS.YIELD_PROPOSAL = False
+_C.MODEL.FCOS.YIELD_BOX_FEATURES = False
+
+# ---------------------------------------------------------------------------- #
+# VoVNet backbone
+# ---------------------------------------------------------------------------- #
+_C.MODEL.VOVNET = CN()
+_C.MODEL.VOVNET.CONV_BODY = "V-39-eSE"
+_C.MODEL.VOVNET.OUT_FEATURES = ["stage2", "stage3", "stage4", "stage5"]
+
+# Options: FrozenBN, GN, "SyncBN", "BN"
+_C.MODEL.VOVNET.NORM = "FrozenBN"
+_C.MODEL.VOVNET.OUT_CHANNELS = 256
+_C.MODEL.VOVNET.BACKBONE_OUT_CHANNELS = 256
+
+# ---------------------------------------------------------------------------- #
+# DLA backbone
+# ---------------------------------------------------------------------------- #
+
+_C.MODEL.DLA = CN()
+_C.MODEL.DLA.CONV_BODY = "DLA34"
+_C.MODEL.DLA.OUT_FEATURES = ["stage2", "stage3", "stage4", "stage5"]
+
+# Options: FrozenBN, GN, "SyncBN", "BN"
+_C.MODEL.DLA.NORM = "FrozenBN"
+
+# ---------------------------------------------------------------------------- #
+# BAText Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.BATEXT = CN()
+_C.MODEL.BATEXT.VOC_SIZE = 96
+_C.MODEL.BATEXT.NUM_CHARS = 25
+_C.MODEL.BATEXT.POOLER_RESOLUTION = (8, 32)
+_C.MODEL.BATEXT.IN_FEATURES = ["p2", "p3", "p4"]
+_C.MODEL.BATEXT.POOLER_SCALES = (0.25, 0.125, 0.0625)
+_C.MODEL.BATEXT.SAMPLING_RATIO = 1
+_C.MODEL.BATEXT.CONV_DIM = 256
+_C.MODEL.BATEXT.NUM_CONV = 2
+_C.MODEL.BATEXT.RECOGNITION_LOSS = "ctc"
+_C.MODEL.BATEXT.RECOGNIZER = "attn"
+_C.MODEL.BATEXT.CANONICAL_SIZE = 96 # largest min_size for level 3 (stride=8)
+_C.MODEL.BATEXT.USE_COORDCONV = False
+_C.MODEL.BATEXT.USE_AET = False
+_C.MODEL.BATEXT.EVAL_TYPE = 3 # 1: G; 2: W; 3: S
+_C.MODEL.BATEXT.CUSTOM_DICT = "" # Path to the class file.
+
+# ---------------------------------------------------------------------------- #
+# BlendMask Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.BLENDMASK = CN()
+_C.MODEL.BLENDMASK.ATTN_SIZE = 14
+_C.MODEL.BLENDMASK.TOP_INTERP = "bilinear"
+_C.MODEL.BLENDMASK.BOTTOM_RESOLUTION = 56
+_C.MODEL.BLENDMASK.POOLER_TYPE = "ROIAlignV2"
+_C.MODEL.BLENDMASK.POOLER_SAMPLING_RATIO = 1
+_C.MODEL.BLENDMASK.POOLER_SCALES = (0.25,)
+_C.MODEL.BLENDMASK.INSTANCE_LOSS_WEIGHT = 1.0
+_C.MODEL.BLENDMASK.VISUALIZE = False
+
+# ---------------------------------------------------------------------------- #
+# Basis Module Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.BASIS_MODULE = CN()
+_C.MODEL.BASIS_MODULE.NAME = "ProtoNet"
+_C.MODEL.BASIS_MODULE.NUM_BASES = 4
+_C.MODEL.BASIS_MODULE.LOSS_ON = False
+_C.MODEL.BASIS_MODULE.ANN_SET = "coco"
+_C.MODEL.BASIS_MODULE.CONVS_DIM = 128
+_C.MODEL.BASIS_MODULE.IN_FEATURES = ["p3", "p4", "p5"]
+_C.MODEL.BASIS_MODULE.NORM = "SyncBN"
+_C.MODEL.BASIS_MODULE.NUM_CONVS = 3
+_C.MODEL.BASIS_MODULE.COMMON_STRIDE = 8
+_C.MODEL.BASIS_MODULE.NUM_CLASSES = 80
+_C.MODEL.BASIS_MODULE.LOSS_WEIGHT = 0.3
+
+# ---------------------------------------------------------------------------- #
+# MEInst Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.MEInst = CN()
+
+# This is the number of foreground classes.
+_C.MODEL.MEInst.NUM_CLASSES = 80
+_C.MODEL.MEInst.IN_FEATURES = ["p3", "p4", "p5", "p6", "p7"]
+_C.MODEL.MEInst.FPN_STRIDES = [8, 16, 32, 64, 128]
+_C.MODEL.MEInst.PRIOR_PROB = 0.01
+_C.MODEL.MEInst.INFERENCE_TH_TRAIN = 0.05
+_C.MODEL.MEInst.INFERENCE_TH_TEST = 0.05
+_C.MODEL.MEInst.NMS_TH = 0.6
+_C.MODEL.MEInst.PRE_NMS_TOPK_TRAIN = 1000
+_C.MODEL.MEInst.PRE_NMS_TOPK_TEST = 1000
+_C.MODEL.MEInst.POST_NMS_TOPK_TRAIN = 100
+_C.MODEL.MEInst.POST_NMS_TOPK_TEST = 100
+_C.MODEL.MEInst.TOP_LEVELS = 2
+_C.MODEL.MEInst.NORM = "GN" # Support GN or none
+_C.MODEL.MEInst.USE_SCALE = True
+
+# Multiply centerness before threshold
+# This will affect the final performance by about 0.05 AP but save some time
+_C.MODEL.MEInst.THRESH_WITH_CTR = False
+
+# Focal loss parameters
+_C.MODEL.MEInst.LOSS_ALPHA = 0.25
+_C.MODEL.MEInst.LOSS_GAMMA = 2.0
+_C.MODEL.MEInst.SIZES_OF_INTEREST = [64, 128, 256, 512]
+_C.MODEL.MEInst.USE_RELU = True
+_C.MODEL.MEInst.USE_DEFORMABLE = False
+_C.MODEL.MEInst.LAST_DEFORMABLE = False
+_C.MODEL.MEInst.TYPE_DEFORMABLE = "DCNv1" # or DCNv2.
+
+# the number of convolutions used in the cls and bbox tower
+_C.MODEL.MEInst.NUM_CLS_CONVS = 4
+_C.MODEL.MEInst.NUM_BOX_CONVS = 4
+_C.MODEL.MEInst.NUM_SHARE_CONVS = 0
+_C.MODEL.MEInst.CENTER_SAMPLE = True
+_C.MODEL.MEInst.POS_RADIUS = 1.5
+_C.MODEL.MEInst.LOC_LOSS_TYPE = 'giou'
+
+# ---------------------------------------------------------------------------- #
+# Mask Encoding
+# ---------------------------------------------------------------------------- #
+# Whether to use mask branch.
+_C.MODEL.MEInst.MASK_ON = True
+# IOU overlap ratios [IOU_THRESHOLD]
+# Overlap threshold for an RoI to be considered background (if < IOU_THRESHOLD)
+# Overlap threshold for an RoI to be considered foreground (if >= IOU_THRESHOLD)
+_C.MODEL.MEInst.IOU_THRESHOLDS = [0.5]
+_C.MODEL.MEInst.IOU_LABELS = [0, 1]
+# Whether to use class_agnostic or class_specific.
+_C.MODEL.MEInst.AGNOSTIC = True
+# Some operations in mask encoding.
+_C.MODEL.MEInst.WHITEN = True
+_C.MODEL.MEInst.SIGMOID = True
+
+# The number of convolutions used in the mask tower.
+_C.MODEL.MEInst.NUM_MASK_CONVS = 4
+
+# The dim of mask before/after mask encoding.
+_C.MODEL.MEInst.DIM_MASK = 60
+_C.MODEL.MEInst.MASK_SIZE = 28
+# The default path for parameters of mask encoding.
+_C.MODEL.MEInst.PATH_COMPONENTS = "datasets/coco/components/" \
+ "coco_2017_train_class_agnosticTrue_whitenTrue_sigmoidTrue_60.npz"
+# An indicator for encoding parameters loading during training.
+_C.MODEL.MEInst.FLAG_PARAMETERS = False
+# The loss for mask branch, can be mse now.
+_C.MODEL.MEInst.MASK_LOSS_TYPE = "mse"
+
+# Whether to use gcn in mask prediction.
+# Large Kernel Matters -- https://arxiv.org/abs/1703.02719
+_C.MODEL.MEInst.USE_GCN_IN_MASK = False
+_C.MODEL.MEInst.GCN_KERNEL_SIZE = 9
+# Whether to compute loss on original mask (binary mask).
+_C.MODEL.MEInst.LOSS_ON_MASK = False
+
+# ---------------------------------------------------------------------------- #
+# CondInst Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.CONDINST = CN()
+
+# the downsampling ratio of the final instance masks to the input image
+_C.MODEL.CONDINST.MASK_OUT_STRIDE = 4
+_C.MODEL.CONDINST.BOTTOM_PIXELS_REMOVED = -1
+
+# if not -1, we only compute the mask loss for MAX_PROPOSALS random proposals PER GPU
+_C.MODEL.CONDINST.MAX_PROPOSALS = -1
+# if not -1, we only compute the mask loss for top `TOPK_PROPOSALS_PER_IM` proposals
+# PER IMAGE in terms of their detection scores
+_C.MODEL.CONDINST.TOPK_PROPOSALS_PER_IM = -1
+
+_C.MODEL.CONDINST.MASK_HEAD = CN()
+_C.MODEL.CONDINST.MASK_HEAD.CHANNELS = 8
+_C.MODEL.CONDINST.MASK_HEAD.NUM_LAYERS = 3
+_C.MODEL.CONDINST.MASK_HEAD.USE_FP16 = False
+_C.MODEL.CONDINST.MASK_HEAD.DISABLE_REL_COORDS = False
+
+_C.MODEL.CONDINST.MASK_BRANCH = CN()
+_C.MODEL.CONDINST.MASK_BRANCH.OUT_CHANNELS = 8
+_C.MODEL.CONDINST.MASK_BRANCH.IN_FEATURES = ["p3", "p4", "p5"]
+_C.MODEL.CONDINST.MASK_BRANCH.CHANNELS = 128
+_C.MODEL.CONDINST.MASK_BRANCH.NORM = "BN"
+_C.MODEL.CONDINST.MASK_BRANCH.NUM_CONVS = 4
+_C.MODEL.CONDINST.MASK_BRANCH.SEMANTIC_LOSS_ON = False
+
+# The options for BoxInst, which can train the instance segmentation model with box annotations only
+# Please refer to the paper https://arxiv.org/abs/2012.02310
+_C.MODEL.BOXINST = CN()
+# Whether to enable BoxInst
+_C.MODEL.BOXINST.ENABLED = False
+_C.MODEL.BOXINST.BOTTOM_PIXELS_REMOVED = 10
+
+_C.MODEL.BOXINST.PAIRWISE = CN()
+_C.MODEL.BOXINST.PAIRWISE.SIZE = 3
+_C.MODEL.BOXINST.PAIRWISE.DILATION = 2
+_C.MODEL.BOXINST.PAIRWISE.WARMUP_ITERS = 10000
+_C.MODEL.BOXINST.PAIRWISE.COLOR_THRESH = 0.3
+
+# ---------------------------------------------------------------------------- #
+# TOP Module Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.TOP_MODULE = CN()
+_C.MODEL.TOP_MODULE.NAME = "conv"
+_C.MODEL.TOP_MODULE.DIM = 16
+
+# ---------------------------------------------------------------------------- #
+# BiFPN options
+# ---------------------------------------------------------------------------- #
+
+_C.MODEL.BiFPN = CN()
+# Names of the input feature maps to be used by BiFPN
+# They must have contiguous power of 2 strides
+# e.g., ["res2", "res3", "res4", "res5"]
+_C.MODEL.BiFPN.IN_FEATURES = ["res2", "res3", "res4", "res5"]
+_C.MODEL.BiFPN.OUT_CHANNELS = 160
+_C.MODEL.BiFPN.NUM_REPEATS = 6
+
+# Options: "" (no norm), "GN"
+_C.MODEL.BiFPN.NORM = ""
+
+# ---------------------------------------------------------------------------- #
+# SOLOv2 Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.SOLOV2 = CN()
+
+# Instance hyper-parameters
+_C.MODEL.SOLOV2.INSTANCE_IN_FEATURES = ["p2", "p3", "p4", "p5", "p6"]
+_C.MODEL.SOLOV2.FPN_INSTANCE_STRIDES = [8, 8, 16, 32, 32]
+_C.MODEL.SOLOV2.FPN_SCALE_RANGES = ((1, 96), (48, 192), (96, 384), (192, 768), (384, 2048))
+_C.MODEL.SOLOV2.SIGMA = 0.2
+# Channel size for the instance head.
+_C.MODEL.SOLOV2.INSTANCE_IN_CHANNELS = 256
+_C.MODEL.SOLOV2.INSTANCE_CHANNELS = 512
+# Convolutions to use in the instance head.
+_C.MODEL.SOLOV2.NUM_INSTANCE_CONVS = 4
+_C.MODEL.SOLOV2.USE_DCN_IN_INSTANCE = False
+_C.MODEL.SOLOV2.TYPE_DCN = 'DCN'
+_C.MODEL.SOLOV2.NUM_GRIDS = [40, 36, 24, 16, 12]
+# Number of foreground classes.
+_C.MODEL.SOLOV2.NUM_CLASSES = 80
+_C.MODEL.SOLOV2.NUM_KERNELS = 256
+_C.MODEL.SOLOV2.NORM = "GN"
+_C.MODEL.SOLOV2.USE_COORD_CONV = True
+_C.MODEL.SOLOV2.PRIOR_PROB = 0.01
+
+# Mask hyper-parameters.
+# Channel size for the mask tower.
+_C.MODEL.SOLOV2.MASK_IN_FEATURES = ["p2", "p3", "p4", "p5"]
+_C.MODEL.SOLOV2.MASK_IN_CHANNELS = 256
+_C.MODEL.SOLOV2.MASK_CHANNELS = 128
+_C.MODEL.SOLOV2.NUM_MASKS = 256
+
+# Test cfg.
+_C.MODEL.SOLOV2.NMS_PRE = 500
+_C.MODEL.SOLOV2.SCORE_THR = 0.1
+_C.MODEL.SOLOV2.UPDATE_THR = 0.05
+_C.MODEL.SOLOV2.MASK_THR = 0.5
+_C.MODEL.SOLOV2.MAX_PER_IMG = 100
+# NMS type: matrix OR mask.
+_C.MODEL.SOLOV2.NMS_TYPE = "matrix"
+# Matrix NMS kernel type: gaussian OR linear.
+_C.MODEL.SOLOV2.NMS_KERNEL = "gaussian"
+_C.MODEL.SOLOV2.NMS_SIGMA = 2
+# Arguments for PointWSSIS, Weakly Semi-Supervised Instance Segmentation with Point Labels
+_C.MODEL.SOLOV2.PROMPT = "none"
+_C.MODEL.SOLOV2.EVAL_PSEUDO_LABEL = False
+
+# Loss cfg.
+_C.MODEL.SOLOV2.LOSS = CN()
+_C.MODEL.SOLOV2.LOSS.FOCAL_USE_SIGMOID = True
+_C.MODEL.SOLOV2.LOSS.FOCAL_ALPHA = 0.25
+_C.MODEL.SOLOV2.LOSS.FOCAL_GAMMA = 2.0
+_C.MODEL.SOLOV2.LOSS.FOCAL_WEIGHT = 1.0
+_C.MODEL.SOLOV2.LOSS.DICE_WEIGHT = 3.0
+
+
+# ---------------------------------------------------------------------------- #
+# FCPose Options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.FCPOSE = CN()
+_C.MODEL.FCPOSE_ON = False
+_C.MODEL.FCPOSE.ATTN_LEN = 2737
+_C.MODEL.FCPOSE.DYNAMIC_CHANNELS = 32
+_C.MODEL.FCPOSE.MAX_PROPOSALS = 70
+_C.MODEL.FCPOSE.PROPOSALS_PER_INST = 70
+_C.MODEL.FCPOSE.LOSS_WEIGHT_KEYPOINT = 2.5
+_C.MODEL.FCPOSE.FOCAL_LOSS_ALPHA = 0.25
+_C.MODEL.FCPOSE.FOCAL_LOSS_GAMMA = 2.0
+_C.MODEL.FCPOSE.GT_HEATMAP_STRIDE = 2
+_C.MODEL.FCPOSE.SIGMA = 1
+_C.MODEL.FCPOSE.HEATMAP_SIGMA = 1.8
+_C.MODEL.FCPOSE.HEAD_HEATMAP_SIGMA = 0.01
+_C.MODEL.FCPOSE.DISTANCE_NORM = 12.0
+_C.MODEL.FCPOSE.LOSS_WEIGHT_DIRECTION = 9.0
+
+_C.MODEL.FCPOSE.BASIS_MODULE = CN()
+_C.MODEL.FCPOSE.BASIS_MODULE.NUM_BASES = 32
+_C.MODEL.FCPOSE.BASIS_MODULE.CONVS_DIM = 128
+_C.MODEL.FCPOSE.BASIS_MODULE.COMMON_STRIDE = 8
+_C.MODEL.FCPOSE.BASIS_MODULE.NUM_CLASSES = 17
+_C.MODEL.FCPOSE.BASIS_MODULE.LOSS_WEIGHT = 0.2
+_C.MODEL.FCPOSE.BASIS_MODULE.BN_TYPE = "SyncBN"
\ No newline at end of file
diff --git a/AdelaiDet/adet/data/__init__.py b/AdelaiDet/adet/data/__init__.py
new file mode 100755
index 0000000..20954f7
--- /dev/null
+++ b/AdelaiDet/adet/data/__init__.py
@@ -0,0 +1,6 @@
+from . import builtin # ensure the builtin datasets are registered
+from .dataset_mapper import DatasetMapperWithBasis
+from .fcpose_dataset_mapper import FCPoseDatasetMapper
+
+
+__all__ = ["DatasetMapperWithBasis"]
diff --git a/AdelaiDet/adet/data/augmentation.py b/AdelaiDet/adet/data/augmentation.py
new file mode 100755
index 0000000..165cab9
--- /dev/null
+++ b/AdelaiDet/adet/data/augmentation.py
@@ -0,0 +1,110 @@
+import random
+
+import numpy as np
+from fvcore.transforms import transform as T
+
+from detectron2.data.transforms import RandomCrop, StandardAugInput
+from detectron2.structures import BoxMode
+
+
+def gen_crop_transform_with_instance(crop_size, image_size, instances, crop_box=True):
+ """
+ Generate a CropTransform so that the cropping region contains
+ the center of the given instance.
+
+ Args:
+ crop_size (tuple): h, w in pixels
+ image_size (tuple): h, w
+ instance (dict): an annotation dict of one instance, in Detectron2's
+ dataset format.
+ """
+ bbox = random.choice(instances)
+ bbox[::2] = np.clip(bbox[::2], 0, image_size[1])
+ bbox[1::2] = np.clip(bbox[1::2], 0, image_size[0])
+ crop_size = np.asarray(crop_size, dtype=np.int32)
+ center_yx = (bbox[1] + bbox[3]) * 0.5, (bbox[0] + bbox[2]) * 0.5
+ assert (
+ image_size[0] >= center_yx[0] and image_size[1] >= center_yx[1]
+ ), "The annotation bounding box is outside of the image!"
+ assert (
+ image_size[0] >= crop_size[0] and image_size[1] >= crop_size[1]
+ ), "Crop size is larger than image size!"
+
+ min_yx = np.maximum(np.floor(center_yx).astype(np.int32) - crop_size, 0)
+ max_yx = np.maximum(np.asarray(image_size, dtype=np.int32) - crop_size, 0)
+ max_yx = np.minimum(max_yx, np.ceil(center_yx).astype(np.int32))
+
+ y0 = np.random.randint(min_yx[0], max_yx[0] + 1)
+ x0 = np.random.randint(min_yx[1], max_yx[1] + 1)
+
+ # if some instance is cropped extend the box
+ if not crop_box:
+ num_modifications = 0
+ modified = True
+
+ # convert crop_size to float
+ crop_size = crop_size.astype(np.float32)
+ while modified:
+ modified, x0, y0, crop_size = adjust_crop(x0, y0, crop_size, instances)
+ num_modifications += 1
+ if num_modifications > 100:
+ raise ValueError(
+ "Cannot finished cropping adjustment within 100 tries (#instances {}).".format(
+ len(instances)
+ )
+ )
+ return T.CropTransform(0, 0, image_size[1], image_size[0])
+
+ return T.CropTransform(*map(int, (x0, y0, crop_size[1], crop_size[0])))
+
+
+def adjust_crop(x0, y0, crop_size, instances, eps=1e-3):
+ modified = False
+
+ x1 = x0 + crop_size[1]
+ y1 = y0 + crop_size[0]
+
+ for bbox in instances:
+
+ if bbox[0] < x0 - eps and bbox[2] > x0 + eps:
+ crop_size[1] += x0 - bbox[0]
+ x0 = bbox[0]
+ modified = True
+
+ if bbox[0] < x1 - eps and bbox[2] > x1 + eps:
+ crop_size[1] += bbox[2] - x1
+ x1 = bbox[2]
+ modified = True
+
+ if bbox[1] < y0 - eps and bbox[3] > y0 + eps:
+ crop_size[0] += y0 - bbox[1]
+ y0 = bbox[1]
+ modified = True
+
+ if bbox[1] < y1 - eps and bbox[3] > y1 + eps:
+ crop_size[0] += bbox[3] - y1
+ y1 = bbox[3]
+ modified = True
+
+ return modified, x0, y0, crop_size
+
+
+class RandomCropWithInstance(RandomCrop):
+ """ Instance-aware cropping.
+ """
+
+ def __init__(self, crop_type, crop_size, crop_instance=True):
+ """
+ Args:
+ crop_instance (bool): if False, extend cropping boxes to avoid cropping instances
+ """
+ super().__init__(crop_type, crop_size)
+ self.crop_instance = crop_instance
+ self.input_args = ("image", "boxes")
+
+ def get_transform(self, img, boxes):
+ image_size = img.shape[:2]
+ crop_size = self.get_crop_size(image_size)
+ return gen_crop_transform_with_instance(
+ crop_size, image_size, boxes, crop_box=self.crop_instance
+ )
diff --git a/AdelaiDet/adet/data/builtin.py b/AdelaiDet/adet/data/builtin.py
new file mode 100755
index 0000000..e7ecc92
--- /dev/null
+++ b/AdelaiDet/adet/data/builtin.py
@@ -0,0 +1,62 @@
+import os
+
+from detectron2.data.datasets.register_coco import register_coco_instances
+from detectron2.data.datasets.builtin_meta import _get_builtin_metadata
+
+from .datasets.text import register_text_instances
+
+# register plane reconstruction
+
+_PREDEFINED_SPLITS_PIC = {
+ "pic_person_train": ("pic/image/train", "pic/annotations/train_person.json"),
+ "pic_person_val": ("pic/image/val", "pic/annotations/val_person.json"),
+}
+
+metadata_pic = {
+ "thing_classes": ["person"]
+}
+
+_PREDEFINED_SPLITS_TEXT = {
+ "totaltext_train": ("totaltext/train_images", "totaltext/train.json"),
+ "totaltext_val": ("totaltext/test_images", "totaltext/test.json"),
+ "ctw1500_word_train": ("CTW1500/ctwtrain_text_image", "CTW1500/annotations/train_ctw1500_maxlen100_v2.json"),
+ "ctw1500_word_test": ("CTW1500/ctwtest_text_image","CTW1500/annotations/test_ctw1500_maxlen100.json"),
+ "syntext1_train": ("syntext1/images", "syntext1/annotations/train.json"),
+ "syntext2_train": ("syntext2/images", "syntext2/annotations/train.json"),
+ "mltbezier_word_train": ("mlt2017/images","mlt2017/annotations/train.json"),
+ "rects_train": ("ReCTS/ReCTS_train_images", "ReCTS/annotations/rects_train.json"),
+ "rects_val": ("ReCTS/ReCTS_val_images", "ReCTS/annotations/rects_val.json"),
+ "rects_test": ("ReCTS/ReCTS_test_images", "ReCTS/annotations/rects_test.json"),
+ "art_train": ("ArT/rename_artimg_train", "ArT/annotations/abcnet_art_train.json"),
+ "lsvt_train": ("LSVT/rename_lsvtimg_train", "LSVT/annotations/abcnet_lsvt_train.json"),
+ "chnsyn_train": ("ChnSyn/syn_130k_images", "ChnSyn/annotations/chn_syntext.json"),
+ "icdar2013_train": ("icdar2013/train_images", "icdar2013/ic13_train.json"),
+ "icdar2015_train": ("icdar2015/train_images", "icdar2015/ic15_train.json"),
+ "icdar2015_test": ("icdar2015/test_images", "icdar2015/ic15_test.json"),
+}
+
+metadata_text = {
+ "thing_classes": ["text"]
+}
+
+
+def register_all_coco(root="datasets"):
+ for key, (image_root, json_file) in _PREDEFINED_SPLITS_PIC.items():
+ # Assume pre-defined datasets live in `./datasets`.
+ register_coco_instances(
+ key,
+ metadata_pic,
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+ for key, (image_root, json_file) in _PREDEFINED_SPLITS_TEXT.items():
+ # Assume pre-defined datasets live in `./datasets`.
+ register_text_instances(
+ key,
+ metadata_text,
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+
+
+register_all_coco()
diff --git a/AdelaiDet/adet/data/dataset_mapper.py b/AdelaiDet/adet/data/dataset_mapper.py
new file mode 100755
index 0000000..31f9fb2
--- /dev/null
+++ b/AdelaiDet/adet/data/dataset_mapper.py
@@ -0,0 +1,237 @@
+import copy
+import logging
+import os.path as osp
+
+import numpy as np
+import torch
+from fvcore.common.file_io import PathManager
+from PIL import Image
+from pycocotools import mask as maskUtils
+
+from detectron2.data import detection_utils as utils
+from detectron2.data import transforms as T
+from detectron2.data.dataset_mapper import DatasetMapper
+from detectron2.data.detection_utils import SizeMismatchError
+from detectron2.structures import BoxMode
+
+from .augmentation import RandomCropWithInstance
+from .detection_utils import (annotations_to_instances, build_augmentation,
+ transform_instance_annotations)
+
+"""
+This file contains the default mapping that's applied to "dataset dicts".
+"""
+
+__all__ = ["DatasetMapperWithBasis"]
+
+logger = logging.getLogger(__name__)
+
+
+def segmToRLE(segm, img_size):
+ h, w = img_size
+ if type(segm) == list:
+ # polygon -- a single object might consist of multiple parts
+ # we merge all parts into one mask rle code
+ rles = maskUtils.frPyObjects(segm, h, w)
+ rle = maskUtils.merge(rles)
+ elif type(segm["counts"]) == list:
+ # uncompressed RLE
+ rle = maskUtils.frPyObjects(segm, h, w)
+ else:
+ # rle
+ rle = segm
+ return rle
+
+
+def segmToMask(segm, img_size):
+ rle = segmToRLE(segm, img_size)
+ m = maskUtils.decode(rle)
+ return m
+
+
+class DatasetMapperWithBasis(DatasetMapper):
+ """
+ This caller enables the default Detectron2 mapper to read an additional basis semantic label
+ """
+
+ def __init__(self, cfg, is_train=True):
+ super().__init__(cfg, is_train)
+
+ # Rebuild augmentations
+ logger.info(
+ "Rebuilding the augmentations. The previous augmentations will be overridden."
+ )
+ self.augmentation = build_augmentation(cfg, is_train)
+ self.cfg = cfg
+
+ if cfg.INPUT.CROP.ENABLED and is_train:
+ self.augmentation.insert(
+ 0,
+ RandomCropWithInstance(
+ cfg.INPUT.CROP.TYPE,
+ cfg.INPUT.CROP.SIZE,
+ cfg.INPUT.CROP.CROP_INSTANCE,
+ ),
+ )
+ logging.getLogger(__name__).info(
+ "Cropping used in training: " + str(self.augmentation[0])
+ )
+ if cfg.INPUT.IS_ROTATE:
+ self.augmentation.insert(
+ 1,
+ T.RandomRotation(angle=[-30,30],sample_style="range")
+ )
+ logging.getLogger(__name__).info(
+ "Rotation used in training: " + str(self.augmentation[1])
+ )
+
+ self.basis_loss_on = cfg.MODEL.BASIS_MODULE.LOSS_ON
+ self.ann_set = cfg.MODEL.BASIS_MODULE.ANN_SET
+ self.boxinst_enabled = cfg.MODEL.BOXINST.ENABLED
+
+ if self.boxinst_enabled:
+ self.use_instance_mask = False
+ self.recompute_boxes = False
+
+ def __call__(self, dataset_dict):
+ """
+ Args:
+ dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
+
+ Returns:
+ dict: a format that builtin models in detectron2 accept
+ """
+ if self.cfg.INPUT.IS_ROTATE:
+ augmentation = self.augmentation[2:]
+ pp = np.random.rand()
+ if pp < 0.5:
+ augmentation = [self.augmentation[0]] + augmentation
+ pp1 = np.random.rand()
+ if pp1 < 0.5:
+ augmentation = [self.augmentation[1]] + augmentation
+
+ dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
+ # USER: Write your own image loading if it's not from a file
+ try:
+ image = utils.read_image(
+ dataset_dict["file_name"], format=self.image_format
+ )
+ except Exception as e:
+ print(dataset_dict["file_name"])
+ print(e)
+ raise e
+ try:
+ utils.check_image_size(dataset_dict, image)
+ except SizeMismatchError as e:
+ expected_wh = (dataset_dict["width"], dataset_dict["height"])
+ image_wh = (image.shape[1], image.shape[0])
+ if (image_wh[1], image_wh[0]) == expected_wh:
+ print("transposing image {}".format(dataset_dict["file_name"]))
+ image = image.transpose(1, 0, 2)
+ else:
+ raise e
+ if image.shape[1]==0 or image.shape[0]==0:
+ print(dataset_dict)
+ raise e
+ # USER: Remove if you don't do semantic/panoptic segmentation.
+ if "sem_seg_file_name" in dataset_dict:
+ sem_seg_gt = utils.read_image(
+ dataset_dict.pop("sem_seg_file_name"), "L"
+ ).squeeze(2)
+ else:
+ sem_seg_gt = None
+
+ boxes = np.asarray(
+ [
+ BoxMode.convert(
+ instance["bbox"], instance["bbox_mode"], BoxMode.XYXY_ABS
+ )
+ for instance in dataset_dict["annotations"]
+ ]
+ )
+ aug_input = T.StandardAugInput(image, boxes=boxes, sem_seg=sem_seg_gt)
+ transforms = aug_input.apply_augmentations(self.augmentation)
+ image, sem_seg_gt = aug_input.image, aug_input.sem_seg
+
+ image_shape = image.shape[:2] # h, w
+ if image.shape[1]==0 or image.shape[0]==0:
+ print(dataset_dict)
+ raise e
+ # Pytorch's dataloader is efficient on torch.Tensor due to shared-memory,
+ # but not efficient on large generic data structures due to the use of pickle & mp.Queue.
+ # Therefore it's important to use torch.Tensor.
+ dataset_dict["image"] = torch.as_tensor(
+ np.ascontiguousarray(image.transpose(2, 0, 1))
+ )
+ if sem_seg_gt is not None:
+ dataset_dict["sem_seg"] = torch.as_tensor(sem_seg_gt.astype("long"))
+
+ # USER: Remove if you don't use pre-computed proposals.
+ # Most users would not need this feature.
+ if self.proposal_topk:
+ utils.transform_proposals(
+ dataset_dict,
+ image_shape,
+ transforms,
+ proposal_topk=self.proposal_topk,
+ min_box_size=self.proposal_min_box_size,
+ )
+
+ if not self.is_train:
+ dataset_dict.pop("annotations", None)
+ dataset_dict.pop("sem_seg_file_name", None)
+ dataset_dict.pop("pano_seg_file_name", None)
+ return dataset_dict
+
+ if "annotations" in dataset_dict:
+ # USER: Modify this if you want to keep them for some reason.
+ for anno in dataset_dict["annotations"]:
+ if not self.use_instance_mask:
+ anno.pop("segmentation", None)
+ if not self.use_keypoint:
+ anno.pop("keypoints", None)
+
+ # USER: Implement additional transformations if you have other types of data
+ annos = [
+ transform_instance_annotations(
+ obj,
+ transforms,
+ image_shape,
+ keypoint_hflip_indices=self.keypoint_hflip_indices,
+ )
+ for obj in dataset_dict.pop("annotations")
+ if obj.get("iscrowd", 0) == 0
+ ]
+ instances = annotations_to_instances(
+ annos, image_shape, mask_format=self.instance_mask_format
+ )
+
+ # After transforms such as cropping are applied, the bounding box may no longer
+ # tightly bound the object. As an example, imagine a triangle object
+ # [(0,0), (2,0), (0,2)] cropped by a box [(1,0),(2,2)] (XYXY format). The tight
+ # bounding box of the cropped triangle should be [(1,0),(2,1)], which is not equal to
+ if self.recompute_boxes:
+ instances.gt_boxes = instances.gt_masks.get_bounding_boxes()
+ dataset_dict["instances"] = utils.filter_empty_instances(instances)
+
+ if self.basis_loss_on and self.is_train:
+ # load basis supervisions
+ if self.ann_set == "coco":
+ basis_sem_path = (
+ dataset_dict["file_name"]
+ .replace("train2017", "thing_train2017")
+ .replace("image/train", "thing_train")
+ )
+ else:
+ basis_sem_path = (
+ dataset_dict["file_name"]
+ .replace("coco", "lvis")
+ .replace("train2017", "thing_train")
+ )
+ # change extension to npz
+ basis_sem_path = osp.splitext(basis_sem_path)[0] + ".npz"
+ basis_sem_gt = np.load(basis_sem_path)["mask"]
+ basis_sem_gt = transforms.apply_segmentation(basis_sem_gt)
+ basis_sem_gt = torch.as_tensor(basis_sem_gt.astype("long"))
+ dataset_dict["basis_sem"] = basis_sem_gt
+ return dataset_dict
diff --git a/AdelaiDet/adet/data/datasets/text.py b/AdelaiDet/adet/data/datasets/text.py
new file mode 100755
index 0000000..8fd8edf
--- /dev/null
+++ b/AdelaiDet/adet/data/datasets/text.py
@@ -0,0 +1,203 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+import contextlib
+import io
+import logging
+import os
+from fvcore.common.timer import Timer
+from fvcore.common.file_io import PathManager
+
+from detectron2.structures import BoxMode
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+
+"""
+This file contains functions to parse COCO-format text annotations into dicts in "Detectron2 format".
+"""
+
+
+logger = logging.getLogger(__name__)
+
+__all__ = ["load_text_json", "register_text_instances"]
+
+
+def register_text_instances(name, metadata, json_file, image_root):
+ """
+ Register a dataset in json annotation format for text detection and recognition.
+
+ Args:
+ name (str): a name that identifies the dataset, e.g. "lvis_v0.5_train".
+ metadata (dict): extra metadata associated with this dataset. It can be an empty dict.
+ json_file (str): path to the json instance annotation file.
+ image_root (str or path-like): directory which contains all the images.
+ """
+ DatasetCatalog.register(name, lambda: load_text_json(json_file, image_root, name))
+ MetadataCatalog.get(name).set(
+ json_file=json_file, image_root=image_root, evaluator_type="text", **metadata
+ )
+
+
+def load_text_json(json_file, image_root, dataset_name=None, extra_annotation_keys=None):
+ """
+ Load a json file with totaltext annotation format.
+ Currently supports text detection and recognition.
+
+ Args:
+ json_file (str): full path to the json file in totaltext annotation format.
+ image_root (str or path-like): the directory where the images in this json file exists.
+ dataset_name (str): the name of the dataset (e.g., coco_2017_train).
+ If provided, this function will also put "thing_classes" into
+ the metadata associated with this dataset.
+ extra_annotation_keys (list[str]): list of per-annotation keys that should also be
+ loaded into the dataset dict (besides "iscrowd", "bbox", "keypoints",
+ "category_id", "segmentation"). The values for these keys will be returned as-is.
+ For example, the densepose annotations are loaded in this way.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard dataset dicts format. (See
+ `Using Custom Datasets `_ )
+
+ Notes:
+ 1. This function does not read the image files.
+ The results do not have the "image" field.
+ """
+ from pycocotools.coco import COCO
+
+ timer = Timer()
+ json_file = PathManager.get_local_path(json_file)
+ with contextlib.redirect_stdout(io.StringIO()):
+ coco_api = COCO(json_file)
+ if timer.seconds() > 1:
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
+
+ id_map = None
+ if dataset_name is not None:
+ meta = MetadataCatalog.get(dataset_name)
+ cat_ids = sorted(coco_api.getCatIds())
+ cats = coco_api.loadCats(cat_ids)
+ # The categories in a custom json file may not be sorted.
+ thing_classes = [c["name"] for c in sorted(cats, key=lambda x: x["id"])]
+ meta.thing_classes = thing_classes
+
+ # In COCO, certain category ids are artificially removed,
+ # and by convention they are always ignored.
+ # We deal with COCO's id issue and translate
+ # the category ids to contiguous ids in [0, 80).
+
+ # It works by looking at the "categories" field in the json, therefore
+ # if users' own json also have incontiguous ids, we'll
+ # apply this mapping as well but print a warning.
+ if not (min(cat_ids) == 1 and max(cat_ids) == len(cat_ids)):
+ if "coco" not in dataset_name:
+ logger.warning(
+ """
+Category ids in annotations are not in [1, #categories]! We'll apply a mapping for you.
+"""
+ )
+ id_map = {v: i for i, v in enumerate(cat_ids)}
+ meta.thing_dataset_id_to_contiguous_id = id_map
+
+ # sort indices for reproducible results
+ img_ids = sorted(coco_api.imgs.keys())
+ # imgs is a list of dicts, each looks something like:
+ # {'license': 4,
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
+ # 'height': 427,
+ # 'width': 640,
+ # 'date_captured': '2013-11-17 05:57:24',
+ # 'id': 1268}
+ imgs = coco_api.loadImgs(img_ids)
+ # anns is a list[list[dict]], where each dict is an annotation
+ # record for an object. The inner list enumerates the objects in an image
+ # and the outer list enumerates over images. Example of anns[0]:
+ # [{'segmentation': [[192.81,
+ # 247.09,
+ # ...
+ # 219.03,
+ # 249.06]],
+ # 'area': 1035.749,
+ # 'rec': [84, 72, ... 96],
+ # 'bezier_pts': [169.0, 425.0, ..., ]
+ # 'iscrowd': 0,
+ # 'image_id': 1268,
+ # 'bbox': [192.81, 224.8, 74.73, 33.43],
+ # 'category_id': 16,
+ # 'id': 42986},
+ # ...]
+ anns = [coco_api.imgToAnns[img_id] for img_id in img_ids]
+
+ if "minival" not in json_file:
+ # The popular valminusminival & minival annotations for COCO2014 contain this bug.
+ # However the ratio of buggy annotations there is tiny and does not affect accuracy.
+ # Therefore we explicitly white-list them.
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
+ json_file
+ )
+
+ imgs_anns = list(zip(imgs, anns))
+
+ logger.info("Loaded {} images in COCO format from {}".format(len(imgs_anns), json_file))
+
+ dataset_dicts = []
+
+ ann_keys = ["iscrowd", "bbox", "rec", "category_id"] + (extra_annotation_keys or [])
+
+ num_instances_without_valid_segmentation = 0
+
+ for (img_dict, anno_dict_list) in imgs_anns:
+ record = {}
+ record["file_name"] = os.path.join(image_root, img_dict["file_name"])
+ record["height"] = img_dict["height"]
+ record["width"] = img_dict["width"]
+ image_id = record["image_id"] = img_dict["id"]
+
+ objs = []
+ for anno in anno_dict_list:
+ # Check that the image_id in this annotation is the same as
+ # the image_id we're looking at.
+ # This fails only when the data parsing logic or the annotation file is buggy.
+
+ # The original COCO valminusminival2014 & minival2014 annotation files
+ # actually contains bugs that, together with certain ways of using COCO API,
+ # can trigger this assertion.
+ assert anno["image_id"] == image_id
+
+ assert anno.get("ignore", 0) == 0, '"ignore" in COCO json file is not supported.'
+
+ obj = {key: anno[key] for key in ann_keys if key in anno}
+
+ segm = anno.get("segmentation", None)
+ if segm: # either list[list[float]] or dict(RLE)
+ if not isinstance(segm, dict):
+ # filter out invalid polygons (< 3 points)
+ segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
+ if len(segm) == 0:
+ num_instances_without_valid_segmentation += 1
+ continue # ignore this instance
+ obj["segmentation"] = segm
+
+ bezierpts = anno.get("bezier_pts", None)
+ # Bezier Points are the control points for BezierAlign Text recognition (BAText)
+ if bezierpts: # list[float]
+ obj["beziers"] = bezierpts
+
+ text = anno.get("rec", None)
+ if text:
+ obj["text"] = text
+
+ obj["bbox_mode"] = BoxMode.XYWH_ABS
+ if id_map:
+ obj["category_id"] = id_map[obj["category_id"]]
+ objs.append(obj)
+ record["annotations"] = objs
+ dataset_dicts.append(record)
+
+ if num_instances_without_valid_segmentation > 0:
+ logger.warning(
+ "Filtered out {} instances without valid segmentation. "
+ "There might be issues in your dataset generation process.".format(
+ num_instances_without_valid_segmentation
+ )
+ )
+ return dataset_dicts
\ No newline at end of file
diff --git a/AdelaiDet/adet/data/detection_utils.py b/AdelaiDet/adet/data/detection_utils.py
new file mode 100755
index 0000000..6841b8a
--- /dev/null
+++ b/AdelaiDet/adet/data/detection_utils.py
@@ -0,0 +1,219 @@
+import logging
+
+import numpy as np
+import torch
+
+from detectron2.data import transforms as T
+from detectron2.data.detection_utils import \
+ annotations_to_instances as d2_anno_to_inst
+from detectron2.data.detection_utils import \
+ transform_instance_annotations as d2_transform_inst_anno
+
+import math
+
+def transform_instance_annotations(
+ annotation, transforms, image_size, *, keypoint_hflip_indices=None
+):
+
+ annotation = d2_transform_inst_anno(
+ annotation,
+ transforms,
+ image_size,
+ keypoint_hflip_indices=keypoint_hflip_indices,
+ )
+
+ if "beziers" in annotation:
+ beziers = transform_beziers_annotations(annotation["beziers"], transforms)
+ annotation["beziers"] = beziers
+ return annotation
+
+
+def transform_beziers_annotations(beziers, transforms):
+ """
+ Transform keypoint annotations of an image.
+
+ Args:
+ beziers (list[float]): Nx16 float in Detectron2 Dataset format.
+ transforms (TransformList):
+ """
+ # (N*2,) -> (N, 2)
+ beziers = np.asarray(beziers, dtype="float64").reshape(-1, 2)
+ beziers = transforms.apply_coords(beziers).reshape(-1)
+
+ # This assumes that HorizFlipTransform is the only one that does flip
+ do_hflip = (
+ sum(isinstance(t, T.HFlipTransform) for t in transforms.transforms) % 2 == 1
+ )
+ if do_hflip:
+ raise ValueError("Flipping text data is not supported (also disencouraged).")
+
+ return beziers
+
+
+def annotations_to_instances(annos, image_size, mask_format="polygon"):
+ instance = d2_anno_to_inst(annos, image_size, mask_format)
+
+ if not annos:
+ return instance
+
+ # add attributes
+ if "beziers" in annos[0]:
+ beziers = [obj.get("beziers", []) for obj in annos]
+ instance.beziers = torch.as_tensor(beziers, dtype=torch.float32)
+
+ if "rec" in annos[0]:
+ text = [obj.get("rec", []) for obj in annos]
+ instance.text = torch.as_tensor(text, dtype=torch.int32)
+
+ return instance
+
+
+def build_augmentation(cfg, is_train):
+ """
+ With option to don't use hflip
+
+ Returns:
+ list[Augmentation]
+ """
+ if is_train:
+ min_size = cfg.INPUT.MIN_SIZE_TRAIN
+ max_size = cfg.INPUT.MAX_SIZE_TRAIN
+ sample_style = cfg.INPUT.MIN_SIZE_TRAIN_SAMPLING
+ else:
+ min_size = cfg.INPUT.MIN_SIZE_TEST
+ max_size = cfg.INPUT.MAX_SIZE_TEST
+ sample_style = "choice"
+ if sample_style == "range":
+ assert (
+ len(min_size) == 2
+ ), "more than 2 ({}) min_size(s) are provided for ranges".format(len(min_size))
+
+ logger = logging.getLogger(__name__)
+
+ augmentation = []
+ augmentation.append(T.ResizeShortestEdge(min_size, max_size, sample_style))
+ if is_train:
+ if cfg.INPUT.HFLIP_TRAIN:
+ augmentation.append(T.RandomFlip())
+ logger.info("Augmentations used in training: " + str(augmentation))
+ return augmentation
+
+
+build_transform_gen = build_augmentation
+"""
+Alias for backward-compatibility.
+"""
+
+
+
+class HeatmapGenerator():
+ def __init__(self, num_joints, sigma, head_sigma):
+ self.num_joints = num_joints
+ self.sigma = sigma
+ self.head_sigma = head_sigma
+
+ self.p3_sigma = sigma / 2
+
+ size = 2*np.round(3 * sigma) + 3
+ x = np.arange(0, size, 1, float)
+ y = x[:, np.newaxis]
+ x0, y0 = (size - 1) /2, (size - 1) /2
+ self.g = np.exp(- ((x - x0) ** 2 + (y - y0) ** 2) / (2 * sigma ** 2))
+
+ size = 2*np.round(3 * self.p3_sigma) + 3
+ x = np.arange(0, size, 1, float)
+ y = x[:, np.newaxis]
+ x0, y0 = (size - 1) /2, (size - 1) /2
+ self.p3_g = np.exp(- ((x - x0) ** 2 + (y - y0) ** 2) / (2 * self.p3_sigma ** 2))
+
+ size = 2*np.round(3 * head_sigma) + 3
+ x = np.arange(0, size, 1, float)
+ y = x[:, np.newaxis]
+ x0, y0 = (size - 1) /2, (size - 1) /2
+ self.head_g = np.exp(- ((x - x0) ** 2 + (y - y0) ** 2) / (2 * head_sigma ** 2))
+
+ def __call__(self, gt_instance, gt_heatmap_stride):
+ heatmap_size = gt_instance.image_size
+ heatmap_size = [math.ceil(heatmap_size[0]/ 32)*(32/gt_heatmap_stride),
+ math.ceil(heatmap_size[1]/ 32)*(32/gt_heatmap_stride)]
+
+ h,w = heatmap_size
+ h,w = int(h),int(w)
+ joints = gt_instance.gt_keypoints.tensor.numpy().copy()
+ joints[:,:,[0,1]] = joints[:,:,[0,1]] / gt_heatmap_stride
+ sigma = self.sigma
+ head_sigma = self.head_sigma
+ p3_sigma = self.p3_sigma
+
+ output_list = []
+ head_output_list = []
+ for p in joints:
+ hms = np.zeros((self.num_joints, h, w),dtype=np.float32)
+ head_hms = np.zeros((self.num_joints, h, w),dtype=np.float32)
+ for idx, pt in enumerate(p):
+ if pt[2] > 0:
+ x, y = int(pt[0]), int(pt[1])
+ if x < 0 or y < 0 or \
+ x >= w or y >= h:
+ continue
+
+ ul = int(np.round(x - 3 * sigma - 1)), int(np.round(y - 3 * sigma - 1))
+ br = int(np.round(x + 3 * sigma + 2)), int(np.round(y + 3 * sigma + 2))
+
+ c, d = max(0, -ul[0]), min(br[0], w) - ul[0]
+ a, b = max(0, -ul[1]), min(br[1], h) - ul[1]
+
+ cc, dd = max(0, ul[0]), min(br[0], w)
+ aa, bb = max(0, ul[1]), min(br[1], h)
+ hms[idx, aa:bb, cc:dd] = np.maximum(
+ hms[idx, aa:bb, cc:dd], self.g[a:b, c:d])
+
+ ul = int(np.round(x - 3 * head_sigma - 1)), int(np.round(y - 3 * head_sigma - 1))
+ br = int(np.round(x + 3 * head_sigma + 2)), int(np.round(y + 3 * head_sigma + 2))
+
+ c, d = max(0, -ul[0]), min(br[0], w) - ul[0]
+ a, b = max(0, -ul[1]), min(br[1], h) - ul[1]
+
+ cc, dd = max(0, ul[0]), min(br[0], w)
+ aa, bb = max(0, ul[1]), min(br[1], h)
+ head_hms[idx, aa:bb, cc:dd] = np.maximum(
+ head_hms[idx, aa:bb, cc:dd], self.head_g[a:b, c:d])
+
+ hms = torch.from_numpy(hms)
+ head_hms = torch.from_numpy(head_hms)
+ output_list.append(hms)
+ head_output_list.append(head_hms)
+
+ h,w = h//4, w//4
+ p3_output_list = []
+ joints = gt_instance.gt_keypoints.tensor.numpy().copy()
+ joints[:,:,[0,1]] = joints[:,:,[0,1]] / 8
+ for p in joints:
+ p3_hms = np.zeros((self.num_joints, h, w),dtype=np.float32)
+ for idx, pt in enumerate(p):
+ if pt[2] > 0:
+ x, y = int(pt[0]), int(pt[1])
+ if x < 0 or y < 0 or \
+ x >= w or y >= h:
+ continue
+
+ ul = int(np.round(x - 3 * p3_sigma - 1)), int(np.round(y - 3 * p3_sigma - 1))
+ br = int(np.round(x + 3 * p3_sigma + 2)), int(np.round(y + 3 * p3_sigma + 2))
+
+ c, d = max(0, -ul[0]), min(br[0], w) - ul[0]
+ a, b = max(0, -ul[1]), min(br[1], h) - ul[1]
+
+ cc, dd = max(0, ul[0]), min(br[0], w)
+ aa, bb = max(0, ul[1]), min(br[1], h)
+ p3_hms[idx, aa:bb, cc:dd] = np.maximum(
+ p3_hms[idx, aa:bb, cc:dd], self.p3_g[a:b, c:d])
+
+ p3_hms = torch.from_numpy(p3_hms)
+ p3_output_list.append(p3_hms)
+ output_list = torch.stack(output_list,dim=0)
+ p3_output_list = torch.stack(p3_output_list,dim=0)
+ head_output_list = torch.stack(head_output_list,dim=0)
+ gt_instance.keypoint_heatmap = output_list
+ gt_instance.head_heatmap = head_output_list
+ gt_instance.p3_output_list = p3_output_list
+ return gt_instance
\ No newline at end of file
diff --git a/AdelaiDet/adet/data/fcpose_dataset_mapper.py b/AdelaiDet/adet/data/fcpose_dataset_mapper.py
new file mode 100755
index 0000000..4735e45
--- /dev/null
+++ b/AdelaiDet/adet/data/fcpose_dataset_mapper.py
@@ -0,0 +1,62 @@
+import copy
+import logging
+import os.path as osp
+
+import numpy as np
+import torch
+from fvcore.common.file_io import PathManager
+from PIL import Image
+from pycocotools import mask as maskUtils
+
+from detectron2.data import detection_utils as utils
+from detectron2.data import transforms as T
+from detectron2.data.dataset_mapper import DatasetMapper
+from detectron2.data.detection_utils import SizeMismatchError
+from detectron2.structures import BoxMode
+
+from .augmentation import RandomCropWithInstance
+from .detection_utils import (annotations_to_instances, build_augmentation,
+ transform_instance_annotations)
+
+from adet.data.detection_utils import HeatmapGenerator
+from adet.data.dataset_mapper import DatasetMapperWithBasis
+"""
+This file contains the default mapping that's applied to "dataset dicts".
+"""
+
+__all__ = ["DatasetMapperWithBasis"]
+
+logger = logging.getLogger(__name__)
+
+class FCPoseDatasetMapper(DatasetMapperWithBasis):
+ """
+ This caller enables the default Detectron2 mapper to read an additional basis semantic label
+ """
+
+ def __init__(self, cfg, is_train=True):
+ super().__init__(cfg, is_train)
+
+ self.fcpose_on = cfg.MODEL.FCPOSE_ON
+ if self.fcpose_on:
+ self.gt_heatmap_stride = cfg.MODEL.FCPOSE.GT_HEATMAP_STRIDE
+ self.sigma = cfg.MODEL.FCPOSE.HEATMAP_SIGMA
+ self.head_sigma = cfg.MODEL.FCPOSE.HEAD_HEATMAP_SIGMA
+ self.HeatmapGenerator = HeatmapGenerator(17, self.sigma, self.head_sigma)
+
+ def __call__(self, dataset_dict):
+ """
+ Args:
+ dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
+
+ Returns:
+ dict: a format that builtin models in detectron2 accept
+ """
+ for i in range(100):
+ dataset_dict_temp = copy.deepcopy(dataset_dict)
+ dataset_dict_temp = super().__call__(dataset_dict_temp)
+ if len(dataset_dict_temp["instances"]) != 0:
+ if self.is_train:
+ dataset_dict_temp['instances'] = self.HeatmapGenerator(dataset_dict_temp['instances'],
+ self.gt_heatmap_stride)
+ return dataset_dict_temp
+ raise
diff --git a/AdelaiDet/adet/evaluation/__init__.py b/AdelaiDet/adet/evaluation/__init__.py
new file mode 100755
index 0000000..b114e91
--- /dev/null
+++ b/AdelaiDet/adet/evaluation/__init__.py
@@ -0,0 +1,5 @@
+from .text_evaluation_all import TextEvaluator
+from .text_eval_script import text_eval_main
+from .text_eval_script_ic15 import text_eval_main_ic15
+from . import rrc_evaluation_funcs
+from . import rrc_evaluation_funcs_ic15
\ No newline at end of file
diff --git a/AdelaiDet/adet/evaluation/rrc_evaluation_funcs.py b/AdelaiDet/adet/evaluation/rrc_evaluation_funcs.py
new file mode 100755
index 0000000..f383710
--- /dev/null
+++ b/AdelaiDet/adet/evaluation/rrc_evaluation_funcs.py
@@ -0,0 +1,482 @@
+#!/usr/bin/env python2
+#encoding: UTF-8
+import json
+import sys;sys.path.append('./')
+import zipfile
+import re
+import sys
+import os
+import codecs
+import importlib
+from io import StringIO
+
+from shapely.geometry import *
+
+def print_help():
+ sys.stdout.write('Usage: python %s.py -g= -s= [-o= -p=]' %sys.argv[0])
+ sys.exit(2)
+
+
+def load_zip_file_keys(file,fileNameRegExp=''):
+ """
+ Returns an array with the entries of the ZIP file that match with the regular expression.
+ The key's are the names or the file or the capturing group definied in the fileNameRegExp
+ """
+ try:
+ archive=zipfile.ZipFile(file, mode='r', allowZip64=True)
+ except :
+ raise Exception('Error loading the ZIP archive.')
+
+ pairs = []
+
+ for name in archive.namelist():
+ addFile = True
+ keyName = name
+ if fileNameRegExp!="":
+ m = re.match(fileNameRegExp,name)
+ if m == None:
+ addFile = False
+ else:
+ if len(m.groups())>0:
+ keyName = m.group(1)
+
+ if addFile:
+ pairs.append( keyName )
+
+ return pairs
+
+
+def load_zip_file(file,fileNameRegExp='',allEntries=False):
+ """
+ Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file.
+ The key's are the names or the file or the capturing group definied in the fileNameRegExp
+ allEntries validates that all entries in the ZIP file pass the fileNameRegExp
+ """
+ try:
+ archive=zipfile.ZipFile(file, mode='r', allowZip64=True)
+ except :
+ raise Exception('Error loading the ZIP archive')
+
+ pairs = []
+ for name in archive.namelist():
+ addFile = True
+ keyName = name
+ if fileNameRegExp!="":
+ m = re.match(fileNameRegExp,name)
+ if m == None:
+ addFile = False
+ else:
+ if len(m.groups())>0:
+ keyName = m.group(1)
+
+ if addFile:
+ pairs.append( [ keyName , archive.read(name)] )
+ else:
+ if allEntries:
+ raise Exception('ZIP entry not valid: %s' %name)
+
+ return dict(pairs)
+
+def decode_utf8(raw):
+ """
+ Returns a Unicode object on success, or None on failure
+ """
+ try:
+ raw = codecs.decode(raw,'utf-8', 'replace')
+ #extracts BOM if exists
+ raw = raw.encode('utf8')
+ if raw.startswith(codecs.BOM_UTF8):
+ raw = raw.replace(codecs.BOM_UTF8, '', 1)
+ return raw.decode('utf-8')
+ except:
+ return None
+
+def validate_lines_in_file_gt(fileName,file_contents,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
+ """
+ This function validates that all lines of the file calling the Line validation function for each line
+ """
+ utf8File = decode_utf8(file_contents)
+ if (utf8File is None) :
+ raise Exception("The file %s is not UTF-8" %fileName)
+
+ lines = utf8File.split( "\r\n" if CRLF else "\n" )
+ for line in lines:
+ line = line.replace("\r","").replace("\n","")
+ if(line != ""):
+ try:
+ validate_tl_line_gt(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
+ except Exception as e:
+ raise Exception(("Line in sample not valid. Sample: %s Line: %s Error: %s" %(fileName,line,str(e))).encode('utf-8', 'replace'))
+
+def validate_lines_in_file(fileName,file_contents,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
+ """
+ This function validates that all lines of the file calling the Line validation function for each line
+ """
+ utf8File = decode_utf8(file_contents)
+ if (utf8File is None) :
+ raise Exception("The file %s is not UTF-8" %fileName)
+
+ lines = utf8File.split( "\r\n" if CRLF else "\n" )
+ for line in lines:
+ line = line.replace("\r","").replace("\n","")
+ if(line != ""):
+ try:
+ validate_tl_line(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
+ except Exception as e:
+ raise Exception(("Line in sample not valid. Sample: %s Line: %s Error: %s" %(fileName,line,str(e))).encode('utf-8', 'replace'))
+
+def validate_tl_line_gt(line,LTRB=True,withTranscription=True,withConfidence=True,imWidth=0,imHeight=0):
+ """
+ Validate the format of the line. If the line is not valid an exception will be raised.
+ If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
+ Posible values are:
+ LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
+ LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
+ """
+ get_tl_line_values_gt(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
+
+def validate_tl_line(line,LTRB=True,withTranscription=True,withConfidence=True,imWidth=0,imHeight=0):
+ """
+ Validate the format of the line. If the line is not valid an exception will be raised.
+ If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
+ Posible values are:
+ LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
+ LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
+ """
+ get_tl_line_values(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
+
+def get_tl_line_values_gt(line,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
+ """
+ Validate the format of the line. If the line is not valid an exception will be raised.
+ If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
+ Posible values are:
+ LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
+ LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
+ Returns values from a textline. Points , [Confidences], [Transcriptions]
+ """
+ confidence = 0.0
+ transcription = "";
+ points = []
+
+ if LTRB:
+ # do not use
+ raise Exception('Not implemented.')
+
+ else:
+ # if withTranscription and withConfidence:
+ # cors = line.split(',')
+ # assert(len(cors)%2 -2 == 0), 'num cors should be even.'
+ # try:
+ # points = [ float(ic) for ic in cors[:-2]]
+ # except Exception as e:
+ # raise(e)
+ # elif withConfidence:
+ # cors = line.split(',')
+ # assert(len(cors)%2 -1 == 0), 'num cors should be even.'
+ # try:
+ # points = [ float(ic) for ic in cors[:-1]]
+ # except Exception as e:
+ # raise(e)
+ # elif withTranscription:
+ # cors = line.split(',')
+ # assert(len(cors)%2 -1 == 0), 'num cors should be even.'
+ # try:
+ # points = [ float(ic) for ic in cors[:-1]]
+ # except Exception as e:
+ # raise(e)
+ # else:
+ # cors = line.split(',')
+ # assert(len(cors)%2 == 0), 'num cors should be even.'
+ # try:
+ # points = [ float(ic) for ic in cors[:]]
+ # except Exception as e:
+ # raise(e)
+
+ if withTranscription and withConfidence:
+ raise('not implemented')
+ elif withConfidence:
+ raise('not implemented')
+ elif withTranscription:
+ ptr = line.strip().split(',####')
+ cors = ptr[0].split(',')
+ recs = ptr[1].strip()
+ assert(len(cors)%2 == 0), 'num cors should be even.'
+ try:
+ points = [ float(ic) for ic in cors[:]]
+ except Exception as e:
+ raise(e)
+ else:
+ raise('not implemented')
+
+ validate_clockwise_points(points)
+
+ if (imWidth>0 and imHeight>0):
+ for ip in range(0, len(points), 2):
+ validate_point_inside_bounds(points[ip],points[ip+1],imWidth,imHeight);
+
+
+ if withConfidence:
+ try:
+ confidence = 1.0
+ except ValueError:
+ raise Exception("Confidence value must be a float")
+
+ if withTranscription:
+ # posTranscription = numPoints + (2 if withConfidence else 1)
+ # transcription = cors[-1].strip()
+ transcription = recs
+ m2 = re.match(r'^\s*\"(.*)\"\s*$',transcription)
+ if m2 != None : #Transcription with double quotes, we extract the value and replace escaped characters
+ transcription = m2.group(1).replace("\\\\", "\\").replace("\\\"", "\"")
+
+ return points,confidence,transcription
+
+def get_tl_line_values(line,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
+ """
+ Validate the format of the line. If the line is not valid an exception will be raised.
+ If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
+ Posible values are:
+ LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
+ LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
+ Returns values from a textline. Points , [Confidences], [Transcriptions]
+ """
+ confidence = 0.0
+ transcription = "";
+ points = []
+
+ if LTRB:
+ # do not use
+ raise Exception('Not implemented.')
+
+ else:
+ if withTranscription and withConfidence:
+ raise('not implemented')
+ elif withConfidence:
+ raise('not implemented')
+ elif withTranscription:
+ ptr = line.strip().split(',####')
+ cors = ptr[0].split(',')
+ recs = ptr[1].strip()
+ assert(len(cors)%2 == 0), 'num cors should be even.'
+ try:
+ points = [ float(ic) for ic in cors[:]]
+ except Exception as e:
+ raise(e)
+ else:
+ raise('not implemented')
+
+ # print('det clock wise')
+ validate_clockwise_points(points)
+
+ if (imWidth>0 and imHeight>0):
+ for ip in range(0, len(points), 2):
+ validate_point_inside_bounds(points[ip],points[ip+1],imWidth,imHeight);
+
+
+ if withConfidence:
+ try:
+ confidence = 1.0
+ except ValueError:
+ raise Exception("Confidence value must be a float")
+
+ if withTranscription:
+ # posTranscription = numPoints + (2 if withConfidence else 1)
+ transcription = recs
+ m2 = re.match(r'^\s*\"(.*)\"\s*$',transcription)
+ if m2 != None : #Transcription with double quotes, we extract the value and replace escaped characters
+ transcription = m2.group(1).replace("\\\\", "\\").replace("\\\"", "\"")
+
+ return points,confidence,transcription
+
+
+def validate_point_inside_bounds(x,y,imWidth,imHeight):
+ if(x<0 or x>imWidth):
+ raise Exception("X value (%s) not valid. Image dimensions: (%s,%s)" %(xmin,imWidth,imHeight))
+ if(y<0 or y>imHeight):
+ raise Exception("Y value (%s) not valid. Image dimensions: (%s,%s) Sample: %s Line:%s" %(ymin,imWidth,imHeight))
+
+def validate_clockwise_points(points):
+ """
+ Validates that the points that the 4 points that dlimite a polygon are in clockwise order.
+ """
+
+ # if len(points) != 8:
+ # raise Exception("Points list not valid." + str(len(points)))
+
+ # point = [
+ # [int(points[0]) , int(points[1])],
+ # [int(points[2]) , int(points[3])],
+ # [int(points[4]) , int(points[5])],
+ # [int(points[6]) , int(points[7])]
+ # ]
+ # edge = [
+ # ( point[1][0] - point[0][0])*( point[1][1] + point[0][1]),
+ # ( point[2][0] - point[1][0])*( point[2][1] + point[1][1]),
+ # ( point[3][0] - point[2][0])*( point[3][1] + point[2][1]),
+ # ( point[0][0] - point[3][0])*( point[0][1] + point[3][1])
+ # ]
+
+ # summatory = edge[0] + edge[1] + edge[2] + edge[3];
+ # if summatory>0:
+ # raise Exception("Points are not clockwise. The coordinates of bounding quadrilaterals have to be given in clockwise order. Regarding the correct interpretation of 'clockwise' remember that the image coordinate system used is the standard one, with the image origin at the upper left, the X axis extending to the right and Y axis extending downwards.")
+ pts = [(points[j], points[j+1]) for j in range(0,len(points),2)]
+ try:
+ pdet = Polygon(pts)
+ except:
+ assert(0), ('not a valid polygon', pts)
+ # The polygon should be valid.
+ if not pdet.is_valid:
+ assert(0), ('polygon has intersection sides', pts)
+ pRing = LinearRing(pts)
+ if pRing.is_ccw:
+ assert(0), ("Points are not clockwise. The coordinates of bounding quadrilaterals have to be given in clockwise order. Regarding the correct interpretation of 'clockwise' remember that the image coordinate system used is the standard one, with the image origin at the upper left, the X axis extending to the right and Y axis extending downwards.")
+
+def get_tl_line_values_from_file_contents(content,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0,sort_by_confidences=True):
+ """
+ Returns all points, confindences and transcriptions of a file in lists. Valid line formats:
+ xmin,ymin,xmax,ymax,[confidence],[transcription]
+ x1,y1,x2,y2,x3,y3,x4,y4,[confidence],[transcription]
+ """
+ pointsList = []
+ transcriptionsList = []
+ confidencesList = []
+
+ lines = content.split( "\r\n" if CRLF else "\n" )
+ for line in lines:
+ line = line.replace("\r","").replace("\n","")
+ if(line != "") :
+ points, confidence, transcription = get_tl_line_values_gt(line,LTRB,withTranscription,withConfidence,imWidth,imHeight);
+ pointsList.append(points)
+ transcriptionsList.append(transcription)
+ confidencesList.append(confidence)
+
+ if withConfidence and len(confidencesList)>0 and sort_by_confidences:
+ import numpy as np
+ sorted_ind = np.argsort(-np.array(confidencesList))
+ confidencesList = [confidencesList[i] for i in sorted_ind]
+ pointsList = [pointsList[i] for i in sorted_ind]
+ transcriptionsList = [transcriptionsList[i] for i in sorted_ind]
+
+ return pointsList,confidencesList,transcriptionsList
+
+def get_tl_line_values_from_file_contents_det(content,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0,sort_by_confidences=True):
+ """
+ Returns all points, confindences and transcriptions of a file in lists. Valid line formats:
+ xmin,ymin,xmax,ymax,[confidence],[transcription]
+ x1,y1,x2,y2,x3,y3,x4,y4,[confidence],[transcription]
+ """
+ pointsList = []
+ transcriptionsList = []
+ confidencesList = []
+
+ lines = content.split( "\r\n" if CRLF else "\n" )
+ for line in lines:
+ line = line.replace("\r","").replace("\n","")
+ if(line != "") :
+ points, confidence, transcription = get_tl_line_values(line,LTRB,withTranscription,withConfidence,imWidth,imHeight);
+ pointsList.append(points)
+ transcriptionsList.append(transcription)
+ confidencesList.append(confidence)
+
+ if withConfidence and len(confidencesList)>0 and sort_by_confidences:
+ import numpy as np
+ sorted_ind = np.argsort(-np.array(confidencesList))
+ confidencesList = [confidencesList[i] for i in sorted_ind]
+ pointsList = [pointsList[i] for i in sorted_ind]
+ transcriptionsList = [transcriptionsList[i] for i in sorted_ind]
+
+ return pointsList,confidencesList,transcriptionsList
+
+def main_evaluation(p,det_file, gt_file, default_evaluation_params_fn,validate_data_fn,evaluate_method_fn,show_result=True,per_sample=True):
+ """
+ This process validates a method, evaluates it and if it succed generates a ZIP file with a JSON entry for each sample.
+ Params:
+ p: Dictionary of parmeters with the GT/submission locations. If None is passed, the parameters send by the system are used.
+ default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation
+ validate_data_fn: points to a method that validates the corrct format of the submission
+ evaluate_method_fn: points to a function that evaluated the submission and return a Dictionary with the results
+ """
+
+ # if (p == None):
+ # p = dict([s[1:].split('=') for s in sys.argv[1:]])
+ # if(len(sys.argv)<3):
+ # print_help()
+ p = {}
+ p['g'] =gt_file #'tttgt.zip'
+ p['s'] =det_file #'det.zip'
+
+ evalParams = default_evaluation_params_fn()
+ if 'p' in p.keys():
+ evalParams.update( p['p'] if isinstance(p['p'], dict) else json.loads(p['p'][1:-1]) )
+
+ resDict={'calculated':True,'Message':'','method':'{}','per_sample':'{}'}
+ # try:
+ validate_data_fn(p['g'], p['s'], evalParams)
+ evalData = evaluate_method_fn(p['g'], p['s'], evalParams)
+ resDict.update(evalData)
+
+ # except Exception as e:
+ # resDict['Message']= str(e)
+ # resDict['calculated']=False
+
+ if 'o' in p:
+ if not os.path.exists(p['o']):
+ os.makedirs(p['o'])
+
+ resultsOutputname = p['o'] + '/results.zip'
+ outZip = zipfile.ZipFile(resultsOutputname, mode='w', allowZip64=True)
+
+ del resDict['per_sample']
+ if 'output_items' in resDict.keys():
+ del resDict['output_items']
+
+ outZip.writestr('method.json',json.dumps(resDict))
+
+ if not resDict['calculated']:
+ if show_result:
+ sys.stderr.write('Error!\n'+ resDict['Message']+'\n\n')
+ if 'o' in p:
+ outZip.close()
+ return resDict
+
+ if 'o' in p:
+ if per_sample == True:
+ for k,v in evalData['per_sample'].items():
+ outZip.writestr( k + '.json',json.dumps(v))
+
+ if 'output_items' in evalData.keys():
+ for k, v in evalData['output_items'].items():
+ outZip.writestr( k,v)
+
+ outZip.close()
+
+ # if show_result:
+ # sys.stdout.write("Calculated!")
+ # sys.stdout.write('\n')
+ # sys.stdout.write(json.dumps(resDict['e2e_method']))
+ # sys.stdout.write('\n')
+ # sys.stdout.write(json.dumps(resDict['det_only_method']))
+ # sys.stdout.write('\n')
+
+ return resDict
+
+
+def main_validation(default_evaluation_params_fn,validate_data_fn):
+ """
+ This process validates a method
+ Params:
+ default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation
+ validate_data_fn: points to a method that validates the corrct format of the submission
+ """
+ try:
+ p = dict([s[1:].split('=') for s in sys.argv[1:]])
+ evalParams = default_evaluation_params_fn()
+ if 'p' in p.keys():
+ evalParams.update( p['p'] if isinstance(p['p'], dict) else json.loads(p['p'][1:-1]) )
+
+ validate_data_fn(p['g'], p['s'], evalParams)
+ print('SUCCESS')
+ sys.exit(0)
+ except Exception as e:
+ print(str(e))
+ sys.exit(101)
\ No newline at end of file
diff --git a/AdelaiDet/adet/evaluation/rrc_evaluation_funcs_ic15.py b/AdelaiDet/adet/evaluation/rrc_evaluation_funcs_ic15.py
new file mode 100755
index 0000000..4e708aa
--- /dev/null
+++ b/AdelaiDet/adet/evaluation/rrc_evaluation_funcs_ic15.py
@@ -0,0 +1,371 @@
+#!/usr/bin/env python2
+#encoding: UTF-8
+import json
+import sys;sys.path.append('./')
+import zipfile
+import re
+import sys
+import os
+import codecs
+import importlib
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+def print_help():
+ sys.stdout.write('Usage: python %s.py -g= -s= [-o= -p=]' %sys.argv[0])
+ sys.exit(2)
+
+
+def load_zip_file_keys(file,fileNameRegExp=''):
+ """
+ Returns an array with the entries of the ZIP file that match with the regular expression.
+ The key's are the names or the file or the capturing group definied in the fileNameRegExp
+ """
+ try:
+ archive=zipfile.ZipFile(file, mode='r', allowZip64=True)
+ except :
+ raise Exception('Error loading the ZIP archive.')
+
+ pairs = []
+
+ for name in archive.namelist():
+ addFile = True
+ keyName = name
+ if fileNameRegExp!="":
+ m = re.match(fileNameRegExp,name)
+ if m == None:
+ addFile = False
+ else:
+ if len(m.groups())>0:
+ keyName = m.group(1)
+
+ if addFile:
+ pairs.append( keyName )
+
+ return pairs
+
+
+def load_zip_file(file,fileNameRegExp='',allEntries=False):
+ """
+ Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file.
+ The key's are the names or the file or the capturing group definied in the fileNameRegExp
+ allEntries validates that all entries in the ZIP file pass the fileNameRegExp
+ """
+ try:
+ archive=zipfile.ZipFile(file, mode='r', allowZip64=True)
+ except :
+ raise Exception('Error loading the ZIP archive')
+
+ pairs = []
+ for name in archive.namelist():
+ addFile = True
+ keyName = name
+ if fileNameRegExp!="":
+ m = re.match(fileNameRegExp,name)
+ if m == None:
+ addFile = False
+ else:
+ if len(m.groups())>0:
+ keyName = m.group(1)
+
+ if addFile:
+ pairs.append( [ keyName , archive.read(name)] )
+ else:
+ if allEntries:
+ raise Exception('ZIP entry not valid: %s' %name)
+
+ return dict(pairs)
+
+def decode_utf8(raw):
+ """
+ Returns a Unicode object on success, or None on failure
+ """
+ try:
+ raw = codecs.decode(raw,'utf-8', 'replace')
+ #extracts BOM if exists
+ raw = raw.encode('utf8')
+ if raw.startswith(codecs.BOM_UTF8):
+ raw = raw.replace(codecs.BOM_UTF8, '', 1)
+ return raw.decode('utf-8')
+ except:
+ return None
+
+def validate_lines_in_file(fileName,file_contents,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
+ """
+ This function validates that all lines of the file calling the Line validation function for each line
+ """
+ utf8File = decode_utf8(file_contents)
+ if (utf8File is None) :
+ raise Exception("The file %s is not UTF-8" %fileName)
+
+ lines = utf8File.split( "\r\n" if CRLF else "\n" )
+ for line in lines:
+ line = line.replace("\r","").replace("\n","")
+ if(line != ""):
+ try:
+ validate_tl_line(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
+ except Exception as e:
+ raise Exception(("Line in sample not valid. Sample: %s Line: %s Error: %s" %(fileName,line,str(e))).encode('utf-8', 'replace'))
+
+
+
+def validate_tl_line(line,LTRB=True,withTranscription=True,withConfidence=True,imWidth=0,imHeight=0):
+ """
+ Validate the format of the line. If the line is not valid an exception will be raised.
+ If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
+ Posible values are:
+ LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
+ LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
+ """
+ get_tl_line_values(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
+
+
+def get_tl_line_values(line,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
+ """
+ Validate the format of the line. If the line is not valid an exception will be raised.
+ If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
+ Posible values are:
+ LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
+ LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
+ Returns values from a textline. Points , [Confidences], [Transcriptions]
+ """
+ confidence = 0.0
+ transcription = "";
+ points = []
+
+ numPoints = 4;
+
+ if LTRB:
+
+ numPoints = 4;
+
+ if withTranscription and withConfidence:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$',line)
+ if m == None :
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$',line)
+ raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence,transcription")
+ elif withConfidence:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence")
+ elif withTranscription:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,(.*)$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax,transcription")
+ else:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,?\s*$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax")
+
+ xmin = int(m.group(1))
+ ymin = int(m.group(2))
+ xmax = int(m.group(3))
+ ymax = int(m.group(4))
+ if(xmax0 and imHeight>0):
+ validate_point_inside_bounds(xmin,ymin,imWidth,imHeight);
+ validate_point_inside_bounds(xmax,ymax,imWidth,imHeight);
+
+ else:
+
+ numPoints = 8;
+
+ if withTranscription and withConfidence:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence,transcription")
+ elif withConfidence:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence")
+ elif withTranscription:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,(.*)$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,transcription")
+ else:
+ m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*$',line)
+ if m == None :
+ raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4")
+
+ points = [ float(m.group(i)) for i in range(1, (numPoints+1) ) ]
+
+ validate_clockwise_points(points)
+
+ if (imWidth>0 and imHeight>0):
+ validate_point_inside_bounds(points[0],points[1],imWidth,imHeight);
+ validate_point_inside_bounds(points[2],points[3],imWidth,imHeight);
+ validate_point_inside_bounds(points[4],points[5],imWidth,imHeight);
+ validate_point_inside_bounds(points[6],points[7],imWidth,imHeight);
+
+
+ if withConfidence:
+ try:
+ confidence = float(m.group(numPoints+1))
+ except ValueError:
+ raise Exception("Confidence value must be a float")
+
+ if withTranscription:
+ posTranscription = numPoints + (2 if withConfidence else 1)
+ transcription = m.group(posTranscription)
+ m2 = re.match(r'^\s*\"(.*)\"\s*$',transcription)
+ if m2 != None : #Transcription with double quotes, we extract the value and replace escaped characters
+ transcription = m2.group(1).replace("\\\\", "\\").replace("\\\"", "\"")
+
+ return points,confidence,transcription
+
+
+def validate_point_inside_bounds(x,y,imWidth,imHeight):
+ if(x<0 or x>imWidth):
+ raise Exception("X value (%s) not valid. Image dimensions: (%s,%s)" %(xmin,imWidth,imHeight))
+ if(y<0 or y>imHeight):
+ raise Exception("Y value (%s) not valid. Image dimensions: (%s,%s) Sample: %s Line:%s" %(ymin,imWidth,imHeight))
+
+def validate_clockwise_points(points):
+ """
+ Validates that the points that the 4 points that dlimite a polygon are in clockwise order.
+ """
+
+ if len(points) != 8:
+ raise Exception("Points list not valid." + str(len(points)))
+
+ point = [
+ [int(points[0]) , int(points[1])],
+ [int(points[2]) , int(points[3])],
+ [int(points[4]) , int(points[5])],
+ [int(points[6]) , int(points[7])]
+ ]
+ edge = [
+ ( point[1][0] - point[0][0])*( point[1][1] + point[0][1]),
+ ( point[2][0] - point[1][0])*( point[2][1] + point[1][1]),
+ ( point[3][0] - point[2][0])*( point[3][1] + point[2][1]),
+ ( point[0][0] - point[3][0])*( point[0][1] + point[3][1])
+ ]
+
+ summatory = edge[0] + edge[1] + edge[2] + edge[3];
+ if summatory>0:
+ raise Exception("Points are not clockwise. The coordinates of bounding quadrilaterals have to be given in clockwise order. Regarding the correct interpretation of 'clockwise' remember that the image coordinate system used is the standard one, with the image origin at the upper left, the X axis extending to the right and Y axis extending downwards.")
+
+def get_tl_line_values_from_file_contents(content,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0,sort_by_confidences=True):
+ """
+ Returns all points, confindences and transcriptions of a file in lists. Valid line formats:
+ xmin,ymin,xmax,ymax,[confidence],[transcription]
+ x1,y1,x2,y2,x3,y3,x4,y4,[confidence],[transcription]
+ """
+ pointsList = []
+ transcriptionsList = []
+ confidencesList = []
+
+ lines = content.split( "\r\n" if CRLF else "\n" )
+ for line in lines:
+ line = line.replace("\r","").replace("\n","")
+ if(line != "") :
+ points, confidence, transcription = get_tl_line_values(line,LTRB,withTranscription,withConfidence,imWidth,imHeight);
+ pointsList.append(points)
+ transcriptionsList.append(transcription)
+ confidencesList.append(confidence)
+
+ if withConfidence and len(confidencesList)>0 and sort_by_confidences:
+ import numpy as np
+ sorted_ind = np.argsort(-np.array(confidencesList))
+ confidencesList = [confidencesList[i] for i in sorted_ind]
+ pointsList = [pointsList[i] for i in sorted_ind]
+ transcriptionsList = [transcriptionsList[i] for i in sorted_ind]
+
+ return pointsList,confidencesList,transcriptionsList
+
+def main_evaluation(p,default_evaluation_params_fn,validate_data_fn,evaluate_method_fn,show_result=True,per_sample=True):
+ """
+ This process validates a method, evaluates it and if it succed generates a ZIP file with a JSON entry for each sample.
+ Params:
+ p: Dictionary of parmeters with the GT/submission locations. If None is passed, the parameters send by the system are used.
+ default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation
+ validate_data_fn: points to a method that validates the corrct format of the submission
+ evaluate_method_fn: points to a function that evaluated the submission and return a Dictionary with the results
+ """
+
+ if (p == None):
+ p = dict([s[1:].split('=') for s in sys.argv[1:]])
+ if(len(sys.argv)<3):
+ print_help()
+
+ evalParams = default_evaluation_params_fn()
+ if 'p' in p.keys():
+ evalParams.update( p['p'] if isinstance(p['p'], dict) else json.loads(p['p'][1:-1]) )
+
+ resDict={'calculated':True,'Message':'','method':'{}','per_sample':'{}'}
+ try:
+ validate_data_fn(p['g'], p['s'], evalParams)
+ evalData = evaluate_method_fn(p['g'], p['s'], evalParams)
+ resDict.update(evalData)
+
+ except Exception as e:
+ resDict['Message']= str(e)
+ resDict['calculated']=False
+
+ if 'o' in p:
+ if not os.path.exists(p['o']):
+ os.makedirs(p['o'])
+
+ resultsOutputname = p['o'] + '/results.zip'
+ outZip = zipfile.ZipFile(resultsOutputname, mode='w', allowZip64=True)
+
+ del resDict['per_sample']
+ if 'output_items' in resDict.keys():
+ del resDict['output_items']
+
+ outZip.writestr('method.json',json.dumps(resDict))
+
+ if not resDict['calculated']:
+ if show_result:
+ sys.stderr.write('Error!\n'+ resDict['Message']+'\n\n')
+ if 'o' in p:
+ outZip.close()
+ return resDict
+
+ if 'o' in p:
+ if per_sample == True:
+ for k,v in evalData['per_sample'].items():
+ outZip.writestr( k + '.json',json.dumps(v))
+
+ if 'output_items' in evalData.keys():
+ for k, v in evalData['output_items'].items():
+ outZip.writestr( k,v)
+
+ outZip.close()
+
+ # if show_result:
+ # sys.stdout.write("Calculated!")
+ # sys.stdout.write("\n")
+ # sys.stdout.write(json.dumps(resDict['e2e_method']))
+ # sys.stdout.write("\n")
+
+ return resDict
+
+
+def main_validation(default_evaluation_params_fn,validate_data_fn):
+ """
+ This process validates a method
+ Params:
+ default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation
+ validate_data_fn: points to a method that validates the corrct format of the submission
+ """
+ try:
+ p = dict([s[1:].split('=') for s in sys.argv[1:]])
+ evalParams = default_evaluation_params_fn()
+ if 'p' in p.keys():
+ evalParams.update( p['p'] if isinstance(p['p'], dict) else json.loads(p['p'][1:-1]) )
+
+ validate_data_fn(p['g'], p['s'], evalParams)
+ print('SUCCESS')
+ sys.exit(0)
+ except Exception as e:
+ print(str(e))
+ sys.exit(101)
diff --git a/AdelaiDet/adet/evaluation/text_eval_script.py b/AdelaiDet/adet/evaluation/text_eval_script.py
new file mode 100755
index 0000000..8c6cdcd
--- /dev/null
+++ b/AdelaiDet/adet/evaluation/text_eval_script.py
@@ -0,0 +1,472 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# encoding=utf8
+from collections import namedtuple
+from adet.evaluation import rrc_evaluation_funcs
+import importlib
+import sys
+
+import math
+
+from rapidfuzz import string_metric
+
+WORD_SPOTTING =True
+def evaluation_imports():
+ """
+ evaluation_imports: Dictionary ( key = module name , value = alias ) with python modules used in the evaluation.
+ """
+ return {
+ 'Polygon':'plg',
+ 'numpy':'np'
+ }
+
+def default_evaluation_params():
+ """
+ default_evaluation_params: Default parameters to use for the validation and evaluation.
+ """
+ global WORD_SPOTTING
+ return {
+ 'IOU_CONSTRAINT' :0.5,
+ 'AREA_PRECISION_CONSTRAINT' :0.5,
+ 'WORD_SPOTTING' :WORD_SPOTTING,
+ 'MIN_LENGTH_CARE_WORD' :3,
+ 'GT_SAMPLE_NAME_2_ID':'([0-9]+).txt',
+ 'DET_SAMPLE_NAME_2_ID':'([0-9]+).txt',
+ 'LTRB':False, #LTRB:2points(left,top,right,bottom) or 4 points(x1,y1,x2,y2,x3,y3,x4,y4)
+ 'CRLF':False, # Lines are delimited by Windows CRLF format
+ 'CONFIDENCES':False, #Detections must include confidence value. MAP and MAR will be calculated,
+ 'SPECIAL_CHARACTERS':str('!?.:,*"()·[]/\''),
+ 'ONLY_REMOVE_FIRST_LAST_CHARACTER' : True
+ }
+
+def validate_data(gtFilePath, submFilePath, evaluationParams):
+ """
+ Method validate_data: validates that all files in the results folder are correct (have the correct name contents).
+ Validates also that there are no missing files in the folder.
+ If some error detected, the method raises the error
+ """
+ gt = rrc_evaluation_funcs.load_zip_file(gtFilePath, evaluationParams['GT_SAMPLE_NAME_2_ID'])
+
+ subm = rrc_evaluation_funcs.load_zip_file(submFilePath, evaluationParams['DET_SAMPLE_NAME_2_ID'], True)
+
+ #Validate format of GroundTruth
+ for k in gt:
+ rrc_evaluation_funcs.validate_lines_in_file_gt(k,gt[k],evaluationParams['CRLF'],evaluationParams['LTRB'],True)
+
+ #Validate format of results
+ for k in subm:
+ if (k in gt) == False :
+ raise Exception("The sample %s not present in GT" %k)
+
+ rrc_evaluation_funcs.validate_lines_in_file(k,subm[k],evaluationParams['CRLF'],evaluationParams['LTRB'],True,evaluationParams['CONFIDENCES'])
+
+
+def evaluate_method(gtFilePath, submFilePath, evaluationParams):
+ """
+ Method evaluate_method: evaluate method and returns the results
+ Results. Dictionary with the following values:
+ - method (required) Global method metrics. Ex: { 'Precision':0.8,'Recall':0.9 }
+ - samples (optional) Per sample metrics. Ex: {'sample1' : { 'Precision':0.8,'Recall':0.9 } , 'sample2' : { 'Precision':0.8,'Recall':0.9 }
+ """
+ for module,alias in evaluation_imports().items():
+ globals()[alias] = importlib.import_module(module)
+
+ def polygon_from_points(points):
+ """
+ Returns a Polygon object to use with the Polygon2 class from a list of 8 points: x1,y1,x2,y2,x3,y3,x4,y4
+ """
+ num_points = len(points)
+ # resBoxes=np.empty([1,num_points],dtype='int32')
+ resBoxes=np.empty([1,num_points],dtype='float32')
+ for inp in range(0, num_points, 2):
+ resBoxes[0, int(inp/2)] = float(points[int(inp)])
+ resBoxes[0, int(inp/2+num_points/2)] = float(points[int(inp+1)])
+ pointMat = resBoxes[0].reshape([2,int(num_points/2)]).T
+ return plg.Polygon(pointMat)
+
+ def rectangle_to_polygon(rect):
+ resBoxes=np.empty([1,8],dtype='int32')
+ resBoxes[0,0]=int(rect.xmin)
+ resBoxes[0,4]=int(rect.ymax)
+ resBoxes[0,1]=int(rect.xmin)
+ resBoxes[0,5]=int(rect.ymin)
+ resBoxes[0,2]=int(rect.xmax)
+ resBoxes[0,6]=int(rect.ymin)
+ resBoxes[0,3]=int(rect.xmax)
+ resBoxes[0,7]=int(rect.ymax)
+
+ pointMat = resBoxes[0].reshape([2,4]).T
+
+ return plg.Polygon( pointMat)
+
+ def rectangle_to_points(rect):
+ points = [int(rect.xmin), int(rect.ymax), int(rect.xmax), int(rect.ymax), int(rect.xmax), int(rect.ymin), int(rect.xmin), int(rect.ymin)]
+ return points
+
+ def get_union(pD,pG):
+ areaA = pD.area();
+ areaB = pG.area();
+ return areaA + areaB - get_intersection(pD, pG);
+
+ def get_intersection_over_union(pD,pG):
+ try:
+ return get_intersection(pD, pG) / get_union(pD, pG);
+ except:
+ return 0
+
+ def get_intersection(pD,pG):
+ pInt = pD & pG
+ if len(pInt) == 0:
+ return 0
+ return pInt.area()
+
+ def compute_ap(confList, matchList,numGtCare):
+ correct = 0
+ AP = 0
+ if len(confList)>0:
+ confList = np.array(confList)
+ matchList = np.array(matchList)
+ sorted_ind = np.argsort(-confList)
+ confList = confList[sorted_ind]
+ matchList = matchList[sorted_ind]
+ for n in range(len(confList)):
+ match = matchList[n]
+ if match:
+ correct += 1
+ AP += float(correct)/(n + 1)
+
+ if numGtCare>0:
+ AP /= numGtCare
+
+ return AP
+
+ def transcription_match(transGt,transDet,specialCharacters=str(r'!?.:,*"()·[]/\''),onlyRemoveFirstLastCharacterGT=True):
+
+ if onlyRemoveFirstLastCharacterGT:
+ #special characters in GT are allowed only at initial or final position
+ if (transGt==transDet):
+ return True
+
+ if specialCharacters.find(transGt[0])>-1:
+ if transGt[1:]==transDet:
+ return True
+
+ if specialCharacters.find(transGt[-1])>-1:
+ if transGt[0:len(transGt)-1]==transDet:
+ return True
+
+ if specialCharacters.find(transGt[0])>-1 and specialCharacters.find(transGt[-1])>-1:
+ if transGt[1:len(transGt)-1]==transDet:
+ return True
+ return False
+ else:
+ #Special characters are removed from the begining and the end of both Detection and GroundTruth
+ while len(transGt)>0 and specialCharacters.find(transGt[0])>-1:
+ transGt = transGt[1:]
+
+ while len(transDet)>0 and specialCharacters.find(transDet[0])>-1:
+ transDet = transDet[1:]
+
+ while len(transGt)>0 and specialCharacters.find(transGt[-1])>-1 :
+ transGt = transGt[0:len(transGt)-1]
+
+ while len(transDet)>0 and specialCharacters.find(transDet[-1])>-1:
+ transDet = transDet[0:len(transDet)-1]
+
+ return transGt == transDet
+
+
+ def include_in_dictionary(transcription):
+ """
+ Function used in Word Spotting that finds if the Ground Truth transcription meets the rules to enter into the dictionary. If not, the transcription will be cared as don't care
+ """
+ #special case 's at final
+ if transcription[len(transcription)-2:]=="'s" or transcription[len(transcription)-2:]=="'S":
+ transcription = transcription[0:len(transcription)-2]
+
+ #hypens at init or final of the word
+ transcription = transcription.strip('-');
+
+ specialCharacters = str("'!?.:,*\"()·[]/");
+ for character in specialCharacters:
+ transcription = transcription.replace(character,' ')
+
+ transcription = transcription.strip()
+
+ if len(transcription) != len(transcription.replace(" ","")) :
+ return False;
+
+ if len(transcription) < evaluationParams['MIN_LENGTH_CARE_WORD']:
+ return False;
+
+ notAllowed = str("×÷·");
+
+ range1 = [ ord(u'a'), ord(u'z') ]
+ range2 = [ ord(u'A'), ord(u'Z') ]
+ range3 = [ ord(u'À'), ord(u'ƿ') ]
+ range4 = [ ord(u'DŽ'), ord(u'ɿ') ]
+ range5 = [ ord(u'Ά'), ord(u'Ͽ') ]
+ range6 = [ ord(u'-'), ord(u'-') ]
+
+ for char in transcription :
+ charCode = ord(char)
+ if(notAllowed.find(char) != -1):
+ return False
+
+ valid = ( charCode>=range1[0] and charCode<=range1[1] ) or ( charCode>=range2[0] and charCode<=range2[1] ) or ( charCode>=range3[0] and charCode<=range3[1] ) or ( charCode>=range4[0] and charCode<=range4[1] ) or ( charCode>=range5[0] and charCode<=range5[1] ) or ( charCode>=range6[0] and charCode<=range6[1] )
+ if valid == False:
+ return False
+
+ return True
+
+ def include_in_dictionary_transcription(transcription):
+ """
+ Function applied to the Ground Truth transcriptions used in Word Spotting. It removes special characters or terminations
+ """
+ #special case 's at final
+ if transcription[len(transcription)-2:]=="'s" or transcription[len(transcription)-2:]=="'S":
+ transcription = transcription[0:len(transcription)-2]
+
+ #hypens at init or final of the word
+ transcription = transcription.strip('-');
+
+ specialCharacters = str("'!?.:,*\"()·[]/");
+ for character in specialCharacters:
+ transcription = transcription.replace(character,' ')
+
+ transcription = transcription.strip()
+
+ return transcription
+
+ perSampleMetrics = {}
+
+ matchedSum = 0
+ det_only_matchedSum = 0
+
+ Rectangle = namedtuple('Rectangle', 'xmin ymin xmax ymax')
+
+ gt = rrc_evaluation_funcs.load_zip_file(gtFilePath,evaluationParams['GT_SAMPLE_NAME_2_ID'])
+ subm = rrc_evaluation_funcs.load_zip_file(submFilePath,evaluationParams['DET_SAMPLE_NAME_2_ID'],True)
+
+ numGlobalCareGt = 0;
+ numGlobalCareDet = 0;
+ det_only_numGlobalCareGt = 0;
+ det_only_numGlobalCareDet = 0;
+
+ arrGlobalConfidences = [];
+ arrGlobalMatches = [];
+
+ for resFile in gt:
+ # print('resgt', resFile)
+ gtFile = rrc_evaluation_funcs.decode_utf8(gt[resFile])
+ if (gtFile is None) :
+ raise Exception("The file %s is not UTF-8" %resFile)
+
+ recall = 0
+ precision = 0
+ hmean = 0
+ detCorrect = 0
+ detOnlyCorrect = 0
+ iouMat = np.empty([1,1])
+ gtPols = []
+ detPols = []
+ gtTrans = []
+ detTrans = []
+ gtPolPoints = []
+ detPolPoints = []
+ gtDontCarePolsNum = [] #Array of Ground Truth Polygons' keys marked as don't Care
+ det_only_gtDontCarePolsNum = []
+ detDontCarePolsNum = [] #Array of Detected Polygons' matched with a don't Care GT
+ det_only_detDontCarePolsNum = []
+ detMatchedNums = []
+ pairs = []
+
+ arrSampleConfidences = [];
+ arrSampleMatch = [];
+ sampleAP = 0;
+
+ pointsList,_,transcriptionsList = rrc_evaluation_funcs.get_tl_line_values_from_file_contents(gtFile,evaluationParams['CRLF'],evaluationParams['LTRB'],True,False)
+
+ for n in range(len(pointsList)):
+ points = pointsList[n]
+ transcription = transcriptionsList[n]
+ det_only_dontCare = dontCare = transcription == "###" # ctw1500 and total_text gt have been modified to the same format.
+ if evaluationParams['LTRB']:
+ gtRect = Rectangle(*points)
+ gtPol = rectangle_to_polygon(gtRect)
+ else:
+ gtPol = polygon_from_points(points)
+ gtPols.append(gtPol)
+ gtPolPoints.append(points)
+
+ #On word spotting we will filter some transcriptions with special characters
+ if evaluationParams['WORD_SPOTTING'] :
+ if dontCare == False :
+ if include_in_dictionary(transcription) == False :
+ dontCare = True
+ else:
+ transcription = include_in_dictionary_transcription(transcription)
+
+ gtTrans.append(transcription)
+ if dontCare:
+ gtDontCarePolsNum.append( len(gtPols)-1 )
+ if det_only_dontCare:
+ det_only_gtDontCarePolsNum.append( len(gtPols)-1 )
+
+
+ if resFile in subm:
+
+ detFile = rrc_evaluation_funcs.decode_utf8(subm[resFile])
+
+ pointsList,confidencesList,transcriptionsList = rrc_evaluation_funcs.get_tl_line_values_from_file_contents_det(detFile,evaluationParams['CRLF'],evaluationParams['LTRB'],True,evaluationParams['CONFIDENCES'])
+
+ for n in range(len(pointsList)):
+ points = pointsList[n]
+ transcription = transcriptionsList[n]
+
+ if evaluationParams['LTRB']:
+ detRect = Rectangle(*points)
+ detPol = rectangle_to_polygon(detRect)
+ else:
+ detPol = polygon_from_points(points)
+ detPols.append(detPol)
+ detPolPoints.append(points)
+ detTrans.append(transcription)
+
+ if len(gtDontCarePolsNum)>0 :
+ for dontCarePol in gtDontCarePolsNum:
+ dontCarePol = gtPols[dontCarePol]
+ intersected_area = get_intersection(dontCarePol,detPol)
+ pdDimensions = detPol.area()
+ precision = 0 if pdDimensions == 0 else intersected_area / pdDimensions
+ if (precision > evaluationParams['AREA_PRECISION_CONSTRAINT'] ):
+ detDontCarePolsNum.append( len(detPols)-1 )
+ break
+
+ if len(det_only_gtDontCarePolsNum)>0 :
+ for dontCarePol in det_only_gtDontCarePolsNum:
+ dontCarePol = gtPols[dontCarePol]
+ intersected_area = get_intersection(dontCarePol,detPol)
+ pdDimensions = detPol.area()
+ precision = 0 if pdDimensions == 0 else intersected_area / pdDimensions
+ if (precision > evaluationParams['AREA_PRECISION_CONSTRAINT'] ):
+ det_only_detDontCarePolsNum.append( len(detPols)-1 )
+ break
+
+
+ if len(gtPols)>0 and len(detPols)>0:
+ #Calculate IoU and precision matrixs
+ outputShape=[len(gtPols),len(detPols)]
+ iouMat = np.empty(outputShape)
+ gtRectMat = np.zeros(len(gtPols),np.int8)
+ detRectMat = np.zeros(len(detPols),np.int8)
+ det_only_gtRectMat = np.zeros(len(gtPols),np.int8)
+ det_only_detRectMat = np.zeros(len(detPols),np.int8)
+ for gtNum in range(len(gtPols)):
+ for detNum in range(len(detPols)):
+ pG = gtPols[gtNum]
+ pD = detPols[detNum]
+ iouMat[gtNum,detNum] = get_intersection_over_union(pD,pG)
+
+ for gtNum in range(len(gtPols)):
+ for detNum in range(len(detPols)):
+ if gtRectMat[gtNum] == 0 and detRectMat[detNum] == 0 and gtNum not in gtDontCarePolsNum and detNum not in detDontCarePolsNum :
+ if iouMat[gtNum,detNum]>evaluationParams['IOU_CONSTRAINT']:
+ gtRectMat[gtNum] = 1
+ detRectMat[detNum] = 1
+ #detection matched only if transcription is equal
+ # det_only_correct = True
+ # detOnlyCorrect += 1
+ if evaluationParams['WORD_SPOTTING']:
+ edd = string_metric.levenshtein(gtTrans[gtNum].upper(), detTrans[detNum].upper())
+ if edd<=0:
+ correct = True
+ else:
+ correct = False
+ # correct = gtTrans[gtNum].upper() == detTrans[detNum].upper()
+ else:
+ try:
+ correct = transcription_match(gtTrans[gtNum].upper(),detTrans[detNum].upper(),evaluationParams['SPECIAL_CHARACTERS'],evaluationParams['ONLY_REMOVE_FIRST_LAST_CHARACTER'])==True
+ except: # empty
+ correct = False
+ detCorrect += (1 if correct else 0)
+ if correct:
+ detMatchedNums.append(detNum)
+
+ for gtNum in range(len(gtPols)):
+ for detNum in range(len(detPols)):
+ if det_only_gtRectMat[gtNum] == 0 and det_only_detRectMat[detNum] == 0 and gtNum not in det_only_gtDontCarePolsNum and detNum not in det_only_detDontCarePolsNum:
+ if iouMat[gtNum,detNum]>evaluationParams['IOU_CONSTRAINT']:
+ det_only_gtRectMat[gtNum] = 1
+ det_only_detRectMat[detNum] = 1
+ #detection matched only if transcription is equal
+ det_only_correct = True
+ detOnlyCorrect += 1
+
+
+ numGtCare = (len(gtPols) - len(gtDontCarePolsNum))
+ numDetCare = (len(detPols) - len(detDontCarePolsNum))
+ det_only_numGtCare = (len(gtPols) - len(det_only_gtDontCarePolsNum))
+ det_only_numDetCare = (len(detPols) - len(det_only_detDontCarePolsNum))
+ if numGtCare == 0:
+ recall = float(1)
+ precision = float(0) if numDetCare >0 else float(1)
+ else:
+ recall = float(detCorrect) / numGtCare
+ precision = 0 if numDetCare==0 else float(detCorrect) / numDetCare
+
+ if det_only_numGtCare == 0:
+ det_only_recall = float(1)
+ det_only_precision = float(0) if det_only_numDetCare >0 else float(1)
+ else:
+ det_only_recall = float(detOnlyCorrect) / det_only_numGtCare
+ det_only_precision = 0 if det_only_numDetCare==0 else float(detOnlyCorrect) / det_only_numDetCare
+
+
+ hmean = 0 if (precision + recall)==0 else 2.0 * precision * recall / (precision + recall)
+ det_only_hmean = 0 if (det_only_precision + det_only_recall)==0 else 2.0 * det_only_precision * det_only_recall / (det_only_precision + det_only_recall)
+
+ matchedSum += detCorrect
+ det_only_matchedSum += detOnlyCorrect
+ numGlobalCareGt += numGtCare
+ numGlobalCareDet += numDetCare
+ det_only_numGlobalCareGt += det_only_numGtCare
+ det_only_numGlobalCareDet += det_only_numDetCare
+
+ perSampleMetrics[resFile] = {
+ 'precision':precision,
+ 'recall':recall,
+ 'hmean':hmean,
+ 'iouMat':[] if len(detPols)>100 else iouMat.tolist(),
+ 'gtPolPoints':gtPolPoints,
+ 'detPolPoints':detPolPoints,
+ 'gtTrans':gtTrans,
+ 'detTrans':detTrans,
+ 'gtDontCare':gtDontCarePolsNum,
+ 'detDontCare':detDontCarePolsNum,
+ 'evaluationParams': evaluationParams,
+ }
+
+
+ methodRecall = 0 if numGlobalCareGt == 0 else float(matchedSum)/numGlobalCareGt
+ methodPrecision = 0 if numGlobalCareDet == 0 else float(matchedSum)/numGlobalCareDet
+ methodHmean = 0 if methodRecall + methodPrecision==0 else 2* methodRecall * methodPrecision / (methodRecall + methodPrecision)
+
+ det_only_methodRecall = 0 if det_only_numGlobalCareGt == 0 else float(det_only_matchedSum)/det_only_numGlobalCareGt
+ det_only_methodPrecision = 0 if det_only_numGlobalCareDet == 0 else float(det_only_matchedSum)/det_only_numGlobalCareDet
+ det_only_methodHmean = 0 if det_only_methodRecall + det_only_methodPrecision==0 else 2* det_only_methodRecall * det_only_methodPrecision / (det_only_methodRecall + det_only_methodPrecision)
+
+
+ methodMetrics = r"E2E_RESULTS: precision: {}, recall: {}, hmean: {}".format(methodPrecision, methodRecall, methodHmean)
+ det_only_methodMetrics = r"DETECTION_ONLY_RESULTS: precision: {}, recall: {}, hmean: {}".format(det_only_methodPrecision, det_only_methodRecall, det_only_methodHmean)
+
+
+ resDict = {'calculated':True,'Message':'','e2e_method': methodMetrics,'det_only_method': det_only_methodMetrics,'per_sample': perSampleMetrics}
+
+
+ return resDict;
+
+def text_eval_main(det_file, gt_file, is_word_spotting):
+ global WORD_SPOTTING
+ WORD_SPOTTING = is_word_spotting
+ return rrc_evaluation_funcs.main_evaluation(None,det_file, gt_file, default_evaluation_params,validate_data,evaluate_method)
diff --git a/AdelaiDet/adet/evaluation/text_eval_script_ic15.py b/AdelaiDet/adet/evaluation/text_eval_script_ic15.py
new file mode 100755
index 0000000..5b023db
--- /dev/null
+++ b/AdelaiDet/adet/evaluation/text_eval_script_ic15.py
@@ -0,0 +1,501 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# encoding=utf8
+from collections import namedtuple
+from adet.evaluation import rrc_evaluation_funcs_ic15 as rrc_evaluation_funcs
+import importlib
+import sys
+
+import math
+
+from rapidfuzz import string_metric
+
+WORD_SPOTTING =True
+def evaluation_imports():
+ """
+ evaluation_imports: Dictionary ( key = module name , value = alias ) with python modules used in the evaluation.
+ """
+ return {
+ 'Polygon':'plg',
+ 'numpy':'np'
+ }
+
+def default_evaluation_params():
+ """
+ default_evaluation_params: Default parameters to use for the validation and evaluation.
+ """
+ global WORD_SPOTTING
+ return {
+ 'IOU_CONSTRAINT' :0.5,
+ 'AREA_PRECISION_CONSTRAINT' :0.5,
+ 'WORD_SPOTTING' :WORD_SPOTTING,
+ 'MIN_LENGTH_CARE_WORD' :3,
+ 'GT_SAMPLE_NAME_2_ID':'gt_img_([0-9]+).txt',
+ 'DET_SAMPLE_NAME_2_ID':'res_img_([0-9]+).txt',
+ 'LTRB':False, #LTRB:2points(left,top,right,bottom) or 4 points(x1,y1,x2,y2,x3,y3,x4,y4)
+ 'CRLF':False, # Lines are delimited by Windows CRLF format
+ 'CONFIDENCES':False, #Detections must include confidence value. MAP and MAR will be calculated,
+ 'SPECIAL_CHARACTERS':'!?.:,*"()·[]/\'',
+ 'ONLY_REMOVE_FIRST_LAST_CHARACTER' : True
+ }
+
+def validate_data(gtFilePath, submFilePath, evaluationParams):
+ """
+ Method validate_data: validates that all files in the results folder are correct (have the correct name contents).
+ Validates also that there are no missing files in the folder.
+ If some error detected, the method raises the error
+ """
+ gt = rrc_evaluation_funcs.load_zip_file(gtFilePath, evaluationParams['GT_SAMPLE_NAME_2_ID'])
+ subm = rrc_evaluation_funcs.load_zip_file(submFilePath, evaluationParams['DET_SAMPLE_NAME_2_ID'], True)
+ #Validate format of GroundTruth
+ for k in gt:
+ rrc_evaluation_funcs.validate_lines_in_file(k,gt[k],evaluationParams['CRLF'],evaluationParams['LTRB'],True)
+
+ #Validate format of results
+ for k in subm:
+ if (k in gt) == False :
+ raise Exception("The sample %s not present in GT" %k)
+
+ rrc_evaluation_funcs.validate_lines_in_file(k,subm[k],evaluationParams['CRLF'],evaluationParams['LTRB'],True,evaluationParams['CONFIDENCES'])
+
+
+def evaluate_method(gtFilePath, submFilePath, evaluationParams):
+ """
+ Method evaluate_method: evaluate method and returns the results
+ Results. Dictionary with the following values:
+ - method (required) Global method metrics. Ex: { 'Precision':0.8,'Recall':0.9 }
+ - samples (optional) Per sample metrics. Ex: {'sample1' : { 'Precision':0.8,'Recall':0.9 } , 'sample2' : { 'Precision':0.8,'Recall':0.9 }
+ """
+ for module,alias in evaluation_imports().items():
+ globals()[alias] = importlib.import_module(module)
+
+ def polygon_from_points(points,correctOffset=False):
+ """
+ Returns a Polygon object to use with the Polygon2 class from a list of 8 points: x1,y1,x2,y2,x3,y3,x4,y4
+ """
+
+ if correctOffset: #this will substract 1 from the coordinates that correspond to the xmax and ymax
+ points[2] -= 1
+ points[4] -= 1
+ points[5] -= 1
+ points[7] -= 1
+
+ resBoxes=np.empty([1,8],dtype='int32')
+ resBoxes[0,0]=int(points[0])
+ resBoxes[0,4]=int(points[1])
+ resBoxes[0,1]=int(points[2])
+ resBoxes[0,5]=int(points[3])
+ resBoxes[0,2]=int(points[4])
+ resBoxes[0,6]=int(points[5])
+ resBoxes[0,3]=int(points[6])
+ resBoxes[0,7]=int(points[7])
+ pointMat = resBoxes[0].reshape([2,4]).T
+ return plg.Polygon( pointMat)
+
+ def rectangle_to_polygon(rect):
+ resBoxes=np.empty([1,8],dtype='int32')
+ resBoxes[0,0]=int(rect.xmin)
+ resBoxes[0,4]=int(rect.ymax)
+ resBoxes[0,1]=int(rect.xmin)
+ resBoxes[0,5]=int(rect.ymin)
+ resBoxes[0,2]=int(rect.xmax)
+ resBoxes[0,6]=int(rect.ymin)
+ resBoxes[0,3]=int(rect.xmax)
+ resBoxes[0,7]=int(rect.ymax)
+
+ pointMat = resBoxes[0].reshape([2,4]).T
+
+ return plg.Polygon( pointMat)
+
+ def rectangle_to_points(rect):
+ points = [int(rect.xmin), int(rect.ymax), int(rect.xmax), int(rect.ymax), int(rect.xmax), int(rect.ymin), int(rect.xmin), int(rect.ymin)]
+ return points
+
+ def get_union(pD,pG):
+ areaA = pD.area();
+ areaB = pG.area();
+ return areaA + areaB - get_intersection(pD, pG);
+
+ def get_intersection_over_union(pD,pG):
+ try:
+ return get_intersection(pD, pG) / get_union(pD, pG);
+ except:
+ return 0
+
+ def get_intersection(pD,pG):
+ pInt = pD & pG
+ if len(pInt) == 0:
+ return 0
+ return pInt.area()
+
+ def compute_ap(confList, matchList,numGtCare):
+ correct = 0
+ AP = 0
+ if len(confList)>0:
+ confList = np.array(confList)
+ matchList = np.array(matchList)
+ sorted_ind = np.argsort(-confList)
+ confList = confList[sorted_ind]
+ matchList = matchList[sorted_ind]
+ for n in range(len(confList)):
+ match = matchList[n]
+ if match:
+ correct += 1
+ AP += float(correct)/(n + 1)
+
+ if numGtCare>0:
+ AP /= numGtCare
+
+ return AP
+
+ def transcription_match(transGt,transDet,specialCharacters='!?.:,*"()·[]/\'',onlyRemoveFirstLastCharacterGT=True):
+
+ if onlyRemoveFirstLastCharacterGT:
+ #special characters in GT are allowed only at initial or final position
+ if (transGt==transDet):
+ return True
+
+ if specialCharacters.find(transGt[0])>-1:
+ if transGt[1:]==transDet:
+ return True
+
+ if specialCharacters.find(transGt[-1])>-1:
+ if transGt[0:len(transGt)-1]==transDet:
+ return True
+
+ if specialCharacters.find(transGt[0])>-1 and specialCharacters.find(transGt[-1])>-1:
+ if transGt[1:len(transGt)-1]==transDet:
+ return True
+ return False
+ else:
+ #Special characters are removed from the begining and the end of both Detection and GroundTruth
+ while len(transGt)>0 and specialCharacters.find(transGt[0])>-1:
+ transGt = transGt[1:]
+
+ while len(transDet)>0 and specialCharacters.find(transDet[0])>-1:
+ transDet = transDet[1:]
+
+ while len(transGt)>0 and specialCharacters.find(transGt[-1])>-1 :
+ transGt = transGt[0:len(transGt)-1]
+
+ while len(transDet)>0 and specialCharacters.find(transDet[-1])>-1:
+ transDet = transDet[0:len(transDet)-1]
+
+ return transGt == transDet
+
+
+ def include_in_dictionary(transcription):
+ """
+ Function used in Word Spotting that finds if the Ground Truth transcription meets the rules to enter into the dictionary. If not, the transcription will be cared as don't care
+ """
+ #special case 's at final
+ if transcription[len(transcription)-2:]=="'s" or transcription[len(transcription)-2:]=="'S":
+ transcription = transcription[0:len(transcription)-2]
+
+ #hypens at init or final of the word
+ transcription = transcription.strip('-');
+
+ specialCharacters = "'!?.:,*\"()·[]/";
+ for character in specialCharacters:
+ transcription = transcription.replace(character,' ')
+
+ transcription = transcription.strip()
+
+ if len(transcription) != len(transcription.replace(" ","")) :
+ return False;
+
+ if len(transcription) < evaluationParams['MIN_LENGTH_CARE_WORD']:
+ return False;
+
+ notAllowed = "×÷·";
+
+ range1 = [ ord(u'a'), ord(u'z') ]
+ range2 = [ ord(u'A'), ord(u'Z') ]
+ range3 = [ ord(u'À'), ord(u'ƿ') ]
+ range4 = [ ord(u'DŽ'), ord(u'ɿ') ]
+ range5 = [ ord(u'Ά'), ord(u'Ͽ') ]
+ range6 = [ ord(u'-'), ord(u'-') ]
+
+ for char in transcription :
+ charCode = ord(char)
+ if(notAllowed.find(char) != -1):
+ return False
+
+ valid = ( charCode>=range1[0] and charCode<=range1[1] ) or ( charCode>=range2[0] and charCode<=range2[1] ) or ( charCode>=range3[0] and charCode<=range3[1] ) or ( charCode>=range4[0] and charCode<=range4[1] ) or ( charCode>=range5[0] and charCode<=range5[1] ) or ( charCode>=range6[0] and charCode<=range6[1] )
+ if valid == False:
+ return False
+
+ return True
+
+ def include_in_dictionary_transcription(transcription):
+ """
+ Function applied to the Ground Truth transcriptions used in Word Spotting. It removes special characters or terminations
+ """
+ #special case 's at final
+ if transcription[len(transcription)-2:]=="'s" or transcription[len(transcription)-2:]=="'S":
+ transcription = transcription[0:len(transcription)-2]
+
+ #hypens at init or final of the word
+ transcription = transcription.strip('-');
+
+ specialCharacters = "'!?.:,*\"()·[]/";
+ for character in specialCharacters:
+ transcription = transcription.replace(character,' ')
+
+ transcription = transcription.strip()
+
+ return transcription
+
+ perSampleMetrics = {}
+
+ matchedSum = 0
+ det_only_matchedSum = 0
+
+ Rectangle = namedtuple('Rectangle', 'xmin ymin xmax ymax')
+
+ gt = rrc_evaluation_funcs.load_zip_file(gtFilePath,evaluationParams['GT_SAMPLE_NAME_2_ID'])
+ subm = rrc_evaluation_funcs.load_zip_file(submFilePath,evaluationParams['DET_SAMPLE_NAME_2_ID'],True)
+
+ numGlobalCareGt = 0;
+ numGlobalCareDet = 0;
+ det_only_numGlobalCareGt = 0;
+ det_only_numGlobalCareDet = 0;
+
+ arrGlobalConfidences = [];
+ arrGlobalMatches = [];
+
+ for resFile in gt:
+
+ gtFile = rrc_evaluation_funcs.decode_utf8(gt[resFile])
+ if (gtFile is None) :
+ raise Exception("The file %s is not UTF-8" %resFile)
+
+ recall = 0
+ precision = 0
+ hmean = 0
+ detCorrect = 0
+ detOnlyCorrect = 0
+ iouMat = np.empty([1,1])
+ gtPols = []
+ detPols = []
+ gtTrans = []
+ detTrans = []
+ gtPolPoints = []
+ detPolPoints = []
+ gtDontCarePolsNum = [] #Array of Ground Truth Polygons' keys marked as don't Care
+ det_only_gtDontCarePolsNum = []
+ detDontCarePolsNum = [] #Array of Detected Polygons' matched with a don't Care GT
+ det_only_detDontCarePolsNum = []
+ detMatchedNums = []
+ pairs = []
+
+ arrSampleConfidences = [];
+ arrSampleMatch = [];
+ sampleAP = 0;
+
+ evaluationLog = ""
+
+ pointsList,_,transcriptionsList = rrc_evaluation_funcs.get_tl_line_values_from_file_contents(gtFile,evaluationParams['CRLF'],evaluationParams['LTRB'],True,False)
+ for n in range(len(pointsList)):
+ points = pointsList[n]
+ transcription = transcriptionsList[n]
+ # dontCare = transcription == "###"
+ det_only_dontCare = dontCare = transcription == "###" # ctw1500 and total_text gt have been modified to the same format.
+ if evaluationParams['LTRB']:
+ gtRect = Rectangle(*points)
+ gtPol = rectangle_to_polygon(gtRect)
+ else:
+ gtPol = polygon_from_points(points)
+ gtPols.append(gtPol)
+ gtPolPoints.append(points)
+
+ #On word spotting we will filter some transcriptions with special characters
+ if evaluationParams['WORD_SPOTTING'] :
+ if dontCare == False :
+ if include_in_dictionary(transcription) == False :
+ dontCare = True
+ else:
+ transcription = include_in_dictionary_transcription(transcription)
+
+ gtTrans.append(transcription)
+ if dontCare:
+ gtDontCarePolsNum.append( len(gtPols)-1 )
+ if det_only_dontCare:
+ det_only_gtDontCarePolsNum.append( len(gtPols)-1 )
+
+ evaluationLog += "GT polygons: " + str(len(gtPols)) + (" (" + str(len(gtDontCarePolsNum)) + " don't care)\n" if len(gtDontCarePolsNum)>0 else "\n")
+
+ if resFile in subm:
+
+ detFile = rrc_evaluation_funcs.decode_utf8(subm[resFile])
+
+ pointsList,confidencesList,transcriptionsList = rrc_evaluation_funcs.get_tl_line_values_from_file_contents(detFile,evaluationParams['CRLF'],evaluationParams['LTRB'],True,evaluationParams['CONFIDENCES'])
+
+ for n in range(len(pointsList)):
+ points = pointsList[n]
+ transcription = transcriptionsList[n]
+
+ if evaluationParams['LTRB']:
+ detRect = Rectangle(*points)
+ detPol = rectangle_to_polygon(detRect)
+ else:
+ detPol = polygon_from_points(points)
+ detPols.append(detPol)
+ detPolPoints.append(points)
+ detTrans.append(transcription)
+
+ if len(gtDontCarePolsNum)>0 :
+ for dontCarePol in gtDontCarePolsNum:
+ dontCarePol = gtPols[dontCarePol]
+ intersected_area = get_intersection(dontCarePol,detPol)
+ pdDimensions = detPol.area()
+ precision = 0 if pdDimensions == 0 else intersected_area / pdDimensions
+ if (precision > evaluationParams['AREA_PRECISION_CONSTRAINT'] ):
+ detDontCarePolsNum.append( len(detPols)-1 )
+ break
+
+
+ if len(det_only_gtDontCarePolsNum)>0 :
+ for dontCarePol in det_only_gtDontCarePolsNum:
+ dontCarePol = gtPols[dontCarePol]
+ intersected_area = get_intersection(dontCarePol,detPol)
+ pdDimensions = detPol.area()
+ precision = 0 if pdDimensions == 0 else intersected_area / pdDimensions
+ if (precision > evaluationParams['AREA_PRECISION_CONSTRAINT'] ):
+ det_only_detDontCarePolsNum.append( len(detPols)-1 )
+ break
+
+ evaluationLog += "DET polygons: " + str(len(detPols)) + (" (" + str(len(detDontCarePolsNum)) + " don't care)\n" if len(detDontCarePolsNum)>0 else "\n")
+
+ if len(gtPols)>0 and len(detPols)>0:
+ #Calculate IoU and precision matrixs
+ outputShape=[len(gtPols),len(detPols)]
+ iouMat = np.empty(outputShape)
+ gtRectMat = np.zeros(len(gtPols),np.int8)
+ detRectMat = np.zeros(len(detPols),np.int8)
+ det_only_gtRectMat = np.zeros(len(gtPols),np.int8)
+ det_only_detRectMat = np.zeros(len(detPols),np.int8)
+ for gtNum in range(len(gtPols)):
+ for detNum in range(len(detPols)):
+ pG = gtPols[gtNum]
+ pD = detPols[detNum]
+ iouMat[gtNum,detNum] = get_intersection_over_union(pD,pG)
+
+ for gtNum in range(len(gtPols)):
+ for detNum in range(len(detPols)):
+ if gtRectMat[gtNum] == 0 and detRectMat[detNum] == 0 and gtNum not in gtDontCarePolsNum and detNum not in detDontCarePolsNum :
+ if iouMat[gtNum,detNum]>evaluationParams['IOU_CONSTRAINT']:
+ gtRectMat[gtNum] = 1
+ detRectMat[detNum] = 1
+ #detection matched only if transcription is equal
+ if evaluationParams['WORD_SPOTTING']:
+ correct = gtTrans[gtNum].upper() == detTrans[detNum].upper()
+ else:
+ correct = transcription_match(gtTrans[gtNum].upper(),detTrans[detNum].upper(),evaluationParams['SPECIAL_CHARACTERS'],evaluationParams['ONLY_REMOVE_FIRST_LAST_CHARACTER'])==True
+ detCorrect += (1 if correct else 0)
+ if correct:
+ detMatchedNums.append(detNum)
+ pairs.append({'gt':gtNum,'det':detNum,'correct':correct})
+ evaluationLog += "Match GT #" + str(gtNum) + " with Det #" + str(detNum) + " trans. correct: " + str(correct) + "\n"
+
+ for gtNum in range(len(gtPols)):
+ for detNum in range(len(detPols)):
+ if det_only_gtRectMat[gtNum] == 0 and det_only_detRectMat[detNum] == 0 and gtNum not in det_only_gtDontCarePolsNum and detNum not in det_only_detDontCarePolsNum:
+ if iouMat[gtNum,detNum]>evaluationParams['IOU_CONSTRAINT']:
+ det_only_gtRectMat[gtNum] = 1
+ det_only_detRectMat[detNum] = 1
+ #detection matched only if transcription is equal
+ det_only_correct = True
+ detOnlyCorrect += 1
+
+ if evaluationParams['CONFIDENCES']:
+ for detNum in range(len(detPols)):
+ if detNum not in detDontCarePolsNum :
+ #we exclude the don't care detections
+ match = detNum in detMatchedNums
+
+ arrSampleConfidences.append(confidencesList[detNum])
+ arrSampleMatch.append(match)
+
+ arrGlobalConfidences.append(confidencesList[detNum]);
+ arrGlobalMatches.append(match);
+
+ numGtCare = (len(gtPols) - len(gtDontCarePolsNum))
+ numDetCare = (len(detPols) - len(detDontCarePolsNum))
+ det_only_numGtCare = (len(gtPols) - len(det_only_gtDontCarePolsNum))
+ det_only_numDetCare = (len(detPols) - len(det_only_detDontCarePolsNum))
+ if numGtCare == 0:
+ recall = float(1)
+ precision = float(0) if numDetCare >0 else float(1)
+ sampleAP = precision
+ else:
+ recall = float(detCorrect) / numGtCare
+ precision = 0 if numDetCare==0 else float(detCorrect) / numDetCare
+ if evaluationParams['CONFIDENCES']:
+ sampleAP = compute_ap(arrSampleConfidences, arrSampleMatch, numGtCare )
+
+ if det_only_numGtCare == 0:
+ det_only_recall = float(1)
+ det_only_precision = float(0) if det_only_numDetCare >0 else float(1)
+ else:
+ det_only_recall = float(detOnlyCorrect) / det_only_numGtCare
+ det_only_precision = 0 if det_only_numDetCare==0 else float(detOnlyCorrect) / det_only_numDetCare
+
+ hmean = 0 if (precision + recall)==0 else 2.0 * precision * recall / (precision + recall)
+ det_only_hmean = 0 if (det_only_precision + det_only_recall)==0 else 2.0 * det_only_precision * det_only_recall / (det_only_precision + det_only_recall)
+
+ matchedSum += detCorrect
+ det_only_matchedSum += detOnlyCorrect
+ numGlobalCareGt += numGtCare
+ numGlobalCareDet += numDetCare
+ det_only_numGlobalCareGt += det_only_numGtCare
+ det_only_numGlobalCareDet += det_only_numDetCare
+
+ perSampleMetrics[resFile] = {
+ 'precision':precision,
+ 'recall':recall,
+ 'hmean':hmean,
+ 'pairs':pairs,
+ 'AP':sampleAP,
+ 'iouMat':[] if len(detPols)>100 else iouMat.tolist(),
+ 'gtPolPoints':gtPolPoints,
+ 'detPolPoints':detPolPoints,
+ 'gtTrans':gtTrans,
+ 'detTrans':detTrans,
+ 'gtDontCare':gtDontCarePolsNum,
+ 'detDontCare':detDontCarePolsNum,
+ 'evaluationParams': evaluationParams,
+ 'evaluationLog': evaluationLog
+ }
+
+ # Compute AP
+ AP = 0
+ if evaluationParams['CONFIDENCES']:
+ AP = compute_ap(arrGlobalConfidences, arrGlobalMatches, numGlobalCareGt)
+
+ methodRecall = 0 if numGlobalCareGt == 0 else float(matchedSum)/numGlobalCareGt
+ methodPrecision = 0 if numGlobalCareDet == 0 else float(matchedSum)/numGlobalCareDet
+ methodHmean = 0 if methodRecall + methodPrecision==0 else 2* methodRecall * methodPrecision / (methodRecall + methodPrecision)
+
+ det_only_methodRecall = 0 if det_only_numGlobalCareGt == 0 else float(det_only_matchedSum)/det_only_numGlobalCareGt
+ det_only_methodPrecision = 0 if det_only_numGlobalCareDet == 0 else float(det_only_matchedSum)/det_only_numGlobalCareDet
+ det_only_methodHmean = 0 if det_only_methodRecall + det_only_methodPrecision==0 else 2* det_only_methodRecall * det_only_methodPrecision / (det_only_methodRecall + det_only_methodPrecision)
+
+ methodMetrics = r"E2E_RESULTS: precision: {}, recall: {}, hmean: {}".format(methodPrecision, methodRecall, methodHmean)
+ det_only_methodMetrics = r"DETECTION_ONLY_RESULTS: precision: {}, recall: {}, hmean: {}".format(det_only_methodPrecision, det_only_methodRecall, det_only_methodHmean)
+
+ resDict = {'calculated':True,'Message':'','e2e_method': methodMetrics, 'det_only_method': det_only_methodMetrics, 'per_sample': perSampleMetrics}
+
+
+ return resDict;
+
+
+
+def text_eval_main_ic15(det_file, gt_file, is_word_spotting):
+ global WORD_SPOTTING
+ WORD_SPOTTING = is_word_spotting
+ p = {
+ 'g': gt_file,
+ 's': det_file
+ }
+ return rrc_evaluation_funcs.main_evaluation(p,default_evaluation_params,validate_data,evaluate_method)
diff --git a/AdelaiDet/adet/evaluation/text_evaluation_all.py b/AdelaiDet/adet/evaluation/text_evaluation_all.py
new file mode 100755
index 0000000..1021d3c
--- /dev/null
+++ b/AdelaiDet/adet/evaluation/text_evaluation_all.py
@@ -0,0 +1,505 @@
+import contextlib
+import copy
+import io
+import itertools
+import json
+import logging
+import numpy as np
+import os
+import re
+import torch
+from collections import OrderedDict
+from fvcore.common.file_io import PathManager
+from pycocotools.coco import COCO
+
+from detectron2.utils import comm
+from detectron2.data import MetadataCatalog
+from detectron2.evaluation.evaluator import DatasetEvaluator
+
+import glob
+import shutil
+from shapely.geometry import Polygon, LinearRing
+from adet.evaluation import text_eval_script
+from adet.evaluation import text_eval_script_ic15
+import zipfile
+import pickle
+import editdistance
+import cv2
+class TextEvaluator():
+ """
+ Evaluate text proposals and recognition.
+ """
+
+ def __init__(self, dataset_name, cfg, distributed, output_dir=None):
+ self._tasks = ("polygon", "recognition")
+ self._distributed = distributed
+ self._output_dir = output_dir
+
+ self._cpu_device = torch.device("cpu")
+ self._logger = logging.getLogger(__name__)
+
+ self._metadata = MetadataCatalog.get(dataset_name)
+ if not hasattr(self._metadata, "json_file"):
+ raise AttributeError(
+ f"json_file was not found in MetaDataCatalog for '{dataset_name}'."
+ )
+
+ self.voc_size = cfg.MODEL.BATEXT.VOC_SIZE
+ self.use_customer_dictionary = cfg.MODEL.BATEXT.CUSTOM_DICT
+ if not self.use_customer_dictionary:
+ self.CTLABELS = [' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_','`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','{','|','}','~']
+ else:
+ with open(self.use_customer_dictionary, 'rb') as fp:
+ self.CTLABELS = pickle.load(fp)
+ assert(int(self.voc_size - 1) == len(self.CTLABELS)), "voc_size is not matched dictionary size, got {} and {}.".format(int(self.voc_size - 1), len(self.CTLABELS))
+
+ json_file = PathManager.get_local_path(self._metadata.json_file)
+ with contextlib.redirect_stdout(io.StringIO()):
+ self._coco_api = COCO(json_file)
+ self.dataset_name = dataset_name
+ # use dataset_name to decide eval_gt_path
+ self.lexicon_type = cfg.MODEL.BATEXT.EVAL_TYPE
+ if "totaltext" in dataset_name:
+ self._text_eval_gt_path = "datasets/evaluation/gt_totaltext.zip"
+ self._word_spotting = True
+ self.dataset_name = "totaltext"
+ elif "ctw1500" in dataset_name:
+ self._text_eval_gt_path = "datasets/evaluation/gt_ctw1500.zip"
+ self._word_spotting = False
+ self.dataset_name = "ctw1500"
+ elif "icdar2015" in dataset_name:
+ self._text_eval_gt_path = "datasets/evaluation/gt_icdar2015.zip"
+ self._word_spotting = False
+ self.dataset_name = "icdar2015"
+ elif "custom" in dataset_name:
+ self._text_eval_gt_path = "datasets/evaluation/gt_custom.zip"
+ self._word_spotting = False
+
+ self._text_eval_confidence = cfg.MODEL.FCOS.INFERENCE_TH_TEST
+ def reset(self):
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+ instances = output["instances"].to(self._cpu_device)
+ prediction["instances"] = self.instances_to_coco_json(instances, input)
+ self._predictions.append(prediction)
+
+ def to_eval_format(self, file_path, temp_dir="temp_det_results", cf_th=0.5):
+ def fis_ascii(s):
+ a = (ord(c) < 128 for c in s)
+ return all(a)
+
+ def de_ascii(s):
+ a = [c for c in s if ord(c) < 128]
+ outa = ''
+ for i in a:
+ outa +=i
+ return outa
+
+ with open(file_path, 'r') as f:
+ data = json.load(f)
+ with open('temp_all_det_cors.txt', 'w') as f2:
+ for ix in range(len(data)):
+ if data[ix]['score'] > 0.1:
+ outstr = '{}: '.format(data[ix]['image_id'])
+ xmin = 1000000
+ ymin = 1000000
+ xmax = 0
+ ymax = 0
+ for i in range(len(data[ix]['polys'])):
+ outstr = outstr + str(int(data[ix]['polys'][i][0])) +','+str(int(data[ix]['polys'][i][1])) +','
+ # ass = de_ascii(data[ix]['rec'])
+ ass = str(data[ix]['rec'])
+ if len(ass)>=0: #
+ outstr = outstr + str(round(data[ix]['score'], 3)) +',####'+ass+'\n'
+ f2.writelines(outstr)
+ f2.close()
+ dirn = temp_dir
+ lsc = [cf_th]
+ fres = open('temp_all_det_cors.txt', 'r').readlines()
+ for isc in lsc:
+ if not os.path.isdir(dirn):
+ os.mkdir(dirn)
+ for line in fres:
+ line = line.strip()
+ s = line.split(': ')
+ filename = '{:07d}.txt'.format(int(s[0]))
+ outName = os.path.join(dirn, filename)
+ with open(outName, 'a') as fout:
+ ptr = s[1].strip().split(',####')
+ score = ptr[0].split(',')[-1]
+ if float(score) < isc:
+ continue
+ if "icdar2015" in self.dataset_name and float(score) < 0.45:
+ continue
+ cors = ','.join(e for e in ptr[0].split(',')[:-1])
+ fout.writelines(cors+',####'+str(ptr[1])+'\n')
+ os.remove("temp_all_det_cors.txt")
+
+ def sort_detection(self, temp_dir):
+ origin_file = temp_dir
+ output_file = "final_"+temp_dir
+ output_file_full = "full_final_"+temp_dir
+ if not os.path.isdir(output_file_full):
+ os.mkdir(output_file_full)
+ if not os.path.isdir(output_file):
+ os.mkdir(output_file)
+ files = glob.glob(origin_file+'*.txt')
+ files.sort()
+ if "totaltext" in self.dataset_name:
+ if not self.lexicon_type == None:
+ lexicon_path = 'datasets/totaltext/weak_voc_new.txt'
+ lexicon_fid=open(lexicon_path, 'r')
+ pair_list = open('datasets/totaltext/weak_voc_pair_list.txt', 'r')
+ pairs = dict()
+ for line in pair_list.readlines():
+ line=line.strip()
+ word = line.split(' ')[0].upper()
+ word_gt = line[len(word)+1:]
+ pairs[word] = word_gt
+ lexicon_fid=open(lexicon_path, 'r')
+ lexicon=[]
+ for line in lexicon_fid.readlines():
+ line=line.strip()
+ lexicon.append(line)
+ elif "ctw1500" in self.dataset_name:
+ if not self.lexicon_type == None:
+ lexicon_path = 'datasets/CTW1500/weak_voc_new.txt'
+ lexicon_fid=open(lexicon_path, 'r')
+ pair_list = open('datasets/CTW1500/weak_voc_pair_list.txt', 'r')
+ pairs = dict()
+ lexicon_fid=open(lexicon_path, 'r')
+ lexicon=[]
+ for line in lexicon_fid.readlines():
+ line=line.strip()
+ lexicon.append(line)
+ pairs[line.upper()] = line
+ elif "icdar2015" in self.dataset_name:
+ if self.lexicon_type==1:
+ # generic lexicon
+ lexicon_path = 'datasets/icdar2015/GenericVocabulary_new.txt'
+ lexicon_fid=open(lexicon_path, 'r')
+ pair_list = open('datasets/icdar2015/GenericVocabulary_pair_list.txt', 'r')
+ pairs = dict()
+ for line in pair_list.readlines():
+ line=line.strip()
+ word = line.split(' ')[0].upper()
+ word_gt = line[len(word)+1:]
+ pairs[word] = word_gt
+ lexicon_fid=open(lexicon_path, 'r')
+ lexicon=[]
+ for line in lexicon_fid.readlines():
+ line=line.strip()
+ lexicon.append(line)
+ if self.lexicon_type==2:
+ # weak lexicon
+ lexicon_path = 'datasets/icdar2015/ch4_test_vocabulary_new.txt'
+ lexicon_fid=open(lexicon_path, 'r')
+ pair_list = open('datasets/icdar2015/ch4_test_vocabulary_pair_list.txt', 'r')
+ pairs = dict()
+ for line in pair_list.readlines():
+ line=line.strip()
+ word = line.split(' ')[0].upper()
+ word_gt = line[len(word)+1:]
+ pairs[word] = word_gt
+ lexicon_fid=open(lexicon_path, 'r')
+ lexicon=[]
+ for line in lexicon_fid.readlines():
+ line=line.strip()
+ lexicon.append(line)
+
+ def find_match_word(rec_str, pairs, lexicon=None):
+ rec_str = rec_str.upper()
+ dist_min = 100
+ dist_min_pre = 100
+ match_word = ''
+ match_dist = 100
+ for word in lexicon:
+ word = word.upper()
+ ed = editdistance.eval(rec_str, word)
+ length_dist = abs(len(word) - len(rec_str))
+ dist = ed
+ if dist ps[0][1]:
+ px1 = ps[0][0] * scale + start_x
+ py1 = ps[0][1] * scale + start_y
+ px4 = ps[1][0] * scale + start_x
+ py4 = ps[1][1] * scale + start_y
+ else:
+ px1 = ps[1][0] * scale + start_x
+ py1 = ps[1][1] * scale + start_y
+ px4 = ps[0][0] * scale + start_x
+ py4 = ps[0][1] * scale + start_y
+ if ps[3][1] > ps[2][1]:
+ px2 = ps[2][0] * scale + start_x
+ py2 = ps[2][1] * scale + start_y
+ px3 = ps[3][0] * scale + start_x
+ py3 = ps[3][1] * scale + start_y
+ else:
+ px2 = ps[3][0] * scale + start_x
+ py2 = ps[3][1] * scale + start_y
+ px3 = ps[2][0] * scale + start_x
+ py3 = ps[2][1] * scale + start_y
+
+ px1 = min(max(px1, 1), image_width - 1)
+ px2 = min(max(px2, 1), image_width - 1)
+ px3 = min(max(px3, 1), image_width - 1)
+ px4 = min(max(px4, 1), image_width - 1)
+ py1 = min(max(py1, 1), image_height - 1)
+ py2 = min(max(py2, 1), image_height - 1)
+ py3 = min(max(py3, 1), image_height - 1)
+ py4 = min(max(py4, 1), image_height - 1)
+ return [px1, py1, px2, py2, px3, py3, px4, py4]
diff --git a/AdelaiDet/adet/layers/__init__.py b/AdelaiDet/adet/layers/__init__.py
new file mode 100755
index 0000000..ca175b5
--- /dev/null
+++ b/AdelaiDet/adet/layers/__init__.py
@@ -0,0 +1,10 @@
+from .deform_conv import DFConv2d
+from .ml_nms import ml_nms
+from .iou_loss import IOULoss
+from .conv_with_kaiming_uniform import conv_with_kaiming_uniform
+from .bezier_align import BezierAlign
+from .def_roi_align import DefROIAlign
+from .naive_group_norm import NaiveGroupNorm
+from .gcn import GCN
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
\ No newline at end of file
diff --git a/AdelaiDet/adet/layers/bezier_align.py b/AdelaiDet/adet/layers/bezier_align.py
new file mode 100755
index 0000000..435adf7
--- /dev/null
+++ b/AdelaiDet/adet/layers/bezier_align.py
@@ -0,0 +1,97 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from torch import nn
+from torch.autograd import Function
+from torch.autograd.function import once_differentiable
+from torch.nn.modules.utils import _pair
+
+from adet import _C
+
+
+class _BezierAlign(Function):
+ @staticmethod
+ def forward(ctx, input, roi, output_size, spatial_scale, sampling_ratio, aligned):
+ ctx.save_for_backward(roi)
+ ctx.output_size = _pair(output_size)
+ ctx.spatial_scale = spatial_scale
+ ctx.sampling_ratio = sampling_ratio
+ ctx.input_shape = input.size()
+ ctx.aligned = aligned
+ output = _C.bezier_align_forward(
+ input, roi, spatial_scale, output_size[0], output_size[1], sampling_ratio, aligned
+ )
+ return output
+
+ @staticmethod
+ @once_differentiable
+ def backward(ctx, grad_output):
+ rois, = ctx.saved_tensors
+ output_size = ctx.output_size
+ spatial_scale = ctx.spatial_scale
+ sampling_ratio = ctx.sampling_ratio
+ bs, ch, h, w = ctx.input_shape
+ grad_input = _C.bezier_align_backward(
+ grad_output,
+ rois,
+ spatial_scale,
+ output_size[0],
+ output_size[1],
+ bs,
+ ch,
+ h,
+ w,
+ sampling_ratio,
+ ctx.aligned,
+ )
+ return grad_input, None, None, None, None, None
+
+
+bezier_align = _BezierAlign.apply
+
+
+class BezierAlign(nn.Module):
+ def __init__(self, output_size, spatial_scale, sampling_ratio, aligned=True):
+ """
+ Args:
+ output_size (tuple): h, w
+ spatial_scale (float): scale the input boxes by this number
+ sampling_ratio (int): number of inputs samples to take for each output
+ sample. 0 to take samples densely.
+ aligned (bool): if False, use the legacy implementation in
+ Detectron. If True, align the results more perfectly.
+
+ Note:
+ The meaning of aligned=True:
+
+ With `aligned=True`,
+ we first appropriately scale the ROI and then shift it by -0.5
+ prior to calling bezier_align. This produces the correct neighbors; see
+ adet/tests/test_bezier_align.py for verification.
+
+ The difference does not make a difference to the model's performance if
+ ROIAlign is used together with conv layers.
+ """
+ super(BezierAlign, self).__init__()
+ self.output_size = output_size
+ self.spatial_scale = spatial_scale
+ self.sampling_ratio = sampling_ratio
+ self.aligned = aligned
+
+ def forward(self, input, rois):
+ """
+ Args:
+ input: NCHW images
+ rois: Bx17 boxes. First column is the index into N. The other 16 columns are [xy]x8.
+ """
+ assert rois.dim() == 2 and rois.size(1) == 17
+ return bezier_align(
+ input, rois, self.output_size, self.spatial_scale, self.sampling_ratio, self.aligned
+ )
+
+ def __repr__(self):
+ tmpstr = self.__class__.__name__ + "("
+ tmpstr += "output_size=" + str(self.output_size)
+ tmpstr += ", spatial_scale=" + str(self.spatial_scale)
+ tmpstr += ", sampling_ratio=" + str(self.sampling_ratio)
+ tmpstr += ", aligned=" + str(self.aligned)
+ tmpstr += ")"
+ return tmpstr
diff --git a/AdelaiDet/adet/layers/conv_with_kaiming_uniform.py b/AdelaiDet/adet/layers/conv_with_kaiming_uniform.py
new file mode 100755
index 0000000..88cb682
--- /dev/null
+++ b/AdelaiDet/adet/layers/conv_with_kaiming_uniform.py
@@ -0,0 +1,52 @@
+from torch import nn
+
+from detectron2.layers import Conv2d
+from .deform_conv import DFConv2d
+from detectron2.layers.batch_norm import get_norm
+
+
+def conv_with_kaiming_uniform(
+ norm=None, activation=None,
+ use_deformable=False, use_sep=False):
+ def make_conv(
+ in_channels, out_channels, kernel_size, stride=1, dilation=1
+ ):
+ if use_deformable:
+ conv_func = DFConv2d
+ else:
+ conv_func = Conv2d
+ if use_sep:
+ assert in_channels == out_channels
+ groups = in_channels
+ else:
+ groups = 1
+ conv = conv_func(
+ in_channels,
+ out_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=dilation * (kernel_size - 1) // 2,
+ dilation=dilation,
+ groups=groups,
+ bias=(norm is None)
+ )
+ if not use_deformable:
+ # Caffe2 implementation uses XavierFill, which in fact
+ # corresponds to kaiming_uniform_ in PyTorch
+ nn.init.kaiming_uniform_(conv.weight, a=1)
+ if norm is None:
+ nn.init.constant_(conv.bias, 0)
+ module = [conv,]
+ if norm is not None and len(norm) > 0:
+ if norm == "GN":
+ norm_module = nn.GroupNorm(32, out_channels)
+ else:
+ norm_module = get_norm(norm, out_channels)
+ module.append(norm_module)
+ if activation is not None:
+ module.append(nn.ReLU(inplace=True))
+ if len(module) > 1:
+ return nn.Sequential(*module)
+ return conv
+
+ return make_conv
diff --git a/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign.h b/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign.h
new file mode 100755
index 0000000..2808f28
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign.h
@@ -0,0 +1,130 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+#pragma once
+#include
+
+namespace adet {
+
+at::Tensor BezierAlign_forward_cpu(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ bool aligned);
+
+at::Tensor BezierAlign_backward_cpu(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ bool aligned);
+
+#ifdef WITH_CUDA
+at::Tensor BezierAlign_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ bool aligned);
+
+at::Tensor BezierAlign_backward_cuda(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ bool aligned);
+#endif
+
+// Interface for Python
+inline at::Tensor BezierAlign_forward(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ bool aligned) {
+ if (input.type().is_cuda()) {
+#ifdef WITH_CUDA
+ return BezierAlign_forward_cuda(
+ input,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ aligned);
+#else
+ AT_ERROR("Not compiled with GPU support");
+#endif
+ }
+ return BezierAlign_forward_cpu(
+ input,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ aligned);
+}
+
+inline at::Tensor BezierAlign_backward(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ bool aligned) {
+ if (grad.type().is_cuda()) {
+#ifdef WITH_CUDA
+ return BezierAlign_backward_cuda(
+ grad,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio,
+ aligned);
+#else
+ AT_ERROR("Not compiled with GPU support");
+#endif
+ }
+ return BezierAlign_backward_cpu(
+ grad,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio,
+ aligned);
+}
+
+} // namespace detectron2
diff --git a/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign_cpu.cpp b/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign_cpu.cpp
new file mode 100755
index 0000000..1809509
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign_cpu.cpp
@@ -0,0 +1,567 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+#include
+#include "BezierAlign.h"
+
+namespace {
+
+// implementation taken from Caffe2
+template
+struct PreCalc {
+ int pos1;
+ int pos2;
+ int pos3;
+ int pos4;
+ T w1;
+ T w2;
+ T w3;
+ T w4;
+};
+
+
+template
+T bezier_curve(
+ const T p0,
+ const T p1,
+ const T p2,
+ const T p3,
+ const T u) {
+ return (
+ (1. - u) * (1. - u) * (1. - u) * p0
+ + 3. * u * (1. - u) * (1. - u) * p1
+ + 3. * u * u * (1. - u) * p2
+ + u * u * u * p3);
+}
+
+
+template
+void pre_calc_for_bilinear_interpolate(
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int iy_upper,
+ const int ix_upper,
+ T p0_x, T p0_y, T p1_x, T p1_y,
+ T p2_x, T p2_y, T p3_x, T p3_y,
+ T p4_x, T p4_y, T p5_x, T p5_y,
+ T p6_x, T p6_y, T p7_x, T p7_y,
+ T bin_size_h,
+ T bin_size_w,
+ int roi_bin_grid_h,
+ int roi_bin_grid_w,
+ std::vector>& pre_calc) {
+ int pre_calc_index = 0;
+ for (int ph = 0; ph < pooled_height; ph++) {
+ for (int pw = 0; pw < pooled_width; pw++) {
+ // compute the coords
+ const T u = pw / static_cast(pooled_width);
+ const T v = ph / static_cast(pooled_height);
+ const T x0 = bezier_curve(p0_x, p1_x, p2_x, p3_x, u);
+ const T y0 = bezier_curve(p0_y, p1_y, p2_y, p3_y, u);
+ const T x1 = bezier_curve(p4_x, p5_x, p6_x, p7_x, u);
+ const T y1 = bezier_curve(p4_y, p5_y, p6_y, p7_y, u);
+ const T x_center = x1 * v + x0 * (1. - v);
+ const T y_center = y1 * v + y0 * (1. - v);
+ for (int iy = 0; iy < iy_upper; iy++) {
+ const T yy = y_center - (T)0.5 * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < ix_upper; ix++) {
+ const T xx = x_center - (T)0.5 * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ T x = xx;
+ T y = yy;
+ // deal with: inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ PreCalc pc;
+ pc.pos1 = 0;
+ pc.pos2 = 0;
+ pc.pos3 = 0;
+ pc.pos4 = 0;
+ pc.w1 = 0;
+ pc.w2 = 0;
+ pc.w3 = 0;
+ pc.w4 = 0;
+ pre_calc[pre_calc_index] = pc;
+ pre_calc_index += 1;
+ continue;
+ }
+
+ if (y <= 0) {
+ y = 0;
+ }
+ if (x <= 0) {
+ x = 0;
+ }
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ // save weights and indices
+ PreCalc pc;
+ pc.pos1 = y_low * width + x_low;
+ pc.pos2 = y_low * width + x_high;
+ pc.pos3 = y_high * width + x_low;
+ pc.pos4 = y_high * width + x_high;
+ pc.w1 = w1;
+ pc.w2 = w2;
+ pc.w3 = w3;
+ pc.w4 = w4;
+ pre_calc[pre_calc_index] = pc;
+
+ pre_calc_index += 1;
+ }
+ }
+ }
+ }
+}
+
+template
+void BezierAlignForward(
+ const int nthreads,
+ const T* input,
+ const T& spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* rois,
+ T* output,
+ bool aligned) {
+ int n_rois = nthreads / channels / pooled_width / pooled_height;
+ // (n, c, ph, pw) is an element in the pooled output
+ // can be parallelized using omp
+ // #pragma omp parallel for num_threads(32)
+ for (int n = 0; n < n_rois; n++) {
+ int index_n = n * channels * pooled_width * pooled_height;
+
+ // beziers have size Nx(1+8*2) = Nx17
+ const T* offset_rois = rois + n * 17;
+ int roi_batch_ind = offset_rois[0];
+
+ T offset = aligned ? (T)0.5 : (T)0.0;
+ // Do not use rounding; this implementation detail is critical
+ T p0_x = offset_rois[1] * spatial_scale;
+ T p0_y = offset_rois[2] * spatial_scale;
+ T p1_x = offset_rois[3] * spatial_scale;
+ T p1_y = offset_rois[4] * spatial_scale;
+ T p2_x = offset_rois[5] * spatial_scale;
+ T p2_y = offset_rois[6] * spatial_scale;
+ T p3_x = offset_rois[7] * spatial_scale;
+ T p3_y = offset_rois[8] * spatial_scale;
+ T p4_x = offset_rois[15] * spatial_scale;
+ T p4_y = offset_rois[16] * spatial_scale;
+ T p5_x = offset_rois[13] * spatial_scale;
+ T p5_y = offset_rois[14] * spatial_scale;
+ T p6_x = offset_rois[11] * spatial_scale;
+ T p6_y = offset_rois[12] * spatial_scale;
+ T p7_x = offset_rois[9 ] * spatial_scale;
+ T p7_y = offset_rois[10] * spatial_scale;
+
+ T roi_width = std::max(std::abs(p0_x - p3_x), std::abs(p4_x - p7_x));
+ T roi_height = std::max(std::abs(p0_y - p3_y), std::abs(p4_y - p7_y));
+ if (aligned) {
+ AT_ASSERTM(
+ roi_width >= 0 && roi_height >= 0,
+ "ROIs in ROIAlign cannot have non-negative size!");
+ } else { // for backward-compatibility only
+ roi_width = std::max(roi_width, (T)1.);
+ roi_height = std::max(roi_height, (T)1.);
+ }
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // We do average (integral) pooling inside a bin
+ // When the grid is empty, output zeros == 0/1, instead of NaN.
+ const T count = std::max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ // we want to precalculate indices and weights shared by all channels,
+ // this is the key point of optimization
+ std::vector> pre_calc(
+ roi_bin_grid_h * roi_bin_grid_w * pooled_width * pooled_height);
+ pre_calc_for_bilinear_interpolate(
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ roi_bin_grid_h,
+ roi_bin_grid_w,
+ p0_x, p0_y, p1_x, p1_y,
+ p2_x, p2_y, p3_x, p3_y,
+ p4_x, p4_y, p5_x, p5_y,
+ p6_x, p6_y, p7_x, p7_y,
+ bin_size_h,
+ bin_size_w,
+ roi_bin_grid_h,
+ roi_bin_grid_w,
+ pre_calc);
+
+ for (int c = 0; c < channels; c++) {
+ int index_n_c = index_n + c * pooled_width * pooled_height;
+ const T* offset_input =
+ input + (roi_batch_ind * channels + c) * height * width;
+ int pre_calc_index = 0;
+
+ for (int ph = 0; ph < pooled_height; ph++) {
+ for (int pw = 0; pw < pooled_width; pw++) {
+ int index = index_n_c + ph * pooled_width + pw;
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) {
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ PreCalc pc = pre_calc[pre_calc_index];
+ output_val += pc.w1 * offset_input[pc.pos1] +
+ pc.w2 * offset_input[pc.pos2] +
+ pc.w3 * offset_input[pc.pos3] + pc.w4 * offset_input[pc.pos4];
+
+ pre_calc_index += 1;
+ }
+ }
+ output_val /= count;
+
+ output[index] = output_val;
+ } // for pw
+ } // for ph
+ } // for c
+ } // for n
+}
+
+template
+void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high,
+ const int index /* index for debug only*/) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y <= 0)
+ y = 0;
+ if (x <= 0)
+ x = 0;
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = input[y_low * width + x_low];
+ // T v2 = input[y_low * width + x_high];
+ // T v3 = input[y_high * width + x_low];
+ // T v4 = input[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+template
+inline void add(T* address, const T& val) {
+ *address += val;
+}
+
+template
+void BezierAlignBackward(
+ const int nthreads,
+ const T* grad_output,
+ const T& spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* grad_input,
+ const T* rois,
+ const int n_stride,
+ const int c_stride,
+ const int h_stride,
+ const int w_stride,
+ bool aligned) {
+ for (int index = 0; index < nthreads; index++) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* offset_rois = rois + n * 17;
+ int roi_batch_ind = offset_rois[0];
+
+ // Do not use rounding; this implementation detail is critical
+ T offset = aligned ? (T)0.5 : (T)0.0;
+ T p0_x = offset_rois[1] * spatial_scale;
+ T p0_y = offset_rois[2] * spatial_scale;
+ T p1_x = offset_rois[3] * spatial_scale;
+ T p1_y = offset_rois[4] * spatial_scale;
+ T p2_x = offset_rois[5] * spatial_scale;
+ T p2_y = offset_rois[6] * spatial_scale;
+ T p3_x = offset_rois[7] * spatial_scale;
+ T p3_y = offset_rois[8] * spatial_scale;
+ T p4_x = offset_rois[15] * spatial_scale;
+ T p4_y = offset_rois[16] * spatial_scale;
+ T p5_x = offset_rois[13] * spatial_scale;
+ T p5_y = offset_rois[14] * spatial_scale;
+ T p6_x = offset_rois[11] * spatial_scale;
+ T p6_y = offset_rois[12] * spatial_scale;
+ T p7_x = offset_rois[9 ] * spatial_scale;
+ T p7_y = offset_rois[10] * spatial_scale;
+
+ // compute the coords
+ const T u = pw / static_cast(pooled_width);
+ const T v = ph / static_cast(pooled_height);
+ const T x0 = bezier_curve(p0_x, p1_x, p2_x, p3_x, u);
+ const T y0 = bezier_curve(p0_y, p1_y, p2_y, p3_y, u);
+ const T x1 = bezier_curve(p4_x, p5_x, p6_x, p7_x, u);
+ const T y1 = bezier_curve(p4_y, p5_y, p6_y, p7_y, u);
+ const T x_center = x1 * v + x0 * (1. - v) - offset;
+ const T y_center = y1 * v + y0 * (1. - v) - offset;
+
+ T roi_width = std::max(std::abs(p0_x - p3_x), std::abs(p4_x - p7_x));
+ T roi_height = std::max(std::abs(p0_y - p3_y), std::abs(p4_y - p7_y));
+ if (aligned) {
+ AT_ASSERTM(
+ roi_width >= 0 && roi_height >= 0,
+ "ROIs in ROIAlign do not have non-negative size!");
+ } else { // for backward-compatibility only
+ roi_width = std::max(roi_width, (T)1.);
+ roi_height = std::max(roi_height, (T)1.);
+ }
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_grad_input =
+ grad_input + ((roi_batch_ind * channels + c) * height * width);
+
+ int output_offset = n * n_stride + c * c_stride;
+ const T* offset_grad_output = grad_output + output_offset;
+ const T grad_output_this_bin =
+ offset_grad_output[ph * h_stride + pw * w_stride];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) {
+ const T y = y_center - (T)0.5 * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T x = x_center - (T)0.5 * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height,
+ width,
+ y,
+ x,
+ w1,
+ w2,
+ w3,
+ w4,
+ x_low,
+ x_high,
+ y_low,
+ y_high,
+ index);
+
+ T g1 = grad_output_this_bin * w1 / count;
+ T g2 = grad_output_this_bin * w2 / count;
+ T g3 = grad_output_this_bin * w3 / count;
+ T g4 = grad_output_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ // atomic add is not needed for now since it is single threaded
+ add(offset_grad_input + y_low * width + x_low, static_cast(g1));
+ add(offset_grad_input + y_low * width + x_high, static_cast(g2));
+ add(offset_grad_input + y_high * width + x_low, static_cast(g3));
+ add(offset_grad_input + y_high * width + x_high, static_cast(g4));
+ } // if
+ } // ix
+ } // iy
+ } // for
+} // BezierAlignBackward
+
+} // namespace
+
+namespace adet {
+
+at::Tensor BezierAlign_forward_cpu(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ bool aligned) {
+ AT_ASSERTM(input.device().is_cpu(), "input must be a CPU tensor");
+ AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor");
+
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "BezierAlign_forward_cpu";
+ at::checkAllSameType(c, {input_t, rois_t});
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ at::Tensor output = at::zeros(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+
+ if (output.numel() == 0)
+ return output;
+
+ AT_DISPATCH_FLOATING_TYPES_AND_HALF(input.type(), "BezierAlign_forward", [&] {
+ BezierAlignForward(
+ output_size,
+ input.contiguous().data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois.contiguous().data_ptr(),
+ output.data_ptr(),
+ aligned);
+ });
+ return output;
+}
+
+at::Tensor BezierAlign_backward_cpu(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ bool aligned) {
+ AT_ASSERTM(grad.device().is_cpu(), "grad must be a CPU tensor");
+ AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "BezierAlign_backward_cpu";
+ at::checkAllSameType(c, {grad_t, rois_t});
+
+ at::Tensor grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ // handle possibly empty gradients
+ if (grad.numel() == 0) {
+ return grad_input;
+ }
+
+ // get stride values to ensure indexing into gradients is correct.
+ int n_stride = grad.stride(0);
+ int c_stride = grad.stride(1);
+ int h_stride = grad.stride(2);
+ int w_stride = grad.stride(3);
+
+ AT_DISPATCH_FLOATING_TYPES_AND_HALF(grad.type(), "BezierAlign_forward", [&] {
+ BezierAlignBackward(
+ grad.numel(),
+ grad.contiguous().data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ grad_input.data_ptr(),
+ rois.contiguous().data_ptr(),
+ n_stride,
+ c_stride,
+ h_stride,
+ w_stride,
+ aligned);
+ });
+ return grad_input;
+}
+
+} // namespace adet
diff --git a/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign_cuda.cu b/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign_cuda.cu
new file mode 100755
index 0000000..9581c9f
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/BezierAlign/BezierAlign_cuda.cu
@@ -0,0 +1,491 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+#include
+#include
+#include
+#include
+
+// TODO make it in a common file
+#define CUDA_1D_KERNEL_LOOP(i, n) \
+ for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \
+ i += blockDim.x * gridDim.x)
+
+
+template
+__device__ T bezier_curve(
+ const T p0,
+ const T p1,
+ const T p2,
+ const T p3,
+ const T u) {
+ return (
+ (1. - u) * (1. - u) * (1. - u) * p0
+ + 3. * u * (1. - u) * (1. - u) * p1
+ + 3. * u * u * (1. - u) * p2
+ + u * u * u * p3);
+}
+
+template
+__device__ T bilinear_interpolate(
+ const T* bottom_data,
+ const int height,
+ const int width,
+ T y,
+ T x,
+ const int index /* index for debug only*/) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ return 0;
+ }
+
+ if (y <= 0)
+ y = 0;
+ if (x <= 0)
+ x = 0;
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ // do bilinear interpolation
+ T v1 = bottom_data[y_low * width + x_low];
+ T v2 = bottom_data[y_low * width + x_high];
+ T v3 = bottom_data[y_high * width + x_low];
+ T v4 = bottom_data[y_high * width + x_high];
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ return val;
+}
+
+template
+__global__ void BezierAlignForward(
+ const int nthreads,
+ const T* bottom_data,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* bottom_rois, // bottom rois contains the bezier curve
+ T* top_data,
+ bool aligned) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ // beziers have size Nx(1+8*2) = Nx17
+ const T* offset_bottom_rois = bottom_rois + n * 17;
+ int roi_batch_ind = offset_bottom_rois[0];
+
+ // Do not use rounding; this implementation detail is critical
+ T offset = aligned ? (T)0.5 : (T)0.0;
+
+ // TODO: avoid this by using parallel annotation, for good
+ T p0_x = offset_bottom_rois[1 ] * spatial_scale;
+ T p0_y = offset_bottom_rois[2 ] * spatial_scale;
+ T p1_x = offset_bottom_rois[3 ] * spatial_scale;
+ T p1_y = offset_bottom_rois[4 ] * spatial_scale;
+ T p2_x = offset_bottom_rois[5 ] * spatial_scale;
+ T p2_y = offset_bottom_rois[6 ] * spatial_scale;
+ T p3_x = offset_bottom_rois[7 ] * spatial_scale;
+ T p3_y = offset_bottom_rois[8 ] * spatial_scale;
+ T p4_x = offset_bottom_rois[15] * spatial_scale;
+ T p4_y = offset_bottom_rois[16] * spatial_scale;
+ T p5_x = offset_bottom_rois[13] * spatial_scale;
+ T p5_y = offset_bottom_rois[14] * spatial_scale;
+ T p6_x = offset_bottom_rois[11] * spatial_scale;
+ T p6_y = offset_bottom_rois[12] * spatial_scale;
+ T p7_x = offset_bottom_rois[9 ] * spatial_scale;
+ T p7_y = offset_bottom_rois[10] * spatial_scale;
+
+ // compute the coords
+ const T u = pw / static_cast(pooled_width);
+ const T v = ph / static_cast(pooled_height);
+ const T x0 = bezier_curve(p0_x, p1_x, p2_x, p3_x, u);
+ const T y0 = bezier_curve(p0_y, p1_y, p2_y, p3_y, u);
+ const T x1 = bezier_curve(p4_x, p5_x, p6_x, p7_x, u);
+ const T y1 = bezier_curve(p4_y, p5_y, p6_y, p7_y, u);
+ const T x_center = x1 * v + x0 * (1. - v) - offset;
+ const T y_center = y1 * v + y0 * (1. - v) - offset;
+
+ T roi_width = max(abs(p0_x - p3_x), abs(p4_x - p7_x));
+ T roi_height = max(abs(p0_y - p3_y), abs(p4_y - p7_y));
+ if (!aligned) { // for backward-compatibility only
+ roi_width = max(roi_width, (T)1.);
+ roi_height = max(roi_height, (T)1.);
+ }
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ const T* offset_bottom_data =
+ bottom_data + (roi_batch_ind * channels + c) * height * width;
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // We do average (integral) pooling inside a bin
+ // When the grid is empty, output zeros == 0/1, instead of NaN.
+ const T count = max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T y = y_center - (T)0.5 * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T x = x_center - (T)0.5 * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ T val = bilinear_interpolate(
+ offset_bottom_data, height, width, y, x, index);
+ output_val += val;
+ }
+ }
+ output_val /= count;
+
+ top_data[index] = output_val;
+ }
+}
+
+template
+__device__ void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high,
+ const int index /* index for debug only*/) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y <= 0)
+ y = 0;
+ if (x <= 0)
+ x = 0;
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = bottom_data[y_low * width + x_low];
+ // T v2 = bottom_data[y_low * width + x_high];
+ // T v3 = bottom_data[y_high * width + x_low];
+ // T v4 = bottom_data[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+template
+__global__ void BezierAlignBackwardFeature(
+ const int nthreads,
+ const T* top_diff,
+ const int num_rois,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* bottom_diff,
+ const T* bottom_rois,
+ bool aligned) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ // beziers have size Nx(1+8*2) = Nx17
+ const T* offset_bottom_rois = bottom_rois + n * 17;
+ int roi_batch_ind = offset_bottom_rois[0];
+
+ // Do not use rounding; this implementation detail is critical
+ T offset = aligned ? (T)0.5 : (T)0.0;
+ T p0_x = offset_bottom_rois[1 ] * spatial_scale;
+ T p0_y = offset_bottom_rois[2 ] * spatial_scale;
+ T p1_x = offset_bottom_rois[3 ] * spatial_scale;
+ T p1_y = offset_bottom_rois[4 ] * spatial_scale;
+ T p2_x = offset_bottom_rois[5 ] * spatial_scale;
+ T p2_y = offset_bottom_rois[6 ] * spatial_scale;
+ T p3_x = offset_bottom_rois[7 ] * spatial_scale;
+ T p3_y = offset_bottom_rois[8 ] * spatial_scale;
+ T p4_x = offset_bottom_rois[15] * spatial_scale;
+ T p4_y = offset_bottom_rois[16] * spatial_scale;
+ T p5_x = offset_bottom_rois[13] * spatial_scale;
+ T p5_y = offset_bottom_rois[14] * spatial_scale;
+ T p6_x = offset_bottom_rois[11] * spatial_scale;
+ T p6_y = offset_bottom_rois[12] * spatial_scale;
+ T p7_x = offset_bottom_rois[9 ] * spatial_scale;
+ T p7_y = offset_bottom_rois[10] * spatial_scale;
+
+ // compute the coords
+ const T u = pw / static_cast(pooled_width);
+ const T v = ph / static_cast(pooled_height);
+ const T x0 = bezier_curve(p0_x, p1_x, p2_x, p3_x, u);
+ const T y0 = bezier_curve(p0_y, p1_y, p2_y, p3_y, u);
+ const T x1 = bezier_curve(p4_x, p5_x, p6_x, p7_x, u);
+ const T y1 = bezier_curve(p4_y, p5_y, p6_y, p7_y, u);
+ const T x_center = x1 * v + x0 * (1. - v) - offset;
+ const T y_center = y1 * v + y0 * (1. - v) - offset;
+
+ T roi_width = max(abs(p0_x - p3_x), abs(p4_x - p7_x));
+ T roi_height = max(abs(p0_y - p3_y), abs(p4_y - p7_y));
+ if (!aligned) { // for backward-compatibility only
+ roi_width = max(roi_width, (T)1.);
+ roi_height = max(roi_height, (T)1.);
+ }
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_bottom_diff =
+ bottom_diff + (roi_batch_ind * channels + c) * height * width;
+
+ int top_offset = (n * channels + c) * pooled_height * pooled_width;
+ const T* offset_top_diff = top_diff + top_offset;
+ const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T y = y_center - (T)0.5 * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T x = x_center - (T)0.5 * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height,
+ width,
+ y,
+ x,
+ w1,
+ w2,
+ w3,
+ w4,
+ x_low,
+ x_high,
+ y_low,
+ y_high,
+ index);
+
+ T g1 = top_diff_this_bin * w1 / count;
+ T g2 = top_diff_this_bin * w2 / count;
+ T g3 = top_diff_this_bin * w3 / count;
+ T g4 = top_diff_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_low, static_cast(g1));
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_high, static_cast(g2));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_low, static_cast(g3));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_high, static_cast(g4));
+ } // if
+ } // ix
+ } // iy
+ } // CUDA_1D_KERNEL_LOOP
+} // RoIAlignBackward
+
+namespace adet {
+
+at::Tensor BezierAlign_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ bool aligned) {
+ AT_ASSERTM(input.device().is_cuda(), "input must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlign_forward_cuda";
+ at::checkAllSameGPU(c, {input_t, rois_t});
+ at::checkAllSameType(c, {input_t, rois_t});
+ at::cuda::CUDAGuard device_guard(input.device());
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ auto output = at::empty(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(output_size), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ if (output.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+ }
+
+ AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), "BezierAlign_forward", [&] {
+ BezierAlignForward<<>>(
+ output_size,
+ input.contiguous().data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois.contiguous().data_ptr(),
+ output.data_ptr(),
+ aligned);
+ });
+ cudaDeviceSynchronize();
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+}
+
+// TODO remove the dependency on input and use instead its sizes -> save memory
+at::Tensor BezierAlign_backward_cuda(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ bool aligned) {
+ AT_ASSERTM(grad.device().is_cuda(), "grad must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2};
+ at::CheckedFrom c = "ROIAlign_backward_cuda";
+ at::checkAllSameGPU(c, {grad_t, rois_t});
+ at::checkAllSameType(c, {grad_t, rois_t});
+ at::cuda::CUDAGuard device_guard(grad.device());
+
+ auto num_rois = rois.size(0);
+ auto grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(grad.numel()), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ // handle possibly empty gradients
+ if (grad.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return grad_input;
+ }
+
+ AT_DISPATCH_FLOATING_TYPES(grad.scalar_type(), "BezierAlign_backward", [&] {
+ BezierAlignBackwardFeature<<>>(
+ grad.numel(),
+ grad.contiguous().data_ptr(),
+ num_rois,
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ grad_input.data_ptr(),
+ rois.contiguous().data_ptr(),
+ aligned);
+ });
+ AT_CUDA_CHECK(cudaGetLastError());
+ return grad_input;
+}
+
+} // namespace detectron2
diff --git a/AdelaiDet/adet/layers/csrc/DefROIAlign/DefROIAlign.h b/AdelaiDet/adet/layers/csrc/DefROIAlign/DefROIAlign.h
new file mode 100755
index 0000000..bee6fcc
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/DefROIAlign/DefROIAlign.h
@@ -0,0 +1,107 @@
+#pragma once
+#include
+
+namespace adet {
+
+#ifdef WITH_CUDA
+at::Tensor DefROIAlign_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const at::Tensor& offsets, // def added
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const float trans_std, // def added
+ bool aligned);
+
+at::Tensor DefROIAlign_backward_cuda(
+ const at::Tensor& input, // def added
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const at::Tensor& offsets, // def added
+ const at::Tensor& grad_offsets, // def added
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ const float trans_std, // def added
+ bool aligned);
+#endif
+
+// Interface for Python
+inline at::Tensor DefROIAlign_forward(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const at::Tensor& offsets, // def added
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const float trans_std, // def added
+ bool aligned) {
+ if (input.type().is_cuda()) {
+#ifdef WITH_CUDA
+ return DefROIAlign_forward_cuda(
+ input,
+ rois,
+ offsets,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ trans_std,
+ aligned);
+#else
+ AT_ERROR("Not compiled with GPU support");
+#endif
+ }
+ AT_ERROR("CPU version not supported");
+}
+
+inline at::Tensor DefROIAlign_backward(
+ const at::Tensor& input, // def added
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const at::Tensor& offsets, // def added
+ const at::Tensor& grad_offsets, // def added
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ const float trans_std, // def added
+ bool aligned) {
+ if (grad.type().is_cuda()) {
+#ifdef WITH_CUDA
+ return DefROIAlign_backward_cuda(
+ input, // def added
+ grad,
+ rois,
+ offsets, // def added
+ grad_offsets, // def added
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio,
+ trans_std, // def added
+ aligned);
+#else
+ AT_ERROR("Not compiled with GPU support");
+#endif
+ }
+ AT_ERROR("CPU version not supported");
+}
+
+} // namespace adet
diff --git a/AdelaiDet/adet/layers/csrc/DefROIAlign/DefROIAlign_cuda.cu b/AdelaiDet/adet/layers/csrc/DefROIAlign/DefROIAlign_cuda.cu
new file mode 100755
index 0000000..65c68fc
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/DefROIAlign/DefROIAlign_cuda.cu
@@ -0,0 +1,476 @@
+#include
+#include
+#include
+#include
+
+// TODO make it in a common file
+#define CUDA_1D_KERNEL_LOOP(i, n) \
+ for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \
+ i += blockDim.x * gridDim.x)
+
+
+template
+__device__ T bilinear_interpolate(
+ const T* bottom_data,
+ const int height,
+ const int width,
+ T y,
+ T x,
+ const int index /* index for debug only*/) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ return 0;
+ }
+
+ if (y <= 0)
+ y = 0;
+ if (x <= 0)
+ x = 0;
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ // do bilinear interpolation
+ T v1 = bottom_data[y_low * width + x_low];
+ T v2 = bottom_data[y_low * width + x_high];
+ T v3 = bottom_data[y_high * width + x_low];
+ T v4 = bottom_data[y_high * width + x_high];
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ return val;
+}
+
+template
+__global__ void DefRoIAlignForward(
+ const int nthreads,
+ const T* bottom_data,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* bottom_rois,
+ const T* bottom_trans, // def added
+ const T trans_std, // def added
+ T* top_data,
+ bool aligned) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* offset_bottom_rois = bottom_rois + n * 5;
+ int roi_batch_ind = offset_bottom_rois[0];
+
+ // Do not use rounding; this implementation detail is critical
+ T offset = aligned ? (T)0.5 : (T)0.0;
+ T roi_start_w = offset_bottom_rois[1] * spatial_scale - offset;
+ T roi_start_h = offset_bottom_rois[2] * spatial_scale - offset;
+ T roi_end_w = offset_bottom_rois[3] * spatial_scale - offset;
+ T roi_end_h = offset_bottom_rois[4] * spatial_scale - offset;
+
+ T roi_width = roi_end_w - roi_start_w;
+ T roi_height = roi_end_h - roi_start_h;
+ if (!aligned) { // for backward-compatibility only
+ roi_width = max(roi_width, (T)1.);
+ roi_height = max(roi_height, (T)1.);
+ }
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ const T* offset_bottom_data =
+ bottom_data + (roi_batch_ind * channels + c) * height * width;
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi output is the same size
+ T trans_x = bottom_trans[((n * 2) * pooled_height + ph) * pooled_width + pw] * trans_std;
+ T trans_y = bottom_trans[((n * 2 + 1) * pooled_height + ph) * pooled_width + pw] * trans_std;
+
+ // We do average (integral) pooling inside a bin
+ // When the grid is empty, output zeros == 0/1, instead of NaN.
+ // Should make bin size adaptive as well
+ const T count = max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T y = roi_start_h + ph * bin_size_h + trans_y * roi_height +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T x = roi_start_w + pw * bin_size_w + trans_x * roi_width +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ T val = bilinear_interpolate(
+ offset_bottom_data, height, width, y, x, index);
+ output_val += val;
+ }
+ }
+ output_val /= count;
+
+ top_data[index] = output_val;
+ }
+}
+
+template
+__device__ void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high,
+ const int index /* index for debug only*/) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y <= 0)
+ y = 0;
+ if (x <= 0)
+ x = 0;
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = bottom_data[y_low * width + x_low];
+ // T v2 = bottom_data[y_low * width + x_high];
+ // T v3 = bottom_data[y_high * width + x_low];
+ // T v4 = bottom_data[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+template
+__global__ void DefRoIAlignBackwardFeature(
+ const int nthreads,
+ const T* top_diff,
+ const int num_rois,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* bottom_diff,
+ T* bottom_trans_diff, // added for deformable
+ const T* bottom_data, // added for deformable
+ const T* bottom_rois,
+ const T* bottom_trans, // added for deformable
+ const T trans_std, // added for deformable
+ bool aligned) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* offset_bottom_rois = bottom_rois + n * 5;
+ int roi_batch_ind = offset_bottom_rois[0];
+
+ // Do not use rounding; this implementation detail is critical
+ T offset = aligned ? (T)0.5 : (T)0.0;
+ T roi_start_w = offset_bottom_rois[1] * spatial_scale - offset;
+ T roi_start_h = offset_bottom_rois[2] * spatial_scale - offset;
+ T roi_end_w = offset_bottom_rois[3] * spatial_scale - offset;
+ T roi_end_h = offset_bottom_rois[4] * spatial_scale - offset;
+
+ T roi_width = roi_end_w - roi_start_w;
+ T roi_height = roi_end_h - roi_start_h;
+ if (!aligned) { // for backward-compatibility only
+ roi_width = max(roi_width, (T)1.);
+ roi_height = max(roi_height, (T)1.);
+ }
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_bottom_diff =
+ bottom_diff + (roi_batch_ind * channels + c) * height * width;
+
+ // compute input data location for offset gradient
+ const T* offset_bottom_data =
+ bottom_data + (roi_batch_ind * channels + c) * height * width;
+
+
+ int top_offset = (n * channels + c) * pooled_height * pooled_width;
+ const T* offset_top_diff = top_diff + top_offset;
+ const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ T trans_x = bottom_trans[((n * 2) * pooled_height + ph) * pooled_width + pw] * trans_std;
+ T trans_y = bottom_trans[((n * 2 + 1) * pooled_height + ph) * pooled_width + pw] * trans_std;
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T y = roi_start_h + ph * bin_size_h + trans_y * roi_height +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T x = roi_start_w + pw * bin_size_w + trans_x * roi_width +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height,
+ width,
+ y,
+ x,
+ w1,
+ w2,
+ w3,
+ w4,
+ x_low,
+ x_high,
+ y_low,
+ y_high,
+ index);
+
+ T g1 = top_diff_this_bin * w1 / count;
+ T g2 = top_diff_this_bin * w2 / count;
+ T g3 = top_diff_this_bin * w3 / count;
+ T g4 = top_diff_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_low, static_cast(g1));
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_high, static_cast(g2));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_low, static_cast(g3));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_high, static_cast(g4));
+
+ T U00 = offset_bottom_data[y_low * width + x_low];
+ T U01 = offset_bottom_data[y_high * width + x_low];
+ T U10 = offset_bottom_data[y_low * width + x_high];
+ T U11 = offset_bottom_data[y_high * width + x_high];
+ T dist_x = x - x_low, dist_y = y - y_low;
+ T diff_val = top_diff_this_bin / count;
+ T diff_x = (U11 * dist_y + U10 * (1 - dist_y) - U01 * dist_y - U00 * (1 - dist_y)) * trans_std * diff_val;
+ diff_x *= roi_width;
+ T diff_y = (U11 * dist_x + U01 * (1 - dist_x) - U10 * dist_x - U00 * (1 - dist_x)) * trans_std * diff_val;
+ diff_y *= roi_height;
+
+ atomicAdd(bottom_trans_diff + ((n * 2) * pooled_height + ph) * pooled_width + pw, diff_x);
+ atomicAdd(bottom_trans_diff + ((n * 2 + 1) * pooled_height + ph) * pooled_width + pw, diff_y);
+ } // if
+ } // ix
+ } // iy
+ } // CUDA_1D_KERNEL_LOOP
+} // RoIAlignBackward
+
+namespace adet {
+
+at::Tensor DefROIAlign_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const at::Tensor& offsets, // def added
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const float trans_std, // def added
+ bool aligned) {
+ AT_ASSERTM(input.device().is_cuda(), "input must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+ AT_ASSERTM(offsets.device().is_cuda(), "offsets must be a CUDA tensor");
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2}, offsets_t{offsets, "offsets", 3};
+
+ at::CheckedFrom c = "DefROIAlign_forward_cuda";
+ at::checkAllSameGPU(c, {input_t, rois_t, offsets_t});
+ at::checkAllSameType(c, {input_t, rois_t, offsets_t});
+ at::cuda::CUDAGuard device_guard(input.device());
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ auto output = at::empty(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(output_size), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ if (output.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+ }
+
+ AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), "DefROIAlign_forward", [&] {
+ DefRoIAlignForward<<>>(
+ output_size,
+ input.contiguous().data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois.contiguous().data_ptr(),
+ offsets.contiguous().data_ptr(), // def added
+ trans_std, //def added
+ output.data_ptr(),
+ aligned);
+ });
+ cudaDeviceSynchronize();
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+}
+
+// TODO remove the dependency on input and use instead its sizes -> save memory
+at::Tensor DefROIAlign_backward_cuda(
+ const at::Tensor& input, // def added
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const at::Tensor& offsets, // def added
+ const at::Tensor& grad_offsets, // def added
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio,
+ const float trans_std, // def added
+ bool aligned) {
+ AT_ASSERTM(grad.device().is_cuda(), "grad must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+ AT_ASSERTM(input.device().is_cuda(), "input must be a CUDA tensor");
+ AT_ASSERTM(offsets.device().is_cuda(), "offsets must be a CUDA tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2}, input_t{input, "input", 3}, offsets_t{offsets, "offsets", 4};
+ at::CheckedFrom c = "DefROIAlign_backward_cuda";
+ at::checkAllSameGPU(c, {grad_t, rois_t, input_t, offsets_t});
+ at::checkAllSameType(c, {grad_t, rois_t, input_t, offsets_t});
+ at::cuda::CUDAGuard device_guard(grad.device());
+
+ auto num_rois = rois.size(0);
+ auto grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(grad.numel()), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ // handle possibly empty gradients
+ if (grad.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return grad_input;
+ }
+
+ AT_DISPATCH_FLOATING_TYPES(grad.scalar_type(), "DefROIAlign_backward", [&] {
+ DefRoIAlignBackwardFeature<<>>(
+ grad.numel(),
+ grad.contiguous().data_ptr(),
+ num_rois,
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ grad_input.data_ptr(),
+ grad_offsets.data_ptr(), // def added
+ input.contiguous().data_ptr(), // def added
+ rois.contiguous().data_ptr(),
+ offsets.contiguous().data_ptr(), // def added
+ trans_std, // def added
+ aligned);
+ });
+ AT_CUDA_CHECK(cudaGetLastError());
+ return grad_input;
+}
+
+} // namespace adet
diff --git a/AdelaiDet/adet/layers/csrc/cuda_version.cu b/AdelaiDet/adet/layers/csrc/cuda_version.cu
new file mode 100755
index 0000000..1b9bbfe
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/cuda_version.cu
@@ -0,0 +1,7 @@
+#include
+
+namespace adet {
+int get_cudart_version() {
+ return CUDART_VERSION;
+}
+} // namespace adet
diff --git a/AdelaiDet/adet/layers/csrc/ml_nms/ml_nms.cu b/AdelaiDet/adet/layers/csrc/ml_nms/ml_nms.cu
new file mode 100755
index 0000000..f1c1a42
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/ml_nms/ml_nms.cu
@@ -0,0 +1,139 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+int const threadsPerBlock = sizeof(unsigned long long) * 8;
+
+__device__ inline float devIoU(float const * const a, float const * const b) {
+ if (a[5] != b[5]) {
+ return 0.0;
+ }
+ float left = max(a[0], b[0]), right = min(a[2], b[2]);
+ float top = max(a[1], b[1]), bottom = min(a[3], b[3]);
+ float width = max(right - left + 1, 0.f), height = max(bottom - top + 1, 0.f);
+ float interS = width * height;
+ float Sa = (a[2] - a[0] + 1) * (a[3] - a[1] + 1);
+ float Sb = (b[2] - b[0] + 1) * (b[3] - b[1] + 1);
+ return interS / (Sa + Sb - interS);
+}
+
+__global__ void ml_nms_kernel(const int n_boxes, const float nms_overlap_thresh,
+ const float *dev_boxes, unsigned long long *dev_mask) {
+ const int row_start = blockIdx.y;
+ const int col_start = blockIdx.x;
+
+ // if (row_start > col_start) return;
+
+ const int row_size =
+ min(n_boxes - row_start * threadsPerBlock, threadsPerBlock);
+ const int col_size =
+ min(n_boxes - col_start * threadsPerBlock, threadsPerBlock);
+
+ __shared__ float block_boxes[threadsPerBlock * 6];
+ if (threadIdx.x < col_size) {
+ block_boxes[threadIdx.x * 6 + 0] =
+ dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 6 + 0];
+ block_boxes[threadIdx.x * 6 + 1] =
+ dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 6 + 1];
+ block_boxes[threadIdx.x * 6 + 2] =
+ dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 6 + 2];
+ block_boxes[threadIdx.x * 6 + 3] =
+ dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 6 + 3];
+ block_boxes[threadIdx.x * 6 + 4] =
+ dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 6 + 4];
+ block_boxes[threadIdx.x * 6 + 5] =
+ dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 6 + 5];
+ }
+ __syncthreads();
+
+ if (threadIdx.x < row_size) {
+ const int cur_box_idx = threadsPerBlock * row_start + threadIdx.x;
+ const float *cur_box = dev_boxes + cur_box_idx * 6;
+ int i = 0;
+ unsigned long long t = 0;
+ int start = 0;
+ if (row_start == col_start) {
+ start = threadIdx.x + 1;
+ }
+ for (i = start; i < col_size; i++) {
+ if (devIoU(cur_box, block_boxes + i * 6) > nms_overlap_thresh) {
+ t |= 1ULL << i;
+ }
+ }
+ const int col_blocks = THCCeilDiv(n_boxes, threadsPerBlock);
+ dev_mask[cur_box_idx * col_blocks + col_start] = t;
+ }
+}
+
+namespace adet {
+
+// boxes is a N x 6 tensor
+at::Tensor ml_nms_cuda(const at::Tensor boxes, const float nms_overlap_thresh) {
+ using scalar_t = float;
+ AT_ASSERTM(boxes.type().is_cuda(), "boxes must be a CUDA tensor");
+ auto scores = boxes.select(1, 4);
+ auto order_t = std::get<1>(scores.sort(0, /* descending=*/true));
+ auto boxes_sorted = boxes.index_select(0, order_t);
+
+ int boxes_num = boxes.size(0);
+
+ const int col_blocks = THCCeilDiv(boxes_num, threadsPerBlock);
+
+ scalar_t* boxes_dev = boxes_sorted.data();
+
+ THCState *state = at::globalContext().lazyInitCUDA(); // TODO replace with getTHCState
+
+ unsigned long long* mask_dev = NULL;
+ //THCudaCheck(THCudaMalloc(state, (void**) &mask_dev,
+ // boxes_num * col_blocks * sizeof(unsigned long long)));
+
+ mask_dev = (unsigned long long*) THCudaMalloc(state, boxes_num * col_blocks * sizeof(unsigned long long));
+
+ dim3 blocks(THCCeilDiv(boxes_num, threadsPerBlock),
+ THCCeilDiv(boxes_num, threadsPerBlock));
+ dim3 threads(threadsPerBlock);
+ ml_nms_kernel<<>>(boxes_num,
+ nms_overlap_thresh,
+ boxes_dev,
+ mask_dev);
+
+ std::vector mask_host(boxes_num * col_blocks);
+ THCudaCheck(cudaMemcpy(&mask_host[0],
+ mask_dev,
+ sizeof(unsigned long long) * boxes_num * col_blocks,
+ cudaMemcpyDeviceToHost));
+
+ std::vector remv(col_blocks);
+ memset(&remv[0], 0, sizeof(unsigned long long) * col_blocks);
+
+ at::Tensor keep = at::empty({boxes_num}, boxes.options().dtype(at::kLong).device(at::kCPU));
+ int64_t* keep_out = keep.data();
+
+ int num_to_keep = 0;
+ for (int i = 0; i < boxes_num; i++) {
+ int nblock = i / threadsPerBlock;
+ int inblock = i % threadsPerBlock;
+
+ if (!(remv[nblock] & (1ULL << inblock))) {
+ keep_out[num_to_keep++] = i;
+ unsigned long long *p = &mask_host[0] + i * col_blocks;
+ for (int j = nblock; j < col_blocks; j++) {
+ remv[j] |= p[j];
+ }
+ }
+ }
+
+ THCudaFree(state, mask_dev);
+ // TODO improve this part
+ return std::get<0>(order_t.index({
+ keep.narrow(/*dim=*/0, /*start=*/0, /*length=*/num_to_keep).to(
+ order_t.device(), keep.scalar_type())
+ }).sort(0, false));
+}
+
+} // namespace adet
\ No newline at end of file
diff --git a/AdelaiDet/adet/layers/csrc/ml_nms/ml_nms.h b/AdelaiDet/adet/layers/csrc/ml_nms/ml_nms.h
new file mode 100755
index 0000000..f33851a
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/ml_nms/ml_nms.h
@@ -0,0 +1,32 @@
+#pragma once
+#include
+
+namespace adet {
+
+
+#ifdef WITH_CUDA
+at::Tensor ml_nms_cuda(
+ const at::Tensor dets,
+ const float threshold);
+#endif
+
+at::Tensor ml_nms(const at::Tensor& dets,
+ const at::Tensor& scores,
+ const at::Tensor& labels,
+ const float threshold) {
+
+ if (dets.type().is_cuda()) {
+#ifdef WITH_CUDA
+ // TODO raise error if not compiled with CUDA
+ if (dets.numel() == 0)
+ return at::empty({0}, dets.options().dtype(at::kLong).device(at::kCPU));
+ auto b = at::cat({dets, scores.unsqueeze(1), labels.unsqueeze(1)}, 1);
+ return ml_nms_cuda(b, threshold);
+#else
+ AT_ERROR("Not compiled with GPU support");
+#endif
+ }
+ AT_ERROR("CPU version not implemented");
+}
+
+} // namespace adet
diff --git a/AdelaiDet/adet/layers/csrc/vision.cpp b/AdelaiDet/adet/layers/csrc/vision.cpp
new file mode 100755
index 0000000..d780a95
--- /dev/null
+++ b/AdelaiDet/adet/layers/csrc/vision.cpp
@@ -0,0 +1,63 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+
+#include "ml_nms/ml_nms.h"
+#include "DefROIAlign/DefROIAlign.h"
+#include "BezierAlign/BezierAlign.h"
+
+namespace adet {
+
+#ifdef WITH_CUDA
+extern int get_cudart_version();
+#endif
+
+std::string get_cuda_version() {
+#ifdef WITH_CUDA
+ std::ostringstream oss;
+
+ // copied from
+ // https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/cuda/detail/CUDAHooks.cpp#L231
+ auto printCudaStyleVersion = [&](int v) {
+ oss << (v / 1000) << "." << (v / 10 % 100);
+ if (v % 10 != 0) {
+ oss << "." << (v % 10);
+ }
+ };
+ printCudaStyleVersion(get_cudart_version());
+ return oss.str();
+#else
+ return std::string("not available");
+#endif
+}
+
+// similar to
+// https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/Version.cpp
+std::string get_compiler_version() {
+ std::ostringstream ss;
+#if defined(__GNUC__)
+#ifndef __clang__
+ { ss << "GCC " << __GNUC__ << "." << __GNUC_MINOR__; }
+#endif
+#endif
+
+#if defined(__clang_major__)
+ {
+ ss << "clang " << __clang_major__ << "." << __clang_minor__ << "."
+ << __clang_patchlevel__;
+ }
+#endif
+
+#if defined(_MSC_VER)
+ { ss << "MSVC " << _MSC_FULL_VER; }
+#endif
+ return ss.str();
+}
+
+PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
+ m.def("ml_nms", &ml_nms, "Multi-Label NMS");
+ m.def("def_roi_align_forward", &DefROIAlign_forward, "def_roi_align_forward");
+ m.def("def_roi_align_backward", &DefROIAlign_backward, "def_roi_align_backward");
+ m.def("bezier_align_forward", &BezierAlign_forward, "bezier_align_forward");
+ m.def("bezier_align_backward", &BezierAlign_backward, "bezier_align_backward");
+}
+
+} // namespace adet
diff --git a/AdelaiDet/adet/layers/def_roi_align.py b/AdelaiDet/adet/layers/def_roi_align.py
new file mode 100755
index 0000000..f3ea8bb
--- /dev/null
+++ b/AdelaiDet/adet/layers/def_roi_align.py
@@ -0,0 +1,101 @@
+import torch
+from torch import nn
+from torch.autograd import Function
+from torch.autograd.function import once_differentiable
+from torch.nn.modules.utils import _pair
+
+from adet import _C
+
+
+class _DefROIAlign(Function):
+ @staticmethod
+ def forward(ctx, input, roi, offsets, output_size, spatial_scale, sampling_ratio, trans_std, aligned):
+ ctx.save_for_backward(input, roi, offsets)
+ ctx.output_size = _pair(output_size)
+ ctx.spatial_scale = spatial_scale
+ ctx.sampling_ratio = sampling_ratio
+ ctx.trans_std = trans_std
+ ctx.input_shape = input.size()
+ ctx.aligned = aligned
+ output = _C.def_roi_align_forward(
+ input, roi, offsets, spatial_scale, output_size[0], output_size[1],
+ sampling_ratio, trans_std, aligned
+ )
+ return output
+
+ @staticmethod
+ @once_differentiable
+ def backward(ctx, grad_output):
+ data, rois, offsets = ctx.saved_tensors
+ output_size = ctx.output_size
+ spatial_scale = ctx.spatial_scale
+ sampling_ratio = ctx.sampling_ratio
+ trans_std = ctx.trans_std
+ bs, ch, h, w = ctx.input_shape
+ grad_offsets = torch.zeros_like(offsets)
+
+ grad_input = _C.def_roi_align_backward(
+ data,
+ grad_output,
+ rois,
+ offsets,
+ grad_offsets,
+ spatial_scale,
+ output_size[0],
+ output_size[1],
+ bs,
+ ch,
+ h,
+ w,
+ sampling_ratio,
+ trans_std,
+ ctx.aligned,
+ )
+ return grad_input, None, grad_offsets, None, None, None, None, None
+
+
+def_roi_align = _DefROIAlign.apply
+
+
+class DefROIAlign(nn.Module):
+ def __init__(self, output_size, spatial_scale,
+ sampling_ratio, trans_std, aligned=True):
+ """
+ Args:
+ output_size (tuple): h, w
+ spatial_scale (float): scale the input boxes by this number
+ sampling_ratio (int): number of inputs samples to take for each output
+ sample. 0 to take samples densely.
+ trans_std (float): offset scale according to the normalized roi size
+ aligned (bool): if False, use the legacy implementation in
+ Detectron. If True, align the results more perfectly.
+ """
+ super(DefROIAlign, self).__init__()
+ self.output_size = output_size
+ self.spatial_scale = spatial_scale
+ self.sampling_ratio = sampling_ratio
+ self.trans_std = trans_std
+ self.aligned = aligned
+
+ def forward(self, input, rois, offsets):
+ """
+ Args:
+ input: NCHW images
+ rois: Bx5 boxes. First column is the index into N. The other 4 columns are xyxy.
+ """
+ assert rois.dim() == 2 and rois.size(1) == 5
+ return def_roi_align(
+ input, rois, offsets, self.output_size,
+ self.spatial_scale, self.sampling_ratio,
+ self.trans_std, self.aligned
+ )
+
+ def __repr__(self):
+ tmpstr = self.__class__.__name__ + "("
+ tmpstr += "output_size=" + str(self.output_size)
+ tmpstr += ", spatial_scale=" + str(self.spatial_scale)
+ tmpstr += ", sampling_ratio=" + str(self.sampling_ratio)
+ tmpstr += ", trans_std=" + str(self.trans_std)
+ tmpstr += ", aligned=" + str(self.aligned)
+ tmpstr += ")"
+ return tmpstr
diff --git a/AdelaiDet/adet/layers/deform_conv.py b/AdelaiDet/adet/layers/deform_conv.py
new file mode 100755
index 0000000..6671200
--- /dev/null
+++ b/AdelaiDet/adet/layers/deform_conv.py
@@ -0,0 +1,120 @@
+import torch
+from torch import nn
+
+from detectron2.layers import Conv2d
+
+
+class _NewEmptyTensorOp(torch.autograd.Function):
+ @staticmethod
+ def forward(ctx, x, new_shape):
+ ctx.shape = x.shape
+ return x.new_empty(new_shape)
+
+ @staticmethod
+ def backward(ctx, grad):
+ shape = ctx.shape
+ return _NewEmptyTensorOp.apply(grad, shape), None
+
+
+class DFConv2d(nn.Module):
+ """
+ Deformable convolutional layer with configurable
+ deformable groups, dilations and groups.
+
+ Code is from:
+ https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/layers/misc.py
+
+
+ """
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ with_modulated_dcn=True,
+ kernel_size=3,
+ stride=1,
+ groups=1,
+ dilation=1,
+ deformable_groups=1,
+ bias=False,
+ padding=None
+ ):
+ super(DFConv2d, self).__init__()
+ if isinstance(kernel_size, (list, tuple)):
+ assert isinstance(stride, (list, tuple))
+ assert isinstance(dilation, (list, tuple))
+ assert len(kernel_size) == 2
+ assert len(stride) == 2
+ assert len(dilation) == 2
+ padding = (
+ dilation[0] * (kernel_size[0] - 1) // 2,
+ dilation[1] * (kernel_size[1] - 1) // 2
+ )
+ offset_base_channels = kernel_size[0] * kernel_size[1]
+ else:
+ padding = dilation * (kernel_size - 1) // 2
+ offset_base_channels = kernel_size * kernel_size
+ if with_modulated_dcn:
+ from detectron2.layers.deform_conv import ModulatedDeformConv
+ offset_channels = offset_base_channels * 3 # default: 27
+ conv_block = ModulatedDeformConv
+ else:
+ from detectron2.layers.deform_conv import DeformConv
+ offset_channels = offset_base_channels * 2 # default: 18
+ conv_block = DeformConv
+ self.offset = Conv2d(
+ in_channels,
+ deformable_groups * offset_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=padding,
+ groups=1,
+ dilation=dilation
+ )
+ for l in [self.offset, ]:
+ nn.init.kaiming_uniform_(l.weight, a=1)
+ torch.nn.init.constant_(l.bias, 0.)
+ self.conv = conv_block(
+ in_channels,
+ out_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=padding,
+ dilation=dilation,
+ groups=groups,
+ deformable_groups=deformable_groups,
+ bias=bias
+ )
+ self.with_modulated_dcn = with_modulated_dcn
+ self.kernel_size = kernel_size
+ self.stride = stride
+ self.padding = padding
+ self.dilation = dilation
+ self.offset_split = offset_base_channels * deformable_groups * 2
+
+ def forward(self, x, return_offset=False):
+ if x.numel() > 0:
+ if not self.with_modulated_dcn:
+ offset_mask = self.offset(x)
+ x = self.conv(x, offset_mask)
+ else:
+ offset_mask = self.offset(x)
+ offset = offset_mask[:, :self.offset_split, :, :]
+ mask = offset_mask[:, self.offset_split:, :, :].sigmoid()
+ x = self.conv(x, offset, mask)
+ if return_offset:
+ return x, offset_mask
+ return x
+ # get output shape
+ output_shape = [
+ (i + 2 * p - (di * (k - 1) + 1)) // d + 1
+ for i, p, di, k, d in zip(
+ x.shape[-2:],
+ self.padding,
+ self.dilation,
+ self.kernel_size,
+ self.stride
+ )
+ ]
+ output_shape = [x.shape[0], self.conv.weight.shape[0]] + output_shape
+ return _NewEmptyTensorOp.apply(x, output_shape)
diff --git a/AdelaiDet/adet/layers/gcn.py b/AdelaiDet/adet/layers/gcn.py
new file mode 100755
index 0000000..b47585c
--- /dev/null
+++ b/AdelaiDet/adet/layers/gcn.py
@@ -0,0 +1,74 @@
+# coding:utf-8
+import torch.nn as nn
+import torch.nn.functional as F
+
+
+class Conv2D(nn.Module):
+ def __init__(self, in_channels, out_channels, kernel_size, padding='same',
+ stride=1, dilation=1, groups=1):
+ super(Conv2D, self).__init__()
+
+ assert type(kernel_size) in [int, tuple], "Allowed kernel type [int or tuple], not {}".format(type(kernel_size))
+ assert padding == 'same', "Allowed padding type {}, not {}".format('same', padding)
+
+ self.kernel_size = kernel_size
+ if isinstance(kernel_size, tuple):
+ self.h_kernel = kernel_size[0]
+ self.w_kernel = kernel_size[1]
+ else:
+ self.h_kernel = kernel_size
+ self.w_kernel = kernel_size
+
+ self.padding = padding
+ self.stride = stride
+ self.dilation = dilation
+ self.groups = groups
+ self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,
+ stride=self.stride, dilation=self.dilation, groups=self.groups)
+
+ def forward(self, x):
+
+ if self.padding == 'same':
+
+ height, width = x.shape[2:]
+
+ h_pad_need = max(0, (height - 1) * self.stride + self.h_kernel - height)
+ w_pad_need = max(0, (width - 1) * self.stride + self.w_kernel - width)
+
+ pad_left = w_pad_need // 2
+ pad_right = w_pad_need - pad_left
+ pad_top = h_pad_need // 2
+ pad_bottom = h_pad_need - pad_top
+
+ padding = (pad_left, pad_right, pad_top, pad_bottom)
+
+ x = F.pad(x, padding, 'constant', 0)
+
+ x = self.conv(x)
+
+ return x
+
+
+class GCN(nn.Module):
+ """
+ Large Kernel Matters -- https://arxiv.org/abs/1703.02719
+ """
+ def __init__(self, in_channels, out_channels, k=3):
+ super(GCN, self).__init__()
+
+ self.conv_l1 = Conv2D(in_channels=in_channels, out_channels=out_channels, kernel_size=(k, 1), padding='same')
+ self.conv_l2 = Conv2D(in_channels=out_channels, out_channels=out_channels, kernel_size=(1, k), padding='same')
+
+ self.conv_r1 = Conv2D(in_channels=in_channels, out_channels=out_channels, kernel_size=(1, k), padding='same')
+ self.conv_r2 = Conv2D(in_channels=out_channels, out_channels=out_channels, kernel_size=(k, 1), padding='same')
+
+ def forward(self, x):
+ x1 = self.conv_l1(x)
+ x1 = self.conv_l2(x1)
+
+ x2 = self.conv_r1(x)
+ x2 = self.conv_r2(x2)
+
+ out = x1 + x2
+
+ return out
diff --git a/AdelaiDet/adet/layers/iou_loss.py b/AdelaiDet/adet/layers/iou_loss.py
new file mode 100755
index 0000000..d04d7e5
--- /dev/null
+++ b/AdelaiDet/adet/layers/iou_loss.py
@@ -0,0 +1,32 @@
+import torch
+from torch import nn
+
+
+class IOULoss(nn.Module):
+ """
+ Intersetion Over Union (IoU) loss which supports three
+ different IoU computations:
+
+ * IoU
+ * Linear IoU
+ * gIoU
+ """
+ def __init__(self, loc_loss_type='iou'):
+ super(IOULoss, self).__init__()
+ self.loc_loss_type = loc_loss_type
+
+ def forward(self, ious, gious=None, weight=None):
+ if self.loc_loss_type == 'iou':
+ losses = -torch.log(ious)
+ elif self.loc_loss_type == 'linear_iou':
+ losses = 1 - ious
+ elif self.loc_loss_type == 'giou':
+ assert gious is not None
+ losses = 1 - gious
+ else:
+ raise NotImplementedError
+
+ if weight is not None:
+ return (losses * weight).sum()
+ else:
+ return losses.sum()
diff --git a/AdelaiDet/adet/layers/ml_nms.py b/AdelaiDet/adet/layers/ml_nms.py
new file mode 100755
index 0000000..c052780
--- /dev/null
+++ b/AdelaiDet/adet/layers/ml_nms.py
@@ -0,0 +1,26 @@
+from detectron2.layers import batched_nms
+
+
+def ml_nms(boxlist, nms_thresh, max_proposals=-1,
+ score_field="scores", label_field="labels"):
+ """
+ Performs non-maximum suppression on a boxlist, with scores specified
+ in a boxlist field via score_field.
+
+ Args:
+ boxlist (detectron2.structures.Boxes):
+ nms_thresh (float):
+ max_proposals (int): if > 0, then only the top max_proposals are kept
+ after non-maximum suppression
+ score_field (str):
+ """
+ if nms_thresh <= 0:
+ return boxlist
+ boxes = boxlist.pred_boxes.tensor
+ scores = boxlist.scores
+ labels = boxlist.pred_classes
+ keep = batched_nms(boxes, scores, labels, nms_thresh)
+ if max_proposals > 0:
+ keep = keep[: max_proposals]
+ boxlist = boxlist[keep]
+ return boxlist
diff --git a/AdelaiDet/adet/layers/naive_group_norm.py b/AdelaiDet/adet/layers/naive_group_norm.py
new file mode 100755
index 0000000..20d70d9
--- /dev/null
+++ b/AdelaiDet/adet/layers/naive_group_norm.py
@@ -0,0 +1,74 @@
+import torch
+from torch.nn import Module, Parameter
+from torch.nn import init
+
+
+class NaiveGroupNorm(Module):
+ r"""NaiveGroupNorm implements Group Normalization with the high-level matrix operations in PyTorch.
+ It is a temporary solution to export GN by ONNX before the official GN can be exported by ONNX.
+ The usage of NaiveGroupNorm is exactly the same as the official :class:`torch.nn.GroupNorm`.
+ Args:
+ num_groups (int): number of groups to separate the channels into
+ num_channels (int): number of channels expected in input
+ eps: a value added to the denominator for numerical stability. Default: 1e-5
+ affine: a boolean value that when set to ``True``, this module
+ has learnable per-channel affine parameters initialized to ones (for weights)
+ and zeros (for biases). Default: ``True``.
+
+ Shape:
+ - Input: :math:`(N, C, *)` where :math:`C=\text{num\_channels}`
+ - Output: :math:`(N, C, *)` (same shape as input)
+
+ Examples::
+
+ >>> input = torch.randn(20, 6, 10, 10)
+ >>> # Separate 6 channels into 3 groups
+ >>> m = NaiveGroupNorm(3, 6)
+ >>> # Separate 6 channels into 6 groups (equivalent with InstanceNorm)
+ >>> m = NaiveGroupNorm(6, 6)
+ >>> # Put all 6 channels into a single group (equivalent with LayerNorm)
+ >>> m = NaiveGroupNorm(1, 6)
+ >>> # Activating the module
+ >>> output = m(input)
+
+ .. _`Group Normalization`: https://arxiv.org/abs/1803.08494
+ """
+ __constants__ = ['num_groups', 'num_channels', 'eps', 'affine', 'weight',
+ 'bias']
+
+ def __init__(self, num_groups, num_channels, eps=1e-5, affine=True):
+ super(NaiveGroupNorm, self).__init__()
+ self.num_groups = num_groups
+ self.num_channels = num_channels
+ self.eps = eps
+ self.affine = affine
+ if self.affine:
+ self.weight = Parameter(torch.Tensor(num_channels))
+ self.bias = Parameter(torch.Tensor(num_channels))
+ else:
+ self.register_parameter('weight', None)
+ self.register_parameter('bias', None)
+ self.reset_parameters()
+
+ def reset_parameters(self):
+ if self.affine:
+ init.ones_(self.weight)
+ init.zeros_(self.bias)
+
+ def forward(self, input):
+ N, C, H, W = input.size()
+ assert C % self.num_groups == 0
+ input = input.reshape(N, self.num_groups, -1)
+ mean = input.mean(dim=-1, keepdim=True)
+ var = (input ** 2).mean(dim=-1, keepdim=True) - mean ** 2
+ std = torch.sqrt(var + self.eps)
+
+ input = (input - mean) / std
+ input = input.reshape(N, C, H, W)
+ if self.affine:
+ input = input * self.weight.reshape(1, C, 1, 1) + self.bias.reshape(1, C, 1, 1)
+ return input
+
+ def extra_repr(self):
+ return '{num_groups}, {num_channels}, eps={eps}, ' \
+ 'affine={affine}'.format(**self.__dict__)
diff --git a/AdelaiDet/adet/modeling/MEInst/LME/MaskLoader.py b/AdelaiDet/adet/modeling/MEInst/LME/MaskLoader.py
new file mode 100755
index 0000000..b5ebd73
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/LME/MaskLoader.py
@@ -0,0 +1,81 @@
+# coding:utf-8
+
+import os
+import json
+import numpy as np
+
+import torch.utils.data as data
+
+from detectron2.structures import (
+ Boxes,
+ PolygonMasks,
+ BoxMode
+)
+
+
+DATASETS = {
+ "coco_2017_train": {
+ "img_dir": "coco/train2017",
+ "ann_file": "coco/annotations/instances_train2017.json"
+ },
+ "coco_2017_val": {
+ "img_dir": "coco/val2017",
+ "ann_file": "coco/annotations/instances_val2017.json"
+ }
+}
+
+
+class MaskLoader(data.Dataset):
+ """
+ Dataloader for Local Mask.
+
+ Arguments:
+ root (string): filepath to dataset folder.
+ dataset (string): mask to use (eg. 'train', 'val').
+ size (tuple): The size used for train/val (height, width).
+ transform (callable, optional): transformation to perform on the input mask.
+
+ """
+
+ def __init__(self, root="datasets", dataset="coco_2017_train", size=28, transform=False):
+ self.root = root
+ self.dataset = dataset
+ self.transform = transform
+
+ if isinstance(size, int):
+ self.size = size
+ else:
+ raise TypeError
+
+ data_info = DATASETS[dataset]
+ img_dir, ann_file = data_info['img_dir'], data_info['ann_file']
+ img_dir = os.path.join(self.root, img_dir) # actually we do not use it.
+ ann_file = os.path.join(self.root, ann_file)
+
+ with open(ann_file, 'r') as f:
+ anns = json.load(f)
+ anns = anns['annotations']
+ coco = list()
+ for ann in anns:
+ if ann.get('iscrowd', 0) == 0:
+ coco.append(ann)
+ self.coco = coco
+ print("Removed {} images with no usable annotations. {} images left.".format(
+ len(anns) - len(self.coco), len(self.coco)))
+
+ def __len__(self):
+ return len(self.coco)
+
+ def __getitem__(self, index):
+ ann = self.coco[index]
+
+ # bbox transform.
+ bbox = np.array([ann["bbox"]]) # xmin, ymin, w, h
+ bbox = BoxMode.convert(bbox, BoxMode.XYWH_ABS, BoxMode.XYXY_ABS) # x1y1x2y2
+ bbox = Boxes(bbox)
+
+ # mask transform.
+ mask = PolygonMasks([ann["segmentation"]])
+ mask = mask.crop_and_resize(bbox.tensor, self.size).float()
+
+ return mask
diff --git a/AdelaiDet/adet/modeling/MEInst/LME/__init__.py b/AdelaiDet/adet/modeling/MEInst/LME/__init__.py
new file mode 100755
index 0000000..f2869e4
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/LME/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from .MaskLoader import MaskLoader
+from .utils import inverse_sigmoid, direct_sigmoid, IOUMetric, transform, inverse_transform
+
+__all__ = ["MaskLoader", "IOUMetric",
+ "inverse_sigmoid", "direct_sigmoid",
+ "transform", "inverse_transform"]
diff --git a/AdelaiDet/adet/modeling/MEInst/LME/mask_evaluation.py b/AdelaiDet/adet/modeling/MEInst/LME/mask_evaluation.py
new file mode 100755
index 0000000..93cef4c
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/LME/mask_evaluation.py
@@ -0,0 +1,104 @@
+# coding:utf-8
+
+import os
+import argparse
+import numpy as np
+from torch.utils.data import DataLoader
+
+from MaskLoader import MaskLoader
+from utils import (
+ IOUMetric,
+ transform,
+ inverse_transform,
+ direct_sigmoid,
+ inverse_sigmoid
+)
+
+
+VALUE_MAX = 0.05
+VALUE_MIN = 0.01
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Evaluation for PCA Mask Encoding.')
+ parser.add_argument('--root', default='datasets', type=str)
+ parser.add_argument('--dataset', default='coco_2017_train', type=str)
+ parser.add_argument('--matrix', default='coco/components/coco_2017_train'
+ '_class_agnosticTrue_whitenTrue_sigmoidTrue_60.npz', type=str)
+ # mask encoding params.
+ parser.add_argument('--mask_size', default=28, type=int)
+ parser.add_argument('--n_components', default=60, type=int)
+ parser.add_argument('--class_agnostic', default=True, type=bool)
+ parser.add_argument('--whiten', default=True, type=bool)
+ parser.add_argument('--sigmoid', default=True, type=bool)
+ parser.add_argument('--batch-size', default=1024, type=int)
+ args = parser.parse_args()
+ return args
+
+
+if __name__ == "__main__":
+ args = parse_args()
+ # parse args.
+ mask_size = args.mask_size
+ n_components = args.n_components
+ class_agnostic = args.class_agnostic
+ whiten = args.whiten
+ sigmoid = args.sigmoid
+
+ cur_path = os.path.abspath(os.path.dirname(__file__))
+ root_path = cur_path[:cur_path.find("AdelaiDet") + len("AdelaiDet")]
+ dataset_root = os.path.join(root_path, args.root)
+ matrix_path = os.path.join(dataset_root, args.matrix)
+
+ # load matrix.
+ print("Loading matrix parameters: {}".format(matrix_path))
+ parameters = np.load(matrix_path)
+ components_c = parameters['components_c']
+ mean_c = parameters['mean_c']
+ ratio_c = parameters['ratio_c']
+ explained_variance_c = parameters['explained_variance_c']
+ if class_agnostic:
+ components_c = np.squeeze(components_c)
+ mean_c = np.squeeze(mean_c)
+ explained_variance_c = np.squeeze(explained_variance_c)
+ assert n_components == components_c.shape[0], \
+ print("The n_components in component_ must equal to the supposed shape.")
+ else:
+ # TODO: We have not achieve the function in class-specific.
+ raise NotImplementedError
+
+ # build data loader.
+ mask_data = MaskLoader(root=dataset_root, dataset=args.dataset, size=mask_size)
+ mask_loader = DataLoader(mask_data, batch_size=args.batch_size, shuffle=False, num_workers=4)
+ size_data = len(mask_loader)
+
+ # evaluation.
+ IoUevaluate = IOUMetric(2)
+ print("Start Eva ...")
+ for i, masks in enumerate(mask_loader):
+ print("Eva [{} / {}]".format(i, size_data))
+ # generate the reconstruction mask.
+ masks = masks.view(masks.shape[0], -1).numpy()
+ masks = masks.astype(np.float32)
+ # pre-process.
+ if sigmoid:
+ value_random = VALUE_MAX * np.random.rand(masks.shape[0], masks.shape[1])
+ value_random = np.maximum(value_random, VALUE_MIN)
+ masks_random = np.where(masks > value_random, 1 - value_random, value_random)
+ masks_random = inverse_sigmoid(masks_random)
+ else:
+ masks_random = masks
+ # --> encode --> decode.
+ mask_rc = transform(masks_random, components_=components_c, explained_variance_=explained_variance_c,
+ mean_=mean_c, whiten=whiten)
+ mask_rc = inverse_transform(mask_rc, components_=components_c, explained_variance_=explained_variance_c,
+ mean_=mean_c, whiten=whiten)
+ # post-process.
+ if sigmoid:
+ mask_rc = direct_sigmoid(mask_rc)
+ # eva.
+ mask_rc = np.where(mask_rc >= 0.5, 1, 0)
+ IoUevaluate.add_batch(mask_rc, masks)
+
+ _, _, _, mean_iu, _ = IoUevaluate.evaluate()
+ print("The mIoU for {}: {}".format(args.matrix, mean_iu))
diff --git a/AdelaiDet/adet/modeling/MEInst/LME/mask_generation.py b/AdelaiDet/adet/modeling/MEInst/LME/mask_generation.py
new file mode 100755
index 0000000..5172c6f
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/LME/mask_generation.py
@@ -0,0 +1,115 @@
+# coding:utf-8
+
+import os
+import argparse
+import time
+import numpy as np
+import torch
+from torch.utils.data import DataLoader
+from sklearn.decomposition import IncrementalPCA
+
+from MaskLoader import MaskLoader
+from utils import inverse_sigmoid
+
+
+VALUE_MAX = 0.05
+VALUE_MIN = 0.01
+
+
+def mask_encoding(masks, n_components=60, class_agnostic=True, whiten=True, sigmoid=True, batch_size=1024):
+ components_c = []
+ mean_c = []
+ ratio_c = []
+ explained_variance_c = []
+ if class_agnostic:
+ if sigmoid:
+ value_random = VALUE_MAX * np.random.rand(masks.shape[0], masks.shape[1])
+ value_random = np.maximum(value_random, VALUE_MIN)
+ masks = np.where(masks > value_random, 1-value_random, value_random)
+ masks = inverse_sigmoid(masks)
+ pca = IncrementalPCA(n_components=n_components, copy=False, whiten=whiten, batch_size=batch_size)
+ pca.fit(masks)
+ components_c.append(pca.components_[np.newaxis, :, :])
+ mean_c.append(pca.mean_[np.newaxis, :])
+ ratio_c.append(pca.explained_variance_ratio_[np.newaxis, :])
+ explained_variance_c.append(pca.explained_variance_[np.newaxis, :])
+ ratio = pca.explained_variance_ratio_.sum()
+ else:
+ # TODO: We have not achieve the function in class-specific.
+ raise NotImplemented
+
+ return components_c, mean_c, ratio_c, explained_variance_c, ratio
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='PCA Mask Encoding for local mask.')
+ parser.add_argument('--root', default='datasets', type=str)
+ parser.add_argument('--dataset', default='coco_2017_train', type=str)
+ parser.add_argument('--output', default='coco/components', type=str)
+ # mask encoding params.
+ parser.add_argument('--mask_size', default=28, type=int)
+ parser.add_argument('--n_components', default=60, type=int)
+ parser.add_argument('--class_agnostic', default=True, type=bool)
+ parser.add_argument('--whiten', default=True, type=bool)
+ parser.add_argument('--sigmoid', default=True, type=bool)
+ parser.add_argument('--batch-size', default=1024, type=int)
+ args = parser.parse_args()
+ return args
+
+
+if __name__ == "__main__":
+ args = parse_args()
+ # parse args.
+ mask_size = args.mask_size
+ n_components = args.n_components
+ class_agnostic = args.class_agnostic
+ whiten = args.whiten
+ sigmoid = args.sigmoid
+
+ cur_path = os.path.abspath(os.path.dirname(__file__))
+ root_path = cur_path[:cur_path.find("AdelaiDet") + len("AdelaiDet")]
+ dataset_root = os.path.join(root_path, args.root)
+ output_dir = os.path.join(dataset_root, args.output)
+ os.makedirs(output_dir, exist_ok=True)
+
+ # build data loader.
+ mask_data = MaskLoader(root=dataset_root, dataset=args.dataset, size=mask_size)
+ mask_loader = DataLoader(mask_data, batch_size=args.batch_size, shuffle=False, num_workers=4)
+
+ # loading masks.
+ masks = list()
+ print("Start Loading Masks.")
+ tic = time.time()
+ for mask in mask_loader:
+ masks.append(mask.squeeze(1))
+ toc = time.time() - tic
+ print("Finish Loading Masks in {}s.".format(toc))
+ masks = torch.cat(masks, 0)
+ masks = masks.view(masks.shape[0], -1).numpy()
+ masks = masks.astype(np.float32)
+
+ # mask encoding.
+ print("Start to mask encoding ...")
+ print("It may take several times, please wait patiently ...")
+ tic = time.time()
+ components_c, mean_c, ratio_c, explained_variance_c, ratio = \
+ mask_encoding(masks, n_components, class_agnostic, whiten, sigmoid, args.batch_size)
+ toc = time.time() - tic
+ print("Finish the mask encoding in {}s.".format(toc))
+
+ components_c = np.concatenate(components_c).mean(0)[np.newaxis, :, :].astype(np.float32)
+ mean_c = np.concatenate(mean_c).mean(0)[np.newaxis, :].astype(np.float32)
+ ratio_c = np.concatenate(ratio_c).mean(0)[np.newaxis, :].astype(np.float32)
+ explained_variance_c = np.concatenate(explained_variance_c).mean(0)[np.newaxis, :].astype(np.float32)
+ print("The mean variance_ratio for all categories is {}".format(np.mean(ratio)))
+
+ # save the parameters.
+ output_path = os.path.join(output_dir, args.dataset + '_class_agnostic' + str(class_agnostic)
+ + '_whiten' + str(whiten) + '_sigmoid' + str(sigmoid) + '_' + str(n_components)
+ + '.npz')
+ print("Save the local mask encoding matrix: " + output_path)
+ np.savez(output_path,
+ components_c=components_c,
+ mean_c=mean_c,
+ ratio_c=ratio_c,
+ explained_variance_c=explained_variance_c)
diff --git a/AdelaiDet/adet/modeling/MEInst/LME/utils.py b/AdelaiDet/adet/modeling/MEInst/LME/utils.py
new file mode 100755
index 0000000..afaf664
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/LME/utils.py
@@ -0,0 +1,120 @@
+# coding:utf-8
+
+import numpy as np
+
+
+def direct_sigmoid(x):
+ """Apply the sigmoid operation.
+ """
+ y = 1./(1.+1./np.exp(x))
+ dy = y*(1-y)
+ return y
+
+
+def inverse_sigmoid(x):
+ """Apply the inverse sigmoid operation.
+ y = -ln(1-x/x)
+ """
+ y = -1 * np.log((1-x)/x)
+ return y
+
+
+def transform(X, components_, explained_variance_, mean_=None, whiten=False):
+ """Apply dimensionality reduction to X.
+ X is projected on the first principal components previously extracted
+ from a training set.
+ Parameters
+ ----------
+ X: array-like, shape (n_samples, n_features)
+ New data, where n_samples is the number of samples
+ and n_features is the number of features.
+ components_: array-like, shape (n_components, n_features)
+ mean_: array-like, shape (n_features,)
+ explained_variance_: array-like, shape (n_components,)
+ Variance explained by each of the selected components.
+ whiten : bool, optional
+ When True (False by default) the ``components_`` vectors are divided
+ by ``n_samples`` times ``components_`` to ensure uncorrelated outputs
+ with unit component-wise variances.
+ Whitening will remove some information from the transformed signal
+ (the relative variance scales of the components) but can sometimes
+ improve the predictive accuracy of the downstream estimators by
+ making data respect some hard-wired assumptions.
+ Returns
+ -------
+ X_new : array-like, shape (n_samples, n_components)
+ """
+
+ if mean_ is not None:
+ X = X - mean_
+ X_transformed = np.dot(X, components_.T)
+ if whiten:
+ X_transformed /= np.sqrt(explained_variance_)
+ return X_transformed
+
+
+def inverse_transform(X, components_, explained_variance_, mean_=None, whiten=False):
+ """Transform data back to its original space.
+ In other words, return an input X_original whose transform would be X.
+ Parameters
+ ----------
+ X : array-like, shape (n_samples, n_components)
+ New data, where n_samples is the number of samples
+ and n_components is the number of components.
+ components_: array-like, shape (n_components, n_features)
+ mean_: array-like, shape (n_features,)
+ explained_variance_: array-like, shape (n_components,)
+ Variance explained by each of the selected components.
+ whiten : bool, optional
+ When True (False by default) the ``components_`` vectors are divided
+ by ``n_samples`` times ``components_`` to ensure uncorrelated outputs
+ with unit component-wise variances.
+ Whitening will remove some information from the transformed signal
+ (the relative variance scales of the components) but can sometimes
+ improve the predictive accuracy of the downstream estimators by
+ making data respect some hard-wired assumptions.
+
+ Returns
+ -------
+ X_original array-like, shape (n_samples, n_features)
+ """
+ if whiten:
+ X_transformed = np.dot(X, np.sqrt(explained_variance_[:, np.newaxis]) * components_)
+ else:
+ X_transformed = np.dot(X, components_)
+
+ if mean_ is not None:
+ X_transformed = X_transformed + mean_
+
+ return X_transformed
+
+
+class IOUMetric(object):
+ """
+ Class to calculate mean-iou using fast_hist method
+ """
+
+ def __init__(self, num_classes):
+ self.num_classes = num_classes
+ self.hist = np.zeros((num_classes, num_classes))
+
+ def _fast_hist(self, label_pred, label_true):
+ mask = (label_true >= 0) & (label_true < self.num_classes)
+ hist = np.bincount(
+ self.num_classes * label_true[mask].astype(int) +
+ label_pred[mask], minlength=self.num_classes ** 2).reshape(self.num_classes, self.num_classes)
+ return hist
+
+ def add_batch(self, predictions, gts):
+ for lp, lt in zip(predictions, gts):
+ self.hist += self._fast_hist(lp.flatten(), lt.flatten())
+
+ def evaluate(self):
+ acc = np.diag(self.hist).sum() / self.hist.sum()
+ acc_cls = np.diag(self.hist) / self.hist.sum(axis=1)
+ acc_cls = np.nanmean(acc_cls)
+ iu = np.diag(self.hist) / (self.hist.sum(axis=1) + self.hist.sum(axis=0) - np.diag(self.hist))
+ mean_iu = np.nanmean(iu)
+ freq = self.hist.sum(axis=1) / self.hist.sum()
+ fwavacc = (freq[freq > 0] * iu[freq > 0]).sum()
+ return acc, acc_cls, iu, mean_iu, fwavacc
\ No newline at end of file
diff --git a/AdelaiDet/adet/modeling/MEInst/MEInst.py b/AdelaiDet/adet/modeling/MEInst/MEInst.py
new file mode 100755
index 0000000..d5e1822
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/MEInst.py
@@ -0,0 +1,328 @@
+import math
+from typing import List, Dict
+import numpy as np
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.proposal_generator.build import PROPOSAL_GENERATOR_REGISTRY
+
+from adet.layers import DFConv2d, IOULoss, NaiveGroupNorm, GCN
+from .MEInst_outputs import MEInstOutputs
+from .MaskEncoding import PCAMaskEncoding
+
+
+__all__ = ["MEInst"]
+
+INF = 100000000
+
+
+class Scale(nn.Module):
+ def __init__(self, init_value=1.0):
+ super(Scale, self).__init__()
+ self.scale = nn.Parameter(torch.FloatTensor([init_value]))
+
+ def forward(self, input):
+ return input * self.scale
+
+
+@PROPOSAL_GENERATOR_REGISTRY.register()
+class MEInst(nn.Module):
+ """
+ Implement MEInst (https://arxiv.org/abs/2003.11712).
+ """
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ super().__init__()
+ # fmt: off
+ self.cfg = cfg
+ self.in_features = cfg.MODEL.MEInst.IN_FEATURES
+ self.fpn_strides = cfg.MODEL.MEInst.FPN_STRIDES
+ self.focal_loss_alpha = cfg.MODEL.MEInst.LOSS_ALPHA
+ self.focal_loss_gamma = cfg.MODEL.MEInst.LOSS_GAMMA
+ self.center_sample = cfg.MODEL.MEInst.CENTER_SAMPLE
+ self.strides = cfg.MODEL.MEInst.FPN_STRIDES
+ self.radius = cfg.MODEL.MEInst.POS_RADIUS
+ self.pre_nms_thresh_train = cfg.MODEL.MEInst.INFERENCE_TH_TRAIN
+ self.pre_nms_thresh_test = cfg.MODEL.MEInst.INFERENCE_TH_TEST
+ self.pre_nms_topk_train = cfg.MODEL.MEInst.PRE_NMS_TOPK_TRAIN
+ self.pre_nms_topk_test = cfg.MODEL.MEInst.PRE_NMS_TOPK_TEST
+ self.nms_thresh = cfg.MODEL.MEInst.NMS_TH
+ self.post_nms_topk_train = cfg.MODEL.MEInst.POST_NMS_TOPK_TRAIN
+ self.post_nms_topk_test = cfg.MODEL.MEInst.POST_NMS_TOPK_TEST
+ self.thresh_with_ctr = cfg.MODEL.MEInst.THRESH_WITH_CTR
+ # fmt: on
+ self.iou_loss = IOULoss(cfg.MODEL.MEInst.LOC_LOSS_TYPE)
+ # generate sizes of interest
+ soi = []
+ prev_size = -1
+ for s in cfg.MODEL.MEInst.SIZES_OF_INTEREST:
+ soi.append([prev_size, s])
+ prev_size = s
+ soi.append([prev_size, INF])
+ self.sizes_of_interest = soi
+ self.MEInst_head = MEInstHead(cfg, [input_shape[f] for f in self.in_features])
+
+ self.flag_parameters = cfg.MODEL.MEInst.FLAG_PARAMETERS
+ self.mask_encoding = PCAMaskEncoding(cfg)
+
+ def forward(self, images, features, gt_instances):
+ """
+ Arguments:
+ images (list[Tensor] or ImageList): images to be processed
+ targets (list[BoxList]): ground-truth present in the image (optional)
+
+ Returns:
+ result (list[BoxList] or dict[Tensor]): the output from the model.
+ During training, it returns a dict[Tensor] which contains the losses.
+ During testing, it returns list[BoxList] contains additional fields
+ like `scores`, `labels` and `mask` (for Mask R-CNN models).
+
+ """
+ features = [features[f] for f in self.in_features]
+ locations = self.compute_locations(features)
+ logits_pred, reg_pred, ctrness_pred, bbox_towers, mask_regression = self.MEInst_head(features)
+
+ if self.training:
+ pre_nms_thresh = self.pre_nms_thresh_train
+ pre_nms_topk = self.pre_nms_topk_train
+ post_nms_topk = self.post_nms_topk_train
+ if not self.flag_parameters:
+ # encoding parameters.
+ components_path = self.cfg.MODEL.MEInst.PATH_COMPONENTS
+ # update parameters.
+ components_path = components_path.replace('agnosticTrue', 'agnostic' + str(self.cfg.MODEL.MEInst.AGNOSTIC))
+ components_path = components_path.replace('whitenTrue', 'whiten' + str(self.cfg.MODEL.MEInst.WHITEN))
+ components_path = components_path.replace('sigmoidTrue', 'sigmoid' + str(self.cfg.MODEL.MEInst.SIGMOID))
+ components_path = components_path.replace('60', str(self.cfg.MODEL.MEInst.DIM_MASK))
+ parameters = np.load(components_path)
+ device = torch.device(self.cfg.MODEL.DEVICE)
+ with torch.no_grad():
+ if self.cfg.MODEL.MEInst.AGNOSTIC:
+ components = nn.Parameter(torch.from_numpy(parameters['components_c'][0]).float().to(device),
+ requires_grad=False)
+ explained_variances = nn.Parameter(torch.from_numpy(parameters['explained_variance_c'][0])
+ .float().to(device), requires_grad=False)
+ means = nn.Parameter(torch.from_numpy(parameters['mean_c'][0]).float().to(device),
+ requires_grad=False)
+ self.mask_encoding.components = components
+ self.mask_encoding.explained_variances = explained_variances
+ self.mask_encoding.means = means
+ else:
+ raise NotImplementedError
+ self.flag_parameters = True
+ else:
+ pre_nms_thresh = self.pre_nms_thresh_test
+ pre_nms_topk = self.pre_nms_topk_test
+ post_nms_topk = self.post_nms_topk_test
+
+ outputs = MEInstOutputs(
+ images,
+ locations,
+ logits_pred,
+ reg_pred,
+ ctrness_pred,
+ mask_regression,
+ self.mask_encoding,
+ self.focal_loss_alpha,
+ self.focal_loss_gamma,
+ self.iou_loss,
+ self.center_sample,
+ self.sizes_of_interest,
+ self.strides,
+ self.radius,
+ self.MEInst_head.num_classes,
+ pre_nms_thresh,
+ pre_nms_topk,
+ self.nms_thresh,
+ post_nms_topk,
+ self.thresh_with_ctr,
+ gt_instances,
+ cfg=self.cfg
+ )
+
+ if self.training:
+ losses, _ = outputs.losses()
+ return None, losses
+ else:
+ proposals = outputs.predict_proposals()
+ return proposals, {}
+
+ def compute_locations(self, features):
+ locations = []
+ for level, feature in enumerate(features):
+ h, w = feature.size()[-2:]
+ locations_per_level = self.compute_locations_per_level(
+ h, w, self.fpn_strides[level],
+ feature.device
+ )
+ locations.append(locations_per_level)
+ return locations
+
+ @staticmethod
+ def compute_locations_per_level(h, w, stride, device):
+ shifts_x = torch.arange(
+ 0, w * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shifts_y = torch.arange(
+ 0, h * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
+ shift_x = shift_x.reshape(-1)
+ shift_y = shift_y.reshape(-1)
+ locations = torch.stack((shift_x, shift_y), dim=1) + stride // 2
+ return locations
+
+
+class MEInstHead(nn.Module):
+ def __init__(self, cfg, input_shape: List[ShapeSpec]):
+ """
+ Arguments:
+ in_channels (int): number of channels of the input feature
+ """
+ super().__init__()
+ # TODO: Implement the sigmoid version first.
+ self.num_classes = cfg.MODEL.MEInst.NUM_CLASSES
+ self.fpn_strides = cfg.MODEL.MEInst.FPN_STRIDES
+ self.dim_mask = cfg.MODEL.MEInst.DIM_MASK
+ self.use_gcn_in_mask = cfg.MODEL.MEInst.USE_GCN_IN_MASK
+ self.gcn_kernel_size = cfg.MODEL.MEInst.GCN_KERNEL_SIZE
+
+ head_configs = {"cls": (cfg.MODEL.MEInst.NUM_CLS_CONVS,
+ cfg.MODEL.MEInst.USE_DEFORMABLE),
+ "bbox": (cfg.MODEL.MEInst.NUM_BOX_CONVS,
+ cfg.MODEL.MEInst.USE_DEFORMABLE),
+ "share": (cfg.MODEL.MEInst.NUM_SHARE_CONVS,
+ cfg.MODEL.MEInst.USE_DEFORMABLE),
+ "mask": (cfg.MODEL.MEInst.NUM_MASK_CONVS,
+ cfg.MODEL.MEInst.USE_DEFORMABLE)}
+
+ self.type_deformable = cfg.MODEL.MEInst.TYPE_DEFORMABLE
+ self.last_deformable = cfg.MODEL.MEInst.LAST_DEFORMABLE
+ norm = None if cfg.MODEL.MEInst.NORM == "none" else cfg.MODEL.MEInst.NORM
+
+ in_channels = [s.channels for s in input_shape]
+ assert len(set(in_channels)) == 1, "Each level must have the same channel!"
+ in_channels = in_channels[0]
+
+ for head in head_configs:
+ tower = []
+ num_convs, use_deformable = head_configs[head]
+ for i in range(num_convs):
+ # conv type.
+ if use_deformable:
+ if self.last_deformable:
+ if i == num_convs - 1:
+ conv_func = DFConv2d
+ type_func = self.type_deformable
+ else:
+ conv_func = nn.Conv2d
+ type_func = "Conv2d"
+ else:
+ conv_func = DFConv2d
+ type_func = self.type_deformable
+ else:
+ conv_func = nn.Conv2d
+ type_func = "Conv2d"
+ # conv operation.
+ if type_func == "DCNv1":
+ tower.append(conv_func(
+ in_channels, in_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=False,
+ with_modulated_dcn=False
+ ))
+ elif type_func == "DCNv2":
+ tower.append(conv_func(
+ in_channels, in_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=False,
+ with_modulated_dcn=True
+ ))
+ elif type_func == "Conv2d":
+ tower.append(conv_func(
+ in_channels, in_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=True
+ ))
+ else:
+ raise NotImplementedError
+ # norm.
+ if norm == "GN":
+ tower.append(nn.GroupNorm(32, in_channels))
+ elif norm == "NaiveGN":
+ tower.append(NaiveGroupNorm(32, in_channels))
+ # activation.
+ tower.append(nn.ReLU())
+ self.add_module('{}_tower'.format(head),
+ nn.Sequential(*tower))
+
+ self.cls_logits = nn.Conv2d(
+ in_channels, self.num_classes,
+ kernel_size=3, stride=1,
+ padding=1
+ )
+ self.bbox_pred = nn.Conv2d(
+ in_channels, 4, kernel_size=3,
+ stride=1, padding=1
+ )
+ self.ctrness = nn.Conv2d(
+ in_channels, 1, kernel_size=3,
+ stride=1, padding=1
+ )
+
+ if self.use_gcn_in_mask:
+ self.mask_pred = GCN(in_channels, self.dim_mask, k=self.gcn_kernel_size)
+ else:
+ self.mask_pred = nn.Conv2d(
+ in_channels, self.dim_mask, kernel_size=3,
+ stride=1, padding=1
+ )
+
+ if cfg.MODEL.MEInst.USE_SCALE:
+ self.scales = nn.ModuleList([Scale(init_value=1.0) for _ in self.fpn_strides])
+ else:
+ self.scales = None
+
+ for modules in [
+ self.cls_tower, self.bbox_tower,
+ self.share_tower, self.cls_logits,
+ self.bbox_pred, self.ctrness,
+ self.mask_tower, self.mask_pred
+ ]:
+ for l in modules.modules():
+ if isinstance(l, nn.Conv2d):
+ torch.nn.init.normal_(l.weight, std=0.01)
+ torch.nn.init.constant_(l.bias, 0)
+
+ # initialize the bias for focal loss
+ prior_prob = cfg.MODEL.MEInst.PRIOR_PROB
+ bias_value = -math.log((1 - prior_prob) / prior_prob)
+ torch.nn.init.constant_(self.cls_logits.bias, bias_value)
+
+ def forward(self, x):
+ logits = []
+ bbox_reg = []
+ ctrness = []
+ bbox_towers = []
+ mask_reg = []
+ for l, feature in enumerate(x):
+ feature = self.share_tower(feature)
+ cls_tower = self.cls_tower(feature)
+ bbox_tower = self.bbox_tower(feature)
+
+ logits.append(self.cls_logits(cls_tower))
+ ctrness.append(self.ctrness(bbox_tower))
+ reg = self.bbox_pred(bbox_tower)
+ if self.scales is not None:
+ reg = self.scales[l](reg)
+ # Note that we use relu, as in the improved MEInst, instead of exp.
+ bbox_reg.append(F.relu(reg))
+
+ # Mask Encoding
+ mask_tower = self.mask_tower(feature)
+ mask_reg.append(self.mask_pred(mask_tower))
+
+ return logits, bbox_reg, ctrness, bbox_towers, mask_reg
diff --git a/AdelaiDet/adet/modeling/MEInst/MEInst_outputs.py b/AdelaiDet/adet/modeling/MEInst/MEInst_outputs.py
new file mode 100755
index 0000000..884abad
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/MEInst_outputs.py
@@ -0,0 +1,645 @@
+import logging
+from typing import List
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from detectron2.layers import cat
+from detectron2.structures import Instances, Boxes, pairwise_iou
+
+from detectron2.utils.comm import get_world_size
+from detectron2.modeling.matcher import Matcher
+
+from fvcore.nn import sigmoid_focal_loss_jit
+
+from adet.utils.comm import reduce_sum
+from adet.layers import ml_nms
+
+
+logger = logging.getLogger(__name__)
+
+INF = 100000000
+
+"""
+Shape shorthand in this module:
+
+ N: number of images in the minibatch
+ L: number of feature maps per image on which RPN is run
+ Hi, Wi: height and width of the i-th feature map
+
+Naming convention:
+
+ labels: refers to the ground-truth class of an position.
+
+ reg_targets: refers to the 4-d (left, top, right, bottom) distances that parameterize the ground-truth box.
+
+ logits_pred: predicted classification scores in [-inf, +inf];
+
+ reg_pred: the predicted (left, top, right, bottom), corresponding to reg_targets
+
+ ctrness_pred: predicted centerness scores
+
+ mask_regression: the predicted mask coefficients (D)
+
+"""
+
+
+def compute_ctrness_targets(reg_targets):
+ if len(reg_targets) == 0:
+ return reg_targets.new_zeros(len(reg_targets))
+ left_right = reg_targets[:, [0, 2]]
+ top_bottom = reg_targets[:, [1, 3]]
+ ctrness = (left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * \
+ (top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])
+ return torch.sqrt(ctrness)
+
+
+class MEInstOutputs(object):
+ def __init__(
+ self,
+ images,
+ locations,
+ logits_pred,
+ reg_pred,
+ ctrness_pred,
+ mask_regression,
+ mask_encoding,
+ focal_loss_alpha,
+ focal_loss_gamma,
+ iou_loss,
+ center_sample,
+ sizes_of_interest,
+ strides,
+ radius,
+ num_classes,
+ pre_nms_thresh,
+ pre_nms_top_n,
+ nms_thresh,
+ fpn_post_nms_top_n,
+ thresh_with_ctr,
+ gt_instances=None,
+ cfg=None,
+ ):
+ self.cfg = cfg
+ self.logits_pred = logits_pred
+ self.reg_pred = reg_pred
+ self.ctrness_pred = ctrness_pred
+ self.locations = locations
+ self.mask_regression = mask_regression
+ self.mask_encoding = mask_encoding
+
+ self.gt_instances = gt_instances
+ self.num_feature_maps = len(logits_pred)
+ self.num_images = len(images)
+ self.image_sizes = images.image_sizes
+ self.focal_loss_alpha = focal_loss_alpha
+ self.focal_loss_gamma = focal_loss_gamma
+ self.iou_loss = iou_loss
+ self.center_sample = center_sample
+ self.sizes_of_interest = sizes_of_interest
+ self.strides = strides
+ self.radius = radius
+ self.num_classes = num_classes
+ self.pre_nms_thresh = pre_nms_thresh
+ self.pre_nms_top_n = pre_nms_top_n
+ self.nms_thresh = nms_thresh
+ self.fpn_post_nms_top_n = fpn_post_nms_top_n
+ self.thresh_with_ctr = thresh_with_ctr
+
+ self.loss_on_mask = cfg.MODEL.MEInst.LOSS_ON_MASK
+ self.mask_loss_type = cfg.MODEL.MEInst.MASK_LOSS_TYPE
+ self.dim_mask = cfg.MODEL.MEInst.DIM_MASK
+ self.mask_size = cfg.MODEL.MEInst.MASK_SIZE
+ if self.loss_on_mask:
+ self.mask_loss_func = nn.BCEWithLogitsLoss(reduction="none")
+ elif self.mask_loss_type == 'mse':
+ self.mask_loss_func = nn.MSELoss(reduction="none")
+ else:
+ raise NotImplementedError
+
+ # Matcher to assign box proposals to gt boxes
+ self.proposal_matcher = Matcher(
+ cfg.MODEL.MEInst.IOU_THRESHOLDS,
+ cfg.MODEL.MEInst.IOU_LABELS,
+ allow_low_quality_matches=False,
+ )
+
+ @torch.no_grad()
+ def prepare_masks(
+ self, proposals: List[Instances], targets: List[Instances]
+ ) -> List[Instances]:
+ proposals_with_gt = []
+ for proposals_per_image, targets_per_image in zip(proposals, targets):
+ # No proposal boxes available for images during training.
+ if not len(proposals_per_image):
+ gt_boxes = Boxes(
+ targets_per_image.gt_boxes.tensor.new_zeros((0, 4))
+ )
+ proposals_per_image.gt_boxes = gt_boxes
+ proposals_with_gt.append(proposals_per_image)
+ continue
+
+ has_gt = len(targets_per_image) > 0
+ match_quality_matrix = pairwise_iou(
+ targets_per_image.gt_boxes, proposals_per_image.pos_boxes
+ )
+
+ matched_idxs, matched_labels = self.proposal_matcher(match_quality_matrix)
+
+ # We index all the attributes of targets that start with "gt_"
+ # and have not been added to proposals yet (="gt_classes").
+ if has_gt:
+ for (trg_name, trg_value) in targets_per_image.get_fields().items():
+ if trg_name.startswith("gt_") and not proposals_per_image.has(trg_name):
+ proposals_per_image.set(trg_name, trg_value[matched_idxs])
+ else:
+ gt_boxes = Boxes(
+ targets_per_image.gt_boxes.tensor.new_zeros((len(matched_idxs), 4))
+ )
+ proposals_per_image.gt_boxes = gt_boxes
+
+ proposals_with_gt.append(proposals_per_image)
+
+ return proposals_with_gt
+
+ @staticmethod
+ def _transpose(training_targets, num_loc_list):
+ '''
+ This function is used to transpose image first training targets to level first ones
+ :return: level first training targets
+ '''
+ for im_i in range(len(training_targets)):
+ training_targets[im_i] = torch.split(
+ training_targets[im_i], num_loc_list, dim=0
+ )
+
+ targets_level_first = []
+ for targets_per_level in zip(*training_targets):
+ targets_level_first.append(
+ torch.cat(targets_per_level, dim=0)
+ )
+ return targets_level_first
+
+ def _get_ground_truth(self):
+ num_loc_list = [len(loc) for loc in self.locations]
+ self.num_loc_list = num_loc_list
+
+ # compute locations to size ranges
+ loc_to_size_range = []
+ for l, loc_per_level in enumerate(self.locations):
+ loc_to_size_range_per_level = loc_per_level.new_tensor(self.sizes_of_interest[l])
+ loc_to_size_range.append(
+ loc_to_size_range_per_level[None].expand(num_loc_list[l], -1)
+ )
+
+ loc_to_size_range = torch.cat(loc_to_size_range, dim=0)
+ locations = torch.cat(self.locations, dim=0)
+
+ training_targets = self.compute_targets_for_locations(
+ locations, self.gt_instances, loc_to_size_range
+ )
+
+ # Mask Encoding
+ mask_targets = training_targets.pop("mask_targets")
+ mask_indices = training_targets.pop("mask_indices")
+ mask_targets = self.prepare_masks(mask_targets, self.gt_instances)
+ mask_targets_split = []
+ for mask_target_per_img, mask_index_per_img in zip(mask_targets, mask_indices):
+ assert len(mask_target_per_img) == len(mask_index_per_img), print(
+ "The number(positive) should be equal between mask_target and mask_index, "
+ "which means that the function(prepare_masks) should not filter any proposals, "
+ "the mask should be generated one by one according to the input proposals.")
+ # there is no gt target assigned to the image.
+ if len(mask_target_per_img) == 0:
+ continue
+ mask_level = []
+ level_s = 0
+ for level_e in num_loc_list:
+ level_e += level_s
+ level_ge = mask_index_per_img.ge(level_s)
+ level_lt = mask_index_per_img.lt(level_e)
+ index_level = torch.nonzero(level_ge * level_lt).squeeze(1)
+ mask_target_per_level = mask_target_per_img[index_level].gt_masks.crop_and_resize(
+ mask_target_per_img[index_level].pos_boxes.tensor,
+ self.mask_size).float()
+ mask_level.append(mask_target_per_level)
+ level_s = level_e
+ mask_targets_split.append(mask_level)
+
+ mask_targets_level_first = []
+ for level in range(len(self.locations)):
+ mask_targets_level_first.append(
+ torch.cat([mask_per_im[level] for mask_per_im in mask_targets_split], dim=0)
+ )
+
+ # transpose im first training_targets to level first ones
+ training_targets = {
+ k: self._transpose(v, num_loc_list) for k, v in training_targets.items()
+ }
+
+ labels_level_first = training_targets["labels"]
+ for labels_per_level, mask_targets_level in zip(labels_level_first, mask_targets_level_first):
+ num_pos = (labels_per_level != self.num_classes).nonzero().numel()
+ assert num_pos == mask_targets_level.shape[0], \
+ print("The number(positive) should be equal between labels_per_level and mask_targets_level.")
+
+ # append mask_targets to training targets.
+ training_targets["mask_targets"] = mask_targets_level_first
+
+ # we normalize reg_targets by FPN's strides here
+ reg_targets = training_targets["reg_targets"]
+ for l in range(len(reg_targets)):
+ reg_targets[l] = reg_targets[l] / float(self.strides[l])
+
+ return training_targets
+
+ @staticmethod
+ def get_sample_region(gt, strides, num_loc_list, loc_xs, loc_ys, radius=1):
+ num_gts = gt.shape[0]
+ K = len(loc_xs)
+ gt = gt[None].expand(K, num_gts, 4)
+ center_x = (gt[..., 0] + gt[..., 2]) / 2
+ center_y = (gt[..., 1] + gt[..., 3]) / 2
+ center_gt = gt.new_zeros(gt.shape)
+ # no gt
+ if center_x.numel() == 0 or center_x[..., 0].sum() == 0:
+ return loc_xs.new_zeros(loc_xs.shape, dtype=torch.uint8)
+ beg = 0
+ for level, num_loc in enumerate(num_loc_list):
+ end = beg + num_loc
+ stride = strides[level] * radius
+ xmin = center_x[beg:end] - stride
+ ymin = center_y[beg:end] - stride
+ xmax = center_x[beg:end] + stride
+ ymax = center_y[beg:end] + stride
+ # limit sample region in gt
+ center_gt[beg:end, :, 0] = torch.where(xmin > gt[beg:end, :, 0], xmin, gt[beg:end, :, 0])
+ center_gt[beg:end, :, 1] = torch.where(ymin > gt[beg:end, :, 1], ymin, gt[beg:end, :, 1])
+ center_gt[beg:end, :, 2] = torch.where(xmax > gt[beg:end, :, 2], gt[beg:end, :, 2], xmax)
+ center_gt[beg:end, :, 3] = torch.where(ymax > gt[beg:end, :, 3], gt[beg:end, :, 3], ymax)
+ beg = end
+ left = loc_xs[:, None] - center_gt[..., 0]
+ right = center_gt[..., 2] - loc_xs[:, None]
+ top = loc_ys[:, None] - center_gt[..., 1]
+ bottom = center_gt[..., 3] - loc_ys[:, None]
+ center_bbox = torch.stack((left, top, right, bottom), -1)
+ inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0
+ return inside_gt_bbox_mask
+
+ def compute_targets_for_locations(self, locations, targets, size_ranges):
+ labels = []
+ reg_targets = []
+ mask_targets = []
+ mask_indices = []
+ xs, ys = locations[:, 0], locations[:, 1]
+
+ for im_i in range(len(targets)):
+ targets_per_im = targets[im_i]
+ bboxes = targets_per_im.gt_boxes.tensor
+ labels_per_im = targets_per_im.gt_classes
+
+ # no gt
+ if bboxes.numel() == 0:
+ labels.append(labels_per_im.new_zeros(locations.size(0)) + self.num_classes)
+ reg_targets.append(locations.new_zeros((locations.size(0), 4)))
+ continue
+
+ area = targets_per_im.gt_boxes.area()
+
+ l = xs[:, None] - bboxes[:, 0][None]
+ t = ys[:, None] - bboxes[:, 1][None]
+ r = bboxes[:, 2][None] - xs[:, None]
+ b = bboxes[:, 3][None] - ys[:, None]
+ reg_targets_per_im = torch.stack([l, t, r, b], dim=2)
+
+ if self.center_sample:
+ is_in_boxes = self.get_sample_region(
+ bboxes, self.strides, self.num_loc_list,
+ xs, ys, radius=self.radius
+ )
+ else:
+ is_in_boxes = reg_targets_per_im.min(dim=2)[0] > 0
+
+ max_reg_targets_per_im = reg_targets_per_im.max(dim=2)[0]
+ # limit the regression range for each location
+ is_cared_in_the_level = \
+ (max_reg_targets_per_im >= size_ranges[:, [0]]) & \
+ (max_reg_targets_per_im <= size_ranges[:, [1]])
+
+ locations_to_gt_area = area[None].repeat(len(locations), 1)
+ locations_to_gt_area[is_in_boxes == 0] = INF
+ locations_to_gt_area[is_cared_in_the_level == 0] = INF
+
+ # if there are still more than one objects for a location,
+ # we choose the one with minimal area
+ locations_to_min_area, locations_to_gt_inds = locations_to_gt_area.min(dim=1)
+
+ reg_targets_per_im = reg_targets_per_im[range(len(locations)), locations_to_gt_inds]
+
+ labels_per_im = labels_per_im[locations_to_gt_inds]
+ labels_per_im[locations_to_min_area == INF] = self.num_classes
+
+ labels.append(labels_per_im)
+ reg_targets.append(reg_targets_per_im)
+
+ # Mask Encoding.
+ pos_inds = torch.nonzero(labels_per_im != self.num_classes).squeeze(1)
+ pos_labels = labels_per_im[pos_inds]
+ pos_reg_targets = reg_targets_per_im[pos_inds]
+ pos_locations = locations[pos_inds]
+ bbs = torch.stack([
+ pos_locations[:, 0] - pos_reg_targets[:, 0],
+ pos_locations[:, 1] - pos_reg_targets[:, 1],
+ pos_locations[:, 0] + pos_reg_targets[:, 2],
+ pos_locations[:, 1] + pos_reg_targets[:, 3],
+ ], dim=1)
+ bbs = Boxes(bbs)
+
+ mask_targets_per_im = Instances(targets_per_im.image_size)
+ mask_targets_per_im.set("pos_classes", pos_labels)
+ mask_targets_per_im.set("pos_boxes", bbs)
+
+ mask_targets.append(mask_targets_per_im)
+ mask_indices.append(pos_inds)
+
+ return {"labels": labels, "reg_targets": reg_targets,
+ "mask_targets": mask_targets, "mask_indices": mask_indices}
+
+ def MEInst_losses(
+ self,
+ labels,
+ reg_targets,
+ logits_pred,
+ reg_pred,
+ ctrness_pred,
+ mask_pred,
+ mask_targets
+ ):
+ num_classes = logits_pred.size(1)
+ labels = labels.flatten()
+
+ pos_inds = torch.nonzero(labels != num_classes).squeeze(1)
+ num_pos_local = pos_inds.numel()
+ num_gpus = get_world_size()
+ total_num_pos = reduce_sum(pos_inds.new_tensor([num_pos_local])).item()
+ num_pos_avg = max(total_num_pos / num_gpus, 1.0)
+
+ # prepare one_hot
+ class_target = torch.zeros_like(logits_pred)
+ class_target[pos_inds, labels[pos_inds]] = 1
+
+ class_loss = sigmoid_focal_loss_jit(
+ logits_pred,
+ class_target,
+ alpha=self.focal_loss_alpha,
+ gamma=self.focal_loss_gamma,
+ reduction="sum",
+ ) / num_pos_avg
+
+ reg_pred = reg_pred[pos_inds]
+ reg_targets = reg_targets[pos_inds]
+ ctrness_pred = ctrness_pred[pos_inds]
+ mask_pred = mask_pred[pos_inds]
+ assert mask_pred.shape[0] == mask_targets.shape[0], \
+ print("The number(positive) should be equal between "
+ "masks_pred(prediction) and mask_targets(target).")
+
+ ctrness_targets = compute_ctrness_targets(reg_targets)
+ ctrness_targets_sum = ctrness_targets.sum()
+ ctrness_norm = max(reduce_sum(ctrness_targets_sum).item() / num_gpus, 1e-6)
+
+ reg_loss = self.iou_loss(
+ reg_pred,
+ reg_targets,
+ ctrness_targets
+ ) / ctrness_norm
+
+ ctrness_loss = F.binary_cross_entropy_with_logits(
+ ctrness_pred,
+ ctrness_targets,
+ reduction="sum"
+ ) / num_pos_avg
+
+ if self.loss_on_mask:
+ # n_components predictions --> m*m mask predictions without sigmoid
+ # as sigmoid function is combined in loss.
+ mask_pred = self.mask_encoding.decoder(mask_pred, is_train=True)
+ mask_loss = self.mask_loss_func(
+ mask_pred,
+ mask_targets
+ )
+ mask_loss = mask_loss.sum(1) * ctrness_targets
+ mask_loss = mask_loss.sum() / max(ctrness_norm * self.mask_size ** 2, 1.0)
+ else:
+ # m*m mask labels --> n_components encoding labels
+ mask_targets = self.mask_encoding.encoder(mask_targets)
+ if self.mask_loss_type == 'mse':
+ mask_loss = self.mask_loss_func(
+ mask_pred,
+ mask_targets
+ )
+ mask_loss = mask_loss.sum(1) * ctrness_targets
+ mask_loss = mask_loss.sum() / max(ctrness_norm * self.dim_mask, 1.0)
+ else:
+ raise NotImplementedError
+
+ losses = {
+ "loss_MEInst_cls": class_loss,
+ "loss_MEInst_loc": reg_loss,
+ "loss_MEInst_ctr": ctrness_loss,
+ "loss_MEInst_mask": mask_loss,
+ }
+ return losses, {}
+
+ def losses(self):
+ """
+ Return the losses from a set of MEInst predictions and their associated ground-truth.
+
+ Returns:
+ dict[loss name -> loss value]: A dict mapping from loss name to loss value.
+ """
+
+ training_targets = self._get_ground_truth()
+ labels, reg_targets, mask_targets = training_targets["labels"], training_targets["reg_targets"], \
+ training_targets["mask_targets"]
+
+ # Collect all logits and regression predictions over feature maps
+ # and images to arrive at the same shape as the labels and targets
+ # The final ordering is L, N, H, W from slowest to fastest axis.
+ logits_pred = cat(
+ [
+ # Reshape: (N, C, Hi, Wi) -> (N, Hi, Wi, C) -> (N*Hi*Wi, C)
+ x.permute(0, 2, 3, 1).reshape(-1, self.num_classes)
+ for x in self.logits_pred
+ ], dim=0,)
+ reg_pred = cat(
+ [
+ # Reshape: (N, B, Hi, Wi) -> (N, Hi, Wi, B) -> (N*Hi*Wi, B)
+ x.permute(0, 2, 3, 1).reshape(-1, 4)
+ for x in self.reg_pred
+ ], dim=0,)
+ ctrness_pred = cat(
+ [
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.reshape(-1) for x in self.ctrness_pred
+ ], dim=0,)
+
+ labels = cat(
+ [
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.reshape(-1) for x in labels
+ ], dim=0,)
+
+ reg_targets = cat(
+ [
+ # Reshape: (N, Hi, Wi, 4) -> (N*Hi*Wi, 4)
+ x.reshape(-1, 4) for x in reg_targets
+ ], dim=0,)
+
+ mask_pred = cat(
+ [
+ # Reshape: (N, D, Hi, Wi) -> (N, Hi, Wi, D) -> (N*Hi*Wi, D)
+ x.permute(0, 2, 3, 1).reshape(-1, self.dim_mask)
+ for x in self.mask_regression
+ ], dim=0,)
+
+ mask_targets = cat(
+ [
+ # Reshape: (N, Hi, Wi, mask_size^2) -> (N*Hi*Wi, mask_size^2)
+ x.reshape(-1, self.mask_size ** 2) for x in mask_targets
+ ], dim=0,)
+
+ return self.MEInst_losses(
+ labels,
+ reg_targets,
+ logits_pred,
+ reg_pred,
+ ctrness_pred,
+ mask_pred,
+ mask_targets
+ )
+
+ def predict_proposals(self):
+ sampled_boxes = []
+
+ bundle = (
+ self.locations, self.logits_pred,
+ self.reg_pred, self.ctrness_pred,
+ self.strides, self.mask_regression
+ )
+
+ for i, (l, o, r, c, s, mr) in enumerate(zip(*bundle)):
+ # recall that during training, we normalize regression targets with FPN's stride.
+ # we denormalize them here.
+ r = r * s
+ sampled_boxes.append(
+ self.forward_for_single_feature_map(
+ l, o, r, c, mr, self.image_sizes
+ )
+ )
+
+ boxlists = list(zip(*sampled_boxes))
+ boxlists = [Instances.cat(boxlist) for boxlist in boxlists]
+ boxlists = self.select_over_all_levels(boxlists)
+
+ num_images = len(boxlists)
+ for i in range(num_images):
+ per_image_masks = boxlists[i].pred_masks
+ per_image_masks = self.mask_encoding.decoder(per_image_masks, is_train=False)
+ per_image_masks = per_image_masks.view(-1, 1, self.mask_size, self.mask_size)
+ boxlists[i].pred_masks = per_image_masks
+
+ return boxlists
+
+ def forward_for_single_feature_map(
+ self, locations, box_cls,
+ reg_pred, ctrness, mask_regression, image_sizes
+ ):
+ N, C, H, W = box_cls.shape
+
+ # put in the same format as locations
+ box_cls = box_cls.view(N, C, H, W).permute(0, 2, 3, 1)
+ box_cls = box_cls.reshape(N, -1, C).sigmoid()
+ box_regression = reg_pred.view(N, 4, H, W).permute(0, 2, 3, 1)
+ box_regression = box_regression.reshape(N, -1, 4)
+ ctrness = ctrness.view(N, 1, H, W).permute(0, 2, 3, 1)
+ ctrness = ctrness.reshape(N, -1).sigmoid()
+ mask_regression = mask_regression.view(N, self.dim_mask, H, W).permute(0, 2, 3, 1)
+ mask_regression = mask_regression.reshape(N, -1, self.dim_mask)
+
+ # if self.thresh_with_ctr is True, we multiply the classification
+ # scores with centerness scores before applying the threshold.
+ if self.thresh_with_ctr:
+ box_cls = box_cls * ctrness[:, :, None]
+ candidate_inds = box_cls > self.pre_nms_thresh
+ pre_nms_top_n = candidate_inds.view(N, -1).sum(1)
+ pre_nms_top_n = pre_nms_top_n.clamp(max=self.pre_nms_top_n)
+
+ if not self.thresh_with_ctr:
+ box_cls = box_cls * ctrness[:, :, None]
+
+ results = []
+ for i in range(N):
+ per_box_cls = box_cls[i]
+ per_candidate_inds = candidate_inds[i]
+ per_box_cls = per_box_cls[per_candidate_inds]
+
+ per_candidate_nonzeros = per_candidate_inds.nonzero()
+ per_box_loc = per_candidate_nonzeros[:, 0]
+ per_class = per_candidate_nonzeros[:, 1]
+
+ per_box_regression = box_regression[i]
+ per_box_regression = per_box_regression[per_box_loc]
+ per_locations = locations[per_box_loc]
+
+ per_box_mask = mask_regression[i]
+ per_box_mask = per_box_mask[per_box_loc]
+
+ per_pre_nms_top_n = pre_nms_top_n[i]
+
+ if per_candidate_inds.sum().item() > per_pre_nms_top_n.item():
+ per_box_cls, top_k_indices = \
+ per_box_cls.topk(per_pre_nms_top_n, sorted=False)
+ per_class = per_class[top_k_indices]
+ per_box_regression = per_box_regression[top_k_indices]
+ per_locations = per_locations[top_k_indices]
+ per_box_mask = per_box_mask[top_k_indices]
+
+ detections = torch.stack([
+ per_locations[:, 0] - per_box_regression[:, 0],
+ per_locations[:, 1] - per_box_regression[:, 1],
+ per_locations[:, 0] + per_box_regression[:, 2],
+ per_locations[:, 1] + per_box_regression[:, 3],
+ ], dim=1)
+
+ boxlist = Instances(image_sizes[i])
+ boxlist.pred_boxes = Boxes(detections)
+ boxlist.scores = torch.sqrt(per_box_cls)
+ boxlist.pred_classes = per_class
+ boxlist.locations = per_locations
+ boxlist.pred_masks = per_box_mask
+
+ results.append(boxlist)
+
+ return results
+
+ def select_over_all_levels(self, boxlists):
+ num_images = len(boxlists)
+ results = []
+ for i in range(num_images):
+ # multiclass nms
+ result = ml_nms(boxlists[i], self.nms_thresh)
+ number_of_detections = len(result)
+
+ # Limit to max_per_image detections **over all classes**
+ if number_of_detections > self.fpn_post_nms_top_n > 0:
+ cls_scores = result.scores
+ image_thresh, _ = torch.kthvalue(
+ cls_scores.cpu(),
+ number_of_detections - self.fpn_post_nms_top_n + 1
+ )
+ keep = cls_scores >= image_thresh.item()
+ keep = torch.nonzero(keep).squeeze(1)
+ result = result[keep]
+ results.append(result)
+ return results
diff --git a/AdelaiDet/adet/modeling/MEInst/MaskEncoding.py b/AdelaiDet/adet/modeling/MEInst/MaskEncoding.py
new file mode 100755
index 0000000..ae5c718
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/MaskEncoding.py
@@ -0,0 +1,125 @@
+# coding:utf-8
+import torch
+import torch.nn as nn
+
+VALUE_MAX = 0.05
+VALUE_MIN = 0.01
+
+
+@torch.no_grad()
+class PCAMaskEncoding(nn.Module):
+ """
+ To do the mask encoding of PCA.
+ components_: (tensor), shape (n_components, n_features) if agnostic=True
+ else (n_samples, n_components, n_features)
+ explained_variance_: Variance explained by each of the selected components.
+ (tensor), shape (n_components) if agnostic=True
+ else (n_samples, n_components)
+ mean_: (tensor), shape (n_features) if agnostic=True
+ else (n_samples, n_features)
+ agnostic: (bool), whether class_agnostic or class_specific.
+ whiten : (bool), optional
+ When True (False by default) the ``components_`` vectors are divided
+ by ``n_samples`` times ``components_`` to ensure uncorrelated outputs
+ with unit component-wise variances.
+ Whitening will remove some information from the transformed signal
+ (the relative variance scales of the components) but can sometimes
+ improve the predictive accuracy of the downstream estimators by
+ making data respect some hard-wired assumptions.
+ sigmoid: (bool) whether to apply inverse sigmoid before transform.
+ """
+ def __init__(self, cfg):
+ super().__init__()
+ self.cfg = cfg
+ self.agnostic = cfg.MODEL.MEInst.AGNOSTIC
+ self.whiten = cfg.MODEL.MEInst.WHITEN
+ self.sigmoid = cfg.MODEL.MEInst.SIGMOID
+ self.dim_mask = cfg.MODEL.MEInst.DIM_MASK
+ self.mask_size = cfg.MODEL.MEInst.MASK_SIZE
+
+ if self.agnostic:
+ self.components = nn.Parameter(torch.zeros(self.dim_mask, self.mask_size**2), requires_grad=False)
+ self.explained_variances = nn.Parameter(torch.zeros(self.dim_mask), requires_grad=False)
+ self.means = nn.Parameter(torch.zeros(self.mask_size**2), requires_grad=False)
+ else:
+ raise NotImplementedError
+
+ def inverse_sigmoid(self, x):
+ """Apply the inverse sigmoid operation.
+ y = -ln(1-x/x)
+ """
+ # In case of overflow
+ value_random = VALUE_MAX * torch.rand_like(x)
+ value_random = torch.where(value_random > VALUE_MIN, value_random, VALUE_MIN * torch.ones_like(x))
+ x = torch.where(x > value_random, 1 - value_random, value_random)
+ # inverse sigmoid
+ y = -1 * torch.log((1 - x) / x)
+ return y
+
+ def encoder(self, X):
+ """Apply dimensionality reduction to X.
+ X is projected on the first principal components previously extracted
+ from a training set.
+ Parameters
+ ----------
+ X : Original features(tensor), shape (n_samples, n_features)
+ New data, where n_samples is the number of samples
+ and n_features is the number of features.
+
+ Returns
+ -------
+ X_transformed : Transformed features(tensor), shape (n_samples, n_features)
+ """
+ assert X.shape[1] == self.mask_size**2, print("The original mask_size of input"
+ " should be equal to the supposed size.")
+
+ if self.sigmoid:
+ X = self.inverse_sigmoid(X)
+
+ if self.agnostic:
+ if self.means is not None:
+ X_transformed = X - self.means
+ X_transformed = torch.matmul(X_transformed, self.components.T)
+ if self.whiten:
+ X_transformed /= torch.sqrt(self.explained_variances)
+ else:
+ # TODO: The class-specific version has not implemented.
+ raise NotImplementedError
+
+ return X_transformed
+
+ def decoder(self, X, is_train=False):
+ """Transform data back to its original space.
+ In other words, return an input X_original whose transform would be X.
+ Parameters
+ ----------
+ X : Encoded features(tensor), shape (n_samples, n_components)
+ New data, where n_samples is the number of samples
+ and n_components is the number of components.
+
+ Returns
+ -------
+ X_original original features(tensor), shape (n_samples, n_features)
+ """
+ assert X.shape[1] == self.dim_mask, print("The dim of transformed data "
+ "should be equal to the supposed dim.")
+
+ if self.agnostic:
+ if self.whiten:
+ components_ = self.components * torch.sqrt(self.explained_variances.unsqueeze(1))
+ X_transformed = torch.matmul(X, components_)
+ if self.means is not None:
+ X_transformed = X_transformed + self.means
+ else:
+ # TODO: The class-specific version has not implemented.
+ raise NotImplementedError
+
+ if is_train:
+ pass
+ else:
+ if self.sigmoid:
+ X_transformed = torch.sigmoid(X_transformed)
+ else:
+ X_transformed = torch.clamp(X_transformed, min=0.01, max=0.99)
+
+ return X_transformed
diff --git a/AdelaiDet/adet/modeling/MEInst/__init__.py b/AdelaiDet/adet/modeling/MEInst/__init__.py
new file mode 100755
index 0000000..648b794
--- /dev/null
+++ b/AdelaiDet/adet/modeling/MEInst/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from .MEInst import MEInst
+from .MaskEncoding import PCAMaskEncoding
diff --git a/AdelaiDet/adet/modeling/__init__.py b/AdelaiDet/adet/modeling/__init__.py
new file mode 100755
index 0000000..c7caf1a
--- /dev/null
+++ b/AdelaiDet/adet/modeling/__init__.py
@@ -0,0 +1,14 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from .fcos import FCOS
+from .blendmask import BlendMask
+from .backbone import build_fcos_resnet_fpn_backbone
+from .one_stage_detector import OneStageDetector, OneStageRCNN
+from .roi_heads.text_head import TextHead
+from .batext import BAText
+from .MEInst import MEInst
+from .condinst import condinst
+from .solov2 import SOLOv2
+from .fcpose import FCPose
+
+_EXCLUDE = {"torch", "ShapeSpec"}
+__all__ = [k for k in globals().keys() if k not in _EXCLUDE and not k.startswith("_")]
diff --git a/AdelaiDet/adet/modeling/backbone/__init__.py b/AdelaiDet/adet/modeling/backbone/__init__.py
new file mode 100755
index 0000000..93af744
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/__init__.py
@@ -0,0 +1,5 @@
+from .fpn import build_fcos_resnet_fpn_backbone
+from .vovnet import build_vovnet_fpn_backbone, build_vovnet_backbone
+from .dla import build_fcos_dla_fpn_backbone
+from .resnet_lpf import build_resnet_lpf_backbone
+from .bifpn import build_fcos_resnet_bifpn_backbone
diff --git a/AdelaiDet/adet/modeling/backbone/bifpn.py b/AdelaiDet/adet/modeling/backbone/bifpn.py
new file mode 100755
index 0000000..7c2c385
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/bifpn.py
@@ -0,0 +1,410 @@
+import torch
+import torch.nn.functional as F
+from torch import nn
+
+from detectron2.layers import Conv2d, ShapeSpec, get_norm
+
+from detectron2.modeling.backbone import Backbone, build_resnet_backbone
+from detectron2.modeling import BACKBONE_REGISTRY
+from .mobilenet import build_mnv2_backbone
+
+__all__ = []
+
+
+def swish(x):
+ return x * x.sigmoid()
+
+
+def split_name(name):
+ for i, c in enumerate(name):
+ if not c.isalpha():
+ return name[:i], int(name[i:])
+ raise ValueError()
+
+
+class FeatureMapResampler(nn.Module):
+ def __init__(self, in_channels, out_channels, stride, norm=""):
+ super(FeatureMapResampler, self).__init__()
+ if in_channels != out_channels:
+ self.reduction = Conv2d(
+ in_channels, out_channels, kernel_size=1,
+ bias=(norm == ""),
+ norm=get_norm(norm, out_channels),
+ activation=None
+ )
+ else:
+ self.reduction = None
+
+ assert stride <= 2
+ self.stride = stride
+
+ def forward(self, x):
+ if self.reduction is not None:
+ x = self.reduction(x)
+
+ if self.stride == 2:
+ x = F.max_pool2d(
+ x, kernel_size=self.stride + 1,
+ stride=self.stride, padding=1
+ )
+ elif self.stride == 1:
+ pass
+ else:
+ raise NotImplementedError()
+ return x
+
+
+class BackboneWithTopLevels(Backbone):
+ def __init__(self, backbone, out_channels, num_top_levels, norm=""):
+ super(BackboneWithTopLevels, self).__init__()
+ self.backbone = backbone
+ backbone_output_shape = backbone.output_shape()
+
+ self._out_feature_channels = {name: shape.channels for name, shape in backbone_output_shape.items()}
+ self._out_feature_strides = {name: shape.stride for name, shape in backbone_output_shape.items()}
+ self._out_features = list(self._out_feature_strides.keys())
+
+ last_feature_name = max(self._out_feature_strides.keys(), key=lambda x: split_name(x)[1])
+ self.last_feature_name = last_feature_name
+ self.num_top_levels = num_top_levels
+
+ last_channels = self._out_feature_channels[last_feature_name]
+ last_stride = self._out_feature_strides[last_feature_name]
+
+ prefix, suffix = split_name(last_feature_name)
+ prev_channels = last_channels
+ for i in range(num_top_levels):
+ name = prefix + str(suffix + i + 1)
+ self.add_module(name, FeatureMapResampler(
+ prev_channels, out_channels, 2, norm
+ ))
+ prev_channels = out_channels
+
+ self._out_feature_channels[name] = out_channels
+ self._out_feature_strides[name] = last_stride * 2 ** (i + 1)
+ self._out_features.append(name)
+
+ def forward(self, x):
+ outputs = self.backbone(x)
+ last_features = outputs[self.last_feature_name]
+ prefix, suffix = split_name(self.last_feature_name)
+
+ x = last_features
+ for i in range(self.num_top_levels):
+ name = prefix + str(suffix + i + 1)
+ x = self.__getattr__(name)(x)
+ outputs[name] = x
+
+ return outputs
+
+
+class SingleBiFPN(Backbone):
+ """
+ This module implements Feature Pyramid Network.
+ It creates pyramid features built on top of some input feature maps.
+ """
+
+ def __init__(
+ self, in_channels_list, out_channels, norm=""
+ ):
+ """
+ Args:
+ bottom_up (Backbone): module representing the bottom up subnetwork.
+ Must be a subclass of :class:`Backbone`. The multi-scale feature
+ maps generated by the bottom up network, and listed in `in_features`,
+ are used to generate FPN levels.
+ in_features (list[str]): names of the input feature maps coming
+ from the backbone to which FPN is attached. For example, if the
+ backbone produces ["res2", "res3", "res4"], any *contiguous* sublist
+ of these may be used; order must be from high to low resolution.
+ out_channels (int): number of channels in the output feature maps.
+ norm (str): the normalization to use.
+ """
+ super(SingleBiFPN, self).__init__()
+
+ self.out_channels = out_channels
+ # build 5-levels bifpn
+ if len(in_channels_list) == 5:
+ self.nodes = [
+ {'feat_level': 3, 'inputs_offsets': [3, 4]},
+ {'feat_level': 2, 'inputs_offsets': [2, 5]},
+ {'feat_level': 1, 'inputs_offsets': [1, 6]},
+ {'feat_level': 0, 'inputs_offsets': [0, 7]},
+ {'feat_level': 1, 'inputs_offsets': [1, 7, 8]},
+ {'feat_level': 2, 'inputs_offsets': [2, 6, 9]},
+ {'feat_level': 3, 'inputs_offsets': [3, 5, 10]},
+ {'feat_level': 4, 'inputs_offsets': [4, 11]},
+ ]
+ elif len(in_channels_list) == 6:
+ self.nodes = [
+ {'feat_level': 4, 'inputs_offsets': [4, 5]},
+ {'feat_level': 3, 'inputs_offsets': [3, 6]},
+ {'feat_level': 2, 'inputs_offsets': [2, 7]},
+ {'feat_level': 1, 'inputs_offsets': [1, 8]},
+ {'feat_level': 0, 'inputs_offsets': [0, 9]},
+ {'feat_level': 1, 'inputs_offsets': [1, 9, 10]},
+ {'feat_level': 2, 'inputs_offsets': [2, 8, 11]},
+ {'feat_level': 3, 'inputs_offsets': [3, 7, 12]},
+ {'feat_level': 4, 'inputs_offsets': [4, 6, 13]},
+ {'feat_level': 5, 'inputs_offsets': [5, 14]},
+ ]
+ elif len(in_channels_list) == 3:
+ self.nodes = [
+ {'feat_level': 1, 'inputs_offsets': [1, 2]},
+ {'feat_level': 0, 'inputs_offsets': [0, 3]},
+ {'feat_level': 1, 'inputs_offsets': [1, 3, 4]},
+ {'feat_level': 2, 'inputs_offsets': [2, 5]},
+ ]
+ else:
+ raise NotImplementedError
+
+ node_info = [_ for _ in in_channels_list]
+
+ num_output_connections = [0 for _ in in_channels_list]
+ for fnode in self.nodes:
+ feat_level = fnode["feat_level"]
+ inputs_offsets = fnode["inputs_offsets"]
+ inputs_offsets_str = "_".join(map(str, inputs_offsets))
+ for input_offset in inputs_offsets:
+ num_output_connections[input_offset] += 1
+
+ in_channels = node_info[input_offset]
+ if in_channels != out_channels:
+ lateral_conv = Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ norm=get_norm(norm, out_channels)
+ )
+ self.add_module(
+ "lateral_{}_f{}".format(input_offset, feat_level), lateral_conv
+ )
+ node_info.append(out_channels)
+ num_output_connections.append(0)
+
+ # generate attention weights
+ name = "weights_f{}_{}".format(feat_level, inputs_offsets_str)
+ self.__setattr__(name, nn.Parameter(
+ torch.ones(len(inputs_offsets), dtype=torch.float32),
+ requires_grad=True
+ ))
+
+ # generate convolutions after combination
+ name = "outputs_f{}_{}".format(feat_level, inputs_offsets_str)
+ self.add_module(name, Conv2d(
+ out_channels,
+ out_channels,
+ kernel_size=3,
+ padding=1,
+ norm=get_norm(norm, out_channels),
+ bias=(norm == "")
+ ))
+
+ def forward(self, feats):
+ """
+ Args:
+ input (dict[str->Tensor]): mapping feature map name (e.g., "p5") to
+ feature map tensor for each feature level in high to low resolution order.
+
+ Returns:
+ dict[str->Tensor]:
+ mapping from feature map name to FPN feature map tensor
+ in high to low resolution order. Returned feature names follow the FPN
+ paper convention: "p", where stage has stride = 2 ** stage e.g.,
+ ["n2", "n3", ..., "n6"].
+ """
+ feats = [_ for _ in feats]
+ num_levels = len(feats)
+ num_output_connections = [0 for _ in feats]
+ for fnode in self.nodes:
+ feat_level = fnode["feat_level"]
+ inputs_offsets = fnode["inputs_offsets"]
+ inputs_offsets_str = "_".join(map(str, inputs_offsets))
+ input_nodes = []
+ _, _, target_h, target_w = feats[feat_level].size()
+ for input_offset in inputs_offsets:
+ num_output_connections[input_offset] += 1
+ input_node = feats[input_offset]
+
+ # reduction
+ if input_node.size(1) != self.out_channels:
+ name = "lateral_{}_f{}".format(input_offset, feat_level)
+ input_node = self.__getattr__(name)(input_node)
+
+ # maybe downsample
+ _, _, h, w = input_node.size()
+ if h > target_h and w > target_w:
+ height_stride_size = int((h - 1) // target_h + 1)
+ width_stride_size = int((w - 1) // target_w + 1)
+ assert height_stride_size == width_stride_size == 2
+ input_node = F.max_pool2d(
+ input_node, kernel_size=(height_stride_size + 1, width_stride_size + 1),
+ stride=(height_stride_size, width_stride_size), padding=1
+ )
+ elif h <= target_h and w <= target_w:
+ if h < target_h or w < target_w:
+ input_node = F.interpolate(
+ input_node,
+ size=(target_h, target_w),
+ mode="nearest"
+ )
+ else:
+ raise NotImplementedError()
+ input_nodes.append(input_node)
+
+ # attention
+ name = "weights_f{}_{}".format(feat_level, inputs_offsets_str)
+ weights = F.relu(self.__getattr__(name))
+ norm_weights = weights / (weights.sum() + 0.0001)
+
+ new_node = torch.stack(input_nodes, dim=-1)
+ new_node = (norm_weights * new_node).sum(dim=-1)
+ new_node = swish(new_node)
+
+ name = "outputs_f{}_{}".format(feat_level, inputs_offsets_str)
+ feats.append(self.__getattr__(name)(new_node))
+
+ num_output_connections.append(0)
+
+ output_feats = []
+ for idx in range(num_levels):
+ for i, fnode in enumerate(reversed(self.nodes)):
+ if fnode['feat_level'] == idx:
+ output_feats.append(feats[-1 - i])
+ break
+ else:
+ raise ValueError()
+ return output_feats
+
+
+class BiFPN(Backbone):
+ """
+ This module implements Feature Pyramid Network.
+ It creates pyramid features built on top of some input feature maps.
+ """
+
+ def __init__(
+ self, bottom_up, in_features, out_channels, num_top_levels, num_repeats, norm=""
+ ):
+ """
+ Args:
+ bottom_up (Backbone): module representing the bottom up subnetwork.
+ Must be a subclass of :class:`Backbone`. The multi-scale feature
+ maps generated by the bottom up network, and listed in `in_features`,
+ are used to generate FPN levels.
+ in_features (list[str]): names of the input feature maps coming
+ from the backbone to which FPN is attached. For example, if the
+ backbone produces ["res2", "res3", "res4"], any *contiguous* sublist
+ of these may be used; order must be from high to low resolution.
+ out_channels (int): number of channels in the output feature maps.
+ num_top_levels (int): the number of the top levels (p6 or p7).
+ num_repeats (int): the number of repeats of BiFPN.
+ norm (str): the normalization to use.
+ """
+ super(BiFPN, self).__init__()
+ assert isinstance(bottom_up, Backbone)
+
+ # add extra feature levels (i.e., 6 and 7)
+ self.bottom_up = BackboneWithTopLevels(
+ bottom_up, out_channels,
+ num_top_levels, norm
+ )
+ bottom_up_output_shapes = self.bottom_up.output_shape()
+
+ in_features = sorted(in_features, key=lambda x: split_name(x)[1])
+ self._size_divisibility = bottom_up_output_shapes[in_features[-1]].stride
+ self.out_channels = out_channels
+ self.min_level = split_name(in_features[0])[1]
+
+ # add the names for top blocks
+ prefix, last_suffix = split_name(in_features[-1])
+ for i in range(num_top_levels):
+ in_features.append(prefix + str(last_suffix + i + 1))
+ self.in_features = in_features
+
+ # generate output features
+ self._out_features = ["p{}".format(split_name(name)[1]) for name in in_features]
+ self._out_feature_strides = {
+ out_name: bottom_up_output_shapes[in_name].stride
+ for out_name, in_name in zip(self._out_features, in_features)
+ }
+ self._out_feature_channels = {k: out_channels for k in self._out_features}
+
+ # build bifpn
+ self.repeated_bifpn = nn.ModuleList()
+ for i in range(num_repeats):
+ if i == 0:
+ in_channels_list = [
+ bottom_up_output_shapes[name].channels for name in in_features
+ ]
+ else:
+ in_channels_list = [
+ self._out_feature_channels[name] for name in self._out_features
+ ]
+ self.repeated_bifpn.append(SingleBiFPN(
+ in_channels_list, out_channels, norm
+ ))
+
+ @property
+ def size_divisibility(self):
+ return self._size_divisibility
+
+ def forward(self, x):
+ """
+ Args:
+ input (dict[str->Tensor]): mapping feature map name (e.g., "p5") to
+ feature map tensor for each feature level in high to low resolution order.
+
+ Returns:
+ dict[str->Tensor]:
+ mapping from feature map name to FPN feature map tensor
+ in high to low resolution order. Returned feature names follow the FPN
+ paper convention: "p", where stage has stride = 2 ** stage e.g.,
+ ["n2", "n3", ..., "n6"].
+ """
+ bottom_up_features = self.bottom_up(x)
+ feats = [bottom_up_features[f] for f in self.in_features]
+
+ for bifpn in self.repeated_bifpn:
+ feats = bifpn(feats)
+
+ return dict(zip(self._out_features, feats))
+
+
+def _assert_strides_are_log2_contiguous(strides):
+ """
+ Assert that each stride is 2x times its preceding stride, i.e. "contiguous in log2".
+ """
+ for i, stride in enumerate(strides[1:], 1):
+ assert stride == 2 * strides[i - 1], "Strides {} {} are not log2 contiguous".format(
+ stride, strides[i - 1]
+ )
+
+
+@BACKBONE_REGISTRY.register()
+def build_fcos_resnet_bifpn_backbone(cfg, input_shape: ShapeSpec):
+ """
+ Args:
+ cfg: a detectron2 CfgNode
+
+ Returns:
+ backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
+ """
+ if cfg.MODEL.MOBILENET:
+ bottom_up = build_mnv2_backbone(cfg, input_shape)
+ else:
+ bottom_up = build_resnet_backbone(cfg, input_shape)
+ in_features = cfg.MODEL.BiFPN.IN_FEATURES
+ out_channels = cfg.MODEL.BiFPN.OUT_CHANNELS
+ num_repeats = cfg.MODEL.BiFPN.NUM_REPEATS
+ top_levels = cfg.MODEL.FCOS.TOP_LEVELS
+
+ backbone = BiFPN(
+ bottom_up=bottom_up,
+ in_features=in_features,
+ out_channels=out_channels,
+ num_top_levels=top_levels,
+ num_repeats=num_repeats,
+ norm=cfg.MODEL.BiFPN.NORM
+ )
+ return backbone
diff --git a/AdelaiDet/adet/modeling/backbone/dla.py b/AdelaiDet/adet/modeling/backbone/dla.py
new file mode 100755
index 0000000..9039952
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/dla.py
@@ -0,0 +1,441 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# this file is from https://github.com/ucbdrive/dla/blob/master/dla.py.
+
+import math
+from os.path import join
+
+import torch
+from torch import nn
+import torch.utils.model_zoo as model_zoo
+import torch.nn.functional as F
+import fvcore.nn.weight_init as weight_init
+
+from detectron2.modeling.backbone import FPN
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.backbone.build import BACKBONE_REGISTRY
+from detectron2.layers.batch_norm import get_norm
+from detectron2.modeling.backbone import Backbone
+
+from .fpn import LastLevelP6, LastLevelP6P7
+
+
+WEB_ROOT = 'http://dl.yf.io/dla/models'
+
+
+def get_model_url(data, name):
+ return join(WEB_ROOT, data.name,
+ '{}-{}.pth'.format(name, data.model_hash[name]))
+
+
+def conv3x3(in_planes, out_planes, stride=1):
+ "3x3 convolution with padding"
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
+ padding=1, bias=False)
+
+
+class BasicBlock(nn.Module):
+ def __init__(self, cfg, inplanes, planes, stride=1, dilation=1):
+ super(BasicBlock, self).__init__()
+ self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3,
+ stride=stride, padding=dilation,
+ bias=False, dilation=dilation)
+ self.bn1 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.relu = nn.ReLU(inplace=True)
+ self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
+ stride=1, padding=dilation,
+ bias=False, dilation=dilation)
+ self.bn2 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.stride = stride
+
+ def forward(self, x, residual=None):
+ if residual is None:
+ residual = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+
+ out += residual
+ out = self.relu(out)
+
+ return out
+
+
+class Bottleneck(nn.Module):
+ expansion = 2
+
+ def __init__(self, cfg, inplanes, planes, stride=1, dilation=1):
+ super(Bottleneck, self).__init__()
+ expansion = Bottleneck.expansion
+ bottle_planes = planes // expansion
+ self.conv1 = nn.Conv2d(inplanes, bottle_planes,
+ kernel_size=1, bias=False)
+ self.bn1 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.conv2 = nn.Conv2d(bottle_planes, bottle_planes, kernel_size=3,
+ stride=stride, padding=dilation,
+ bias=False, dilation=dilation)
+ self.bn2 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.conv3 = nn.Conv2d(bottle_planes, planes,
+ kernel_size=1, bias=False)
+ self.bn3 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.relu = nn.ReLU(inplace=True)
+ self.stride = stride
+
+ def forward(self, x, residual=None):
+ if residual is None:
+ residual = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.relu(out)
+
+ out = self.conv3(out)
+ out = self.bn3(out)
+
+ out += residual
+ out = self.relu(out)
+
+ return out
+
+
+class BottleneckX(nn.Module):
+ expansion = 2
+ cardinality = 32
+
+ def __init__(self, cfg, inplanes, planes, stride=1, dilation=1):
+ super(BottleneckX, self).__init__()
+ cardinality = BottleneckX.cardinality
+ # dim = int(math.floor(planes * (BottleneckV5.expansion / 64.0)))
+ # bottle_planes = dim * cardinality
+ bottle_planes = planes * cardinality // 32
+ self.conv1 = nn.Conv2d(inplanes, bottle_planes,
+ kernel_size=1, bias=False)
+ self.bn1 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.conv2 = nn.Conv2d(bottle_planes, bottle_planes, kernel_size=3,
+ stride=stride, padding=dilation, bias=False,
+ dilation=dilation, groups=cardinality)
+ self.bn2 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.conv3 = nn.Conv2d(bottle_planes, planes,
+ kernel_size=1, bias=False)
+ self.bn3 = get_norm(cfg.MODEL.DLA.NORM, planes)
+ self.relu = nn.ReLU(inplace=True)
+ self.stride = stride
+
+ def forward(self, x, residual=None):
+ if residual is None:
+ residual = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.relu(out)
+
+ out = self.conv3(out)
+ out = self.bn3(out)
+
+ out += residual
+ out = self.relu(out)
+
+ return out
+
+
+class Root(nn.Module):
+ def __init__(self, cfg, in_channels, out_channels, kernel_size, residual):
+ super(Root, self).__init__()
+ self.conv = nn.Conv2d(
+ in_channels, out_channels, kernel_size,
+ stride=1, bias=False, padding=(kernel_size - 1) // 2)
+ self.bn = get_norm(cfg.MODEL.DLA.NORM, out_channels)
+ self.relu = nn.ReLU(inplace=True)
+ self.residual = residual
+
+ def forward(self, *x):
+ children = x
+ x = self.conv(torch.cat(x, 1))
+ x = self.bn(x)
+ if self.residual:
+ x += children[0]
+ x = self.relu(x)
+
+ return x
+
+
+class Tree(nn.Module):
+ def __init__(self, cfg, levels, block, in_channels, out_channels, stride=1,
+ level_root=False, root_dim=0, root_kernel_size=1,
+ dilation=1, root_residual=False):
+ super(Tree, self).__init__()
+ if root_dim == 0:
+ root_dim = 2 * out_channels
+ if level_root:
+ root_dim += in_channels
+ if levels == 1:
+ self.tree1 = block(cfg, in_channels, out_channels, stride,
+ dilation=dilation)
+ self.tree2 = block(cfg, out_channels, out_channels, 1,
+ dilation=dilation)
+ else:
+ self.tree1 = Tree(cfg, levels - 1, block, in_channels, out_channels,
+ stride, root_dim=0,
+ root_kernel_size=root_kernel_size,
+ dilation=dilation, root_residual=root_residual)
+ self.tree2 = Tree(cfg, levels - 1, block, out_channels, out_channels,
+ root_dim=root_dim + out_channels,
+ root_kernel_size=root_kernel_size,
+ dilation=dilation, root_residual=root_residual)
+ if levels == 1:
+ self.root = Root(cfg, root_dim, out_channels, root_kernel_size,
+ root_residual)
+ self.level_root = level_root
+ self.root_dim = root_dim
+ self.downsample = None
+ self.project = None
+ self.levels = levels
+ if stride > 1:
+ self.downsample = nn.MaxPool2d(stride, stride=stride)
+ if in_channels != out_channels:
+ self.project = nn.Sequential(
+ nn.Conv2d(in_channels, out_channels,
+ kernel_size=1, stride=1, bias=False),
+ get_norm(cfg.MODEL.DLA.NORM, out_channels)
+ )
+
+ def forward(self, x, residual=None, children=None):
+ if self.training and residual is not None:
+ x = x + residual.sum() * 0.0
+ children = [] if children is None else children
+ bottom = self.downsample(x) if self.downsample else x
+ residual = self.project(bottom) if self.project else bottom
+ if self.level_root:
+ children.append(bottom)
+ x1 = self.tree1(x, residual)
+ if self.levels == 1:
+ x2 = self.tree2(x1)
+ x = self.root(x2, x1, *children)
+ else:
+ children.append(x1)
+ x = self.tree2(x1, children=children)
+ return x
+
+
+class DLA(Backbone):
+ def __init__(self, cfg, levels, channels, block=BasicBlock, residual_root=False):
+ super(DLA, self).__init__()
+ self.cfg = cfg
+ self.channels = channels
+
+ self._out_features = ["level{}".format(i) for i in range(6)]
+ self._out_feature_channels = {k: channels[i] for i, k in enumerate(self._out_features)}
+ self._out_feature_strides = {k: 2 ** i for i, k in enumerate(self._out_features)}
+
+ self.base_layer = nn.Sequential(
+ nn.Conv2d(3, channels[0], kernel_size=7, stride=1,
+ padding=3, bias=False),
+ get_norm(cfg.MODEL.DLA.NORM, channels[0]),
+ nn.ReLU(inplace=True))
+ self.level0 = self._make_conv_level(
+ channels[0], channels[0], levels[0])
+ self.level1 = self._make_conv_level(
+ channels[0], channels[1], levels[1], stride=2)
+ self.level2 = Tree(cfg, levels[2], block, channels[1], channels[2], 2,
+ level_root=False,
+ root_residual=residual_root)
+ self.level3 = Tree(cfg, levels[3], block, channels[2], channels[3], 2,
+ level_root=True, root_residual=residual_root)
+ self.level4 = Tree(cfg, levels[4], block, channels[3], channels[4], 2,
+ level_root=True, root_residual=residual_root)
+ self.level5 = Tree(cfg, levels[5], block, channels[4], channels[5], 2,
+ level_root=True, root_residual=residual_root)
+
+ # self.avgpool = nn.AvgPool2d(pool_size)
+ # self.fc = nn.Conv2d(channels[-1], num_classes, kernel_size=1,
+ # stride=1, padding=0, bias=True)
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
+ m.weight.data.normal_(0, math.sqrt(2. / n))
+
+ def _make_level(self, block, inplanes, planes, blocks, stride=1):
+ downsample = None
+ if stride != 1 or inplanes != planes:
+ downsample = nn.Sequential(
+ nn.MaxPool2d(stride, stride=stride),
+ nn.Conv2d(inplanes, planes,
+ kernel_size=1, stride=1, bias=False),
+ get_norm(self.cfg.MODEL.DLA.NORM, planes),
+ )
+
+ layers = []
+ layers.append(block(inplanes, planes, stride, downsample=downsample))
+ for i in range(1, blocks):
+ layers.append(block(inplanes, planes))
+
+ return nn.Sequential(*layers)
+
+ def _make_conv_level(self, inplanes, planes, convs, stride=1, dilation=1):
+ modules = []
+ for i in range(convs):
+ modules.extend([
+ nn.Conv2d(inplanes, planes, kernel_size=3,
+ stride=stride if i == 0 else 1,
+ padding=dilation, bias=False, dilation=dilation),
+ get_norm(self.cfg.MODEL.DLA.NORM, planes),
+ nn.ReLU(inplace=True)])
+ inplanes = planes
+ return nn.Sequential(*modules)
+
+ def forward(self, x):
+ y = {}
+ x = self.base_layer(x)
+ for i in range(6):
+ name = 'level{}'.format(i)
+ x = getattr(self, name)(x)
+ y[name] = x
+ return y
+
+
+def dla34(cfg, pretrained=None, **kwargs): # DLA-34
+ model = DLA(cfg, [1, 1, 1, 2, 2, 1],
+ [16, 32, 64, 128, 256, 512],
+ block=BasicBlock, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla34')
+ return model
+
+
+def dla46_c(cfg, pretrained=None, **kwargs): # DLA-46-C
+ Bottleneck.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 2, 2, 1],
+ [16, 32, 64, 64, 128, 256],
+ block=Bottleneck, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla46_c')
+ return model
+
+
+def dla46x_c(cfg, pretrained=None, **kwargs): # DLA-X-46-C
+ BottleneckX.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 2, 2, 1],
+ [16, 32, 64, 64, 128, 256],
+ block=BottleneckX, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla46x_c')
+ return model
+
+
+def dla60x_c(cfg, pretrained=None, **kwargs): # DLA-X-60-C
+ BottleneckX.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 2, 3, 1],
+ [16, 32, 64, 64, 128, 256],
+ block=BottleneckX, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla60x_c')
+ return model
+
+
+def dla60(cfg, pretrained=None, **kwargs): # DLA-60
+ Bottleneck.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 2, 3, 1],
+ [16, 32, 128, 256, 512, 1024],
+ block=Bottleneck, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla60')
+ return model
+
+
+def dla60x(cfg, pretrained=None, **kwargs): # DLA-X-60
+ BottleneckX.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 2, 3, 1],
+ [16, 32, 128, 256, 512, 1024],
+ block=BottleneckX, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla60x')
+ return model
+
+
+def dla102(cfg, pretrained=None, **kwargs): # DLA-102
+ Bottleneck.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 3, 4, 1], [16, 32, 128, 256, 512, 1024],
+ block=Bottleneck, residual_root=True, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla102')
+ return model
+
+
+def dla102x(cfg, pretrained=None, **kwargs): # DLA-X-102
+ BottleneckX.expansion = 2
+ model = DLA(cfg, [1, 1, 1, 3, 4, 1], [16, 32, 128, 256, 512, 1024],
+ block=BottleneckX, residual_root=True, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla102x')
+ return model
+
+
+def dla102x2(cfg, pretrained=None, **kwargs): # DLA-X-102 64
+ BottleneckX.cardinality = 64
+ model = DLA(cfg, [1, 1, 1, 3, 4, 1], [16, 32, 128, 256, 512, 1024],
+ block=BottleneckX, residual_root=True, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla102x2')
+ return model
+
+
+def dla169(cfg, pretrained=None, **kwargs): # DLA-169
+ Bottleneck.expansion = 2
+ model = DLA(cfg, [1, 1, 2, 3, 5, 1], [16, 32, 128, 256, 512, 1024],
+ block=Bottleneck, residual_root=True, **kwargs)
+ if pretrained is not None:
+ model.load_pretrained_model(pretrained, 'dla169')
+ return model
+
+
+@BACKBONE_REGISTRY.register()
+def build_fcos_dla_fpn_backbone(cfg, input_shape: ShapeSpec):
+ """
+ Args:
+ cfg: a detectron2 CfgNode
+
+ Returns:
+ backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
+ """
+ assert cfg.MODEL.BACKBONE.FREEZE_AT == -1, "Freezing layers does not be supported for DLA"
+
+ depth_to_creator = {"DLA34": dla34}
+ bottom_up = depth_to_creator[cfg.MODEL.DLA.CONV_BODY](cfg)
+ in_features = cfg.MODEL.FPN.IN_FEATURES
+ out_channels = cfg.MODEL.FPN.OUT_CHANNELS
+ top_levels = cfg.MODEL.FCOS.TOP_LEVELS
+ in_channels_top = out_channels
+
+ if top_levels == 2:
+ top_block = LastLevelP6P7(in_channels_top, out_channels, "p5")
+ elif top_levels == 1:
+ top_block = LastLevelP6(in_channels_top, out_channels, "p5")
+ elif top_levels == 0:
+ top_block = None
+ else:
+ raise NotImplementedError()
+
+ backbone = FPN(
+ bottom_up=bottom_up,
+ in_features=in_features,
+ out_channels=out_channels,
+ norm=cfg.MODEL.FPN.NORM,
+ top_block=top_block,
+ fuse_type=cfg.MODEL.FPN.FUSE_TYPE,
+ )
+
+ return backbone
diff --git a/AdelaiDet/adet/modeling/backbone/fpn.py b/AdelaiDet/adet/modeling/backbone/fpn.py
new file mode 100755
index 0000000..70adaa0
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/fpn.py
@@ -0,0 +1,88 @@
+from torch import nn
+import torch.nn.functional as F
+import fvcore.nn.weight_init as weight_init
+
+from detectron2.modeling.backbone import FPN, build_resnet_backbone
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.backbone.build import BACKBONE_REGISTRY
+
+from .resnet_lpf import build_resnet_lpf_backbone
+from .resnet_interval import build_resnet_interval_backbone
+from .mobilenet import build_mnv2_backbone
+
+
+class LastLevelP6P7(nn.Module):
+ """
+ This module is used in RetinaNet and FCOS to generate extra layers, P6 and P7 from
+ C5 or P5 feature.
+ """
+
+ def __init__(self, in_channels, out_channels, in_features="res5"):
+ super().__init__()
+ self.num_levels = 2
+ self.in_feature = in_features
+ self.p6 = nn.Conv2d(in_channels, out_channels, 3, 2, 1)
+ self.p7 = nn.Conv2d(out_channels, out_channels, 3, 2, 1)
+ for module in [self.p6, self.p7]:
+ weight_init.c2_xavier_fill(module)
+
+ def forward(self, x):
+ p6 = self.p6(x)
+ p7 = self.p7(F.relu(p6))
+ return [p6, p7]
+
+
+class LastLevelP6(nn.Module):
+ """
+ This module is used in FCOS to generate extra layers
+ """
+
+ def __init__(self, in_channels, out_channels, in_features="res5"):
+ super().__init__()
+ self.num_levels = 1
+ self.in_feature = in_features
+ self.p6 = nn.Conv2d(in_channels, out_channels, 3, 2, 1)
+ for module in [self.p6]:
+ weight_init.c2_xavier_fill(module)
+
+ def forward(self, x):
+ p6 = self.p6(x)
+ return [p6]
+
+
+@BACKBONE_REGISTRY.register()
+def build_fcos_resnet_fpn_backbone(cfg, input_shape: ShapeSpec):
+ """
+ Args:
+ cfg: a detectron2 CfgNode
+
+ Returns:
+ backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
+ """
+ if cfg.MODEL.BACKBONE.ANTI_ALIAS:
+ bottom_up = build_resnet_lpf_backbone(cfg, input_shape)
+ elif cfg.MODEL.RESNETS.DEFORM_INTERVAL > 1:
+ bottom_up = build_resnet_interval_backbone(cfg, input_shape)
+ elif cfg.MODEL.MOBILENET:
+ bottom_up = build_mnv2_backbone(cfg, input_shape)
+ else:
+ bottom_up = build_resnet_backbone(cfg, input_shape)
+ in_features = cfg.MODEL.FPN.IN_FEATURES
+ out_channels = cfg.MODEL.FPN.OUT_CHANNELS
+ top_levels = cfg.MODEL.FCOS.TOP_LEVELS
+ in_channels_top = out_channels
+ if top_levels == 2:
+ top_block = LastLevelP6P7(in_channels_top, out_channels, "p5")
+ if top_levels == 1:
+ top_block = LastLevelP6(in_channels_top, out_channels, "p5")
+ elif top_levels == 0:
+ top_block = None
+ backbone = FPN(
+ bottom_up=bottom_up,
+ in_features=in_features,
+ out_channels=out_channels,
+ norm=cfg.MODEL.FPN.NORM,
+ top_block=top_block,
+ fuse_type=cfg.MODEL.FPN.FUSE_TYPE,
+ )
+ return backbone
diff --git a/AdelaiDet/adet/modeling/backbone/lpf.py b/AdelaiDet/adet/modeling/backbone/lpf.py
new file mode 100755
index 0000000..a2be70f
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/lpf.py
@@ -0,0 +1,114 @@
+import torch
+import torch.nn.parallel
+import numpy as np
+import torch.nn as nn
+import torch.nn.functional as F
+
+
+class Downsample(nn.Module):
+ def __init__(self, pad_type='reflect', filt_size=3, stride=2, channels=None, pad_off=0):
+ super(Downsample, self).__init__()
+ self.filt_size = filt_size
+ self.pad_off = pad_off
+ self.pad_sizes = [int(1.*(filt_size-1)/2), int(np.ceil(1.*(filt_size-1)/2)), int(1.*(filt_size-1)/2), int(np.ceil(1.*(filt_size-1)/2))]
+ self.pad_sizes = [pad_size+pad_off for pad_size in self.pad_sizes]
+ self.stride = stride
+ self.off = int((self.stride-1)/2.)
+ self.channels = channels
+
+ # print('Filter size [%i]'%filt_size)
+ if(self.filt_size==1):
+ a = np.array([1.,])
+ elif(self.filt_size==2):
+ a = np.array([1., 1.])
+ elif(self.filt_size==3):
+ a = np.array([1., 2., 1.])
+ elif(self.filt_size==4):
+ a = np.array([1., 3., 3., 1.])
+ elif(self.filt_size==5):
+ a = np.array([1., 4., 6., 4., 1.])
+ elif(self.filt_size==6):
+ a = np.array([1., 5., 10., 10., 5., 1.])
+ elif(self.filt_size==7):
+ a = np.array([1., 6., 15., 20., 15., 6., 1.])
+
+ filt = torch.Tensor(a[:,None]*a[None,:])
+ filt = filt/torch.sum(filt)
+ self.register_buffer('filt', filt[None,None,:,:].repeat((self.channels,1,1,1)))
+
+ self.pad = get_pad_layer(pad_type)(self.pad_sizes)
+
+ def forward(self, inp):
+ if(self.filt_size==1):
+ if(self.pad_off==0):
+ return inp[:,:,::self.stride,::self.stride]
+ else:
+ return self.pad(inp)[:,:,::self.stride,::self.stride]
+ else:
+ return F.conv2d(self.pad(inp), self.filt, stride=self.stride, groups=inp.shape[1])
+
+def get_pad_layer(pad_type):
+ if(pad_type in ['refl','reflect']):
+ PadLayer = nn.ReflectionPad2d
+ elif(pad_type in ['repl','replicate']):
+ PadLayer = nn.ReplicationPad2d
+ elif(pad_type=='zero'):
+ PadLayer = nn.ZeroPad2d
+ else:
+ print('Pad type [%s] not recognized'%pad_type)
+ return PadLayer
+
+
+class Downsample1D(nn.Module):
+ def __init__(self, pad_type='reflect', filt_size=3, stride=2, channels=None, pad_off=0):
+ super(Downsample1D, self).__init__()
+ self.filt_size = filt_size
+ self.pad_off = pad_off
+ self.pad_sizes = [int(1. * (filt_size - 1) / 2), int(np.ceil(1. * (filt_size - 1) / 2))]
+ self.pad_sizes = [pad_size + pad_off for pad_size in self.pad_sizes]
+ self.stride = stride
+ self.off = int((self.stride - 1) / 2.)
+ self.channels = channels
+
+ # print('Filter size [%i]' % filt_size)
+ if(self.filt_size == 1):
+ a = np.array([1., ])
+ elif(self.filt_size == 2):
+ a = np.array([1., 1.])
+ elif(self.filt_size == 3):
+ a = np.array([1., 2., 1.])
+ elif(self.filt_size == 4):
+ a = np.array([1., 3., 3., 1.])
+ elif(self.filt_size == 5):
+ a = np.array([1., 4., 6., 4., 1.])
+ elif(self.filt_size == 6):
+ a = np.array([1., 5., 10., 10., 5., 1.])
+ elif(self.filt_size == 7):
+ a = np.array([1., 6., 15., 20., 15., 6., 1.])
+
+ filt = torch.Tensor(a)
+ filt = filt / torch.sum(filt)
+ self.register_buffer('filt', filt[None, None, :].repeat((self.channels, 1, 1)))
+
+ self.pad = get_pad_layer_1d(pad_type)(self.pad_sizes)
+
+ def forward(self, inp):
+ if(self.filt_size == 1):
+ if(self.pad_off == 0):
+ return inp[:, :, ::self.stride]
+ else:
+ return self.pad(inp)[:, :, ::self.stride]
+ else:
+ return F.conv1d(self.pad(inp), self.filt, stride=self.stride, groups=inp.shape[1])
+
+
+def get_pad_layer_1d(pad_type):
+ if(pad_type in ['refl', 'reflect']):
+ PadLayer = nn.ReflectionPad1d
+ elif(pad_type in ['repl', 'replicate']):
+ PadLayer = nn.ReplicationPad1d
+ elif(pad_type == 'zero'):
+ PadLayer = nn.ZeroPad1d
+ else:
+ print('Pad type [%s] not recognized' % pad_type)
+ return PadLayer
diff --git a/AdelaiDet/adet/modeling/backbone/mobilenet.py b/AdelaiDet/adet/modeling/backbone/mobilenet.py
new file mode 100755
index 0000000..cbd231f
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/mobilenet.py
@@ -0,0 +1,155 @@
+# taken from https://github.com/tonylins/pytorch-mobilenet-v2/
+# Published by Ji Lin, tonylins
+# licensed under the Apache License, Version 2.0, January 2004
+
+from torch import nn
+from torch.nn import BatchNorm2d
+#from detectron2.layers.batch_norm import NaiveSyncBatchNorm as BatchNorm2d
+from detectron2.layers import Conv2d
+from detectron2.modeling.backbone.build import BACKBONE_REGISTRY
+from detectron2.modeling.backbone import Backbone
+
+
+def conv_bn(inp, oup, stride):
+ return nn.Sequential(
+ Conv2d(inp, oup, 3, stride, 1, bias=False),
+ BatchNorm2d(oup),
+ nn.ReLU6(inplace=True)
+ )
+
+
+def conv_1x1_bn(inp, oup):
+ return nn.Sequential(
+ Conv2d(inp, oup, 1, 1, 0, bias=False),
+ BatchNorm2d(oup),
+ nn.ReLU6(inplace=True)
+ )
+
+
+class InvertedResidual(nn.Module):
+ def __init__(self, inp, oup, stride, expand_ratio):
+ super(InvertedResidual, self).__init__()
+ self.stride = stride
+ assert stride in [1, 2]
+
+ hidden_dim = int(round(inp * expand_ratio))
+ self.use_res_connect = self.stride == 1 and inp == oup
+
+ if expand_ratio == 1:
+ self.conv = nn.Sequential(
+ # dw
+ Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
+ BatchNorm2d(hidden_dim),
+ nn.ReLU6(inplace=True),
+ # pw-linear
+ Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
+ BatchNorm2d(oup),
+ )
+ else:
+ self.conv = nn.Sequential(
+ # pw
+ Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
+ BatchNorm2d(hidden_dim),
+ nn.ReLU6(inplace=True),
+ # dw
+ Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
+ BatchNorm2d(hidden_dim),
+ nn.ReLU6(inplace=True),
+ # pw-linear
+ Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
+ BatchNorm2d(oup),
+ )
+
+ def forward(self, x):
+ if self.use_res_connect:
+ return x + self.conv(x)
+ else:
+ return self.conv(x)
+
+
+class MobileNetV2(Backbone):
+ """
+ Should freeze bn
+ """
+ def __init__(self, cfg, n_class=1000, input_size=224, width_mult=1.):
+ super(MobileNetV2, self).__init__()
+ block = InvertedResidual
+ input_channel = 32
+ interverted_residual_setting = [
+ # t, c, n, s
+ [1, 16, 1, 1],
+ [6, 24, 2, 2],
+ [6, 32, 3, 2],
+ [6, 64, 4, 2],
+ [6, 96, 3, 1],
+ [6, 160, 3, 2],
+ [6, 320, 1, 1],
+ ]
+
+ # building first layer
+ assert input_size % 32 == 0
+ input_channel = int(input_channel * width_mult)
+ self.return_features_indices = [3, 6, 13, 17]
+ self.return_features_num_channels = []
+ self.features = nn.ModuleList([conv_bn(3, input_channel, 2)])
+ # building inverted residual blocks
+ for t, c, n, s in interverted_residual_setting:
+ output_channel = int(c * width_mult)
+ for i in range(n):
+ if i == 0:
+ self.features.append(block(input_channel, output_channel, s, expand_ratio=t))
+ else:
+ self.features.append(block(input_channel, output_channel, 1, expand_ratio=t))
+ input_channel = output_channel
+ if len(self.features) - 1 in self.return_features_indices:
+ self.return_features_num_channels.append(output_channel)
+
+ self._initialize_weights()
+ self._freeze_backbone(cfg.MODEL.BACKBONE.FREEZE_AT)
+
+ def _freeze_backbone(self, freeze_at):
+ for layer_index in range(freeze_at):
+ for p in self.features[layer_index].parameters():
+ p.requires_grad = False
+
+ def forward(self, x):
+ res = []
+ for i, m in enumerate(self.features):
+ x = m(x)
+ if i in self.return_features_indices:
+ res.append(x)
+ return {'res{}'.format(i + 2): r for i, r in enumerate(res)}
+
+ def _initialize_weights(self):
+ for m in self.modules():
+ if isinstance(m, Conv2d):
+ n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
+ m.weight.data.normal_(0, (2. / n) ** 0.5)
+ if m.bias is not None:
+ m.bias.data.zero_()
+ elif isinstance(m, BatchNorm2d):
+ m.weight.data.fill_(1)
+ m.bias.data.zero_()
+ elif isinstance(m, nn.Linear):
+ n = m.weight.size(1)
+ m.weight.data.normal_(0, 0.01)
+ m.bias.data.zero_()
+
+@BACKBONE_REGISTRY.register()
+def build_mnv2_backbone(cfg, input_shape):
+ """
+ Create a ResNet instance from config.
+
+ Returns:
+ ResNet: a :class:`ResNet` instance.
+ """
+ out_features = cfg.MODEL.RESNETS.OUT_FEATURES
+
+ out_feature_channels = {"res2": 24, "res3": 32,
+ "res4": 96, "res5": 320}
+ out_feature_strides = {"res2": 4, "res3": 8, "res4": 16, "res5": 32}
+ model = MobileNetV2(cfg)
+ model._out_features = out_features
+ model._out_feature_channels = out_feature_channels
+ model._out_feature_strides = out_feature_strides
+ return model
diff --git a/AdelaiDet/adet/modeling/backbone/resnet_interval.py b/AdelaiDet/adet/modeling/backbone/resnet_interval.py
new file mode 100755
index 0000000..b91be6e
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/resnet_interval.py
@@ -0,0 +1,116 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from detectron2.layers import FrozenBatchNorm2d
+from detectron2.modeling.backbone import BACKBONE_REGISTRY
+from detectron2.modeling.backbone.resnet import (
+ BasicStem,
+ DeformBottleneckBlock,
+ BottleneckBlock,
+ ResNet,
+)
+
+
+def make_stage_intervals(block_class, num_blocks, first_stride, **kwargs):
+ """
+ Create a resnet stage by creating many blocks.
+ Args:
+ block_class (class): a subclass of ResNetBlockBase
+ num_blocks (int):
+ first_stride (int): the stride of the first block. The other blocks will have stride=1.
+ A `stride` argument will be passed to the block constructor.
+ kwargs: other arguments passed to the block constructor.
+
+ Returns:
+ list[nn.Module]: a list of block module.
+ """
+ blocks = []
+ conv_kwargs = {key: kwargs[key] for key in kwargs if "deform" not in key}
+ deform_kwargs = {key: kwargs[key] for key in kwargs if key != "deform_interval"}
+ deform_interval = kwargs.get("deform_interval", None)
+ for i in range(num_blocks):
+ if deform_interval and i % deform_interval == 0:
+ blocks.append(block_class(stride=first_stride if i == 0 else 1, **deform_kwargs))
+ else:
+ blocks.append(BottleneckBlock(stride=first_stride if i == 0 else 1, **conv_kwargs))
+ conv_kwargs["in_channels"] = conv_kwargs["out_channels"]
+ deform_kwargs["in_channels"] = deform_kwargs["out_channels"]
+ return blocks
+
+
+@BACKBONE_REGISTRY.register()
+def build_resnet_interval_backbone(cfg, input_shape):
+ """
+ Create a ResNet instance from config.
+
+ Returns:
+ ResNet: a :class:`ResNet` instance.
+ """
+ # need registration of new blocks/stems?
+ norm = cfg.MODEL.RESNETS.NORM
+ stem = BasicStem(
+ in_channels=input_shape.channels,
+ out_channels=cfg.MODEL.RESNETS.STEM_OUT_CHANNELS,
+ norm=norm,
+ )
+ freeze_at = cfg.MODEL.BACKBONE.FREEZE_AT
+
+ if freeze_at >= 1:
+ for p in stem.parameters():
+ p.requires_grad = False
+ stem = FrozenBatchNorm2d.convert_frozen_batchnorm(stem)
+
+ # fmt: off
+ out_features = cfg.MODEL.RESNETS.OUT_FEATURES
+ depth = cfg.MODEL.RESNETS.DEPTH
+ num_groups = cfg.MODEL.RESNETS.NUM_GROUPS
+ width_per_group = cfg.MODEL.RESNETS.WIDTH_PER_GROUP
+ bottleneck_channels = num_groups * width_per_group
+ in_channels = cfg.MODEL.RESNETS.STEM_OUT_CHANNELS
+ out_channels = cfg.MODEL.RESNETS.RES2_OUT_CHANNELS
+ stride_in_1x1 = cfg.MODEL.RESNETS.STRIDE_IN_1X1
+ res5_dilation = cfg.MODEL.RESNETS.RES5_DILATION
+ deform_on_per_stage = cfg.MODEL.RESNETS.DEFORM_ON_PER_STAGE
+ deform_modulated = cfg.MODEL.RESNETS.DEFORM_MODULATED
+ deform_num_groups = cfg.MODEL.RESNETS.DEFORM_NUM_GROUPS
+ deform_interval = cfg.MODEL.RESNETS.DEFORM_INTERVAL
+ # fmt: on
+ assert res5_dilation in {1, 2}, "res5_dilation cannot be {}.".format(res5_dilation)
+
+ num_blocks_per_stage = {50: [3, 4, 6, 3], 101: [3, 4, 23, 3], 152: [3, 8, 36, 3]}[depth]
+
+ stages = []
+
+ # Avoid creating variables without gradients
+ # It consumes extra memory and may cause allreduce to fail
+ out_stage_idx = [{"res2": 2, "res3": 3, "res4": 4, "res5": 5}[f] for f in out_features]
+ max_stage_idx = max(out_stage_idx)
+ for idx, stage_idx in enumerate(range(2, max_stage_idx + 1)):
+ dilation = res5_dilation if stage_idx == 5 else 1
+ first_stride = 1 if idx == 0 or (stage_idx == 5 and dilation == 2) else 2
+ stage_kargs = {
+ "num_blocks": num_blocks_per_stage[idx],
+ "first_stride": first_stride,
+ "in_channels": in_channels,
+ "bottleneck_channels": bottleneck_channels,
+ "out_channels": out_channels,
+ "num_groups": num_groups,
+ "norm": norm,
+ "stride_in_1x1": stride_in_1x1,
+ "dilation": dilation,
+ }
+ if deform_on_per_stage[idx]:
+ stage_kargs["block_class"] = DeformBottleneckBlock
+ stage_kargs["deform_modulated"] = deform_modulated
+ stage_kargs["deform_num_groups"] = deform_num_groups
+ stage_kargs["deform_interval"] = deform_interval
+ else:
+ stage_kargs["block_class"] = BottleneckBlock
+ blocks = make_stage_intervals(**stage_kargs)
+ in_channels = out_channels
+ out_channels *= 2
+ bottleneck_channels *= 2
+
+ if freeze_at >= stage_idx:
+ for block in blocks:
+ block.freeze()
+ stages.append(blocks)
+ return ResNet(stem, stages, out_features=out_features)
diff --git a/AdelaiDet/adet/modeling/backbone/resnet_lpf.py b/AdelaiDet/adet/modeling/backbone/resnet_lpf.py
new file mode 100755
index 0000000..867de6d
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/resnet_lpf.py
@@ -0,0 +1,291 @@
+# This code is built from the PyTorch examples repository: https://github.com/pytorch/vision/tree/master/torchvision/models.
+# Copyright (c) 2017 Torch Contributors.
+# The Pytorch examples are available under the BSD 3-Clause License.
+#
+# ==========================================================================================
+#
+# Adobe’s modifications are Copyright 2019 Adobe. All rights reserved.
+# Adobe’s modifications are licensed under the Creative Commons Attribution-NonCommercial-ShareAlike
+# 4.0 International Public License (CC-NC-SA-4.0). To view a copy of the license, visit
+# https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.
+#
+# ==========================================================================================
+#
+# BSD-3 License
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# * Neither the name of the copyright holder nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+
+import torch.nn as nn
+
+from detectron2.layers.batch_norm import NaiveSyncBatchNorm
+from detectron2.modeling.backbone.build import BACKBONE_REGISTRY
+from detectron2.modeling.backbone import Backbone
+
+from .lpf import *
+
+
+__all__ = ['ResNetLPF', 'build_resnet_lpf_backbone']
+
+
+def conv3x3(in_planes, out_planes, stride=1, groups=1):
+ """3x3 convolution with padding"""
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
+ padding=1, groups=groups, bias=False)
+
+
+def conv1x1(in_planes, out_planes, stride=1):
+ """1x1 convolution"""
+ return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
+
+
+class BasicBlock(nn.Module):
+ expansion = 1
+
+ def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, norm_layer=None, filter_size=1):
+ super(BasicBlock, self).__init__()
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ if groups != 1:
+ raise ValueError('BasicBlock only supports groups=1')
+ # Both self.conv1 and self.downsample layers downsample the input when stride != 1
+ self.conv1 = conv3x3(inplanes, planes)
+ self.bn1 = norm_layer(planes)
+ self.relu = nn.ReLU(inplace=True)
+ if(stride == 1):
+ self.conv2 = conv3x3(planes, planes)
+ else:
+ self.conv2 = nn.Sequential(Downsample(filt_size=filter_size, stride=stride, channels=planes),
+ conv3x3(planes, planes),)
+ self.bn2 = norm_layer(planes)
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ identity = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+
+ if self.downsample is not None:
+ identity = self.downsample(x)
+
+ out += identity
+ out = self.relu(out)
+
+ return out
+
+
+class Bottleneck(nn.Module):
+ expansion = 4
+
+ def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, norm_layer=None, filter_size=1):
+ super(Bottleneck, self).__init__()
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ # Both self.conv2 and self.downsample layers downsample the input when stride != 1
+ self.conv1 = conv1x1(inplanes, planes)
+ self.bn1 = norm_layer(planes)
+ self.conv2 = conv3x3(planes, planes, groups) # stride moved
+ self.bn2 = norm_layer(planes)
+ if(stride == 1):
+ self.conv3 = conv1x1(planes, planes * self.expansion)
+ else:
+ self.conv3 = nn.Sequential(Downsample(filt_size=filter_size, stride=stride, channels=planes),
+ conv1x1(planes, planes * self.expansion))
+ self.bn3 = norm_layer(planes * self.expansion)
+ self.relu = nn.ReLU(inplace=True)
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ identity = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.relu(out)
+
+ out = self.conv3(out)
+ out = self.bn3(out)
+
+ if self.downsample is not None:
+ identity = self.downsample(x)
+
+ out += identity
+ out = self.relu(out)
+
+ return out
+
+
+class ResNetLPF(Backbone):
+
+ def __init__(self, cfg, block, layers, num_classes=1000, zero_init_residual=False,
+ groups=1, width_per_group=64, norm_layer=None, filter_size=1,
+ pool_only=True, return_idx=[0, 1, 2, 3]):
+ super().__init__()
+ self.return_idx = return_idx
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ planes = [int(width_per_group * groups * 2 ** i) for i in range(4)]
+ self.inplanes = planes[0]
+
+ if(pool_only):
+ self.conv1 = nn.Conv2d(3, planes[0], kernel_size=7, stride=2, padding=3, bias=False)
+ else:
+ self.conv1 = nn.Conv2d(3, planes[0], kernel_size=7, stride=1, padding=3, bias=False)
+ self.bn1 = norm_layer(planes[0])
+ self.relu = nn.ReLU(inplace=True)
+
+ if(pool_only):
+ self.maxpool = nn.Sequential(*[nn.MaxPool2d(kernel_size=2, stride=1),
+ Downsample(filt_size=filter_size, stride=2, channels=planes[0])])
+ else:
+ self.maxpool = nn.Sequential(*[Downsample(filt_size=filter_size, stride=2, channels=planes[0]),
+ nn.MaxPool2d(kernel_size=2, stride=1),
+ Downsample(filt_size=filter_size, stride=2, channels=planes[0])])
+
+ self.layer1 = self._make_layer(
+ block, planes[0], layers[0], groups=groups, norm_layer=norm_layer)
+ self.layer2 = self._make_layer(
+ block, planes[1], layers[1], stride=2, groups=groups, norm_layer=norm_layer, filter_size=filter_size)
+ self.layer3 = self._make_layer(
+ block, planes[2], layers[2], stride=2, groups=groups, norm_layer=norm_layer, filter_size=filter_size)
+ self.layer4 = self._make_layer(
+ block, planes[3], layers[3], stride=2, groups=groups, norm_layer=norm_layer, filter_size=filter_size)
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ if(m.in_channels != m.out_channels or m.out_channels != m.groups or m.bias is not None):
+ # don't want to reinitialize downsample layers, code assuming normal conv layers will not have these characteristics
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
+ else:
+ print('Not initializing')
+ elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
+ nn.init.constant_(m.weight, 1)
+ nn.init.constant_(m.bias, 0)
+
+ # Zero-initialize the last BN in each residual branch,
+ # so that the residual branch starts with zeros, and each residual block behaves like an identity.
+ # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
+ if zero_init_residual:
+ for m in self.modules():
+ if isinstance(m, Bottleneck):
+ nn.init.constant_(m.bn3.weight, 0)
+ elif isinstance(m, BasicBlock):
+ nn.init.constant_(m.bn2.weight, 0)
+ self._freeze_backbone(cfg.MODEL.BACKBONE.FREEZE_AT)
+ if False:
+ self._freeze_bn()
+
+ def _freeze_backbone(self, freeze_at):
+ if freeze_at < 0:
+ return
+ for stage_index in range(freeze_at):
+ if stage_index == 0:
+ # stage 0 is the stem
+ for p in self.conv1.parameters():
+ p.requires_grad = False
+ for p in self.bn1.parameters():
+ p.requires_grad = False
+ else:
+ m = getattr(self, "layer" + str(stage_index))
+ for p in m.parameters():
+ p.requires_grad = False
+
+ def _freeze_bn(self):
+ for m in self.modules():
+ if isinstance(m, nn.BatchNorm2d):
+ m.eval()
+
+ def _make_layer(self, block, planes, blocks, stride=1, groups=1, norm_layer=None, filter_size=1):
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ downsample = None
+ if stride != 1 or self.inplanes != planes * block.expansion:
+ # downsample = nn.Sequential(
+ # conv1x1(self.inplanes, planes * block.expansion, stride, filter_size=filter_size),
+ # norm_layer(planes * block.expansion),
+ # )
+
+ downsample = [Downsample(filt_size=filter_size, stride=stride,
+ channels=self.inplanes), ] if(stride != 1) else []
+ downsample += [conv1x1(self.inplanes, planes * block.expansion, 1),
+ norm_layer(planes * block.expansion)]
+ # print(downsample)
+ downsample = nn.Sequential(*downsample)
+
+ layers = []
+ layers.append(block(self.inplanes, planes, stride, downsample,
+ groups, norm_layer, filter_size=filter_size))
+ self.inplanes = planes * block.expansion
+ for _ in range(1, blocks):
+ layers.append(block(self.inplanes, planes, groups=groups,
+ norm_layer=norm_layer, filter_size=filter_size))
+
+ return nn.Sequential(*layers)
+
+ def forward(self, x):
+ x = self.conv1(x)
+ x = self.bn1(x)
+ x = self.relu(x)
+ x = self.maxpool(x)
+
+ outs = []
+ outs.append(self.layer1(x)) # 1/4
+ outs.append(self.layer2(outs[-1])) # 1/8
+ outs.append(self.layer3(outs[-1])) # 1/16
+ outs.append(self.layer4(outs[-1])) # 1/32
+ return {"res{}".format(idx + 2): outs[idx] for idx in self.return_idx}
+
+
+@BACKBONE_REGISTRY.register()
+def build_resnet_lpf_backbone(cfg, input_shape):
+ """
+ Create a ResNet instance from config.
+
+ Returns:
+ ResNet: a :class:`ResNet` instance.
+ """
+ depth = cfg.MODEL.RESNETS.DEPTH
+ out_features = cfg.MODEL.RESNETS.OUT_FEATURES
+
+ num_blocks_per_stage = {50: [3, 4, 6, 3], 101: [3, 4, 23, 3], 152: [3, 8, 36, 3]}[depth]
+ out_stage_idx = [{"res2": 0, "res3": 1, "res4": 2, "res5": 3}[f] for f in out_features]
+ out_feature_channels = {"res2": 256, "res3": 512,
+ "res4": 1024, "res5": 2048}
+ out_feature_strides = {"res2": 4, "res3": 8, "res4": 16, "res5": 32}
+ model = ResNetLPF(cfg, Bottleneck, num_blocks_per_stage, norm_layer=NaiveSyncBatchNorm,
+ filter_size=3, pool_only=True, return_idx=out_stage_idx)
+ model._out_features = out_features
+ model._out_feature_channels = out_feature_channels
+ model._out_feature_strides = out_feature_strides
+ return model
diff --git a/AdelaiDet/adet/modeling/backbone/vovnet.py b/AdelaiDet/adet/modeling/backbone/vovnet.py
new file mode 100755
index 0000000..fde0a1d
--- /dev/null
+++ b/AdelaiDet/adet/modeling/backbone/vovnet.py
@@ -0,0 +1,376 @@
+# Copyright (c) Youngwan Lee (ETRI) All Rights Reserved.
+from collections import OrderedDict
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+import fvcore.nn.weight_init as weight_init
+from detectron2.modeling.backbone import Backbone
+from detectron2.modeling.backbone.build import BACKBONE_REGISTRY
+from detectron2.modeling.backbone.fpn import FPN
+from detectron2.layers import (
+ Conv2d,
+ DeformConv,
+ FrozenBatchNorm2d,
+ ShapeSpec,
+ get_norm,
+)
+from .fpn import LastLevelP6, LastLevelP6P7
+
+__all__ = [
+ "VoVNet",
+ "build_vovnet_backbone",
+ "build_vovnet_fpn_backbone"
+]
+
+_NORM = False
+
+VoVNet19_eSE = {
+ 'stage_conv_ch': [128, 160, 192, 224],
+ 'stage_out_ch': [256, 512, 768, 1024],
+ 'layer_per_block': 3,
+ 'block_per_stage': [1, 1, 1, 1],
+ 'eSE' : True
+}
+
+VoVNet39_eSE = {
+ 'stage_conv_ch': [128, 160, 192, 224],
+ 'stage_out_ch': [256, 512, 768, 1024],
+ 'layer_per_block': 5,
+ 'block_per_stage': [1, 1, 2, 2],
+ 'eSE' : True
+}
+
+VoVNet57_eSE = {
+ 'stage_conv_ch': [128, 160, 192, 224],
+ 'stage_out_ch': [256, 512, 768, 1024],
+ 'layer_per_block': 5,
+ 'block_per_stage': [1, 1, 4, 3],
+ 'eSE' : True
+}
+
+VoVNet99_eSE = {
+ 'stage_conv_ch': [128, 160, 192, 224],
+ 'stage_out_ch': [256, 512, 768, 1024],
+ 'layer_per_block': 5,
+ 'block_per_stage': [1, 3, 9, 3],
+ 'eSE' : True
+}
+
+_STAGE_SPECS = {
+ "V-19-eSE": VoVNet19_eSE,
+ "V-39-eSE": VoVNet39_eSE,
+ "V-57-eSE": VoVNet57_eSE,
+ "V-99-eSE": VoVNet99_eSE
+}
+
+def conv3x3(in_channels, out_channels, module_name, postfix,
+ stride=1, groups=1, kernel_size=3, padding=1):
+ """3x3 convolution with padding"""
+ return [
+ (f'{module_name}_{postfix}/conv',
+ nn.Conv2d(in_channels,
+ out_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=padding,
+ groups=groups,
+ bias=False)),
+ (f'{module_name}_{postfix}/norm', get_norm(_NORM, out_channels)),
+ (f'{module_name}_{postfix}/relu', nn.ReLU(inplace=True))
+ ]
+
+
+def conv1x1(in_channels, out_channels, module_name, postfix,
+ stride=1, groups=1, kernel_size=1, padding=0):
+ """1x1 convolution with padding"""
+ return [
+ (f'{module_name}_{postfix}/conv',
+ nn.Conv2d(in_channels,
+ out_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=padding,
+ groups=groups,
+ bias=False)),
+ (f'{module_name}_{postfix}/norm', get_norm(_NORM, out_channels)),
+ (f'{module_name}_{postfix}/relu', nn.ReLU(inplace=True))
+ ]
+
+class Hsigmoid(nn.Module):
+ def __init__(self, inplace=True):
+ super(Hsigmoid, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return F.relu6(x + 3., inplace=self.inplace) / 6.
+
+
+class eSEModule(nn.Module):
+ def __init__(self, channel, reduction=4):
+ super(eSEModule, self).__init__()
+ self.avg_pool = nn.AdaptiveAvgPool2d(1)
+ self.fc = nn.Conv2d(channel,channel, kernel_size=1,
+ padding=0)
+ self.hsigmoid = Hsigmoid()
+
+ def forward(self, x):
+ input = x
+ x = self.avg_pool(x)
+ x = self.fc(x)
+ x = self.hsigmoid(x)
+ return input * x
+
+
+class _OSA_module(nn.Module):
+
+ def __init__(self,
+ in_ch,
+ stage_ch,
+ concat_ch,
+ layer_per_block,
+ module_name,
+ SE=False,
+ identity=False):
+
+ super(_OSA_module, self).__init__()
+
+ self.identity = identity
+ self.layers = nn.ModuleList()
+ in_channel = in_ch
+ for i in range(layer_per_block):
+ self.layers.append(nn.Sequential(OrderedDict(conv3x3(in_channel, stage_ch, module_name, i))))
+ in_channel = stage_ch
+
+ # feature aggregation
+ in_channel = in_ch + layer_per_block * stage_ch
+ self.concat = nn.Sequential(OrderedDict(conv1x1(in_channel, concat_ch, module_name, 'concat')))
+
+ self.ese = eSEModule(concat_ch)
+
+ def forward(self, x):
+
+ identity_feat = x
+
+ output = []
+ output.append(x)
+ for layer in self.layers:
+ x = layer(x)
+ output.append(x)
+
+ x = torch.cat(output, dim=1)
+ xt = self.concat(x)
+
+ xt = self.ese(xt)
+
+ if self.identity:
+ xt = xt + identity_feat
+
+ return xt
+
+
+class _OSA_stage(nn.Sequential):
+
+ def __init__(self,
+ in_ch,
+ stage_ch,
+ concat_ch,
+ block_per_stage,
+ layer_per_block,
+ stage_num,
+ SE=False):
+ super(_OSA_stage, self).__init__()
+
+ if not stage_num == 2:
+ self.add_module('Pooling', nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True))
+
+ if block_per_stage !=1:
+ SE = False
+ module_name = f'OSA{stage_num}_1'
+ self.add_module(module_name, _OSA_module(in_ch,
+ stage_ch,
+ concat_ch,
+ layer_per_block,
+ module_name,
+ SE))
+ for i in range(block_per_stage - 1):
+ if i != block_per_stage -2: #last block
+ SE = False
+ module_name = f'OSA{stage_num}_{i + 2}'
+ self.add_module(module_name,
+ _OSA_module(concat_ch,
+ stage_ch,
+ concat_ch,
+ layer_per_block,
+ module_name,
+ SE,
+ identity=True))
+
+
+
+class VoVNet(Backbone):
+
+ def __init__(self, cfg, input_ch, out_features=None):
+ """
+ Args:
+ input_ch(int) : the number of input channel
+ out_features (list[str]): name of the layers whose outputs should
+ be returned in forward. Can be anything in "stem", "stage2" ...
+ """
+ super(VoVNet, self).__init__()
+
+ global _NORM
+ _NORM = cfg.MODEL.VOVNET.NORM
+
+ stage_specs = _STAGE_SPECS[cfg.MODEL.VOVNET.CONV_BODY]
+
+ config_stage_ch = stage_specs['stage_conv_ch']
+ config_concat_ch = stage_specs['stage_out_ch']
+ block_per_stage = stage_specs['block_per_stage']
+ layer_per_block = stage_specs['layer_per_block']
+ SE = stage_specs['eSE']
+
+ self._out_features = out_features
+
+
+ # Stem module
+ stem = conv3x3(input_ch, 64, 'stem', '1', 2)
+ stem += conv3x3(64, 64, 'stem', '2', 1)
+ stem += conv3x3(64, 128, 'stem', '3', 2)
+ self.add_module('stem', nn.Sequential((OrderedDict(stem))))
+ current_stirde = 4
+ self._out_feature_strides = {"stem": current_stirde, "stage2": current_stirde}
+ self._out_feature_channels = {"stem": 128}
+
+ stem_out_ch = [128]
+ in_ch_list = stem_out_ch + config_concat_ch[:-1]
+ # OSA stages
+ self.stage_names = []
+ for i in range(4): # num_stages
+ name = 'stage%d' % (i + 2) # stage 2 ... stage 5
+ self.stage_names.append(name)
+ self.add_module(name, _OSA_stage(in_ch_list[i],
+ config_stage_ch[i],
+ config_concat_ch[i],
+ block_per_stage[i],
+ layer_per_block,
+ i + 2,
+ SE))
+
+ self._out_feature_channels[name] = config_concat_ch[i]
+ if not i == 0:
+ self._out_feature_strides[name] = current_stirde = int(
+ current_stirde * 2)
+
+ # initialize weights
+ self._initialize_weights()
+ # Optionally freeze (requires_grad=False) parts of the backbone
+ self._freeze_backbone(cfg.MODEL.BACKBONE.FREEZE_AT)
+
+
+ def _initialize_weights(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(m.weight)
+
+ def _freeze_backbone(self, freeze_at):
+ if freeze_at < 0:
+ return
+ # freeze BN layers
+ for m in self.modules():
+ if isinstance(m, nn.BatchNorm2d):
+ freeze_bn_params(m)
+ for stage_index in range(freeze_at):
+ if stage_index == 0:
+ m = self.stem # stage 0 is the stem
+ else:
+ m = getattr(self, "stage" + str(stage_index+1))
+ for p in m.parameters():
+ p.requires_grad = False
+ FrozenBatchNorm2d.convert_frozen_batchnorm(self)
+
+ def forward(self, x):
+ outputs = {}
+ x = self.stem(x)
+ if "stem" in self._out_features:
+ outputs["stem"] = x
+ for name in self.stage_names:
+ x = getattr(self, name)(x)
+ if name in self._out_features:
+ outputs[name] = x
+
+ return outputs
+
+ def output_shape(self):
+ return {
+ name: ShapeSpec(
+ channels=self._out_feature_channels[name], stride=self._out_feature_strides[name]
+ )
+ for name in self._out_features
+ }
+
+
+@BACKBONE_REGISTRY.register()
+def build_vovnet_backbone(cfg, input_shape):
+ """
+ Create a VoVNet instance from config.
+
+ Returns:
+ VoVNet: a :class:`VoVNet` instance.
+ """
+ out_features = cfg.MODEL.VOVNET.OUT_FEATURES
+ return VoVNet(cfg, input_shape.channels, out_features=out_features)
+
+
+@BACKBONE_REGISTRY.register()
+def build_vovnet_fpn_backbone(cfg, input_shape: ShapeSpec):
+ """
+ Args:
+ cfg: a detectron2 CfgNode
+
+ Returns:
+ backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
+ """
+ bottom_up = build_vovnet_backbone(cfg, input_shape)
+ in_features = cfg.MODEL.FPN.IN_FEATURES
+ out_channels = cfg.MODEL.FPN.OUT_CHANNELS
+ backbone = FPN(
+ bottom_up=bottom_up,
+ in_features=in_features,
+ out_channels=out_channels,
+ norm=cfg.MODEL.FPN.NORM,
+ top_block=LastLevelMaxPool(),
+ fuse_type=cfg.MODEL.FPN.FUSE_TYPE,
+ )
+ return backbone
+
+
+@BACKBONE_REGISTRY.register()
+def build_fcos_vovnet_fpn_backbone(cfg, input_shape: ShapeSpec):
+ """
+ Args:
+ cfg: a detectron2 CfgNode
+
+ Returns:
+ backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
+ """
+ bottom_up = build_vovnet_backbone(cfg, input_shape)
+ in_features = cfg.MODEL.FPN.IN_FEATURES
+ out_channels = cfg.MODEL.FPN.OUT_CHANNELS
+ top_levels = cfg.MODEL.FCOS.TOP_LEVELS
+ in_channels_top = out_channels
+ if top_levels == 2:
+ top_block = LastLevelP6P7(in_channels_top, out_channels, "p5")
+ if top_levels == 1:
+ top_block = LastLevelP6(in_channels_top, out_channels, "p5")
+ elif top_levels == 0:
+ top_block = None
+ backbone = FPN(
+ bottom_up=bottom_up,
+ in_features=in_features,
+ out_channels=out_channels,
+ norm=cfg.MODEL.FPN.NORM,
+ top_block=top_block,
+ fuse_type=cfg.MODEL.FPN.FUSE_TYPE,
+ )
+ return backbone
diff --git a/AdelaiDet/adet/modeling/batext/__init__.py b/AdelaiDet/adet/modeling/batext/__init__.py
new file mode 100755
index 0000000..35cd85f
--- /dev/null
+++ b/AdelaiDet/adet/modeling/batext/__init__.py
@@ -0,0 +1 @@
+from .batext import BAText
diff --git a/AdelaiDet/adet/modeling/batext/batext.py b/AdelaiDet/adet/modeling/batext/batext.py
new file mode 100755
index 0000000..974bec5
--- /dev/null
+++ b/AdelaiDet/adet/modeling/batext/batext.py
@@ -0,0 +1,262 @@
+import math
+from typing import List, Dict
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.proposal_generator.build import PROPOSAL_GENERATOR_REGISTRY
+
+from adet.layers import DFConv2d, IOULoss
+from .batext_outputs import BATextOutputs
+
+
+__all__ = ["BAText"]
+
+INF = 100000000
+
+
+class Scale(nn.Module):
+ def __init__(self, init_value=1.0):
+ super(Scale, self).__init__()
+ self.scale = nn.Parameter(torch.FloatTensor([init_value]))
+
+ def forward(self, input):
+ return input * self.scale
+
+
+@PROPOSAL_GENERATOR_REGISTRY.register()
+class BAText(nn.Module):
+ """
+ A modified version of FCOS with Bezier regression
+ """
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ super().__init__()
+ # fmt: off
+ self.in_features = cfg.MODEL.FCOS.IN_FEATURES
+ self.fpn_strides = cfg.MODEL.FCOS.FPN_STRIDES
+ self.focal_loss_alpha = cfg.MODEL.FCOS.LOSS_ALPHA
+ self.focal_loss_gamma = cfg.MODEL.FCOS.LOSS_GAMMA
+ self.center_sample = cfg.MODEL.FCOS.CENTER_SAMPLE
+ self.strides = cfg.MODEL.FCOS.FPN_STRIDES
+ self.radius = cfg.MODEL.FCOS.POS_RADIUS
+ self.pre_nms_thresh_train = cfg.MODEL.FCOS.INFERENCE_TH_TRAIN
+ self.pre_nms_thresh_test = cfg.MODEL.FCOS.INFERENCE_TH_TEST
+ self.pre_nms_topk_train = cfg.MODEL.FCOS.PRE_NMS_TOPK_TRAIN
+ self.pre_nms_topk_test = cfg.MODEL.FCOS.PRE_NMS_TOPK_TEST
+ self.nms_thresh = cfg.MODEL.FCOS.NMS_TH
+ self.yield_proposal = cfg.MODEL.FCOS.YIELD_PROPOSAL
+ self.post_nms_topk_train = cfg.MODEL.FCOS.POST_NMS_TOPK_TRAIN
+ self.post_nms_topk_test = cfg.MODEL.FCOS.POST_NMS_TOPK_TEST
+ self.thresh_with_ctr = cfg.MODEL.FCOS.THRESH_WITH_CTR
+ # fmt: on
+ self.iou_loss = IOULoss(cfg.MODEL.FCOS.LOC_LOSS_TYPE)
+ # generate sizes of interest
+ soi = []
+ prev_size = -1
+ for s in cfg.MODEL.FCOS.SIZES_OF_INTEREST:
+ soi.append([prev_size, s])
+ prev_size = s
+ soi.append([prev_size, INF])
+ self.sizes_of_interest = soi
+ self.fcos_head = FCOSHead(cfg, [input_shape[f] for f in self.in_features])
+
+ def forward_head(self, features, top_module=None):
+ features = [features[f] for f in self.in_features]
+ pred_class_logits, pred_deltas, pred_centerness, top_feats, bbox_towers = self.fcos_head(
+ features, top_module, self.yield_proposal)
+ return pred_class_logits, pred_deltas, pred_centerness, top_feats, bbox_towers
+
+ def forward(self, images, features, gt_instances=None, top_module=None):
+ """
+ Arguments:
+ images (list[Tensor] or ImageList): images to be processed
+ targets (list[BoxList]): ground-truth boxes present in the image (optional)
+
+ Returns:
+ result (list[BoxList] or dict[Tensor]): the output from the model.
+ During training, it returns a dict[Tensor] which contains the losses.
+ During testing, it returns list[BoxList] contains additional fields
+ like `scores`, `labels` and `mask` (for Mask R-CNN models).
+
+ """
+ features = [features[f] for f in self.in_features]
+ locations = self.compute_locations(features)
+ logits_pred, reg_pred, ctrness_pred, top_feats, bbox_towers = self.fcos_head(
+ features, top_module, self.yield_proposal)
+
+ if self.training:
+ pre_nms_thresh = self.pre_nms_thresh_train
+ pre_nms_topk = self.pre_nms_topk_train
+ post_nms_topk = self.post_nms_topk_train
+ else:
+ pre_nms_thresh = self.pre_nms_thresh_test
+ pre_nms_topk = self.pre_nms_topk_test
+ post_nms_topk = self.post_nms_topk_test
+
+ outputs = BATextOutputs(
+ images,
+ locations,
+ logits_pred,
+ reg_pred,
+ ctrness_pred,
+ top_feats,
+ self.focal_loss_alpha,
+ self.focal_loss_gamma,
+ self.iou_loss,
+ self.center_sample,
+ self.sizes_of_interest,
+ self.strides,
+ self.radius,
+ self.fcos_head.num_classes,
+ pre_nms_thresh,
+ pre_nms_topk,
+ self.nms_thresh,
+ post_nms_topk,
+ self.thresh_with_ctr,
+ gt_instances
+ )
+
+ results = {}
+ if self.yield_proposal:
+ results["features"] = {
+ f: b for f, b in zip(self.in_features, bbox_towers)}
+
+ if self.training:
+ losses = outputs.losses()
+ results = outputs.predict_proposals(top_feats)
+ else:
+ losses = {}
+ with torch.no_grad():
+ proposals = outputs.predict_proposals(top_feats)
+ if self.yield_proposal:
+ results["proposals"] = proposals
+ else:
+ results = proposals
+ return results, losses
+
+ def compute_locations(self, features):
+ locations = []
+ for level, feature in enumerate(features):
+ h, w = feature.size()[-2:]
+ locations_per_level = self.compute_locations_per_level(
+ h, w, self.fpn_strides[level],
+ feature.device
+ )
+ locations.append(locations_per_level)
+ return locations
+
+ def compute_locations_per_level(self, h, w, stride, device):
+ shifts_x = torch.arange(
+ 0, w * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shifts_y = torch.arange(
+ 0, h * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
+ shift_x = shift_x.reshape(-1)
+ shift_y = shift_y.reshape(-1)
+ locations = torch.stack((shift_x, shift_y), dim=1) + stride // 2
+ return locations
+
+
+class FCOSHead(nn.Module):
+ def __init__(self, cfg, input_shape: List[ShapeSpec]):
+ """
+ Arguments:
+ in_channels (int): number of channels of the input feature
+ """
+ super().__init__()
+ # TODO: Implement the sigmoid version first.
+ self.num_classes = cfg.MODEL.FCOS.NUM_CLASSES
+ self.fpn_strides = cfg.MODEL.FCOS.FPN_STRIDES
+ head_configs = {"cls": (cfg.MODEL.FCOS.NUM_CLS_CONVS,
+ False),
+ "bbox": (cfg.MODEL.FCOS.NUM_BOX_CONVS,
+ cfg.MODEL.FCOS.USE_DEFORMABLE),
+ "share": (cfg.MODEL.FCOS.NUM_SHARE_CONVS,
+ cfg.MODEL.FCOS.USE_DEFORMABLE)}
+ norm = None if cfg.MODEL.FCOS.NORM == "none" else cfg.MODEL.FCOS.NORM
+
+ in_channels = [s.channels for s in input_shape]
+ assert len(set(in_channels)) == 1, "Each level must have the same channel!"
+ in_channels = in_channels[0]
+
+ for head in head_configs:
+ tower = []
+ num_convs, use_deformable = head_configs[head]
+ if use_deformable:
+ conv_func = DFConv2d
+ else:
+ conv_func = nn.Conv2d
+ for i in range(num_convs):
+ tower.append(conv_func(
+ in_channels, in_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=True
+ ))
+ if norm == "GN":
+ tower.append(nn.GroupNorm(32, in_channels))
+ tower.append(nn.ReLU())
+ self.add_module('{}_tower'.format(head),
+ nn.Sequential(*tower))
+
+ self.cls_logits = nn.Conv2d(
+ in_channels, self.num_classes,
+ kernel_size=3, stride=1,
+ padding=1
+ )
+ self.bbox_pred = nn.Conv2d(
+ in_channels, 4, kernel_size=3,
+ stride=1, padding=1
+ )
+ self.ctrness = nn.Conv2d(
+ in_channels, 1, kernel_size=3,
+ stride=1, padding=1
+ )
+
+ if cfg.MODEL.FCOS.USE_SCALE:
+ self.scales = nn.ModuleList([Scale(init_value=1.0) for _ in self.fpn_strides])
+ else:
+ self.scales = None
+
+ for modules in [
+ self.cls_tower, self.bbox_tower,
+ self.share_tower, self.cls_logits,
+ self.bbox_pred, self.ctrness
+ ]:
+ for l in modules.modules():
+ if isinstance(l, nn.Conv2d):
+ torch.nn.init.normal_(l.weight, std=0.01)
+ torch.nn.init.constant_(l.bias, 0)
+
+ # initialize the bias for focal loss
+ prior_prob = cfg.MODEL.FCOS.PRIOR_PROB
+ bias_value = -math.log((1 - prior_prob) / prior_prob)
+ torch.nn.init.constant_(self.cls_logits.bias, bias_value)
+
+ def forward(self, x, top_module=None, yield_bbox_towers=False):
+ logits = []
+ bbox_reg = []
+ ctrness = []
+ top_feats = []
+ bbox_towers = []
+ for l, feature in enumerate(x):
+ feature = self.share_tower(feature)
+ cls_tower = self.cls_tower(feature)
+ bbox_tower = self.bbox_tower(feature)
+ if yield_bbox_towers:
+ bbox_towers.append(bbox_tower)
+
+ logits.append(self.cls_logits(cls_tower))
+ ctrness.append(self.ctrness(bbox_tower))
+ reg = self.bbox_pred(bbox_tower)
+ if self.scales is not None:
+ reg = self.scales[l](reg)
+ # Note that we use relu, as in the improved FCOS, instead of exp.
+ bbox_reg.append(F.relu(reg))
+ if top_module is not None:
+ top_feats.append(top_module(bbox_tower))
+ return logits, bbox_reg, ctrness, top_feats, bbox_towers
diff --git a/AdelaiDet/adet/modeling/batext/batext_outputs.py b/AdelaiDet/adet/modeling/batext/batext_outputs.py
new file mode 100755
index 0000000..f2056e8
--- /dev/null
+++ b/AdelaiDet/adet/modeling/batext/batext_outputs.py
@@ -0,0 +1,532 @@
+import logging
+import torch
+import torch.nn.functional as F
+
+from detectron2.layers import cat
+from detectron2.structures import Instances, Boxes
+from adet.utils.comm import get_world_size
+from fvcore.nn import sigmoid_focal_loss_jit
+
+from adet.utils.comm import reduce_sum, compute_ious
+from adet.layers import ml_nms
+
+
+logger = logging.getLogger(__name__)
+
+INF = 100000000
+
+"""
+Shape shorthand in this module:
+
+ N: number of images in the minibatch
+ L: number of feature maps per image on which RPN is run
+ Hi, Wi: height and width of the i-th feature map
+ 4: size of the box parameterization
+
+Naming convention:
+
+ labels: refers to the ground-truth class of an position.
+
+ reg_targets: refers to the 4-d (left, top, right, bottom) distances that parameterize the ground-truth box.
+
+ logits_pred: predicted classification scores in [-inf, +inf];
+
+ reg_pred: the predicted (left, top, right, bottom), corresponding to reg_targets
+
+ ctrness_pred: predicted centerness scores
+
+"""
+
+
+def compute_ctrness_targets(reg_targets):
+ if len(reg_targets) == 0:
+ return reg_targets.new_zeros(len(reg_targets))
+ left_right = reg_targets[:, [0, 2]]
+ top_bottom = reg_targets[:, [1, 3]]
+ ctrness = (left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * \
+ (top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])
+ return torch.sqrt(ctrness)
+
+
+def fcos_losses(
+ labels,
+ reg_targets,
+ bezier_targets,
+ logits_pred,
+ reg_pred,
+ bezier_pred,
+ ctrness_pred,
+ focal_loss_alpha,
+ focal_loss_gamma,
+ iou_loss,
+):
+ num_classes = logits_pred.size(1)
+ labels = labels.flatten()
+
+ pos_inds = torch.nonzero(labels != num_classes).squeeze(1)
+ num_pos_local = pos_inds.numel()
+ num_gpus = get_world_size()
+ total_num_pos = reduce_sum(pos_inds.new_tensor([num_pos_local])).item()
+ num_pos_avg = max(total_num_pos / num_gpus, 1.0)
+
+ # prepare one_hot
+ class_target = torch.zeros_like(logits_pred)
+ class_target[pos_inds, labels[pos_inds]] = 1
+
+ class_loss = sigmoid_focal_loss_jit(
+ logits_pred,
+ class_target,
+ alpha=focal_loss_alpha,
+ gamma=focal_loss_gamma,
+ reduction="sum",
+ ) / num_pos_avg
+
+ reg_pred = reg_pred[pos_inds]
+ bezier_pred = bezier_pred[pos_inds]
+ reg_targets = reg_targets[pos_inds]
+ bezier_targets = bezier_targets[pos_inds]
+ ctrness_pred = ctrness_pred[pos_inds]
+
+ ious, gious = compute_ious(reg_pred, reg_targets)
+ ctrness_targets = compute_ctrness_targets(reg_targets)
+ ctrness_targets_sum = ctrness_targets.sum()
+ loss_denorm = max(reduce_sum(ctrness_targets_sum).item() / num_gpus, 1e-6)
+
+ if pos_inds.numel() > 0:
+ reg_loss = iou_loss(
+ ious,
+ gious,
+ ctrness_targets
+ ) / loss_denorm
+
+ ctrness_loss = F.binary_cross_entropy_with_logits(
+ ctrness_pred,
+ ctrness_targets,
+ reduction="sum"
+ ) / num_pos_avg
+ else:
+ reg_loss = reg_pred.sum() * 0
+ bezier_loss = bezier_pred.sum() * 0
+ ctrness_loss = ctrness_pred.sum() * 0
+
+ bezier_loss = F.smooth_l1_loss(
+ bezier_pred, bezier_targets, reduction="none")
+ bezier_loss = ((bezier_loss.mean(dim=-1) * ctrness_targets).sum()
+ / loss_denorm)
+
+ losses = {
+ "loss_fcos_cls": class_loss,
+ "loss_fcos_loc": reg_loss,
+ "loss_fcos_ctr": ctrness_loss,
+ "loss_fcos_bezier": bezier_loss,
+ }
+ return losses
+
+
+class BATextOutputs(object):
+ def __init__(
+ self,
+ images,
+ locations,
+ logits_pred,
+ reg_pred,
+ ctrness_pred,
+ bezier_pred,
+ focal_loss_alpha,
+ focal_loss_gamma,
+ iou_loss,
+ center_sample,
+ sizes_of_interest,
+ strides,
+ radius,
+ num_classes,
+ pre_nms_thresh,
+ pre_nms_top_n,
+ nms_thresh,
+ fpn_post_nms_top_n,
+ thresh_with_ctr,
+ gt_instances=None,
+ ):
+ self.logits_pred = logits_pred
+ self.reg_pred = reg_pred
+ self.bezier_pred = bezier_pred
+ self.ctrness_pred = ctrness_pred
+ self.locations = locations
+
+ self.gt_instances = gt_instances
+ self.num_feature_maps = len(logits_pred)
+ self.num_images = len(images)
+ self.image_sizes = images.image_sizes
+ self.focal_loss_alpha = focal_loss_alpha
+ self.focal_loss_gamma = focal_loss_gamma
+ self.iou_loss = iou_loss
+ self.center_sample = center_sample
+ self.sizes_of_interest = sizes_of_interest
+ self.strides = strides
+ self.radius = radius
+ self.num_classes = num_classes
+ self.pre_nms_thresh = pre_nms_thresh
+ self.pre_nms_top_n = pre_nms_top_n
+ self.nms_thresh = nms_thresh
+ self.fpn_post_nms_top_n = fpn_post_nms_top_n
+ self.thresh_with_ctr = thresh_with_ctr
+
+ def _transpose(self, training_targets, num_loc_list):
+ '''
+ This function is used to transpose image first training targets to level first ones
+ :return: level first training targets
+ '''
+ for im_i in range(len(training_targets)):
+ training_targets[im_i] = torch.split(
+ training_targets[im_i], num_loc_list, dim=0
+ )
+
+ targets_level_first = []
+ for targets_per_level in zip(*training_targets):
+ targets_level_first.append(
+ torch.cat(targets_per_level, dim=0)
+ )
+ return targets_level_first
+
+ def _get_ground_truth(self):
+ num_loc_list = [len(loc) for loc in self.locations]
+ self.num_loc_list = num_loc_list
+
+ # compute locations to size ranges
+ loc_to_size_range = []
+ for l, loc_per_level in enumerate(self.locations):
+ loc_to_size_range_per_level = loc_per_level.new_tensor(self.sizes_of_interest[l])
+ loc_to_size_range.append(
+ loc_to_size_range_per_level[None].expand(num_loc_list[l], -1)
+ )
+
+ loc_to_size_range = torch.cat(loc_to_size_range, dim=0)
+ locations = torch.cat(self.locations, dim=0)
+
+ training_targets = self.compute_targets_for_locations(
+ locations, self.gt_instances, loc_to_size_range
+ )
+
+ # transpose im first training_targets to level first ones
+ training_targets = {
+ k: self._transpose(v, num_loc_list) for k, v in training_targets.items()
+ }
+
+ # we normalize reg_targets by FPN's strides here
+ reg_targets = training_targets["reg_targets"]
+ bezier_targets = training_targets["bezier_targets"]
+ for l in range(len(reg_targets)):
+ reg_targets[l] = reg_targets[l] / float(self.strides[l])
+ bezier_targets[l] = bezier_targets[l] / float(self.strides[l])
+
+ return training_targets
+
+ def get_sample_region(self, gt, strides, num_loc_list, loc_xs, loc_ys, radius=1):
+ num_gts = gt.shape[0]
+ K = len(loc_xs)
+ gt = gt[None].expand(K, num_gts, 4)
+ center_x = (gt[..., 0] + gt[..., 2]) / 2
+ center_y = (gt[..., 1] + gt[..., 3]) / 2
+ center_gt = gt.new_zeros(gt.shape)
+ # no gt
+ if center_x.numel() == 0 or center_x[..., 0].sum() == 0:
+ return loc_xs.new_zeros(loc_xs.shape, dtype=torch.uint8)
+ beg = 0
+ for level, num_loc in enumerate(num_loc_list):
+ end = beg + num_loc
+ stride = strides[level] * radius
+ xmin = center_x[beg:end] - stride
+ ymin = center_y[beg:end] - stride
+ xmax = center_x[beg:end] + stride
+ ymax = center_y[beg:end] + stride
+ # limit sample region in gt
+ center_gt[beg:end, :, 0] = torch.where(xmin > gt[beg:end, :, 0], xmin, gt[beg:end, :, 0])
+ center_gt[beg:end, :, 1] = torch.where(ymin > gt[beg:end, :, 1], ymin, gt[beg:end, :, 1])
+ center_gt[beg:end, :, 2] = torch.where(xmax > gt[beg:end, :, 2], gt[beg:end, :, 2], xmax)
+ center_gt[beg:end, :, 3] = torch.where(ymax > gt[beg:end, :, 3], gt[beg:end, :, 3], ymax)
+ beg = end
+ left = loc_xs[:, None] - center_gt[..., 0]
+ right = center_gt[..., 2] - loc_xs[:, None]
+ top = loc_ys[:, None] - center_gt[..., 1]
+ bottom = center_gt[..., 3] - loc_ys[:, None]
+ center_bbox = torch.stack((left, top, right, bottom), -1)
+ inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0
+ return inside_gt_bbox_mask
+
+ def compute_targets_for_locations(self, locations, targets, size_ranges):
+ labels = []
+ reg_targets = []
+ bezier_targets = []
+ xs, ys = locations[:, 0], locations[:, 1]
+
+ num_targets = 0
+ for im_i in range(len(targets)):
+ targets_per_im = targets[im_i]
+ bboxes = targets_per_im.gt_boxes.tensor
+ labels_per_im = targets_per_im.gt_classes
+
+ # no gt
+ if bboxes.numel() == 0:
+ labels.append(labels_per_im.new_zeros(locations.size(0)) + self.num_classes)
+ reg_targets.append(locations.new_zeros((locations.size(0), 4)))
+ bezier_targets.append(locations.new_zeros((locations.size(0), 16)))
+ continue
+
+ area = targets_per_im.gt_boxes.area()
+
+ l = xs[:, None] - bboxes[:, 0][None]
+ t = ys[:, None] - bboxes[:, 1][None]
+ r = bboxes[:, 2][None] - xs[:, None]
+ b = bboxes[:, 3][None] - ys[:, None]
+ reg_targets_per_im = torch.stack([l, t, r, b], dim=2)
+
+ # bezier points are relative distances from center to control points
+ bezier_pts = targets_per_im.beziers.view(-1, 8, 2)
+ x_targets = bezier_pts[:, :, 0][None] - xs[:, None, None]
+ y_targets = bezier_pts[:, :, 1][None] - ys[:, None, None]
+ bezier_targets_per_im = torch.stack((x_targets, y_targets), dim=3)
+ bezier_targets_per_im = bezier_targets_per_im.view(xs.size(0), bboxes.size(0), 16)
+
+ if self.center_sample:
+ is_in_boxes = self.get_sample_region(
+ bboxes, self.strides, self.num_loc_list,
+ xs, ys, radius=self.radius
+ )
+ else:
+ is_in_boxes = reg_targets_per_im.min(dim=2)[0] > 0
+
+ max_reg_targets_per_im = reg_targets_per_im.max(dim=2)[0]
+ # limit the regression range for each location
+ is_cared_in_the_level = \
+ (max_reg_targets_per_im >= size_ranges[:, [0]]) & \
+ (max_reg_targets_per_im <= size_ranges[:, [1]])
+
+ locations_to_gt_area = area[None].repeat(len(locations), 1)
+ locations_to_gt_area[is_in_boxes == 0] = INF
+ locations_to_gt_area[is_cared_in_the_level == 0] = INF
+
+ # if there are still more than one objects for a location,
+ # we choose the one with minimal area
+ locations_to_min_area, locations_to_gt_inds = locations_to_gt_area.min(dim=1)
+
+ reg_targets_per_im = reg_targets_per_im[range(len(locations)), locations_to_gt_inds]
+ bezier_targets_per_im = bezier_targets_per_im[range(len(locations)), locations_to_gt_inds]
+ # target_inds_per_im = locations_to_gt_inds + num_targets
+
+ labels_per_im = labels_per_im[locations_to_gt_inds]
+ labels_per_im[locations_to_min_area == INF] = self.num_classes
+
+ labels.append(labels_per_im)
+ reg_targets.append(reg_targets_per_im)
+ bezier_targets.append(bezier_targets_per_im)
+ # target_inds.append(target_inds_per_im)
+
+ return {
+ "labels": labels,
+ "reg_targets": reg_targets,
+ "bezier_targets": bezier_targets}
+
+ def losses(self):
+ """
+ Return the losses from a set of FCOS predictions and their associated ground-truth.
+
+ Returns:
+ dict[loss name -> loss value]: A dict mapping from loss name to loss value.
+ """
+
+ training_targets = self._get_ground_truth()
+ labels, reg_targets, bezier_targets = (
+ training_targets["labels"],
+ training_targets["reg_targets"],
+ training_targets["bezier_targets"])
+
+ # Collect all logits and regression predictions over feature maps
+ # and images to arrive at the same shape as the labels and targets
+ # The final ordering is L, N, H, W from slowest to fastest axis.
+ logits_pred = cat(
+ [
+ # Reshape: (N, C, Hi, Wi) -> (N, Hi, Wi, C) -> (N*Hi*Wi, C)
+ x.permute(0, 2, 3, 1).reshape(-1, self.num_classes)
+ for x in self.logits_pred
+ ], dim=0,)
+ reg_pred = cat(
+ [
+ # Reshape: (N, B, Hi, Wi) -> (N, Hi, Wi, B) -> (N*Hi*Wi, B)
+ x.permute(0, 2, 3, 1).reshape(-1, 4)
+ for x in self.reg_pred
+ ], dim=0,)
+ bezier_pred = cat(
+ [
+ # Reshape: (N, B, Hi, Wi) -> (N, Hi, Wi, B) -> (N*Hi*Wi, B)
+ x.permute(0, 2, 3, 1).reshape(-1, 16)
+ for x in self.bezier_pred
+ ], dim=0,)
+ ctrness_pred = cat(
+ [
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.reshape(-1) for x in self.ctrness_pred
+ ], dim=0,)
+
+ labels = cat(
+ [
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.reshape(-1) for x in labels
+ ], dim=0,)
+
+ bezier_targets = cat(
+ [
+ # Reshape: (N, Hi, Wi, 16) -> (N*Hi*Wi, 16)
+ x.reshape(-1, 16) for x in bezier_targets
+ ], dim=0,)
+
+ reg_targets = cat(
+ [
+ # Reshape: (N, Hi, Wi, 4) -> (N*Hi*Wi, 4)
+ x.reshape(-1, 4) for x in reg_targets
+ ], dim=0,)
+
+ return fcos_losses(
+ labels,
+ reg_targets,
+ bezier_targets,
+ logits_pred,
+ reg_pred,
+ bezier_pred,
+ ctrness_pred,
+ self.focal_loss_alpha,
+ self.focal_loss_gamma,
+ self.iou_loss,
+ )
+
+ def predict_proposals(self, top_feats):
+ sampled_boxes = []
+
+ bundle = {
+ "l": self.locations, "o": self.logits_pred,
+ "r": self.reg_pred, "c": self.ctrness_pred,
+ "s": self.strides,
+ }
+
+ if len(top_feats) > 0:
+ bundle["t"] = top_feats
+
+ for i, instance in enumerate(zip(*bundle.values())):
+ instance_dict = dict(zip(bundle.keys(), instance))
+ # recall that during training, we normalize regression targets with FPN's stride.
+ # we denormalize them here.
+ l = instance_dict["l"]
+ o = instance_dict["o"]
+ r = instance_dict["r"] * instance_dict["s"]
+ c = instance_dict["c"]
+ # top_feat is the bezier regression
+ t = instance_dict["t"] * instance_dict["s"] if "t" in bundle else None
+
+ sampled_boxes.append(
+ self.forward_for_single_feature_map(
+ l, o, r, c, self.image_sizes, t
+ )
+ )
+
+ boxlists = list(zip(*sampled_boxes))
+ boxlists = [Instances.cat(boxlist) for boxlist in boxlists]
+ boxlists = self.select_over_all_levels(boxlists)
+ return boxlists
+
+ def forward_for_single_feature_map(
+ self, locations, box_cls,
+ reg_pred, ctrness,
+ image_sizes, top_feat=None):
+ N, C, H, W = box_cls.shape
+
+ # put in the same format as locations
+ box_cls = box_cls.view(N, C, H, W).permute(0, 2, 3, 1)
+ box_cls = box_cls.reshape(N, -1, C).sigmoid()
+ box_regression = reg_pred.view(N, 4, H, W).permute(0, 2, 3, 1)
+ box_regression = box_regression.reshape(N, -1, 4)
+ ctrness = ctrness.view(N, 1, H, W).permute(0, 2, 3, 1)
+ ctrness = ctrness.reshape(N, -1).sigmoid()
+ if top_feat is not None:
+ top_feat = top_feat.view(N, -1, H, W).permute(0, 2, 3, 1)
+ top_feat = top_feat.reshape(N, H * W, -1)
+
+ # if self.thresh_with_ctr is True, we multiply the classification
+ # scores with centerness scores before applying the threshold.
+ if self.thresh_with_ctr:
+ box_cls = box_cls * ctrness[:, :, None]
+ candidate_inds = box_cls > self.pre_nms_thresh
+ pre_nms_top_n = candidate_inds.view(N, -1).sum(1)
+ pre_nms_top_n = pre_nms_top_n.clamp(max=self.pre_nms_top_n)
+
+ if not self.thresh_with_ctr:
+ box_cls = box_cls * ctrness[:, :, None]
+
+ results = []
+ for i in range(N):
+ per_box_cls = box_cls[i]
+ per_candidate_inds = candidate_inds[i]
+ per_box_cls = per_box_cls[per_candidate_inds]
+
+ per_candidate_nonzeros = per_candidate_inds.nonzero()
+ per_box_loc = per_candidate_nonzeros[:, 0]
+ per_class = per_candidate_nonzeros[:, 1]
+
+ per_box_regression = box_regression[i]
+ per_box_regression = per_box_regression[per_box_loc]
+ per_locations = locations[per_box_loc]
+ if top_feat is not None:
+ per_top_feat = top_feat[i]
+ per_top_feat = per_top_feat[per_box_loc]
+
+ per_pre_nms_top_n = pre_nms_top_n[i]
+
+ if per_candidate_inds.sum().item() > per_pre_nms_top_n.item():
+ per_box_cls, top_k_indices = \
+ per_box_cls.topk(per_pre_nms_top_n, sorted=False)
+ per_class = per_class[top_k_indices]
+ per_box_regression = per_box_regression[top_k_indices]
+ per_locations = per_locations[top_k_indices]
+ if top_feat is not None:
+ per_top_feat = per_top_feat[top_k_indices]
+
+ detections = torch.stack([
+ per_locations[:, 0] - per_box_regression[:, 0],
+ per_locations[:, 1] - per_box_regression[:, 1],
+ per_locations[:, 0] + per_box_regression[:, 2],
+ per_locations[:, 1] + per_box_regression[:, 3],
+ ], dim=1)
+
+ boxlist = Instances(image_sizes[i])
+ boxlist.pred_boxes = Boxes(detections)
+ boxlist.scores = torch.sqrt(per_box_cls)
+ boxlist.pred_classes = per_class
+ boxlist.locations = per_locations
+ if top_feat is not None:
+ # top_feat in batext is bezier regression
+ # decentralize
+ bezier_detections = per_locations.unsqueeze(1) + per_top_feat.view(-1, 8, 2)
+ bezier_detections = bezier_detections.view(-1, 16)
+ boxlist.top_feat = bezier_detections
+ results.append(boxlist)
+
+ return results
+
+ def select_over_all_levels(self, boxlists):
+ num_images = len(boxlists)
+ results = []
+ for i in range(num_images):
+ # multiclass nms
+ result = ml_nms(boxlists[i], self.nms_thresh)
+ number_of_detections = len(result)
+
+ # Limit to max_per_image detections **over all classes**
+ if number_of_detections > self.fpn_post_nms_top_n > 0:
+ cls_scores = result.scores
+ image_thresh, _ = torch.kthvalue(
+ cls_scores.cpu(),
+ number_of_detections - self.fpn_post_nms_top_n + 1
+ )
+ keep = cls_scores >= image_thresh.item()
+ keep = torch.nonzero(keep).squeeze(1)
+ result = result[keep]
+ results.append(result)
+ return results
diff --git a/AdelaiDet/adet/modeling/blendmask/__init__.py b/AdelaiDet/adet/modeling/blendmask/__init__.py
new file mode 100755
index 0000000..66e4125
--- /dev/null
+++ b/AdelaiDet/adet/modeling/blendmask/__init__.py
@@ -0,0 +1,2 @@
+from .basis_module import build_basis_module
+from .blendmask import BlendMask
diff --git a/AdelaiDet/adet/modeling/blendmask/basis_module.py b/AdelaiDet/adet/modeling/blendmask/basis_module.py
new file mode 100755
index 0000000..e1e067d
--- /dev/null
+++ b/AdelaiDet/adet/modeling/blendmask/basis_module.py
@@ -0,0 +1,104 @@
+from typing import Dict
+from torch import nn
+from torch.nn import functional as F
+
+from detectron2.utils.registry import Registry
+from detectron2.layers import ShapeSpec
+
+from adet.layers import conv_with_kaiming_uniform
+
+
+BASIS_MODULE_REGISTRY = Registry("BASIS_MODULE")
+BASIS_MODULE_REGISTRY.__doc__ = """
+Registry for basis module, which produces global bases from feature maps.
+
+The registered object will be called with `obj(cfg, input_shape)`.
+The call should return a `nn.Module` object.
+"""
+
+
+def build_basis_module(cfg, input_shape):
+ name = cfg.MODEL.BASIS_MODULE.NAME
+ return BASIS_MODULE_REGISTRY.get(name)(cfg, input_shape)
+
+
+@BASIS_MODULE_REGISTRY.register()
+class ProtoNet(nn.Module):
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ """
+ TODO: support deconv and variable channel width
+ """
+ # official protonet has a relu after each conv
+ super().__init__()
+ # fmt: off
+ mask_dim = cfg.MODEL.BASIS_MODULE.NUM_BASES
+ planes = cfg.MODEL.BASIS_MODULE.CONVS_DIM
+ self.in_features = cfg.MODEL.BASIS_MODULE.IN_FEATURES
+ self.loss_on = cfg.MODEL.BASIS_MODULE.LOSS_ON
+ norm = cfg.MODEL.BASIS_MODULE.NORM
+ num_convs = cfg.MODEL.BASIS_MODULE.NUM_CONVS
+ self.visualize = cfg.MODEL.BLENDMASK.VISUALIZE
+ # fmt: on
+
+ feature_channels = {k: v.channels for k, v in input_shape.items()}
+
+ conv_block = conv_with_kaiming_uniform(norm, True) # conv relu bn
+ self.refine = nn.ModuleList()
+ for in_feature in self.in_features:
+ self.refine.append(conv_block(
+ feature_channels[in_feature], planes, 3, 1))
+ tower = []
+ for i in range(num_convs):
+ tower.append(
+ conv_block(planes, planes, 3, 1))
+ tower.append(
+ nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False))
+ tower.append(
+ conv_block(planes, planes, 3, 1))
+ tower.append(
+ nn.Conv2d(planes, mask_dim, 1))
+ self.add_module('tower', nn.Sequential(*tower))
+
+ if self.loss_on:
+ # fmt: off
+ self.common_stride = cfg.MODEL.BASIS_MODULE.COMMON_STRIDE
+ num_classes = cfg.MODEL.BASIS_MODULE.NUM_CLASSES + 1
+ self.sem_loss_weight = cfg.MODEL.BASIS_MODULE.LOSS_WEIGHT
+ # fmt: on
+
+ inplanes = feature_channels[self.in_features[0]]
+ self.seg_head = nn.Sequential(nn.Conv2d(inplanes, planes, kernel_size=3,
+ stride=1, padding=1, bias=False),
+ nn.BatchNorm2d(planes),
+ nn.ReLU(),
+ nn.Conv2d(planes, planes, kernel_size=3,
+ stride=1, padding=1, bias=False),
+ nn.BatchNorm2d(planes),
+ nn.ReLU(),
+ nn.Conv2d(planes, num_classes, kernel_size=1,
+ stride=1))
+
+ def forward(self, features, targets=None):
+ for i, f in enumerate(self.in_features):
+ if i == 0:
+ x = self.refine[i](features[f])
+ else:
+ x_p = self.refine[i](features[f])
+ x_p = F.interpolate(x_p, x.size()[2:], mode="bilinear", align_corners=False)
+ # x_p = aligned_bilinear(x_p, x.size(3) // x_p.size(3))
+ x = x + x_p
+ outputs = {"bases": [self.tower(x)]}
+ losses = {}
+ # auxiliary thing semantic loss
+ if self.training and self.loss_on:
+ sem_out = self.seg_head(features[self.in_features[0]])
+ # resize target to reduce memory
+ gt_sem = targets.unsqueeze(1).float()
+ gt_sem = F.interpolate(
+ gt_sem, scale_factor=1 / self.common_stride)
+ seg_loss = F.cross_entropy(
+ sem_out, gt_sem.squeeze(1).long())
+ losses['loss_basis_sem'] = seg_loss * self.sem_loss_weight
+ elif self.visualize and hasattr(self, "seg_head"):
+ outputs["seg_thing_out"] = self.seg_head(features[self.in_features[0]])
+ return outputs, losses
diff --git a/AdelaiDet/adet/modeling/blendmask/blender.py b/AdelaiDet/adet/modeling/blendmask/blender.py
new file mode 100755
index 0000000..009d8c8
--- /dev/null
+++ b/AdelaiDet/adet/modeling/blendmask/blender.py
@@ -0,0 +1,103 @@
+import torch
+from torch.nn import functional as F
+
+from detectron2.layers import cat
+from detectron2.modeling.poolers import ROIPooler
+
+
+def build_blender(cfg):
+ return Blender(cfg)
+
+
+class Blender(object):
+ def __init__(self, cfg):
+
+ # fmt: off
+ self.pooler_resolution = cfg.MODEL.BLENDMASK.BOTTOM_RESOLUTION
+ sampling_ratio = cfg.MODEL.BLENDMASK.POOLER_SAMPLING_RATIO
+ pooler_type = cfg.MODEL.BLENDMASK.POOLER_TYPE
+ pooler_scales = cfg.MODEL.BLENDMASK.POOLER_SCALES
+ self.attn_size = cfg.MODEL.BLENDMASK.ATTN_SIZE
+ self.top_interp = cfg.MODEL.BLENDMASK.TOP_INTERP
+ num_bases = cfg.MODEL.BASIS_MODULE.NUM_BASES
+ # fmt: on
+
+ self.attn_len = num_bases * self.attn_size * self.attn_size
+
+ self.pooler = ROIPooler(
+ output_size=self.pooler_resolution,
+ scales=pooler_scales,
+ sampling_ratio=sampling_ratio,
+ pooler_type=pooler_type,
+ canonical_level=2)
+
+ def __call__(self, bases, proposals, gt_instances):
+ if gt_instances is not None:
+ # training
+ # reshape attns
+ dense_info = proposals["instances"]
+ attns = dense_info.top_feats
+ pos_inds = dense_info.pos_inds
+ if pos_inds.numel() == 0:
+ return None, {"loss_mask": sum([x.sum() * 0 for x in attns]) + bases[0].sum() * 0}
+
+ gt_inds = dense_info.gt_inds
+
+ rois = self.pooler(bases, [x.gt_boxes for x in gt_instances])
+ rois = rois[gt_inds]
+ pred_mask_logits = self.merge_bases(rois, attns)
+
+ # gen targets
+ gt_masks = []
+ for instances_per_image in gt_instances:
+ if len(instances_per_image.gt_boxes.tensor) == 0:
+ continue
+ gt_mask_per_image = instances_per_image.gt_masks.crop_and_resize(
+ instances_per_image.gt_boxes.tensor, self.pooler_resolution
+ ).to(device=pred_mask_logits.device)
+ gt_masks.append(gt_mask_per_image)
+ gt_masks = cat(gt_masks, dim=0)
+ gt_masks = gt_masks[gt_inds]
+ N = gt_masks.size(0)
+ gt_masks = gt_masks.view(N, -1)
+
+ gt_ctr = dense_info.gt_ctrs
+ loss_denorm = proposals["loss_denorm"]
+ mask_losses = F.binary_cross_entropy_with_logits(
+ pred_mask_logits, gt_masks.to(dtype=torch.float32), reduction="none")
+ mask_loss = ((mask_losses.mean(dim=-1) * gt_ctr).sum()
+ / loss_denorm)
+ return None, {"loss_mask": mask_loss}
+ else:
+ # no proposals
+ total_instances = sum([len(x) for x in proposals])
+ if total_instances == 0:
+ # add empty pred_masks results
+ for box in proposals:
+ box.pred_masks = box.pred_classes.view(
+ -1, 1, self.pooler_resolution, self.pooler_resolution)
+ return proposals, {}
+ rois = self.pooler(bases, [x.pred_boxes for x in proposals])
+ attns = cat([x.top_feat for x in proposals], dim=0)
+ pred_mask_logits = self.merge_bases(rois, attns).sigmoid()
+ pred_mask_logits = pred_mask_logits.view(
+ -1, 1, self.pooler_resolution, self.pooler_resolution)
+ start_ind = 0
+ for box in proposals:
+ end_ind = start_ind + len(box)
+ box.pred_masks = pred_mask_logits[start_ind:end_ind]
+ start_ind = end_ind
+ return proposals, {}
+
+ def merge_bases(self, rois, coeffs, location_to_inds=None):
+ # merge predictions
+ N = coeffs.size(0)
+ if location_to_inds is not None:
+ rois = rois[location_to_inds]
+ N, B, H, W = rois.size()
+
+ coeffs = coeffs.view(N, -1, self.attn_size, self.attn_size)
+ coeffs = F.interpolate(coeffs, (H, W),
+ mode=self.top_interp).softmax(dim=1)
+ masks_preds = (rois * coeffs).sum(dim=1)
+ return masks_preds.view(N, -1)
diff --git a/AdelaiDet/adet/modeling/blendmask/blendmask.py b/AdelaiDet/adet/modeling/blendmask/blendmask.py
new file mode 100755
index 0000000..5ebe77c
--- /dev/null
+++ b/AdelaiDet/adet/modeling/blendmask/blendmask.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+
+import torch
+from torch import nn
+
+from detectron2.structures import ImageList
+from detectron2.modeling.postprocessing import detector_postprocess, sem_seg_postprocess
+from detectron2.modeling.proposal_generator import build_proposal_generator
+from detectron2.modeling.backbone import build_backbone
+from detectron2.modeling.meta_arch.panoptic_fpn import combine_semantic_and_instance_outputs
+from detectron2.modeling.meta_arch.build import META_ARCH_REGISTRY
+from detectron2.modeling.meta_arch.semantic_seg import build_sem_seg_head
+
+from .blender import build_blender
+from .basis_module import build_basis_module
+
+__all__ = ["BlendMask"]
+
+
+@META_ARCH_REGISTRY.register()
+class BlendMask(nn.Module):
+ """
+ Main class for BlendMask architectures (see https://arxiv.org/abd/1901.02446).
+ """
+
+ def __init__(self, cfg):
+ super().__init__()
+
+ self.device = torch.device(cfg.MODEL.DEVICE)
+ self.instance_loss_weight = cfg.MODEL.BLENDMASK.INSTANCE_LOSS_WEIGHT
+
+ self.backbone = build_backbone(cfg)
+ self.proposal_generator = build_proposal_generator(cfg, self.backbone.output_shape())
+ self.blender = build_blender(cfg)
+ self.basis_module = build_basis_module(cfg, self.backbone.output_shape())
+
+ # options when combining instance & semantic outputs
+ self.combine_on = cfg.MODEL.PANOPTIC_FPN.COMBINE.ENABLED
+ if self.combine_on:
+ self.panoptic_module = build_sem_seg_head(cfg, self.backbone.output_shape())
+ self.combine_overlap_threshold = cfg.MODEL.PANOPTIC_FPN.COMBINE.OVERLAP_THRESH
+ self.combine_stuff_area_limit = cfg.MODEL.PANOPTIC_FPN.COMBINE.STUFF_AREA_LIMIT
+ self.combine_instances_confidence_threshold = (
+ cfg.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH)
+
+ # build top module
+ in_channels = cfg.MODEL.FPN.OUT_CHANNELS
+ num_bases = cfg.MODEL.BASIS_MODULE.NUM_BASES
+ attn_size = cfg.MODEL.BLENDMASK.ATTN_SIZE
+ attn_len = num_bases * attn_size * attn_size
+ self.top_layer = nn.Conv2d(
+ in_channels, attn_len,
+ kernel_size=3, stride=1, padding=1)
+ torch.nn.init.normal_(self.top_layer.weight, std=0.01)
+ torch.nn.init.constant_(self.top_layer.bias, 0)
+
+ pixel_mean = torch.Tensor(cfg.MODEL.PIXEL_MEAN).to(self.device).view(3, 1, 1)
+ pixel_std = torch.Tensor(cfg.MODEL.PIXEL_STD).to(self.device).view(3, 1, 1)
+ self.normalizer = lambda x: (x - pixel_mean) / pixel_std
+ self.to(self.device)
+
+ def forward(self, batched_inputs):
+ """
+ Args:
+ batched_inputs: a list, batched outputs of :class:`DatasetMapper`.
+ Each item in the list contains the inputs for one image.
+
+ For now, each item in the list is a dict that contains:
+ image: Tensor, image in (C, H, W) format.
+ instances: Instances
+ sem_seg: semantic segmentation ground truth.
+ Other information that's included in the original dicts, such as:
+ "height", "width" (int): the output resolution of the model, used in inference.
+ See :meth:`postprocess` for details.
+
+ Returns:
+ list[dict]: each dict is the results for one image. The dict
+ contains the following keys:
+ "instances": see :meth:`GeneralizedRCNN.forward` for its format.
+ "sem_seg": see :meth:`SemanticSegmentor.forward` for its format.
+ "panoptic_seg": available when `PANOPTIC_FPN.COMBINE.ENABLED`.
+ See the return value of
+ :func:`combine_semantic_and_instance_outputs` for its format.
+ """
+ images = [x["image"].to(self.device) for x in batched_inputs]
+ images = [self.normalizer(x) for x in images]
+ images = ImageList.from_tensors(images, self.backbone.size_divisibility)
+ features = self.backbone(images.tensor)
+
+ if self.combine_on:
+ if "sem_seg" in batched_inputs[0]:
+ gt_sem = [x["sem_seg"].to(self.device) for x in batched_inputs]
+ gt_sem = ImageList.from_tensors(
+ gt_sem, self.backbone.size_divisibility, self.panoptic_module.ignore_value
+ ).tensor
+ else:
+ gt_sem = None
+ sem_seg_results, sem_seg_losses = self.panoptic_module(features, gt_sem)
+
+ if "basis_sem" in batched_inputs[0]:
+ basis_sem = [x["basis_sem"].to(self.device) for x in batched_inputs]
+ basis_sem = ImageList.from_tensors(
+ basis_sem, self.backbone.size_divisibility, 0).tensor
+ else:
+ basis_sem = None
+ basis_out, basis_losses = self.basis_module(features, basis_sem)
+
+ if "instances" in batched_inputs[0]:
+ gt_instances = [x["instances"].to(self.device) for x in batched_inputs]
+ else:
+ gt_instances = None
+ proposals, proposal_losses = self.proposal_generator(
+ images, features, gt_instances, self.top_layer)
+ detector_results, detector_losses = self.blender(
+ basis_out["bases"], proposals, gt_instances)
+
+ if self.training:
+ losses = {}
+ losses.update(basis_losses)
+ losses.update({k: v * self.instance_loss_weight for k, v in detector_losses.items()})
+ losses.update(proposal_losses)
+ if self.combine_on:
+ losses.update(sem_seg_losses)
+ return losses
+
+ processed_results = []
+ for i, (detector_result, input_per_image, image_size) in enumerate(zip(
+ detector_results, batched_inputs, images.image_sizes)):
+ height = input_per_image.get("height", image_size[0])
+ width = input_per_image.get("width", image_size[1])
+ detector_r = detector_postprocess(detector_result, height, width)
+ processed_result = {"instances": detector_r}
+ if self.combine_on:
+ sem_seg_r = sem_seg_postprocess(
+ sem_seg_results[i], image_size, height, width)
+ processed_result["sem_seg"] = sem_seg_r
+ if "seg_thing_out" in basis_out:
+ seg_thing_r = sem_seg_postprocess(
+ basis_out["seg_thing_out"], image_size, height, width)
+ processed_result["sem_thing_seg"] = seg_thing_r
+ if self.basis_module.visualize:
+ processed_result["bases"] = basis_out["bases"]
+ processed_results.append(processed_result)
+
+ if self.combine_on:
+ panoptic_r = combine_semantic_and_instance_outputs(
+ detector_r,
+ sem_seg_r.argmax(dim=0),
+ self.combine_overlap_threshold,
+ self.combine_stuff_area_limit,
+ self.combine_instances_confidence_threshold)
+ processed_results[-1]["panoptic_seg"] = panoptic_r
+ return processed_results
diff --git a/AdelaiDet/adet/modeling/condinst/__init__.py b/AdelaiDet/adet/modeling/condinst/__init__.py
new file mode 100755
index 0000000..395e19f
--- /dev/null
+++ b/AdelaiDet/adet/modeling/condinst/__init__.py
@@ -0,0 +1 @@
+from .condinst import CondInst
diff --git a/AdelaiDet/adet/modeling/condinst/condinst.py b/AdelaiDet/adet/modeling/condinst/condinst.py
new file mode 100755
index 0000000..4e0ed06
--- /dev/null
+++ b/AdelaiDet/adet/modeling/condinst/condinst.py
@@ -0,0 +1,372 @@
+# -*- coding: utf-8 -*-
+import logging
+from skimage import color
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from detectron2.structures import ImageList
+from detectron2.modeling.proposal_generator import build_proposal_generator
+from detectron2.modeling.backbone import build_backbone
+from detectron2.modeling.meta_arch.build import META_ARCH_REGISTRY
+from detectron2.structures.instances import Instances
+from detectron2.structures.masks import PolygonMasks, polygons_to_bitmask
+
+from .dynamic_mask_head import build_dynamic_mask_head
+from .mask_branch import build_mask_branch
+
+from adet.utils.comm import aligned_bilinear
+
+__all__ = ["CondInst"]
+
+
+logger = logging.getLogger(__name__)
+
+
+def unfold_wo_center(x, kernel_size, dilation):
+ assert x.dim() == 4
+ assert kernel_size % 2 == 1
+
+ # using SAME padding
+ padding = (kernel_size + (dilation - 1) * (kernel_size - 1)) // 2
+ unfolded_x = F.unfold(
+ x, kernel_size=kernel_size,
+ padding=padding,
+ dilation=dilation
+ )
+
+ unfolded_x = unfolded_x.reshape(
+ x.size(0), x.size(1), -1, x.size(2), x.size(3)
+ )
+
+ # remove the center pixels
+ size = kernel_size ** 2
+ unfolded_x = torch.cat((
+ unfolded_x[:, :, :size // 2],
+ unfolded_x[:, :, size // 2 + 1:]
+ ), dim=2)
+
+ return unfolded_x
+
+
+def get_images_color_similarity(images, image_masks, kernel_size, dilation):
+ assert images.dim() == 4
+ assert images.size(0) == 1
+
+ unfolded_images = unfold_wo_center(
+ images, kernel_size=kernel_size, dilation=dilation
+ )
+
+ diff = images[:, :, None] - unfolded_images
+ similarity = torch.exp(-torch.norm(diff, dim=1) * 0.5)
+
+ unfolded_weights = unfold_wo_center(
+ image_masks[None, None], kernel_size=kernel_size,
+ dilation=dilation
+ )
+ unfolded_weights = torch.max(unfolded_weights, dim=1)[0]
+
+ return similarity * unfolded_weights
+
+
+@META_ARCH_REGISTRY.register()
+class CondInst(nn.Module):
+ """
+ Main class for CondInst architectures (see https://arxiv.org/abs/2003.05664).
+ """
+
+ def __init__(self, cfg):
+ super().__init__()
+ self.device = torch.device(cfg.MODEL.DEVICE)
+
+ self.backbone = build_backbone(cfg)
+ self.proposal_generator = build_proposal_generator(cfg, self.backbone.output_shape())
+ self.mask_head = build_dynamic_mask_head(cfg)
+ self.mask_branch = build_mask_branch(cfg, self.backbone.output_shape())
+
+ self.mask_out_stride = cfg.MODEL.CONDINST.MASK_OUT_STRIDE
+
+ self.max_proposals = cfg.MODEL.CONDINST.MAX_PROPOSALS
+ self.topk_proposals_per_im = cfg.MODEL.CONDINST.TOPK_PROPOSALS_PER_IM
+
+ # boxinst configs
+ self.boxinst_enabled = cfg.MODEL.BOXINST.ENABLED
+ self.bottom_pixels_removed = cfg.MODEL.BOXINST.BOTTOM_PIXELS_REMOVED
+ self.pairwise_size = cfg.MODEL.BOXINST.PAIRWISE.SIZE
+ self.pairwise_dilation = cfg.MODEL.BOXINST.PAIRWISE.DILATION
+ self.pairwise_color_thresh = cfg.MODEL.BOXINST.PAIRWISE.COLOR_THRESH
+
+ # build top module
+ in_channels = self.proposal_generator.in_channels_to_top_module
+
+ self.controller = nn.Conv2d(
+ in_channels, self.mask_head.num_gen_params,
+ kernel_size=3, stride=1, padding=1
+ )
+ torch.nn.init.normal_(self.controller.weight, std=0.01)
+ torch.nn.init.constant_(self.controller.bias, 0)
+
+ pixel_mean = torch.Tensor(cfg.MODEL.PIXEL_MEAN).to(self.device).view(3, 1, 1)
+ pixel_std = torch.Tensor(cfg.MODEL.PIXEL_STD).to(self.device).view(3, 1, 1)
+ self.normalizer = lambda x: (x - pixel_mean) / pixel_std
+ self.to(self.device)
+
+ def forward(self, batched_inputs):
+ original_images = [x["image"].to(self.device) for x in batched_inputs]
+
+ # normalize images
+ images_norm = [self.normalizer(x) for x in original_images]
+ images_norm = ImageList.from_tensors(images_norm, self.backbone.size_divisibility)
+
+ features = self.backbone(images_norm.tensor)
+
+ if "instances" in batched_inputs[0]:
+ gt_instances = [x["instances"].to(self.device) for x in batched_inputs]
+ if self.boxinst_enabled:
+ original_image_masks = [torch.ones_like(x[0], dtype=torch.float32) for x in original_images]
+
+ # mask out the bottom area where the COCO dataset probably has wrong annotations
+ for i in range(len(original_image_masks)):
+ im_h = batched_inputs[i]["height"]
+ pixels_removed = int(
+ self.bottom_pixels_removed *
+ float(original_images[i].size(1)) / float(im_h)
+ )
+ if pixels_removed > 0:
+ original_image_masks[i][-pixels_removed:, :] = 0
+
+ original_images = ImageList.from_tensors(original_images, self.backbone.size_divisibility)
+ original_image_masks = ImageList.from_tensors(
+ original_image_masks, self.backbone.size_divisibility, pad_value=0.0
+ )
+ self.add_bitmasks_from_boxes(
+ gt_instances, original_images.tensor, original_image_masks.tensor,
+ original_images.tensor.size(-2), original_images.tensor.size(-1)
+ )
+ else:
+ self.add_bitmasks(gt_instances, images_norm.tensor.size(-2), images_norm.tensor.size(-1))
+ else:
+ gt_instances = None
+
+ mask_feats, sem_losses = self.mask_branch(features, gt_instances)
+
+ proposals, proposal_losses = self.proposal_generator(
+ images_norm, features, gt_instances, self.controller
+ )
+
+ if self.training:
+ mask_losses = self._forward_mask_heads_train(proposals, mask_feats, gt_instances)
+
+ losses = {}
+ losses.update(sem_losses)
+ losses.update(proposal_losses)
+ losses.update(mask_losses)
+ return losses
+ else:
+ pred_instances_w_masks = self._forward_mask_heads_test(proposals, mask_feats)
+
+ padded_im_h, padded_im_w = images_norm.tensor.size()[-2:]
+ processed_results = []
+ for im_id, (input_per_image, image_size) in enumerate(zip(batched_inputs, images_norm.image_sizes)):
+ height = input_per_image.get("height", image_size[0])
+ width = input_per_image.get("width", image_size[1])
+
+ instances_per_im = pred_instances_w_masks[pred_instances_w_masks.im_inds == im_id]
+ instances_per_im = self.postprocess(
+ instances_per_im, height, width,
+ padded_im_h, padded_im_w
+ )
+
+ processed_results.append({
+ "instances": instances_per_im
+ })
+
+ return processed_results
+
+ def _forward_mask_heads_train(self, proposals, mask_feats, gt_instances):
+ # prepare the inputs for mask heads
+ pred_instances = proposals["instances"]
+
+ assert (self.max_proposals == -1) or (self.topk_proposals_per_im == -1), \
+ "MAX_PROPOSALS and TOPK_PROPOSALS_PER_IM cannot be used at the same time."
+ if self.max_proposals != -1:
+ if self.max_proposals < len(pred_instances):
+ inds = torch.randperm(len(pred_instances), device=mask_feats.device).long()
+ logger.info("clipping proposals from {} to {}".format(
+ len(pred_instances), self.max_proposals
+ ))
+ pred_instances = pred_instances[inds[:self.max_proposals]]
+ elif self.topk_proposals_per_im != -1:
+ num_images = len(gt_instances)
+
+ kept_instances = []
+ for im_id in range(num_images):
+ instances_per_im = pred_instances[pred_instances.im_inds == im_id]
+ if len(instances_per_im) == 0:
+ kept_instances.append(instances_per_im)
+ continue
+
+ unique_gt_inds = instances_per_im.gt_inds.unique()
+ num_instances_per_gt = max(int(self.topk_proposals_per_im / len(unique_gt_inds)), 1)
+
+ for gt_ind in unique_gt_inds:
+ instances_per_gt = instances_per_im[instances_per_im.gt_inds == gt_ind]
+
+ if len(instances_per_gt) > num_instances_per_gt:
+ scores = instances_per_gt.logits_pred.sigmoid().max(dim=1)[0]
+ ctrness_pred = instances_per_gt.ctrness_pred.sigmoid()
+ inds = (scores * ctrness_pred).topk(k=num_instances_per_gt, dim=0)[1]
+ instances_per_gt = instances_per_gt[inds]
+
+ kept_instances.append(instances_per_gt)
+
+ pred_instances = Instances.cat(kept_instances)
+
+ pred_instances.mask_head_params = pred_instances.top_feats
+
+ loss_mask = self.mask_head(
+ mask_feats, self.mask_branch.out_stride,
+ pred_instances, gt_instances
+ )
+
+ return loss_mask
+
+ def _forward_mask_heads_test(self, proposals, mask_feats):
+ # prepare the inputs for mask heads
+ for im_id, per_im in enumerate(proposals):
+ per_im.im_inds = per_im.locations.new_ones(len(per_im), dtype=torch.long) * im_id
+ pred_instances = Instances.cat(proposals)
+ pred_instances.mask_head_params = pred_instances.top_feat
+
+ pred_instances_w_masks = self.mask_head(
+ mask_feats, self.mask_branch.out_stride, pred_instances
+ )
+
+ return pred_instances_w_masks
+
+ def add_bitmasks(self, instances, im_h, im_w):
+ for per_im_gt_inst in instances:
+ if not per_im_gt_inst.has("gt_masks"):
+ continue
+ start = int(self.mask_out_stride // 2)
+ if isinstance(per_im_gt_inst.get("gt_masks"), PolygonMasks):
+ polygons = per_im_gt_inst.get("gt_masks").polygons
+ per_im_bitmasks = []
+ per_im_bitmasks_full = []
+ for per_polygons in polygons:
+ bitmask = polygons_to_bitmask(per_polygons, im_h, im_w)
+ bitmask = torch.from_numpy(bitmask).to(self.device).float()
+ start = int(self.mask_out_stride // 2)
+ bitmask_full = bitmask.clone()
+ bitmask = bitmask[start::self.mask_out_stride, start::self.mask_out_stride]
+
+ assert bitmask.size(0) * self.mask_out_stride == im_h
+ assert bitmask.size(1) * self.mask_out_stride == im_w
+
+ per_im_bitmasks.append(bitmask)
+ per_im_bitmasks_full.append(bitmask_full)
+
+ per_im_gt_inst.gt_bitmasks = torch.stack(per_im_bitmasks, dim=0)
+ per_im_gt_inst.gt_bitmasks_full = torch.stack(per_im_bitmasks_full, dim=0)
+ else: # RLE format bitmask
+ bitmasks = per_im_gt_inst.get("gt_masks").tensor
+ h, w = bitmasks.size()[1:]
+ # pad to new size
+ bitmasks_full = F.pad(bitmasks, (0, im_w - w, 0, im_h - h), "constant", 0)
+ bitmasks = bitmasks_full[:, start::self.mask_out_stride, start::self.mask_out_stride]
+ per_im_gt_inst.gt_bitmasks = bitmasks
+ per_im_gt_inst.gt_bitmasks_full = bitmasks_full
+
+ def add_bitmasks_from_boxes(self, instances, images, image_masks, im_h, im_w):
+ stride = self.mask_out_stride
+ start = int(stride // 2)
+
+ assert images.size(2) % stride == 0
+ assert images.size(3) % stride == 0
+
+ downsampled_images = F.avg_pool2d(
+ images.float(), kernel_size=stride,
+ stride=stride, padding=0
+ )[:, [2, 1, 0]]
+ image_masks = image_masks[:, start::stride, start::stride]
+
+ for im_i, per_im_gt_inst in enumerate(instances):
+ images_lab = color.rgb2lab(downsampled_images[im_i].byte().permute(1, 2, 0).cpu().numpy())
+ images_lab = torch.as_tensor(images_lab, device=downsampled_images.device, dtype=torch.float32)
+ images_lab = images_lab.permute(2, 0, 1)[None]
+ images_color_similarity = get_images_color_similarity(
+ images_lab, image_masks[im_i],
+ self.pairwise_size, self.pairwise_dilation
+ )
+
+ per_im_boxes = per_im_gt_inst.gt_boxes.tensor
+ per_im_bitmasks = []
+ per_im_bitmasks_full = []
+ for per_box in per_im_boxes:
+ bitmask_full = torch.zeros((im_h, im_w), device=self.device).float()
+ bitmask_full[int(per_box[1]):int(per_box[3] + 1), int(per_box[0]):int(per_box[2] + 1)] = 1.0
+
+ bitmask = bitmask_full[start::stride, start::stride]
+
+ assert bitmask.size(0) * stride == im_h
+ assert bitmask.size(1) * stride == im_w
+
+ per_im_bitmasks.append(bitmask)
+ per_im_bitmasks_full.append(bitmask_full)
+
+ per_im_gt_inst.gt_bitmasks = torch.stack(per_im_bitmasks, dim=0)
+ per_im_gt_inst.gt_bitmasks_full = torch.stack(per_im_bitmasks_full, dim=0)
+ per_im_gt_inst.image_color_similarity = torch.cat([
+ images_color_similarity for _ in range(len(per_im_gt_inst))
+ ], dim=0)
+
+ def postprocess(self, results, output_height, output_width, padded_im_h, padded_im_w, mask_threshold=0.5):
+ """
+ Resize the output instances.
+ The input images are often resized when entering an object detector.
+ As a result, we often need the outputs of the detector in a different
+ resolution from its inputs.
+ This function will resize the raw outputs of an R-CNN detector
+ to produce outputs according to the desired output resolution.
+ Args:
+ results (Instances): the raw outputs from the detector.
+ `results.image_size` contains the input image resolution the detector sees.
+ This object might be modified in-place.
+ output_height, output_width: the desired output resolution.
+ Returns:
+ Instances: the resized output from the model, based on the output resolution
+ """
+ scale_x, scale_y = (output_width / results.image_size[1], output_height / results.image_size[0])
+ resized_im_h, resized_im_w = results.image_size
+ results = Instances((output_height, output_width), **results.get_fields())
+
+ if results.has("pred_boxes"):
+ output_boxes = results.pred_boxes
+ elif results.has("proposal_boxes"):
+ output_boxes = results.proposal_boxes
+
+ output_boxes.scale(scale_x, scale_y)
+ output_boxes.clip(results.image_size)
+
+ results = results[output_boxes.nonempty()]
+
+ if results.has("pred_global_masks"):
+ mask_h, mask_w = results.pred_global_masks.size()[-2:]
+ factor_h = padded_im_h // mask_h
+ factor_w = padded_im_w // mask_w
+ assert factor_h == factor_w
+ factor = factor_h
+ pred_global_masks = aligned_bilinear(
+ results.pred_global_masks, factor
+ )
+ pred_global_masks = pred_global_masks[:, :, :resized_im_h, :resized_im_w]
+ pred_global_masks = F.interpolate(
+ pred_global_masks,
+ size=(output_height, output_width),
+ mode="bilinear", align_corners=False
+ )
+ pred_global_masks = pred_global_masks[:, 0, :, :]
+ results.pred_masks = (pred_global_masks > mask_threshold).float()
+
+ return results
diff --git a/AdelaiDet/adet/modeling/condinst/dynamic_mask_head.py b/AdelaiDet/adet/modeling/condinst/dynamic_mask_head.py
new file mode 100755
index 0000000..7c77ee8
--- /dev/null
+++ b/AdelaiDet/adet/modeling/condinst/dynamic_mask_head.py
@@ -0,0 +1,259 @@
+import torch
+from torch.nn import functional as F
+from torch import nn
+
+from adet.utils.comm import compute_locations, aligned_bilinear
+
+
+def compute_project_term(mask_scores, gt_bitmasks):
+ mask_losses_y = dice_coefficient(
+ mask_scores.max(dim=2, keepdim=True)[0],
+ gt_bitmasks.max(dim=2, keepdim=True)[0]
+ )
+ mask_losses_x = dice_coefficient(
+ mask_scores.max(dim=3, keepdim=True)[0],
+ gt_bitmasks.max(dim=3, keepdim=True)[0]
+ )
+ return (mask_losses_x + mask_losses_y).mean()
+
+
+def compute_pairwise_term(mask_logits, pairwise_size, pairwise_dilation):
+ assert mask_logits.dim() == 4
+
+ log_fg_prob = F.logsigmoid(mask_logits)
+ log_bg_prob = F.logsigmoid(-mask_logits)
+
+ from adet.modeling.condinst.condinst import unfold_wo_center
+ log_fg_prob_unfold = unfold_wo_center(
+ log_fg_prob, kernel_size=pairwise_size,
+ dilation=pairwise_dilation
+ )
+ log_bg_prob_unfold = unfold_wo_center(
+ log_bg_prob, kernel_size=pairwise_size,
+ dilation=pairwise_dilation
+ )
+
+ # the probability of making the same prediction = p_i * p_j + (1 - p_i) * (1 - p_j)
+ # we compute the the probability in log space to avoid numerical instability
+ log_same_fg_prob = log_fg_prob[:, :, None] + log_fg_prob_unfold
+ log_same_bg_prob = log_bg_prob[:, :, None] + log_bg_prob_unfold
+
+ max_ = torch.max(log_same_fg_prob, log_same_bg_prob)
+ log_same_prob = torch.log(
+ torch.exp(log_same_fg_prob - max_) +
+ torch.exp(log_same_bg_prob - max_)
+ ) + max_
+
+ # loss = -log(prob)
+ return -log_same_prob[:, 0]
+
+
+def dice_coefficient(x, target):
+ eps = 1e-5
+ n_inst = x.size(0)
+ x = x.reshape(n_inst, -1)
+ target = target.reshape(n_inst, -1)
+ intersection = (x * target).sum(dim=1)
+ union = (x ** 2.0).sum(dim=1) + (target ** 2.0).sum(dim=1) + eps
+ loss = 1. - (2 * intersection / union)
+ return loss
+
+
+def parse_dynamic_params(params, channels, weight_nums, bias_nums):
+ assert params.dim() == 2
+ assert len(weight_nums) == len(bias_nums)
+ assert params.size(1) == sum(weight_nums) + sum(bias_nums)
+
+ num_insts = params.size(0)
+ num_layers = len(weight_nums)
+
+ params_splits = list(torch.split_with_sizes(
+ params, weight_nums + bias_nums, dim=1
+ ))
+
+ weight_splits = params_splits[:num_layers]
+ bias_splits = params_splits[num_layers:]
+
+ for l in range(num_layers):
+ if l < num_layers - 1:
+ # out_channels x in_channels x 1 x 1
+ weight_splits[l] = weight_splits[l].reshape(num_insts * channels, -1, 1, 1)
+ bias_splits[l] = bias_splits[l].reshape(num_insts * channels)
+ else:
+ # out_channels x in_channels x 1 x 1
+ weight_splits[l] = weight_splits[l].reshape(num_insts * 1, -1, 1, 1)
+ bias_splits[l] = bias_splits[l].reshape(num_insts)
+
+ return weight_splits, bias_splits
+
+
+def build_dynamic_mask_head(cfg):
+ return DynamicMaskHead(cfg)
+
+
+class DynamicMaskHead(nn.Module):
+ def __init__(self, cfg):
+ super(DynamicMaskHead, self).__init__()
+ self.num_layers = cfg.MODEL.CONDINST.MASK_HEAD.NUM_LAYERS
+ self.channels = cfg.MODEL.CONDINST.MASK_HEAD.CHANNELS
+ self.in_channels = cfg.MODEL.CONDINST.MASK_BRANCH.OUT_CHANNELS
+ self.mask_out_stride = cfg.MODEL.CONDINST.MASK_OUT_STRIDE
+ self.disable_rel_coords = cfg.MODEL.CONDINST.MASK_HEAD.DISABLE_REL_COORDS
+
+ soi = cfg.MODEL.FCOS.SIZES_OF_INTEREST
+ self.register_buffer("sizes_of_interest", torch.tensor(soi + [soi[-1] * 2]))
+
+ # boxinst configs
+ self.boxinst_enabled = cfg.MODEL.BOXINST.ENABLED
+ self.bottom_pixels_removed = cfg.MODEL.BOXINST.BOTTOM_PIXELS_REMOVED
+ self.pairwise_size = cfg.MODEL.BOXINST.PAIRWISE.SIZE
+ self.pairwise_dilation = cfg.MODEL.BOXINST.PAIRWISE.DILATION
+ self.pairwise_color_thresh = cfg.MODEL.BOXINST.PAIRWISE.COLOR_THRESH
+ self._warmup_iters = cfg.MODEL.BOXINST.PAIRWISE.WARMUP_ITERS
+
+ weight_nums, bias_nums = [], []
+ for l in range(self.num_layers):
+ if l == 0:
+ if not self.disable_rel_coords:
+ weight_nums.append((self.in_channels + 2) * self.channels)
+ else:
+ weight_nums.append(self.in_channels * self.channels)
+ bias_nums.append(self.channels)
+ elif l == self.num_layers - 1:
+ weight_nums.append(self.channels * 1)
+ bias_nums.append(1)
+ else:
+ weight_nums.append(self.channels * self.channels)
+ bias_nums.append(self.channels)
+
+ self.weight_nums = weight_nums
+ self.bias_nums = bias_nums
+ self.num_gen_params = sum(weight_nums) + sum(bias_nums)
+
+ self.register_buffer("_iter", torch.zeros([1]))
+
+ def mask_heads_forward(self, features, weights, biases, num_insts):
+ '''
+ :param features
+ :param weights: [w0, w1, ...]
+ :param bias: [b0, b1, ...]
+ :return:
+ '''
+ assert features.dim() == 4
+ n_layers = len(weights)
+ x = features
+ for i, (w, b) in enumerate(zip(weights, biases)):
+ x = F.conv2d(
+ x, w, bias=b,
+ stride=1, padding=0,
+ groups=num_insts
+ )
+ if i < n_layers - 1:
+ x = F.relu(x)
+ return x
+
+ def mask_heads_forward_with_coords(
+ self, mask_feats, mask_feat_stride, instances
+ ):
+ locations = compute_locations(
+ mask_feats.size(2), mask_feats.size(3),
+ stride=mask_feat_stride, device=mask_feats.device
+ )
+ n_inst = len(instances)
+
+ im_inds = instances.im_inds
+ mask_head_params = instances.mask_head_params
+
+ N, _, H, W = mask_feats.size()
+
+ if not self.disable_rel_coords:
+ instance_locations = instances.locations
+ relative_coords = instance_locations.reshape(-1, 1, 2) - locations.reshape(1, -1, 2)
+ relative_coords = relative_coords.permute(0, 2, 1).float()
+ soi = self.sizes_of_interest.float()[instances.fpn_levels]
+ relative_coords = relative_coords / soi.reshape(-1, 1, 1)
+ relative_coords = relative_coords.to(dtype=mask_feats.dtype)
+
+ mask_head_inputs = torch.cat([
+ relative_coords, mask_feats[im_inds].reshape(n_inst, self.in_channels, H * W)
+ ], dim=1)
+ else:
+ mask_head_inputs = mask_feats[im_inds].reshape(n_inst, self.in_channels, H * W)
+
+ mask_head_inputs = mask_head_inputs.reshape(1, -1, H, W)
+
+ weights, biases = parse_dynamic_params(
+ mask_head_params, self.channels,
+ self.weight_nums, self.bias_nums
+ )
+
+ mask_logits = self.mask_heads_forward(mask_head_inputs, weights, biases, n_inst)
+
+ mask_logits = mask_logits.reshape(-1, 1, H, W)
+
+ assert mask_feat_stride >= self.mask_out_stride
+ assert mask_feat_stride % self.mask_out_stride == 0
+ mask_logits = aligned_bilinear(mask_logits, int(mask_feat_stride / self.mask_out_stride))
+
+ return mask_logits
+
+ def __call__(self, mask_feats, mask_feat_stride, pred_instances, gt_instances=None):
+ if self.training:
+ self._iter += 1
+
+ gt_inds = pred_instances.gt_inds
+ gt_bitmasks = torch.cat([per_im.gt_bitmasks for per_im in gt_instances])
+ gt_bitmasks = gt_bitmasks[gt_inds].unsqueeze(dim=1).to(dtype=mask_feats.dtype)
+
+ losses = {}
+
+ if len(pred_instances) == 0:
+ dummy_loss = mask_feats.sum() * 0 + pred_instances.mask_head_params.sum() * 0
+ if not self.boxinst_enabled:
+ losses["loss_mask"] = dummy_loss
+ else:
+ losses["loss_prj"] = dummy_loss
+ losses["loss_pairwise"] = dummy_loss
+ else:
+ mask_logits = self.mask_heads_forward_with_coords(
+ mask_feats, mask_feat_stride, pred_instances
+ )
+ mask_scores = mask_logits.sigmoid()
+
+ if self.boxinst_enabled:
+ # box-supervised BoxInst losses
+ image_color_similarity = torch.cat([x.image_color_similarity for x in gt_instances])
+ image_color_similarity = image_color_similarity[gt_inds].to(dtype=mask_feats.dtype)
+
+ loss_prj_term = compute_project_term(mask_scores, gt_bitmasks)
+
+ pairwise_losses = compute_pairwise_term(
+ mask_logits, self.pairwise_size,
+ self.pairwise_dilation
+ )
+
+ weights = (image_color_similarity >= self.pairwise_color_thresh).float() * gt_bitmasks.float()
+ loss_pairwise = (pairwise_losses * weights).sum() / weights.sum().clamp(min=1.0)
+
+ warmup_factor = min(self._iter.item() / float(self._warmup_iters), 1.0)
+ loss_pairwise = loss_pairwise * warmup_factor
+
+ losses.update({
+ "loss_prj": loss_prj_term,
+ "loss_pairwise": loss_pairwise,
+ })
+ else:
+ # fully-supervised CondInst losses
+ mask_losses = dice_coefficient(mask_scores, gt_bitmasks)
+ loss_mask = mask_losses.mean()
+ losses["loss_mask"] = loss_mask
+
+ return losses
+ else:
+ if len(pred_instances) > 0:
+ mask_logits = self.mask_heads_forward_with_coords(
+ mask_feats, mask_feat_stride, pred_instances
+ )
+ pred_instances.pred_global_masks = mask_logits.sigmoid()
+
+ return pred_instances
diff --git a/AdelaiDet/adet/modeling/condinst/mask_branch.py b/AdelaiDet/adet/modeling/condinst/mask_branch.py
new file mode 100755
index 0000000..bb15fcb
--- /dev/null
+++ b/AdelaiDet/adet/modeling/condinst/mask_branch.py
@@ -0,0 +1,138 @@
+from typing import Dict
+import math
+
+import torch
+from torch import nn
+
+from fvcore.nn import sigmoid_focal_loss_jit
+from detectron2.layers import ShapeSpec
+
+from adet.layers import conv_with_kaiming_uniform
+from adet.utils.comm import aligned_bilinear
+
+
+INF = 100000000
+
+
+def build_mask_branch(cfg, input_shape):
+ return MaskBranch(cfg, input_shape)
+
+
+class MaskBranch(nn.Module):
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ super().__init__()
+ self.in_features = cfg.MODEL.CONDINST.MASK_BRANCH.IN_FEATURES
+ self.sem_loss_on = cfg.MODEL.CONDINST.MASK_BRANCH.SEMANTIC_LOSS_ON
+ self.num_outputs = cfg.MODEL.CONDINST.MASK_BRANCH.OUT_CHANNELS
+ norm = cfg.MODEL.CONDINST.MASK_BRANCH.NORM
+ num_convs = cfg.MODEL.CONDINST.MASK_BRANCH.NUM_CONVS
+ channels = cfg.MODEL.CONDINST.MASK_BRANCH.CHANNELS
+ self.out_stride = input_shape[self.in_features[0]].stride
+
+ feature_channels = {k: v.channels for k, v in input_shape.items()}
+
+ conv_block = conv_with_kaiming_uniform(norm, activation=True)
+
+ self.refine = nn.ModuleList()
+ for in_feature in self.in_features:
+ self.refine.append(conv_block(
+ feature_channels[in_feature],
+ channels, 3, 1
+ ))
+
+ tower = []
+ for i in range(num_convs):
+ tower.append(conv_block(
+ channels, channels, 3, 1
+ ))
+ tower.append(nn.Conv2d(
+ channels, max(self.num_outputs, 1), 1
+ ))
+ self.add_module('tower', nn.Sequential(*tower))
+
+ if self.sem_loss_on:
+ num_classes = cfg.MODEL.FCOS.NUM_CLASSES
+ self.focal_loss_alpha = cfg.MODEL.FCOS.LOSS_ALPHA
+ self.focal_loss_gamma = cfg.MODEL.FCOS.LOSS_GAMMA
+
+ in_channels = feature_channels[self.in_features[0]]
+ self.seg_head = nn.Sequential(
+ conv_block(in_channels, channels, kernel_size=3, stride=1),
+ conv_block(channels, channels, kernel_size=3, stride=1)
+ )
+
+ self.logits = nn.Conv2d(channels, num_classes, kernel_size=1, stride=1)
+
+ prior_prob = cfg.MODEL.FCOS.PRIOR_PROB
+ bias_value = -math.log((1 - prior_prob) / prior_prob)
+ torch.nn.init.constant_(self.logits.bias, bias_value)
+
+ def forward(self, features, gt_instances=None):
+ for i, f in enumerate(self.in_features):
+ if i == 0:
+ x = self.refine[i](features[f])
+ else:
+ x_p = self.refine[i](features[f])
+
+ target_h, target_w = x.size()[2:]
+ h, w = x_p.size()[2:]
+ assert target_h % h == 0
+ assert target_w % w == 0
+ factor_h, factor_w = target_h // h, target_w // w
+ assert factor_h == factor_w
+ x_p = aligned_bilinear(x_p, factor_h)
+ x = x + x_p
+
+ mask_feats = self.tower(x)
+
+ if self.num_outputs == 0:
+ mask_feats = mask_feats[:, :self.num_outputs]
+
+ losses = {}
+ # auxiliary thing semantic loss
+ if self.training and self.sem_loss_on:
+ logits_pred = self.logits(self.seg_head(
+ features[self.in_features[0]]
+ ))
+
+ # compute semantic targets
+ semantic_targets = []
+ for per_im_gt in gt_instances:
+ h, w = per_im_gt.gt_bitmasks_full.size()[-2:]
+ areas = per_im_gt.gt_bitmasks_full.sum(dim=-1).sum(dim=-1)
+ areas = areas[:, None, None].repeat(1, h, w)
+ areas[per_im_gt.gt_bitmasks_full == 0] = INF
+ areas = areas.permute(1, 2, 0).reshape(h * w, -1)
+ min_areas, inds = areas.min(dim=1)
+ per_im_sematic_targets = per_im_gt.gt_classes[inds] + 1
+ per_im_sematic_targets[min_areas == INF] = 0
+ per_im_sematic_targets = per_im_sematic_targets.reshape(h, w)
+ semantic_targets.append(per_im_sematic_targets)
+
+ semantic_targets = torch.stack(semantic_targets, dim=0)
+
+ # resize target to reduce memory
+ semantic_targets = semantic_targets[
+ :, None, self.out_stride // 2::self.out_stride,
+ self.out_stride // 2::self.out_stride
+ ]
+
+ # prepare one-hot targets
+ num_classes = logits_pred.size(1)
+ class_range = torch.arange(
+ num_classes, dtype=logits_pred.dtype,
+ device=logits_pred.device
+ )[:, None, None]
+ class_range = class_range + 1
+ one_hot = (semantic_targets == class_range).float()
+ num_pos = (one_hot > 0).sum().float().clamp(min=1.0)
+
+ loss_sem = sigmoid_focal_loss_jit(
+ logits_pred, one_hot,
+ alpha=self.focal_loss_alpha,
+ gamma=self.focal_loss_gamma,
+ reduction="sum",
+ ) / num_pos
+ losses['loss_sem'] = loss_sem
+
+ return mask_feats, losses
diff --git a/AdelaiDet/adet/modeling/fcos/__init__.py b/AdelaiDet/adet/modeling/fcos/__init__.py
new file mode 100755
index 0000000..6571ba1
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcos/__init__.py
@@ -0,0 +1 @@
+from .fcos import FCOS
diff --git a/AdelaiDet/adet/modeling/fcos/fcos.py b/AdelaiDet/adet/modeling/fcos/fcos.py
new file mode 100755
index 0000000..1ee7be8
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcos/fcos.py
@@ -0,0 +1,235 @@
+import math
+from typing import List, Dict
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from detectron2.layers import ShapeSpec, NaiveSyncBatchNorm
+from detectron2.modeling.proposal_generator.build import PROPOSAL_GENERATOR_REGISTRY
+
+from adet.layers import DFConv2d, NaiveGroupNorm
+from adet.utils.comm import compute_locations
+from .fcos_outputs import FCOSOutputs
+
+
+__all__ = ["FCOS"]
+
+INF = 100000000
+
+
+class Scale(nn.Module):
+ def __init__(self, init_value=1.0):
+ super(Scale, self).__init__()
+ self.scale = nn.Parameter(torch.FloatTensor([init_value]))
+
+ def forward(self, input):
+ return input * self.scale
+
+
+class ModuleListDial(nn.ModuleList):
+ def __init__(self, modules=None):
+ super(ModuleListDial, self).__init__(modules)
+ self.cur_position = 0
+
+ def forward(self, x):
+ result = self[self.cur_position](x)
+ self.cur_position += 1
+ if self.cur_position >= len(self):
+ self.cur_position = 0
+ return result
+
+
+@PROPOSAL_GENERATOR_REGISTRY.register()
+class FCOS(nn.Module):
+ """
+ Implement FCOS (https://arxiv.org/abs/1904.01355).
+ """
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ super().__init__()
+ self.in_features = cfg.MODEL.FCOS.IN_FEATURES
+ self.fpn_strides = cfg.MODEL.FCOS.FPN_STRIDES
+ self.yield_proposal = cfg.MODEL.FCOS.YIELD_PROPOSAL
+ self.yield_box_feats = cfg.MODEL.FCOS.YIELD_BOX_FEATURES
+
+ self.fcos_head = FCOSHead(cfg, [input_shape[f] for f in self.in_features])
+ self.in_channels_to_top_module = self.fcos_head.in_channels_to_top_module
+
+ self.fcos_outputs = FCOSOutputs(cfg)
+
+ def forward_head(self, features, top_module=None):
+ features = [features[f] for f in self.in_features]
+ pred_class_logits, pred_deltas, pred_centerness, top_feats, bbox_towers = self.fcos_head(
+ features, top_module, self.yield_proposal)
+ return pred_class_logits, pred_deltas, pred_centerness, top_feats, bbox_towers
+
+ def forward(self, images, features, gt_instances=None, top_module=None):
+ """
+ Arguments:
+ images (list[Tensor] or ImageList): images to be processed
+ targets (list[BoxList]): ground-truth boxes present in the image (optional)
+
+ Returns:
+ result (list[BoxList] or dict[Tensor]): the output from the model.
+ During training, it returns a dict[Tensor] which contains the losses.
+ During testing, it returns list[BoxList] contains additional fields
+ like `scores`, `labels` and `mask` (for Mask R-CNN models).
+
+ """
+ features = [features[f] for f in self.in_features]
+ locations = self.compute_locations(features)
+ logits_pred, reg_pred, ctrness_pred, top_feats, bbox_towers = self.fcos_head(
+ features, top_module, self.yield_proposal or self.yield_box_feats
+ )
+
+ if self.training:
+ results, losses = self.fcos_outputs.losses(
+ logits_pred, reg_pred, ctrness_pred,
+ locations, gt_instances, top_feats
+ )
+
+ if self.yield_proposal:
+ with torch.no_grad():
+ results["proposals"] = self.fcos_outputs.predict_proposals(
+ logits_pred, reg_pred, ctrness_pred,
+ locations, images.image_sizes, top_feats
+ )
+ if self.yield_box_feats:
+ results["box_feats"] = {
+ f: b for f, b in zip(self.in_features, bbox_towers)
+ }
+ return results, losses
+ else:
+ results = self.fcos_outputs.predict_proposals(
+ logits_pred, reg_pred, ctrness_pred,
+ locations, images.image_sizes, top_feats
+ )
+ extras = {}
+ if self.yield_box_feats:
+ extras["box_feats"] = {
+ f: b for f, b in zip(self.in_features, bbox_towers)
+ }
+ return results, extras
+
+ def compute_locations(self, features):
+ locations = []
+ for level, feature in enumerate(features):
+ h, w = feature.size()[-2:]
+ locations_per_level = compute_locations(
+ h, w, self.fpn_strides[level],
+ feature.device
+ )
+ locations.append(locations_per_level)
+ return locations
+
+
+class FCOSHead(nn.Module):
+ def __init__(self, cfg, input_shape: List[ShapeSpec]):
+ """
+ Arguments:
+ in_channels (int): number of channels of the input feature
+ """
+ super().__init__()
+ # TODO: Implement the sigmoid version first.
+ self.num_classes = cfg.MODEL.FCOS.NUM_CLASSES
+ self.fpn_strides = cfg.MODEL.FCOS.FPN_STRIDES
+ head_configs = {"cls": (cfg.MODEL.FCOS.NUM_CLS_CONVS,
+ cfg.MODEL.FCOS.USE_DEFORMABLE),
+ "bbox": (cfg.MODEL.FCOS.NUM_BOX_CONVS,
+ cfg.MODEL.FCOS.USE_DEFORMABLE),
+ "share": (cfg.MODEL.FCOS.NUM_SHARE_CONVS,
+ False)}
+ norm = None if cfg.MODEL.FCOS.NORM == "none" else cfg.MODEL.FCOS.NORM
+ self.num_levels = len(input_shape)
+
+ in_channels = [s.channels for s in input_shape]
+ assert len(set(in_channels)) == 1, "Each level must have the same channel!"
+ in_channels = in_channels[0]
+
+ self.in_channels_to_top_module = in_channels
+
+ for head in head_configs:
+ tower = []
+ num_convs, use_deformable = head_configs[head]
+ for i in range(num_convs):
+ if use_deformable and i == num_convs - 1:
+ conv_func = DFConv2d
+ else:
+ conv_func = nn.Conv2d
+ tower.append(conv_func(
+ in_channels, in_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=True
+ ))
+ if norm == "GN":
+ tower.append(nn.GroupNorm(32, in_channels))
+ elif norm == "NaiveGN":
+ tower.append(NaiveGroupNorm(32, in_channels))
+ elif norm == "BN":
+ tower.append(ModuleListDial([
+ nn.BatchNorm2d(in_channels) for _ in range(self.num_levels)
+ ]))
+ elif norm == "SyncBN":
+ tower.append(ModuleListDial([
+ NaiveSyncBatchNorm(in_channels) for _ in range(self.num_levels)
+ ]))
+ tower.append(nn.ReLU())
+ self.add_module('{}_tower'.format(head),
+ nn.Sequential(*tower))
+
+ self.cls_logits = nn.Conv2d(
+ in_channels, self.num_classes,
+ kernel_size=3, stride=1,
+ padding=1
+ )
+ self.bbox_pred = nn.Conv2d(
+ in_channels, 4, kernel_size=3,
+ stride=1, padding=1
+ )
+ self.ctrness = nn.Conv2d(
+ in_channels, 1, kernel_size=3,
+ stride=1, padding=1
+ )
+
+ if cfg.MODEL.FCOS.USE_SCALE:
+ self.scales = nn.ModuleList([Scale(init_value=1.0) for _ in range(self.num_levels)])
+ else:
+ self.scales = None
+
+ for modules in [
+ self.cls_tower, self.bbox_tower,
+ self.share_tower, self.cls_logits,
+ self.bbox_pred, self.ctrness
+ ]:
+ for l in modules.modules():
+ if isinstance(l, nn.Conv2d):
+ torch.nn.init.normal_(l.weight, std=0.01)
+ torch.nn.init.constant_(l.bias, 0)
+
+ # initialize the bias for focal loss
+ prior_prob = cfg.MODEL.FCOS.PRIOR_PROB
+ bias_value = -math.log((1 - prior_prob) / prior_prob)
+ torch.nn.init.constant_(self.cls_logits.bias, bias_value)
+
+ def forward(self, x, top_module=None, yield_bbox_towers=False):
+ logits = []
+ bbox_reg = []
+ ctrness = []
+ top_feats = []
+ bbox_towers = []
+ for l, feature in enumerate(x):
+ feature = self.share_tower(feature)
+ cls_tower = self.cls_tower(feature)
+ bbox_tower = self.bbox_tower(feature)
+ if yield_bbox_towers:
+ bbox_towers.append(bbox_tower)
+
+ logits.append(self.cls_logits(cls_tower))
+ ctrness.append(self.ctrness(bbox_tower))
+ reg = self.bbox_pred(bbox_tower)
+ if self.scales is not None:
+ reg = self.scales[l](reg)
+ # Note that we use relu, as in the improved FCOS, instead of exp.
+ bbox_reg.append(F.relu(reg))
+ if top_module is not None:
+ top_feats.append(top_module(bbox_tower))
+ return logits, bbox_reg, ctrness, top_feats, bbox_towers
diff --git a/AdelaiDet/adet/modeling/fcos/fcos_outputs.py b/AdelaiDet/adet/modeling/fcos/fcos_outputs.py
new file mode 100755
index 0000000..b61b8a6
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcos/fcos_outputs.py
@@ -0,0 +1,550 @@
+import logging
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from detectron2.layers import cat
+from detectron2.structures import Instances, Boxes
+from detectron2.utils.comm import get_world_size
+from fvcore.nn import sigmoid_focal_loss_jit
+
+from adet.utils.comm import reduce_sum, reduce_mean, compute_ious
+from adet.layers import ml_nms, IOULoss
+
+
+logger = logging.getLogger(__name__)
+
+INF = 100000000
+
+"""
+Shape shorthand in this module:
+
+ N: number of images in the minibatch
+ L: number of feature maps per image on which RPN is run
+ Hi, Wi: height and width of the i-th feature map
+ 4: size of the box parameterization
+
+Naming convention:
+
+ labels: refers to the ground-truth class of an position.
+
+ reg_targets: refers to the 4-d (left, top, right, bottom) distances that parameterize the ground-truth box.
+
+ logits_pred: predicted classification scores in [-inf, +inf];
+
+ reg_pred: the predicted (left, top, right, bottom), corresponding to reg_targets
+
+ ctrness_pred: predicted centerness scores
+
+"""
+
+
+def compute_ctrness_targets(reg_targets):
+ if len(reg_targets) == 0:
+ return reg_targets.new_zeros(len(reg_targets))
+ left_right = reg_targets[:, [0, 2]]
+ top_bottom = reg_targets[:, [1, 3]]
+ ctrness = (left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * \
+ (top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])
+ return torch.sqrt(ctrness)
+
+
+class FCOSOutputs(nn.Module):
+ def __init__(self, cfg):
+ super(FCOSOutputs, self).__init__()
+
+ self.focal_loss_alpha = cfg.MODEL.FCOS.LOSS_ALPHA
+ self.focal_loss_gamma = cfg.MODEL.FCOS.LOSS_GAMMA
+ self.center_sample = cfg.MODEL.FCOS.CENTER_SAMPLE
+ self.radius = cfg.MODEL.FCOS.POS_RADIUS
+ self.pre_nms_thresh_train = cfg.MODEL.FCOS.INFERENCE_TH_TRAIN
+ self.pre_nms_topk_train = cfg.MODEL.FCOS.PRE_NMS_TOPK_TRAIN
+ self.post_nms_topk_train = cfg.MODEL.FCOS.POST_NMS_TOPK_TRAIN
+ self.loc_loss_func = IOULoss(cfg.MODEL.FCOS.LOC_LOSS_TYPE)
+
+ self.pre_nms_thresh_test = cfg.MODEL.FCOS.INFERENCE_TH_TEST
+ self.pre_nms_topk_test = cfg.MODEL.FCOS.PRE_NMS_TOPK_TEST
+ self.post_nms_topk_test = cfg.MODEL.FCOS.POST_NMS_TOPK_TEST
+ self.nms_thresh = cfg.MODEL.FCOS.NMS_TH
+ self.thresh_with_ctr = cfg.MODEL.FCOS.THRESH_WITH_CTR
+ self.box_quality = cfg.MODEL.FCOS.BOX_QUALITY
+
+ self.num_classes = cfg.MODEL.FCOS.NUM_CLASSES
+ self.strides = cfg.MODEL.FCOS.FPN_STRIDES
+
+ # generate sizes of interest
+ soi = []
+ prev_size = -1
+ for s in cfg.MODEL.FCOS.SIZES_OF_INTEREST:
+ soi.append([prev_size, s])
+ prev_size = s
+ soi.append([prev_size, INF])
+ self.sizes_of_interest = soi
+
+ self.loss_normalizer_cls = cfg.MODEL.FCOS.LOSS_NORMALIZER_CLS
+ assert self.loss_normalizer_cls in ("moving_fg", "fg", "all"), \
+ 'MODEL.FCOS.CLS_LOSS_NORMALIZER can only be "moving_fg", "fg", or "all"'
+
+ # For an explanation, please refer to
+ # https://github.com/facebookresearch/detectron2/blob/ea8b17914fc9a5b7d82a46ccc72e7cf6272b40e4/detectron2/modeling/meta_arch/retinanet.py#L148
+ self.moving_num_fg = 100 # initialize with any reasonable #fg that's not too small
+ self.moving_num_fg_momentum = 0.9
+
+ self.loss_weight_cls = cfg.MODEL.FCOS.LOSS_WEIGHT_CLS
+
+ def _transpose(self, training_targets, num_loc_list):
+ '''
+ This function is used to transpose image first training targets to level first ones
+ :return: level first training targets
+ '''
+ for im_i in range(len(training_targets)):
+ training_targets[im_i] = torch.split(
+ training_targets[im_i], num_loc_list, dim=0
+ )
+
+ targets_level_first = []
+ for targets_per_level in zip(*training_targets):
+ targets_level_first.append(
+ torch.cat(targets_per_level, dim=0)
+ )
+ return targets_level_first
+
+ def _get_ground_truth(self, locations, gt_instances):
+ num_loc_list = [len(loc) for loc in locations]
+
+ # compute locations to size ranges
+ loc_to_size_range = []
+ for l, loc_per_level in enumerate(locations):
+ loc_to_size_range_per_level = loc_per_level.new_tensor(self.sizes_of_interest[l])
+ loc_to_size_range.append(
+ loc_to_size_range_per_level[None].expand(num_loc_list[l], -1)
+ )
+
+ loc_to_size_range = torch.cat(loc_to_size_range, dim=0)
+ locations = torch.cat(locations, dim=0)
+
+ training_targets = self.compute_targets_for_locations(
+ locations, gt_instances, loc_to_size_range, num_loc_list
+ )
+
+ training_targets["locations"] = [locations.clone() for _ in range(len(gt_instances))]
+ training_targets["im_inds"] = [
+ locations.new_ones(locations.size(0), dtype=torch.long) * i for i in range(len(gt_instances))
+ ]
+
+ # transpose im first training_targets to level first ones
+ training_targets = {
+ k: self._transpose(v, num_loc_list) for k, v in training_targets.items()
+ }
+
+ training_targets["fpn_levels"] = [
+ loc.new_ones(len(loc), dtype=torch.long) * level
+ for level, loc in enumerate(training_targets["locations"])
+ ]
+
+ # we normalize reg_targets by FPN's strides here
+ reg_targets = training_targets["reg_targets"]
+ for l in range(len(reg_targets)):
+ reg_targets[l] = reg_targets[l] / float(self.strides[l])
+
+ return training_targets
+
+ def get_sample_region(self, boxes, strides, num_loc_list, loc_xs, loc_ys, bitmasks=None, radius=1):
+ if bitmasks is not None:
+ _, h, w = bitmasks.size()
+
+ ys = torch.arange(0, h, dtype=torch.float32, device=bitmasks.device)
+ xs = torch.arange(0, w, dtype=torch.float32, device=bitmasks.device)
+
+ m00 = bitmasks.sum(dim=-1).sum(dim=-1).clamp(min=1e-6)
+ m10 = (bitmasks * xs).sum(dim=-1).sum(dim=-1)
+ m01 = (bitmasks * ys[:, None]).sum(dim=-1).sum(dim=-1)
+ center_x = m10 / m00
+ center_y = m01 / m00
+ else:
+ center_x = boxes[..., [0, 2]].sum(dim=-1) * 0.5
+ center_y = boxes[..., [1, 3]].sum(dim=-1) * 0.5
+
+ num_gts = boxes.shape[0]
+ K = len(loc_xs)
+ boxes = boxes[None].expand(K, num_gts, 4)
+ center_x = center_x[None].expand(K, num_gts)
+ center_y = center_y[None].expand(K, num_gts)
+ center_gt = boxes.new_zeros(boxes.shape)
+ # no gt
+ if center_x.numel() == 0 or center_x[..., 0].sum() == 0:
+ return loc_xs.new_zeros(loc_xs.shape, dtype=torch.uint8)
+ beg = 0
+ for level, num_loc in enumerate(num_loc_list):
+ end = beg + num_loc
+ stride = strides[level] * radius
+ xmin = center_x[beg:end] - stride
+ ymin = center_y[beg:end] - stride
+ xmax = center_x[beg:end] + stride
+ ymax = center_y[beg:end] + stride
+ # limit sample region in gt
+ center_gt[beg:end, :, 0] = torch.where(xmin > boxes[beg:end, :, 0], xmin, boxes[beg:end, :, 0])
+ center_gt[beg:end, :, 1] = torch.where(ymin > boxes[beg:end, :, 1], ymin, boxes[beg:end, :, 1])
+ center_gt[beg:end, :, 2] = torch.where(xmax > boxes[beg:end, :, 2], boxes[beg:end, :, 2], xmax)
+ center_gt[beg:end, :, 3] = torch.where(ymax > boxes[beg:end, :, 3], boxes[beg:end, :, 3], ymax)
+ beg = end
+ left = loc_xs[:, None] - center_gt[..., 0]
+ right = center_gt[..., 2] - loc_xs[:, None]
+ top = loc_ys[:, None] - center_gt[..., 1]
+ bottom = center_gt[..., 3] - loc_ys[:, None]
+ center_bbox = torch.stack((left, top, right, bottom), -1)
+ inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0
+ return inside_gt_bbox_mask
+
+ def compute_targets_for_locations(self, locations, targets, size_ranges, num_loc_list):
+ labels = []
+ reg_targets = []
+ target_inds = []
+ xs, ys = locations[:, 0], locations[:, 1]
+
+ num_targets = 0
+ for im_i in range(len(targets)):
+ targets_per_im = targets[im_i]
+ bboxes = targets_per_im.gt_boxes.tensor
+ labels_per_im = targets_per_im.gt_classes
+
+ # no gt
+ if bboxes.numel() == 0:
+ labels.append(labels_per_im.new_zeros(locations.size(0)) + self.num_classes)
+ reg_targets.append(locations.new_zeros((locations.size(0), 4)))
+ target_inds.append(labels_per_im.new_zeros(locations.size(0)) - 1)
+ continue
+
+ area = targets_per_im.gt_boxes.area()
+
+ l = xs[:, None] - bboxes[:, 0][None]
+ t = ys[:, None] - bboxes[:, 1][None]
+ r = bboxes[:, 2][None] - xs[:, None]
+ b = bboxes[:, 3][None] - ys[:, None]
+ reg_targets_per_im = torch.stack([l, t, r, b], dim=2)
+
+ if self.center_sample:
+ if targets_per_im.has("gt_bitmasks_full"):
+ bitmasks = targets_per_im.gt_bitmasks_full
+ else:
+ bitmasks = None
+ is_in_boxes = self.get_sample_region(
+ bboxes, self.strides, num_loc_list, xs, ys,
+ bitmasks=bitmasks, radius=self.radius
+ )
+ else:
+ is_in_boxes = reg_targets_per_im.min(dim=2)[0] > 0
+
+ max_reg_targets_per_im = reg_targets_per_im.max(dim=2)[0]
+ # limit the regression range for each location
+ is_cared_in_the_level = \
+ (max_reg_targets_per_im >= size_ranges[:, [0]]) & \
+ (max_reg_targets_per_im <= size_ranges[:, [1]])
+
+ locations_to_gt_area = area[None].repeat(len(locations), 1)
+ locations_to_gt_area[is_in_boxes == 0] = INF
+ locations_to_gt_area[is_cared_in_the_level == 0] = INF
+
+ # if there are still more than one objects for a location,
+ # we choose the one with minimal area
+ locations_to_min_area, locations_to_gt_inds = locations_to_gt_area.min(dim=1)
+
+ reg_targets_per_im = reg_targets_per_im[range(len(locations)), locations_to_gt_inds]
+ target_inds_per_im = locations_to_gt_inds + num_targets
+ num_targets += len(targets_per_im)
+
+ labels_per_im = labels_per_im[locations_to_gt_inds]
+ labels_per_im[locations_to_min_area == INF] = self.num_classes
+
+ labels.append(labels_per_im)
+ reg_targets.append(reg_targets_per_im)
+ target_inds.append(target_inds_per_im)
+
+ return {
+ "labels": labels,
+ "reg_targets": reg_targets,
+ "target_inds": target_inds
+ }
+
+ def losses(self, logits_pred, reg_pred, ctrness_pred, locations, gt_instances, top_feats=None):
+ """
+ Return the losses from a set of FCOS predictions and their associated ground-truth.
+
+ Returns:
+ dict[loss name -> loss value]: A dict mapping from loss name to loss value.
+ """
+
+ training_targets = self._get_ground_truth(locations, gt_instances)
+
+ # Collect all logits and regression predictions over feature maps
+ # and images to arrive at the same shape as the labels and targets
+ # The final ordering is L, N, H, W from slowest to fastest axis.
+
+ instances = Instances((0, 0))
+ instances.labels = cat([
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.reshape(-1) for x in training_targets["labels"]
+ ], dim=0)
+ instances.gt_inds = cat([
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.reshape(-1) for x in training_targets["target_inds"]
+ ], dim=0)
+ instances.im_inds = cat([
+ x.reshape(-1) for x in training_targets["im_inds"]
+ ], dim=0)
+ instances.reg_targets = cat([
+ # Reshape: (N, Hi, Wi, 4) -> (N*Hi*Wi, 4)
+ x.reshape(-1, 4) for x in training_targets["reg_targets"]
+ ], dim=0,)
+ instances.locations = cat([
+ x.reshape(-1, 2) for x in training_targets["locations"]
+ ], dim=0)
+ instances.fpn_levels = cat([
+ x.reshape(-1) for x in training_targets["fpn_levels"]
+ ], dim=0)
+
+ instances.logits_pred = cat([
+ # Reshape: (N, C, Hi, Wi) -> (N, Hi, Wi, C) -> (N*Hi*Wi, C)
+ x.permute(0, 2, 3, 1).reshape(-1, self.num_classes) for x in logits_pred
+ ], dim=0,)
+ instances.reg_pred = cat([
+ # Reshape: (N, B, Hi, Wi) -> (N, Hi, Wi, B) -> (N*Hi*Wi, B)
+ x.permute(0, 2, 3, 1).reshape(-1, 4) for x in reg_pred
+ ], dim=0,)
+ instances.ctrness_pred = cat([
+ # Reshape: (N, 1, Hi, Wi) -> (N*Hi*Wi,)
+ x.permute(0, 2, 3, 1).reshape(-1) for x in ctrness_pred
+ ], dim=0,)
+
+ if len(top_feats) > 0:
+ instances.top_feats = cat([
+ # Reshape: (N, -1, Hi, Wi) -> (N*Hi*Wi, -1)
+ x.permute(0, 2, 3, 1).reshape(-1, x.size(1)) for x in top_feats
+ ], dim=0,)
+
+ return self.fcos_losses(instances)
+
+ def fcos_losses(self, instances):
+ losses, extras = {}, {}
+
+ # 1. compute the cls loss
+ num_classes = instances.logits_pred.size(1)
+ assert num_classes == self.num_classes
+
+ labels = instances.labels.flatten()
+
+ pos_inds = torch.nonzero(labels != num_classes).squeeze(1)
+
+ num_pos_local = torch.ones_like(pos_inds).sum()
+ num_pos_avg = max(reduce_mean(num_pos_local).item(), 1.0)
+
+ # prepare one_hot
+ class_target = torch.zeros_like(instances.logits_pred)
+ class_target[pos_inds, labels[pos_inds]] = 1
+
+ class_loss = sigmoid_focal_loss_jit(
+ instances.logits_pred,
+ class_target,
+ alpha=self.focal_loss_alpha,
+ gamma=self.focal_loss_gamma,
+ reduction="sum"
+ )
+
+ if self.loss_normalizer_cls == "moving_fg":
+ self.moving_num_fg = self.moving_num_fg_momentum * self.moving_num_fg + (
+ 1 - self.moving_num_fg_momentum
+ ) * num_pos_avg
+ class_loss = class_loss / self.moving_num_fg
+ elif self.loss_normalizer_cls == "fg":
+ class_loss = class_loss / num_pos_avg
+ else:
+ num_samples_local = torch.ones_like(labels).sum()
+ num_samples_avg = max(reduce_mean(num_samples_local).item(), 1.0)
+ class_loss = class_loss / num_samples_avg
+
+ losses["loss_fcos_cls"] = class_loss * self.loss_weight_cls
+
+ # 2. compute the box regression and quality loss
+ instances = instances[pos_inds]
+ instances.pos_inds = pos_inds
+
+ ious, gious = compute_ious(instances.reg_pred, instances.reg_targets)
+
+ if self.box_quality == "ctrness":
+ ctrness_targets = compute_ctrness_targets(instances.reg_targets)
+ instances.gt_ctrs = ctrness_targets
+
+ ctrness_targets_sum = ctrness_targets.sum()
+ loss_denorm = max(reduce_mean(ctrness_targets_sum).item(), 1e-6)
+ extras["loss_denorm"] = loss_denorm
+
+ reg_loss = self.loc_loss_func(ious, gious, ctrness_targets) / loss_denorm
+ losses["loss_fcos_loc"] = reg_loss
+
+ ctrness_loss = F.binary_cross_entropy_with_logits(
+ instances.ctrness_pred, ctrness_targets,
+ reduction="sum"
+ ) / num_pos_avg
+ losses["loss_fcos_ctr"] = ctrness_loss
+ elif self.box_quality == "iou":
+ reg_loss = self.loc_loss_func(ious, gious) / num_pos_avg
+ losses["loss_fcos_loc"] = reg_loss
+
+ quality_loss = F.binary_cross_entropy_with_logits(
+ instances.ctrness_pred, ious.detach(),
+ reduction="sum"
+ ) / num_pos_avg
+ losses["loss_fcos_iou"] = quality_loss
+ else:
+ raise NotImplementedError
+
+ extras["instances"] = instances
+
+ return extras, losses
+
+ def predict_proposals(
+ self, logits_pred, reg_pred, ctrness_pred,
+ locations, image_sizes, top_feats=None
+ ):
+ if self.training:
+ self.pre_nms_thresh = self.pre_nms_thresh_train
+ self.pre_nms_topk = self.pre_nms_topk_train
+ self.post_nms_topk = self.post_nms_topk_train
+ else:
+ self.pre_nms_thresh = self.pre_nms_thresh_test
+ self.pre_nms_topk = self.pre_nms_topk_test
+ self.post_nms_topk = self.post_nms_topk_test
+
+ sampled_boxes = []
+
+ bundle = {
+ "l": locations, "o": logits_pred,
+ "r": reg_pred, "c": ctrness_pred,
+ "s": self.strides,
+ }
+
+ if len(top_feats) > 0:
+ bundle["t"] = top_feats
+
+ for i, per_bundle in enumerate(zip(*bundle.values())):
+ # get per-level bundle
+ per_bundle = dict(zip(bundle.keys(), per_bundle))
+ # recall that during training, we normalize regression targets with FPN's stride.
+ # we denormalize them here.
+ l = per_bundle["l"]
+ o = per_bundle["o"]
+ r = per_bundle["r"] * per_bundle["s"]
+ c = per_bundle["c"]
+ t = per_bundle["t"] if "t" in bundle else None
+
+ sampled_boxes.append(
+ self.forward_for_single_feature_map(
+ l, o, r, c, image_sizes, t
+ )
+ )
+
+ for per_im_sampled_boxes in sampled_boxes[-1]:
+ per_im_sampled_boxes.fpn_levels = l.new_ones(
+ len(per_im_sampled_boxes), dtype=torch.long
+ ) * i
+
+ boxlists = list(zip(*sampled_boxes))
+ boxlists = [Instances.cat(boxlist) for boxlist in boxlists]
+ boxlists = self.select_over_all_levels(boxlists)
+
+ return boxlists
+
+ def forward_for_single_feature_map(
+ self, locations, logits_pred, reg_pred,
+ ctrness_pred, image_sizes, top_feat=None
+ ):
+ N, C, H, W = logits_pred.shape
+
+ # put in the same format as locations
+ logits_pred = logits_pred.view(N, C, H, W).permute(0, 2, 3, 1)
+ logits_pred = logits_pred.reshape(N, -1, C).sigmoid()
+ box_regression = reg_pred.view(N, 4, H, W).permute(0, 2, 3, 1)
+ box_regression = box_regression.reshape(N, -1, 4)
+ ctrness_pred = ctrness_pred.view(N, 1, H, W).permute(0, 2, 3, 1)
+ ctrness_pred = ctrness_pred.reshape(N, -1).sigmoid()
+ if top_feat is not None:
+ top_feat = top_feat.view(N, -1, H, W).permute(0, 2, 3, 1)
+ top_feat = top_feat.reshape(N, H * W, -1)
+
+ # if self.thresh_with_ctr is True, we multiply the classification
+ # scores with centerness scores before applying the threshold.
+ if self.thresh_with_ctr:
+ logits_pred = logits_pred * ctrness_pred[:, :, None]
+ candidate_inds = logits_pred > self.pre_nms_thresh
+ pre_nms_top_n = candidate_inds.reshape(N, -1).sum(1)
+ pre_nms_top_n = pre_nms_top_n.clamp(max=self.pre_nms_topk)
+
+ if not self.thresh_with_ctr:
+ logits_pred = logits_pred * ctrness_pred[:, :, None]
+
+ results = []
+ for i in range(N):
+ per_box_cls = logits_pred[i]
+ per_candidate_inds = candidate_inds[i]
+ per_box_cls = per_box_cls[per_candidate_inds]
+
+ per_candidate_nonzeros = per_candidate_inds.nonzero()
+ per_box_loc = per_candidate_nonzeros[:, 0]
+ per_class = per_candidate_nonzeros[:, 1]
+
+ per_box_regression = box_regression[i]
+ per_box_regression = per_box_regression[per_box_loc]
+ per_locations = locations[per_box_loc]
+ if top_feat is not None:
+ per_top_feat = top_feat[i]
+ per_top_feat = per_top_feat[per_box_loc]
+
+ per_pre_nms_top_n = pre_nms_top_n[i]
+
+ if per_candidate_inds.sum().item() > per_pre_nms_top_n.item():
+ per_box_cls, top_k_indices = \
+ per_box_cls.topk(per_pre_nms_top_n, sorted=False)
+ per_class = per_class[top_k_indices]
+ per_box_regression = per_box_regression[top_k_indices]
+ per_locations = per_locations[top_k_indices]
+ if top_feat is not None:
+ per_top_feat = per_top_feat[top_k_indices]
+
+ detections = torch.stack([
+ per_locations[:, 0] - per_box_regression[:, 0],
+ per_locations[:, 1] - per_box_regression[:, 1],
+ per_locations[:, 0] + per_box_regression[:, 2],
+ per_locations[:, 1] + per_box_regression[:, 3],
+ ], dim=1)
+
+ boxlist = Instances(image_sizes[i])
+ boxlist.pred_boxes = Boxes(detections)
+ boxlist.scores = torch.sqrt(per_box_cls)
+ boxlist.pred_classes = per_class
+ boxlist.locations = per_locations
+ if top_feat is not None:
+ boxlist.top_feat = per_top_feat
+ results.append(boxlist)
+
+ return results
+
+ def select_over_all_levels(self, boxlists):
+ num_images = len(boxlists)
+ results = []
+ for i in range(num_images):
+ # multiclass nms
+ result = ml_nms(boxlists[i], self.nms_thresh)
+ number_of_detections = len(result)
+
+ # Limit to max_per_image detections **over all classes**
+ if number_of_detections > self.post_nms_topk > 0:
+ cls_scores = result.scores
+ image_thresh, _ = torch.kthvalue(
+ cls_scores.cpu(),
+ number_of_detections - self.post_nms_topk + 1
+ )
+ keep = cls_scores >= image_thresh.item()
+ keep = torch.nonzero(keep).squeeze(1)
+ result = result[keep]
+ results.append(result)
+ return results
diff --git a/AdelaiDet/adet/modeling/fcpose/__init__.py b/AdelaiDet/adet/modeling/fcpose/__init__.py
new file mode 100755
index 0000000..de917fe
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcpose/__init__.py
@@ -0,0 +1 @@
+from .fcpose_framework import FCPose
diff --git a/AdelaiDet/adet/modeling/fcpose/basis_module.py b/AdelaiDet/adet/modeling/fcpose/basis_module.py
new file mode 100755
index 0000000..1545648
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcpose/basis_module.py
@@ -0,0 +1,120 @@
+from typing import Dict
+from torch import nn
+from torch.nn import functional as F
+import torch
+
+from detectron2.layers import ShapeSpec
+from .utils import aligned_bilinear, compute_loss
+
+from adet.layers import conv_with_kaiming_uniform
+from detectron2.structures import ImageList
+from fvcore.nn import sigmoid_focal_loss_jit
+from detectron2.utils.comm import get_world_size
+from adet.utils.comm import reduce_sum
+import math
+from detectron2.layers import ConvTranspose2d
+from detectron2.layers.batch_norm import get_norm
+
+
+class basis_module(nn.Module):
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ """
+ TODO: support deconv and variable channel width
+ """
+ # official protonet has a relu after each conv
+ super().__init__()
+ # fmt: off
+ mask_dim = cfg.MODEL.FCPOSE.BASIS_MODULE.NUM_BASES
+ planes = cfg.MODEL.FCPOSE.BASIS_MODULE.CONVS_DIM
+ self.device = torch.device(cfg.MODEL.DEVICE)
+ self.in_features = ["p3", "p4", "p5"]
+ self.loss_on = True
+ norm = cfg.MODEL.FCPOSE.BASIS_MODULE.BN_TYPE #"SyncBN"
+ num_convs = 3
+ self.visualize = False
+ # fmt: on
+
+ feature_channels = {k: v.channels for k, v in input_shape.items()}
+
+ conv_block = conv_with_kaiming_uniform(norm, True) # conv relu bn
+ self.refine = nn.ModuleList()
+ for in_feature in self.in_features:
+ self.refine.append(conv_block(
+ feature_channels[in_feature], planes, 3, 1))
+ tower = []
+ for i in range(num_convs):
+ tower.append(
+ conv_block(planes, planes, 3, 1))
+ tower.append(
+ conv_block(planes, planes, 3, 1))
+ tower.append(
+ nn.Conv2d(planes, mask_dim+(2*17), 1))
+ self.add_module('tower', nn.Sequential(*tower))
+
+ if self.loss_on:
+ # fmt: off
+ self.common_stride = cfg.MODEL.FCPOSE.BASIS_MODULE.COMMON_STRIDE
+ self.num_classes = cfg.MODEL.FCPOSE.BASIS_MODULE.NUM_CLASSES
+ self.heatmap_loss_weight = cfg.MODEL.FCPOSE.BASIS_MODULE.LOSS_WEIGHT
+ # self.focal_loss_alpha = cfg.MODEL.FCPOSE.BASIS_MODULE.FOCAL_LOSS_ALPHA
+ # self.focal_loss_gamma = cfg.MODEL.FCPOSE.BASIS_MODULE.FOCAL_LOSS_GAMMA
+
+ # fmt: on
+
+ inplanes = feature_channels[self.in_features[0]]
+ self.seg_head = nn.Sequential(conv_block(planes, planes, 3,1),
+ conv_block(planes, planes, 3,1),)
+ self.p3_logits = nn.Conv2d(planes, self.num_classes, kernel_size=1,
+ stride=1)
+ self.upsampler = nn.Sequential(
+ ConvTranspose2d(planes+self.num_classes, planes, 8, stride=4, padding=6 // 2 - 1),
+ # get_norm(norm, planes),
+ nn.ReLU(),
+ # conv_block(planes, planes, 3,1),
+ )
+ self.p1_logits = nn.Conv2d(planes, self.num_classes, kernel_size=3,
+ stride=1, padding=1)
+
+ prior_prob = cfg.MODEL.FCOS.PRIOR_PROB
+ bias_value = -math.log((1 - prior_prob) / prior_prob)
+ torch.nn.init.constant_(self.p3_logits.bias, 0.0)
+ torch.nn.init.normal_(self.p3_logits.weight, std=0.0001)
+ torch.nn.init.constant_(self.p1_logits.bias, 0.0)
+ torch.nn.init.normal_(self.p1_logits.weight, std=0.0001)
+ # torch.nn.init.constant_(self.upsampler[0].bias, 0.0)
+ # torch.nn.init.normal_(self.upsampler[0].weight, std=0.001)
+ # torch.nn.init.constant_(self.upsampler[1].bias, 0.0)
+ # torch.nn.init.constant_(self.upsampler[1].weight, 1.0)
+ # torch.nn.init.normal_(self.upsampler[3][0].weight, std=0.0001)
+
+ def forward(self, features, p1_heatmap_list=None, p3_heatmap_list=None):
+ for i, f in enumerate(self.in_features):
+ if i == 0:
+ x = self.refine[i](features[f])
+ else:
+ x_p = self.refine[i](features[f])
+ target_h, target_w = x.size()[2:]
+ h, w = x_p.size()[2:]
+ assert target_h % h == 0
+ assert target_w % w == 0
+ factor_h, factor_w = target_h // h, target_w // w
+ assert factor_h == factor_w
+ x_p = aligned_bilinear(x_p, factor_h)
+ x = x + x_p
+ outputs = {"bases": [self.tower(x)]}
+ losses = {}
+ # auxiliary thing semantic loss
+ x = self.seg_head(x)
+ p3_logits = self.p3_logits(x)
+ outputs['basis_seg'] = p3_logits
+ if self.training and self.loss_on:
+ x = torch.cat([x, p3_logits], dim = 1)
+ x = self.upsampler(x)
+ p1_logits = self.p1_logits(x)
+ p1_loss,p3_loss = compute_loss(p1_heatmap_list, p3_heatmap_list, p1_logits, p3_logits)
+ losses['p1_loss'] = p1_loss * self.heatmap_loss_weight
+ losses['p3_loss'] = p3_loss * self.heatmap_loss_weight
+ return outputs, losses
+
+
+
diff --git a/AdelaiDet/adet/modeling/fcpose/fcpose_framework.py b/AdelaiDet/adet/modeling/fcpose/fcpose_framework.py
new file mode 100755
index 0000000..dfe0a6e
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcpose/fcpose_framework.py
@@ -0,0 +1,59 @@
+import math
+from typing import List, Dict
+import torch
+from torch import nn
+from torch.nn import functional as F
+from detectron2.modeling.proposal_generator.build import PROPOSAL_GENERATOR_REGISTRY
+from detectron2.layers import ShapeSpec, NaiveSyncBatchNorm
+from adet.modeling.fcos import FCOS
+from .basis_module import basis_module
+from .fcpose_head import fcpose_head_module
+from .utils import compute_basis_stride, top_module, process_gt_instances
+
+
+
+__all__ = ["FCPose"]
+
+
+
+@PROPOSAL_GENERATOR_REGISTRY.register()
+class FCPose(nn.Module):
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ super().__init__()
+ self.fcos = FCOS(cfg, input_shape)
+ self.top_module = top_module(256, cfg.MODEL.FCPOSE.ATTN_LEN)
+
+ self.basis_module = basis_module(cfg,input_shape)
+
+ self.fcpose_head = fcpose_head_module(cfg)
+
+ self.gt_stride = cfg.MODEL.FCPOSE.GT_HEATMAP_STRIDE
+ self.device = cfg.MODEL.DEVICE
+
+ def forward(self, images, features, gt_instances=None):
+ if gt_instances is not None:
+ basis_gt_heatmap, head_gt_heatmap,p3_heatmap_list = process_gt_instances(gt_instances, self.gt_stride, self.device)
+ else:
+ basis_gt_heatmap, head_gt_heatmap,p3_heatmap_list = None, None, None
+
+ proposals, proposal_losses = self.fcos(images, features, gt_instances, self.top_module)
+
+
+ basis_out, basis_losses = self.basis_module(features, basis_gt_heatmap, p3_heatmap_list)
+ del features, basis_gt_heatmap, p3_heatmap_list
+
+
+ # base_stride = compute_basis_stride(images, basis_out)
+ detector_results, detector_losses = self.fcpose_head(
+ basis_out["bases"], proposals,
+ head_gt_heatmap, gt_instances, basis_out["basis_seg"]
+ )
+
+ losses = {}
+ if self.training:
+ losses.update(proposal_losses)
+ losses.update(basis_losses)
+ losses.update(detector_losses)
+
+
+ return detector_results, losses
\ No newline at end of file
diff --git a/AdelaiDet/adet/modeling/fcpose/fcpose_head.py b/AdelaiDet/adet/modeling/fcpose/fcpose_head.py
new file mode 100755
index 0000000..6382070
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcpose/fcpose_head.py
@@ -0,0 +1,289 @@
+import torch
+from torch.nn import functional as F
+from torch import nn
+
+from detectron2.layers import cat
+from detectron2.modeling.poolers import ROIPooler
+from .utils import aligned_bilinear, compute_loss, compute_loss_softmax
+from fvcore.nn import sigmoid_focal_loss_jit
+from adet.utils.comm import reduce_sum
+from detectron2.utils.comm import get_world_size
+from detectron2.layers import ConvTranspose2d
+from detectron2.structures.instances import Instances
+
+import logging
+
+
+logger = logging.getLogger("detectron2.blender")
+
+
+def build_blender(cfg):
+ return Blender(cfg)
+
+
+def compute_locations_per_level(h, w, stride, device):
+ shifts_x = torch.arange(
+ 0, w * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shifts_y = torch.arange(
+ 0, h * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
+ shift_x = shift_x.reshape(-1)
+ shift_y = shift_y.reshape(-1)
+ locations = torch.stack((shift_x, shift_y), dim=1) + stride // 2
+ return locations
+
+
+def dice_coefficient(x, target):
+ eps = 1e-5
+ n_inst = x.size(0)
+ x = x.reshape(n_inst, -1)
+ target = target.reshape(n_inst, -1)
+ intersection = (x * target).sum(dim=1)
+ union = (x ** 2.0).sum(dim=1) + (target ** 2.0).sum(dim=1) + eps
+ loss = 1. - (2 * intersection / union)
+ return loss
+
+
+def get_subnetworks_params(attns, num_bases, channels):
+ assert attns.dim() == 2
+ n_inst = attns.size(0)
+
+ w0, b0, w1, b1, w2, b2 = torch.split_with_sizes(attns, [
+ (2 + num_bases) * channels, channels,
+ channels * channels, channels,
+ channels * 17, 17
+ ], dim=1)
+
+ # out_channels x in_channels x 1 x 1
+ w0 = w0.reshape(n_inst * channels, 2 + num_bases, 1, 1)
+ b0 = b0.reshape(n_inst * channels)
+ w1 = w1.reshape(n_inst * channels, channels, 1, 1)
+ b1 = b1.reshape(n_inst * channels)
+ w2 = w2.reshape(n_inst * 17, channels, 1, 1)
+ b2 = b2.reshape(n_inst*17)
+
+ return [w0, w1, w2], [b0, b1, b2]
+
+
+def subnetworks_forward(inputs, weights, biases, n_subnets):
+ '''
+ :param inputs: a list of inputs
+ :param weights: [w0, w1, ...]
+ :param bias: [b0, b1, ...]
+ :return:
+ '''
+ assert inputs.dim() == 4
+ n_layer = len(weights)
+ x = inputs
+ for i, (w, b) in enumerate(zip(weights, biases)):
+ x = F.conv2d(
+ x, w, bias=b,
+ stride=1, padding=0,
+ groups=n_subnets
+ )
+ if i < n_layer - 1:
+ x = F.relu(x)
+ return x
+
+
+class fcpose_head_module(nn.Module):
+ def __init__(self, cfg):
+ super(fcpose_head_module, self).__init__()
+ # fmt: off
+
+ self.attn_len = cfg.MODEL.FCPOSE.ATTN_LEN
+ self.dynamic_channels = cfg.MODEL.FCPOSE.DYNAMIC_CHANNELS
+ self.max_proposals_per_im = cfg.MODEL.FCPOSE.MAX_PROPOSALS
+ self.loss_weight_keypoint = cfg.MODEL.FCPOSE.LOSS_WEIGHT_KEYPOINT
+ self.distance_norm = cfg.MODEL.FCPOSE.DISTANCE_NORM
+ self.sizes_of_interest = nn.Parameter(torch.tensor(
+ cfg.MODEL.FCOS.SIZES_OF_INTEREST + [cfg.INPUT.MAX_SIZE_TRAIN]
+ ), requires_grad=False)
+ self.device = cfg.MODEL.DEVICE
+ self.focal_loss_alpha = cfg.MODEL.FCPOSE.FOCAL_LOSS_ALPHA
+ self.focal_loss_gamma = cfg.MODEL.FCPOSE.FOCAL_LOSS_GAMMA
+ self.atten_producer = nn.Linear(256*9, self.attn_len)
+ self.upsampler = nn.Upsample(scale_factor = 4, mode = 'bilinear')
+ self.loss_weight_direction = cfg.MODEL.FCPOSE.LOSS_WEIGHT_DIRECTION
+ self.mask_dim = cfg.MODEL.FCPOSE.BASIS_MODULE.NUM_BASES
+ # fmt: on
+
+ def select_pred_inst(self, proposals):
+ pred_instances = proposals["instances"]
+ N = len(pred_instances.image_size)
+ try:
+ num_instance = pred_instances.gt_inds.max() + 1
+ except: #TODO fix this bug
+ print('error')
+ print(pred_instances)
+ num_instance = 0
+ max_num_instances_per_gt = self.max_proposals_per_im // num_instance
+ max_num_instances_per_gt = max(max_num_instances_per_gt,1)
+
+ kept_instances = []
+ num_loss = []
+ for im_id in range(N):
+ instances_per_im = pred_instances[pred_instances.im_inds == im_id]
+ if len(instances_per_im) == 0:
+ continue
+
+ unique_gt_inds = instances_per_im.gt_inds.unique()
+
+ for gt_ind in unique_gt_inds:
+ instances_per_gt = instances_per_im[instances_per_im.gt_inds == gt_ind]
+
+ if len(instances_per_gt) > max_num_instances_per_gt:
+ scores = instances_per_gt.logits_pred.sigmoid().max(dim=1)[0]
+ ctrness_pred = instances_per_gt.ctrness_pred.sigmoid()
+ inds = (scores * ctrness_pred).topk(k=max_num_instances_per_gt, dim=0)[1]
+ instances_per_gt = instances_per_gt[inds]
+ num_loss_per_inst = unique_gt_inds.new_full([max_num_instances_per_gt], max_num_instances_per_gt)
+ else:
+ num_loss_per_inst = unique_gt_inds.new_full([len(instances_per_gt)], len(instances_per_gt))
+
+
+ kept_instances.append(instances_per_gt)
+ num_loss.append(num_loss_per_inst)
+
+ pred_instances = Instances.cat(kept_instances)
+ num_loss = torch.cat(num_loss,dim=0)
+ # del kept_instances, proposals
+
+ # pred_instances.mask_head_params = pred_instances.top_feats
+ attns = pred_instances.top_feats
+ im_inds = pred_instances.im_inds
+ locations = pred_instances.locations
+ levels = pred_instances.fpn_levels
+ gt_inds = pred_instances.gt_inds
+
+ return attns, im_inds, locations, levels, gt_inds, num_instance, num_loss
+
+ def forward(self, bases, proposals, head_gt_heatmap, gt_instances, basis_seg, base_stride = 8):
+ bases = bases[0]
+ bases, direction = torch.split_with_sizes(bases, [self.mask_dim,34], dim=1)
+ base_locations = compute_locations_per_level(
+ bases.size(2), bases.size(3),
+ stride=base_stride, device=bases.device
+ )
+
+ N,num_bases,H,W = bases.shape
+ direction_locations = base_locations.reshape(H,W,2)
+ direction_locations = direction_locations.permute(2,0,1)[None].repeat(N,17,1,1) # N, 17*2, H ,W
+ direction = direction + direction_locations
+
+ if self.training:
+
+ attns, im_inds, locations, levels, gt_inds, num_instance, num_loss = \
+ self.select_pred_inst(proposals)
+
+ gt_keypoint = []
+ gt_box = []
+ for per_keypoint in gt_instances:
+ gt_keypoint.append(per_keypoint.gt_keypoints.tensor)
+ gt_box.append(per_keypoint.gt_boxes.tensor)
+ gt_keypoint = torch.cat(gt_keypoint,dim=0)[gt_inds]
+ gt_box = torch.cat(gt_box,dim=0)[gt_inds]
+ gt_bitmasks = head_gt_heatmap[gt_inds]
+ direction = direction[im_inds]
+ max_ranges = self.sizes_of_interest[levels.long()]
+
+ n_inst = attns.size(0)
+
+
+ assert not torch.isnan(locations).any()
+ assert not torch.isnan(base_locations).any()
+ assert not torch.isnan(bases).any()
+ offsets = locations.reshape(-1, 1, 2) - base_locations.reshape(1, -1, 2)
+ offsets = offsets.permute(0, 2, 1).float() / max_ranges.reshape(-1, 1, 1).float()
+ offsets = torch.cat([offsets, bases[im_inds].reshape(n_inst, num_bases, -1)], dim=1)
+ offsets = offsets.reshape(1, -1, H, W)
+
+ assert not torch.isnan(attns).any()
+ attns = self.atten_producer(attns)
+ weights, biases = get_subnetworks_params(attns, num_bases, self.dynamic_channels)
+ for weight, biase in zip(weights,biases):
+ assert not torch.isnan(weight).any()
+ assert not torch.isnan(biase).any()
+ assert not torch.isnan(offsets).any()
+
+ mask_logits = subnetworks_forward(offsets, weights, biases, n_inst).squeeze()
+
+ mask_logits = mask_logits.reshape(-1, 17, H, W)
+ larger_mask_logits = self.upsampler(mask_logits)
+ assert not torch.isnan(larger_mask_logits).any()
+
+ assert not torch.isnan(mask_logits).any()
+ mask_logits = mask_logits.flatten(start_dim=2).softmax(dim=2).reshape(-1, 17, H, W)
+ direction = direction[:,:,:,:,None].permute(0,2,3,4,1).reshape(n_inst,H,W,17,2)
+ mask_logits = mask_logits.permute(0,2,3,1)[:,:,:,:,None]
+
+ del weights, biases
+
+
+ gt_box_x = gt_box[:,2] - gt_box[:,0]
+ gt_box_y = gt_box[:,3] - gt_box[:,1]
+ max_ranges = (gt_box_x + gt_box_y) / 2
+ keypoint_loss, direction_loss = \
+ compute_loss_softmax(gt_bitmasks, larger_mask_logits,
+ num_loss, num_instance, direction, mask_logits, gt_keypoint,
+ max_ranges, self.distance_norm)
+
+
+ return None, {"loss_keypoint": keypoint_loss * self.loss_weight_keypoint,
+ "loss_direction": direction_loss * self.loss_weight_direction}
+ else:
+ # no proposals
+ total_instances = sum([len(x) for x in proposals])
+ if total_instances == 0:
+ # add empty pred_masks results
+ for box in proposals:
+ box.pred_keypoints = box.pred_classes.new_full((0,17,3),0).float()
+ return proposals, {}
+
+ N, num_bases, H, W = bases.size()
+ for im_i in range(len(proposals)):
+ per_attns = proposals[im_i].top_feat
+ n_inst = per_attns.size(0)
+
+ per_locations = proposals[im_i].locations
+ max_ranges = self.sizes_of_interest[proposals[im_i].fpn_levels.long()]
+
+ offsets = per_locations.reshape(-1, 1, 2) - base_locations.reshape(1, -1, 2)
+ offsets = offsets.permute(0, 2, 1).float() / max_ranges.reshape(-1, 1, 1).float()
+ # offsets = offsets.tanh()
+ offsets = torch.cat([offsets, bases[im_i].reshape(1, num_bases, -1).expand(n_inst, -1, -1)], dim=1)
+ offsets = offsets.reshape(1, -1, H, W)
+
+ attns = self.atten_producer(per_attns)
+ weights, biases = get_subnetworks_params(attns, num_bases, self.dynamic_channels)
+ pred_mask_logits = subnetworks_forward(offsets, weights, biases, n_inst)
+
+ pred_mask_logits = pred_mask_logits.reshape(-1, 17, H, W)
+ # pred_mask_logits = pred_mask_logits.flatten(start_dim=2).softmax(dim=2).reshape(-1, 17, H, W)
+ direction = direction.repeat(n_inst,1,1,1)
+ direction = direction[:,:,:,:,None].permute(0,2,3,4,1).reshape(n_inst,H,W,17,2)
+ pred_mask_logits = pred_mask_logits.permute(0,2,3,1)[:,:,:,:,None]
+
+ pred_mask_logits = pred_mask_logits.reshape(n_inst,H*W,17).permute(0,2,1)#.sigmoid()
+ pred_mask_logits = pred_mask_logits.reshape(n_inst*17,H*W)
+ max_value, max_index = pred_mask_logits.max(dim = 1)
+ arr = torch.arange(n_inst*17, device=pred_mask_logits.device)
+ direction = direction.permute(0,3,1,2,4).reshape(n_inst*17,H*W,2)
+ direction = direction[arr,max_index]
+ pred_keypoints = direction.reshape(n_inst,17,2)
+
+ vis = max_value.reshape(pred_keypoints.size(0),pred_keypoints.size(1),1)
+ # vis = pred_keypoints.new_ones((pred_keypoints.size(0), pred_keypoints.size(1), 1))
+ pred_keypoints = torch.cat([pred_keypoints, vis], dim = 2)
+
+ proposals[im_i].set("pred_keypoints", pred_keypoints)
+
+
+ return proposals, {}
+
+
+
diff --git a/AdelaiDet/adet/modeling/fcpose/utils.py b/AdelaiDet/adet/modeling/fcpose/utils.py
new file mode 100755
index 0000000..7e836de
--- /dev/null
+++ b/AdelaiDet/adet/modeling/fcpose/utils.py
@@ -0,0 +1,215 @@
+import torch.distributed as dist
+from detectron2.utils.comm import get_world_size
+from torch.nn import functional as F
+from torch import nn
+import torch
+from detectron2.structures import ImageList
+from adet.utils.comm import reduce_sum
+from fvcore.nn import sigmoid_focal_loss_jit
+
+
+def aligned_bilinear(tensor, factor):
+ assert tensor.dim() == 4
+ assert factor >= 1
+ assert int(factor) == factor
+
+ if factor == 1:
+ return tensor
+
+ h, w = tensor.size()[2:]
+ tensor = F.pad(tensor, pad=(0, 1, 0, 1), mode="replicate")
+ oh = factor * h + 1
+ ow = factor * w + 1
+ tensor = F.interpolate(
+ tensor, size=(oh, ow),
+ mode='bilinear',
+ align_corners=True
+ )
+ tensor = F.pad(
+ tensor, pad=(factor // 2, 0, factor // 2, 0),
+ mode="replicate"
+ )
+
+ return tensor[:, :, :oh - 1, :ow - 1]
+
+def compute_basis_stride(images, basis_out):
+ im_h, im_w = images.tensor.size()[-2:]
+ assert len(basis_out["bases"]) == 1
+ base_h, base_w = basis_out["bases"][0].size()[2:]
+ base_stride_h, base_stride_w = im_h // base_h, im_w // base_w
+ assert base_stride_h == base_stride_w
+ base_stride = base_stride_w
+ return base_stride
+
+class folder(nn.Module):
+ def __init__(self):
+ super().__init__()
+ def forward(self, feature_map):
+ N,_,H,W = feature_map.size()
+ feature_map = F.unfold(feature_map,kernel_size=3,padding=1)
+ feature_map = feature_map.view(N,-1,H,W)
+ return feature_map
+
+def top_module(in_channels, attn_len):
+ return folder()
+
+def process_gt_instances(gt_instances, gt_stride, device):
+ basis_heatmap_list = []
+ head_heatmap_list = []
+ p3_heatmap_list = []
+ for instances in gt_instances:
+ one_frame_instances = instances.keypoint_heatmap.to(device = device, dtype = torch.float)
+ one_basis_heatmap = one_frame_instances.max(dim = 0)[0]#.clamp(0,1)
+ basis_heatmap_list.append(one_basis_heatmap)
+
+ p3_output_list = instances.p3_output_list.to(device = device, dtype = torch.float)
+ p3_output_list = p3_output_list.max(dim = 0)[0]#.clamp(0,1)
+ p3_heatmap_list.append(p3_output_list)
+
+ one_frame_instances = instances.head_heatmap.to(device = device, dtype = torch.float)
+ for index_instence in range(len(instances)):
+ head_heatmap_list.append(one_frame_instances[index_instence])
+ basis_heatmap_list = ImageList.from_tensors(basis_heatmap_list)
+ p3_heatmap_list = ImageList.from_tensors(p3_heatmap_list)
+ head_heatmap_list = ImageList.from_tensors(head_heatmap_list)
+ return basis_heatmap_list.tensor, head_heatmap_list.tensor.bool(), p3_heatmap_list.tensor,
+
+
+def compute_loss(p1_heatmap_list, p3_heatmap_list, p1_logits, p3_logits):
+ # gt_bitmasks = gt_bitmasks.float()
+ # mask_logits = mask_logits.sigmoid()
+ num_gpus = get_world_size()
+
+ num_dice = (p1_heatmap_list**2).sum()
+ num_dice = reduce_sum(p1_logits.new_tensor([num_dice])).item()
+ num_dice = max(num_dice / num_gpus, 1.0)
+
+ p1_loss = F.mse_loss(p1_heatmap_list, p1_logits, reduction='sum') / num_dice
+
+ num_dice = (p3_heatmap_list**2).sum()
+ num_dice = reduce_sum(p3_logits.new_tensor([num_dice])).item()
+ num_dice = max(num_dice / num_gpus, 1.0)
+
+ p3_loss = F.mse_loss(p3_heatmap_list, p3_logits, reduction='sum') / num_dice
+
+ # loss = (p1_loss + p3_loss) / 2
+
+ return p1_loss, p3_loss
+
+def compute_loss_softmax(gt_bitmasks, mask_logits, num_loss, num_instances, direction, direction_mask_logits, gt_keypoint, max_ranges, distance_norm):
+ assert not torch.isnan(mask_logits).any()
+ assert not torch.isnan(direction).any()
+ assert not torch.isnan(direction_mask_logits).any()
+ # direction_mask_logits = direction_mask_logits.detach()
+ N,K,H,W = gt_bitmasks.size()
+ # gt_bitmasks = gt_bitmasks.float()
+ num_gpus = get_world_size()
+ assert not (num_loss == 0).any()
+ loss_weight = 1/num_loss #TODO num_loss can be 0
+ sum_loss_weight = loss_weight.sum()
+ assert sum_loss_weight!=0
+ loss_weight = loss_weight[:,None].repeat(1,17).flatten()
+
+ gt_bitmasks = gt_bitmasks.reshape(N*K,H*W)
+ mask_logits = mask_logits.reshape(N*K,H*W)
+ gt_bitmasks_visible_mask = gt_bitmasks.sum(dim=1).bool()
+ # assert gt_bitmasks_visible_mask.sum()!=0 #TODO AssertionError
+ if gt_bitmasks_visible_mask.sum()!=0:
+ loss_weight = loss_weight[gt_bitmasks_visible_mask]
+ mask_logits = mask_logits[gt_bitmasks_visible_mask]
+ gt_bitmasks = gt_bitmasks[gt_bitmasks_visible_mask]
+ mask_logits = F.log_softmax(mask_logits,dim=1)
+
+ total_instances = reduce_sum(mask_logits.new_tensor([num_instances])).item()
+ gpu_balence_factor = num_instances/total_instances
+
+ loss = (- mask_logits[gt_bitmasks])
+ loss = (loss*loss_weight).sum()/17
+ loss = (loss/sum_loss_weight)*gpu_balence_factor
+
+ max_ranges = max_ranges[:,None].repeat(1,17).flatten()[gt_bitmasks_visible_mask]
+ gt_keypoint = gt_keypoint[:,:,[0,1]]
+
+ N,H,W,K,_ = direction_mask_logits.size()
+ direction = direction - gt_keypoint[:,None,None,:,:]
+ direction = direction.permute(0,3,1,2,4).reshape(N*17,H,W,2)
+ direction = direction[gt_bitmasks_visible_mask]
+ direction = (direction[:,:,:,0] ** 2 + direction[:,:,:,1] ** 2).sqrt()[:,:,:,None]
+ assert (max_ranges != 0).all()
+ direction = direction / max_ranges[:,None,None,None]
+ direction = direction * distance_norm
+ direction = (direction.sigmoid()-0.5) * 2
+ direction_mask_logits = direction_mask_logits.permute(0,3,1,2,4).reshape(N*17,H,W,1)
+ direction_mask_logits = direction_mask_logits[gt_bitmasks_visible_mask]
+ direction = direction * direction_mask_logits
+ direction = direction.flatten(start_dim=1).sum(dim=1)
+ direction = direction * loss_weight
+ assert distance_norm != 0
+ direction_loss = (direction/sum_loss_weight * gpu_balence_factor) / distance_norm
+ direction_loss = direction_loss.sum()
+ assert not torch.isnan(direction_loss).any()
+ assert not torch.isnan(loss).any()
+ return loss, direction_loss
+ else:
+ print('gt_bitmasks_visible_mask.sum()==0')
+ total_instances = reduce_sum(mask_logits.new_tensor([num_instances])).item()
+ loss = mask_logits.sum() + direction.sum() + direction_mask_logits.sum()
+ loss = loss*0.0
+ return loss, loss
+
+
+# def compute_loss(gt_bitmasks, mask_logits):
+# # assert torch.isfinite(gt_bitmasks).all() and torch.isfinite(mask_logits).all()
+# gt_bitmasks = gt_bitmasks.float()
+# num_gpus = get_world_size()
+
+# num_dice = gt_bitmasks.sum()
+# num_dice = reduce_sum(mask_logits.new_tensor([num_dice])).item()
+# num_dice = max(num_dice / num_gpus, 1.0)
+
+
+# loss = F.mse_loss(mask_logits, gt_bitmasks, reduction='sum') / num_dice
+
+# # assert torch.isfinite(loss).all()
+# return loss
+
+# def compute_loss(gt_bitmasks, mask_logits):
+# # assert torch.isfinite(gt_bitmasks).all() and torch.isfinite(mask_logits).all()
+# gt_bitmasks = gt_bitmasks.float()
+# eps = 1e-5
+# intersection = (mask_logits * gt_bitmasks).sum(dim=1)
+# union = (mask_logits ** 2.0).sum(dim=1) + (gt_bitmasks ** 2.0).sum(dim=1) + eps
+# loss = 1. - (2 * intersection / union)
+# return loss.mean()
+
+# def compute_loss(gt_bitmasks, mask_logits):
+# # assert torch.isfinite(gt_bitmasks).all() and torch.isfinite(mask_logits).all()
+# gt_bitmasks = gt_bitmasks.float()
+# num_gpus = get_world_size()
+
+# true_point = gt_bitmasks > 0.5
+# num_true = torch.where(true_point)[0].size(0)
+# if num_true == 0:
+# num_true = reduce_sum(mask_logits.new_tensor([num_true])).item()
+# loss1 = mask_logits.sum() * 0.0
+# else:
+# num_true = reduce_sum(mask_logits.new_tensor([num_true])).item()
+# num_true = max(num_true / num_gpus, 1.0)
+# loss1 = F.mse_loss(mask_logits[true_point],
+# gt_bitmasks[true_point], reduction='sum') / num_true
+
+# positive_point = mask_logits > 0.5
+# false_positive_point = (~true_point) | positive_point
+# num_false_positive = torch.where(false_positive_point)[0].size(0)
+# if num_false_positive == 0:
+# num_false_positive = reduce_sum(mask_logits.new_tensor([num_false_positive])).item()
+# loss2 = mask_logits.sum() * 0.0
+# else:
+# num_false_positive = reduce_sum(mask_logits.new_tensor([num_false_positive])).item()
+# num_false_positive = max(num_false_positive / num_gpus, 1.0)
+# loss2 = F.mse_loss(mask_logits[false_positive_point],
+# gt_bitmasks[false_positive_point], reduction='sum') / num_false_positive
+
+# loss = 0.5*loss1 + 0.5*loss2
+
+# return loss
diff --git a/AdelaiDet/adet/modeling/one_stage_detector.py b/AdelaiDet/adet/modeling/one_stage_detector.py
new file mode 100755
index 0000000..ad59cb7
--- /dev/null
+++ b/AdelaiDet/adet/modeling/one_stage_detector.py
@@ -0,0 +1,188 @@
+import logging
+from torch import nn
+
+from detectron2.modeling.meta_arch.build import META_ARCH_REGISTRY
+from detectron2.modeling import ProposalNetwork, GeneralizedRCNN
+from detectron2.utils.events import get_event_storage
+from detectron2.utils.logger import log_first_n
+from detectron2.modeling.postprocessing import detector_postprocess as d2_postprocesss
+
+
+def detector_postprocess(results, output_height, output_width, mask_threshold=0.5):
+ """
+ In addition to the post processing of detectron2, we add scalign for
+ bezier control points.
+ """
+ scale_x, scale_y = (output_width / results.image_size[1], output_height / results.image_size[0])
+ results = d2_postprocesss(results, output_height, output_width, mask_threshold)
+
+ # scale bezier points
+ if results.has("beziers"):
+ beziers = results.beziers
+ # scale and clip in place
+ beziers[:, 0::2] *= scale_x
+ beziers[:, 1::2] *= scale_y
+ h, w = results.image_size
+ beziers[:, 0].clamp_(min=0, max=w)
+ beziers[:, 1].clamp_(min=0, max=h)
+ beziers[:, 6].clamp_(min=0, max=w)
+ beziers[:, 7].clamp_(min=0, max=h)
+ beziers[:, 8].clamp_(min=0, max=w)
+ beziers[:, 9].clamp_(min=0, max=h)
+ beziers[:, 14].clamp_(min=0, max=w)
+ beziers[:, 15].clamp_(min=0, max=h)
+
+ return results
+
+
+@META_ARCH_REGISTRY.register()
+class OneStageDetector(ProposalNetwork):
+ """
+ Same as :class:`detectron2.modeling.ProposalNetwork`.
+ Uses "instances" as the return key instead of using "proposal".
+ """
+ def forward(self, batched_inputs):
+ if self.training:
+ return super().forward(batched_inputs)
+ processed_results = super().forward(batched_inputs)
+ processed_results = [{"instances": r["proposals"]} for r in processed_results]
+ return processed_results
+
+
+def build_top_module(cfg):
+ top_type = cfg.MODEL.TOP_MODULE.NAME
+ if top_type == "conv":
+ inp = cfg.MODEL.FPN.OUT_CHANNELS
+ oup = cfg.MODEL.TOP_MODULE.DIM
+ top_module = nn.Conv2d(
+ inp, oup,
+ kernel_size=3, stride=1, padding=1)
+ else:
+ top_module = None
+ return top_module
+
+
+@META_ARCH_REGISTRY.register()
+class OneStageRCNN(GeneralizedRCNN):
+ """
+ Same as :class:`detectron2.modeling.ProposalNetwork`.
+ Use one stage detector and a second stage for instance-wise prediction.
+ """
+ def __init__(self, cfg):
+ super().__init__(cfg)
+ self.top_module = build_top_module(cfg)
+ self.to(self.device)
+
+ def forward(self, batched_inputs):
+ """
+ Args:
+ batched_inputs: a list, batched outputs of :class:`DatasetMapper` .
+ Each item in the list contains the inputs for one image.
+ For now, each item in the list is a dict that contains:
+
+ * image: Tensor, image in (C, H, W) format.
+ * instances (optional): groundtruth :class:`Instances`
+ * proposals (optional): :class:`Instances`, precomputed proposals.
+
+ Other information that's included in the original dicts, such as:
+
+ * "height", "width" (int): the output resolution of the model, used in inference.
+ See :meth:`postprocess` for details.
+
+ Returns:
+ list[dict]:
+ Each dict is the output for one input image.
+ The dict contains one key "instances" whose value is a :class:`Instances`.
+ The :class:`Instances` object has the following keys:
+ "pred_boxes", "pred_classes", "scores", "pred_masks", "pred_keypoints"
+ """
+ if not self.training:
+ return self.inference(batched_inputs)
+
+ images = self.preprocess_image(batched_inputs)
+ if "instances" in batched_inputs[0]:
+ gt_instances = [x["instances"].to(self.device) for x in batched_inputs]
+ elif "targets" in batched_inputs[0]:
+ log_first_n(
+ logging.WARN, "'targets' in the model inputs is now renamed to 'instances'!", n=10
+ )
+ gt_instances = [x["targets"].to(self.device) for x in batched_inputs]
+ else:
+ gt_instances = None
+
+ features = self.backbone(images.tensor)
+
+ if self.proposal_generator:
+ proposals, proposal_losses = self.proposal_generator(
+ images, features, gt_instances, self.top_module)
+ else:
+ assert "proposals" in batched_inputs[0]
+ proposals = [x["proposals"].to(self.device) for x in batched_inputs]
+ proposal_losses = {}
+
+ _, detector_losses = self.roi_heads(images, features, proposals, gt_instances)
+ if self.vis_period > 0:
+ storage = get_event_storage()
+ if storage.iter % self.vis_period == 0:
+ self.visualize_training(batched_inputs, proposals)
+
+ losses = {}
+ losses.update(detector_losses)
+ losses.update(proposal_losses)
+ return losses
+
+ def inference(self, batched_inputs, detected_instances=None, do_postprocess=True):
+ """
+ Run inference on the given inputs.
+
+ Args:
+ batched_inputs (list[dict]): same as in :meth:`forward`
+ detected_instances (None or list[Instances]): if not None, it
+ contains an `Instances` object per image. The `Instances`
+ object contains "pred_boxes" and "pred_classes" which are
+ known boxes in the image.
+ The inference will then skip the detection of bounding boxes,
+ and only predict other per-ROI outputs.
+ do_postprocess (bool): whether to apply post-processing on the outputs.
+
+ Returns:
+ same as in :meth:`forward`.
+ """
+ assert not self.training
+
+ images = self.preprocess_image(batched_inputs)
+ features = self.backbone(images.tensor)
+
+ if detected_instances is None:
+ if self.proposal_generator:
+ proposals, _ = self.proposal_generator(
+ images, features, None, self.top_module)
+ else:
+ assert "proposals" in batched_inputs[0]
+ proposals = [x["proposals"].to(self.device) for x in batched_inputs]
+
+ results, _ = self.roi_heads(images, features, proposals, None)
+ else:
+ detected_instances = [x.to(self.device) for x in detected_instances]
+ results = self.roi_heads.forward_with_given_boxes(features, detected_instances)
+
+ if do_postprocess:
+ return OneStageRCNN._postprocess(results, batched_inputs, images.image_sizes)
+ else:
+ return results
+
+ @staticmethod
+ def _postprocess(instances, batched_inputs, image_sizes):
+ """
+ Rescale the output instances to the target size.
+ """
+ # note: private function; subject to changes
+ processed_results = []
+ for results_per_image, input_per_image, image_size in zip(
+ instances, batched_inputs, image_sizes
+ ):
+ height = input_per_image.get("height", image_size[0])
+ width = input_per_image.get("width", image_size[1])
+ r = detector_postprocess(results_per_image, height, width)
+ processed_results.append({"instances": r})
+ return processed_results
\ No newline at end of file
diff --git a/AdelaiDet/adet/modeling/poolers.py b/AdelaiDet/adet/modeling/poolers.py
new file mode 100755
index 0000000..3bf77b0
--- /dev/null
+++ b/AdelaiDet/adet/modeling/poolers.py
@@ -0,0 +1,163 @@
+import sys
+import torch
+from torch import nn
+from detectron2.layers import cat
+
+from detectron2.modeling.poolers import (
+ ROIPooler, convert_boxes_to_pooler_format, assign_boxes_to_levels
+)
+
+from adet.layers import BezierAlign
+from adet.structures import Beziers
+
+__all__ = ["TopPooler"]
+
+
+def _box_max_size(boxes):
+ box = boxes.tensor
+ max_size = torch.max(box[:, 2] - box[:, 0], box[:, 3] - box[:, 1])
+ return max_size
+
+
+def _bezier_height(beziers):
+ beziers = beziers.tensor
+ # compute the distance between the first and last control point
+ p1 = beziers[:, :2]
+ p2 = beziers[:, 14:]
+ height = ((p1 - p2) ** 2).sum(dim=1).sqrt()
+ return height
+
+
+def assign_boxes_to_levels_by_metric(
+ box_lists, min_level, max_level, canonical_box_size,
+ canonical_level, metric_fn=_box_max_size):
+ """
+ Map each box in `box_lists` to a feature map level index and return the assignment
+ vector.
+
+ Args:
+ box_lists (list[detectron2.structures.Boxes]): A list of N Boxes or N RotatedBoxes,
+ where N is the number of images in the batch.
+ min_level (int): Smallest feature map level index. The input is considered index 0,
+ the output of stage 1 is index 1, and so.
+ max_level (int): Largest feature map level index.
+ canonical_box_size (int): A canonical box size in pixels (shorter side).
+ canonical_level (int): The feature map level index on which a canonically-sized box
+ should be placed.
+
+ Returns:
+ A tensor of length M, where M is the total number of boxes aggregated over all
+ N batch images. The memory layout corresponds to the concatenation of boxes
+ from all images. Each element is the feature map index, as an offset from
+ `self.min_level`, for the corresponding box (so value i means the box is at
+ `self.min_level + i`).
+ """
+ eps = sys.float_info.epsilon
+ box_sizes = cat([metric_fn(boxes) for boxes in box_lists])
+ # Eqn.(1) in FPN paper
+ level_assignments = torch.floor(
+ canonical_level + torch.log2(box_sizes / canonical_box_size + eps)
+ )
+ level_assignments = torch.clamp(level_assignments, min=min_level, max=max_level)
+ return level_assignments.to(torch.int64) - min_level
+
+
+def assign_boxes_to_levels_max(
+ box_lists, min_level, max_level, canonical_box_size,
+ canonical_level):
+ return assign_boxes_to_levels_by_metric(
+ box_lists, min_level, max_level, canonical_box_size,
+ canonical_level, metric_fn=_box_max_size
+ )
+
+
+def assign_boxes_to_levels_bezier(
+ box_lists, min_level, max_level, canonical_box_size,
+ canonical_level):
+ return assign_boxes_to_levels_by_metric(
+ box_lists, min_level, max_level, canonical_box_size,
+ canonical_level, metric_fn=_bezier_height
+ )
+
+
+class TopPooler(ROIPooler):
+ """
+ ROIPooler with option to assign level by max length. Used by top modules.
+ """
+ def __init__(self,
+ output_size,
+ scales,
+ sampling_ratio,
+ pooler_type,
+ canonical_box_size=224,
+ canonical_level=4,
+ assign_crit="area",):
+ # to reuse the parent initialization, handle unsupported pooler types
+ parent_pooler_type = "ROIAlign" if pooler_type == "BezierAlign" else pooler_type
+ super().__init__(output_size, scales, sampling_ratio, parent_pooler_type,
+ canonical_box_size=canonical_box_size,
+ canonical_level=canonical_level)
+ if parent_pooler_type != pooler_type:
+ # reinit the level_poolers here
+ self.level_poolers = nn.ModuleList(
+ BezierAlign(
+ output_size, spatial_scale=scale,
+ sampling_ratio=sampling_ratio) for scale in scales
+ )
+ self.assign_crit = assign_crit
+
+ def forward(self, x, box_lists):
+ """
+ see
+ """
+ num_level_assignments = len(self.level_poolers)
+
+ assert isinstance(x, list) and isinstance(
+ box_lists, list
+ ), "Arguments to pooler must be lists"
+ assert (
+ len(x) == num_level_assignments
+ ), "unequal value, num_level_assignments={}, but x is list of {} Tensors".format(
+ num_level_assignments, len(x)
+ )
+
+ assert len(box_lists) == x[0].size(
+ 0
+ ), "unequal value, x[0] batch dim 0 is {}, but box_list has length {}".format(
+ x[0].size(0), len(box_lists)
+ )
+
+ if isinstance(box_lists[0], torch.Tensor):
+ # TODO: use Beziers for data_mapper
+ box_lists = [Beziers(x) for x in box_lists]
+ pooler_fmt_boxes = convert_boxes_to_pooler_format(box_lists)
+
+ if num_level_assignments == 1:
+ return self.level_poolers[0](x[0], pooler_fmt_boxes)
+
+ if self.assign_crit == "max":
+ assign_method = assign_boxes_to_levels_max
+ elif self.assign_crit == "bezier":
+ assign_method = assign_boxes_to_levels_bezier
+ else:
+ assign_method = assign_boxes_to_levels
+
+ level_assignments = assign_method(
+ box_lists, self.min_level, self.max_level,
+ self.canonical_box_size, self.canonical_level)
+
+ num_boxes = len(pooler_fmt_boxes)
+ num_channels = x[0].shape[1]
+ output_size = self.output_size
+
+ dtype, device = x[0].dtype, x[0].device
+ output = torch.zeros(
+ (num_boxes, num_channels, output_size[0], output_size[1]), dtype=dtype, device=device
+ )
+
+ for level, (x_level, pooler) in enumerate(zip(x, self.level_poolers)):
+ inds = torch.nonzero(level_assignments == level).squeeze(1)
+ pooler_fmt_boxes_level = pooler_fmt_boxes[inds]
+ output[inds] = pooler(x_level, pooler_fmt_boxes_level)
+
+ return output
diff --git a/AdelaiDet/adet/modeling/roi_heads/__init__.py b/AdelaiDet/adet/modeling/roi_heads/__init__.py
new file mode 100755
index 0000000..8b13789
--- /dev/null
+++ b/AdelaiDet/adet/modeling/roi_heads/__init__.py
@@ -0,0 +1 @@
+
diff --git a/AdelaiDet/adet/modeling/roi_heads/attn_predictor.py b/AdelaiDet/adet/modeling/roi_heads/attn_predictor.py
new file mode 100755
index 0000000..657431a
--- /dev/null
+++ b/AdelaiDet/adet/modeling/roi_heads/attn_predictor.py
@@ -0,0 +1,156 @@
+import random
+import torch
+from torch import nn
+from torch.nn import functional as F
+from torch.autograd import Variable
+
+from adet.layers import conv_with_kaiming_uniform
+
+
+class BidirectionalLSTM(nn.Module):
+
+ def __init__(self, nIn, nHidden, nOut):
+ super(BidirectionalLSTM, self).__init__()
+
+ self.rnn = nn.LSTM(nIn, nHidden, bidirectional=True)
+ self.embedding = nn.Linear(nHidden * 2, nOut)
+
+ def forward(self, input):
+ recurrent, _ = self.rnn(input)
+ T, b, h = recurrent.size()
+ t_rec = recurrent.view(T * b, h)
+
+ output = self.embedding(t_rec) # [T * b, nOut]
+ output = output.view(T, b, -1)
+
+ return output
+
+
+class CRNN(nn.Module):
+ def __init__(self, cfg, in_channels):
+ super(CRNN, self).__init__()
+ conv_func = conv_with_kaiming_uniform(norm="GN", activation=True)
+ convs = []
+ for i in range(2):
+ convs.append(conv_func(in_channels, in_channels, 3, stride=(2, 1)))
+ self.convs = nn.Sequential(*convs)
+ self.rnn = BidirectionalLSTM(in_channels, in_channels, in_channels)
+
+ def forward(self, x):
+ # average along H dimension
+ x = self.convs(x)
+ x = x.mean(dim=2) # NxCxW
+ x = x.permute(2, 0, 1) # WxNxC
+ x = self.rnn(x)
+ return x
+
+
+# apply attention
+class Attention(nn.Module):
+ def __init__(self, cfg, in_channels):
+ super(Attention, self).__init__()
+ self.hidden_size = in_channels
+ self.output_size = cfg.MODEL.BATEXT.VOC_SIZE + 1
+ self.dropout_p = 0.1
+ self.max_len = cfg.MODEL.BATEXT.NUM_CHARS
+
+ self.embedding = nn.Embedding(self.output_size, self.hidden_size)
+ self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)
+ self.dropout = nn.Dropout(self.dropout_p)
+ self.gru = nn.GRU(self.hidden_size, self.hidden_size)
+ self.out = nn.Linear(self.hidden_size, self.output_size)
+
+ # test
+ self.vat = nn.Linear(self.hidden_size, 1)
+
+ def forward(self, input, hidden, encoder_outputs):
+ '''
+ hidden: 1 x n x self.hidden_size
+ encoder_outputs: time_step x n x self.hidden_size (T,N,C)
+ '''
+ embedded = self.embedding(input)
+ embedded = self.dropout(embedded)
+
+ # test
+ batch_size = encoder_outputs.shape[1]
+
+ alpha = hidden + encoder_outputs
+ alpha = alpha.view(-1, alpha.shape[-1]) # (T * n, hidden_size)
+ attn_weights = self.vat(torch.tanh(alpha)) # (T * n, 1)
+ attn_weights = attn_weights.view(-1, 1, batch_size).permute((2,1,0)) # (T, 1, n) -> (n, 1, T)
+ attn_weights = F.softmax(attn_weights, dim=2)
+
+ attn_applied = torch.matmul(attn_weights,
+ encoder_outputs.permute((1, 0, 2)))
+
+ if embedded.dim() == 1:
+ embedded = embedded.unsqueeze(0)
+ output = torch.cat((embedded, attn_applied.squeeze(1)), 1)
+ output = self.attn_combine(output).unsqueeze(0) # (1, n, hidden_size)
+
+ output = F.relu(output)
+ output, hidden = self.gru(output, hidden) # (1, n, hidden_size)
+
+ output = F.log_softmax(self.out(output[0]), dim=1) # (n, hidden_size)
+ return output, hidden, attn_weights
+
+ def initHidden(self, batch_size):
+ result = Variable(torch.zeros(1, batch_size, self.hidden_size))
+ return result
+
+ def prepare_targets(self, targets):
+ target_lengths = (targets != self.output_size - 1).long().sum(dim=-1)
+ sum_targets = [t[:l] for t, l in zip(targets, target_lengths)]
+ return target_lengths, sum_targets
+
+
+class ATTPredictor(nn.Module):
+ def __init__(self, cfg):
+ super(ATTPredictor, self).__init__()
+ in_channels = cfg.MODEL.BATEXT.CONV_DIM
+ self.CRNN = CRNN(cfg, in_channels)
+ self.criterion = torch.nn.NLLLoss()
+ self.attention = Attention(cfg, in_channels)
+ self.teach_prob = 0.5
+
+ def forward(self, rois, targets=None):
+ rois = self.CRNN(rois)
+ if self.training:
+ target_variable = targets
+ _init = torch.zeros((rois.size()[1], 1)).long()
+ _init = torch.LongTensor(_init).to(rois.device)
+ target_variable = torch.cat((_init, target_variable.long()), 1)
+ target_variable = target_variable.to(rois.device)
+ decoder_input = target_variable[:,0] # init decoder, from 0
+ decoder_hidden = self.attention.initHidden(rois.size()[1]).to(rois.device) # batch rois.size[1]
+ loss = 0.0
+
+ for di in range(1, target_variable.shape[1]):
+ decoder_output, decoder_hidden, decoder_attention = self.attention( # decoder_output (nbatch, ncls)
+ decoder_input, decoder_hidden, rois)
+ loss += self.criterion(decoder_output, target_variable[:,di])
+ teach_forcing = True if random.random() > self.teach_prob else False
+ if teach_forcing:
+ decoder_input = target_variable[:,di] # Teacher forcing
+ else:
+ topv, topi = decoder_output.data.topk(1)
+ ni = topi.squeeze()
+ decoder_input = ni
+
+ return None, loss
+ else:
+ n = rois.size()[1]
+ decodes = torch.zeros((n, self.attention.max_len))
+ prob = 1.0
+ decoder_input = torch.zeros(n).long().to(rois.device)
+ decoder_hidden = self.attention.initHidden(n).to(rois.device)
+ for di in range(self.attention.max_len):
+ decoder_output, decoder_hidden, decoder_attention = self.attention(
+ decoder_input, decoder_hidden, rois)
+ probs = torch.exp(decoder_output)
+ topv, topi = decoder_output.data.topk(1)
+ ni = topi.squeeze()
+ decoder_input = ni
+ prob *= probs[:, ni]
+ decodes[:, di] = decoder_input
+ return decodes, None
\ No newline at end of file
diff --git a/AdelaiDet/adet/modeling/roi_heads/text_head.py b/AdelaiDet/adet/modeling/roi_heads/text_head.py
new file mode 100755
index 0000000..b40d170
--- /dev/null
+++ b/AdelaiDet/adet/modeling/roi_heads/text_head.py
@@ -0,0 +1,230 @@
+import math
+
+from typing import Dict, List
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from detectron2.layers import ShapeSpec, cat
+from detectron2.modeling import ROI_HEADS_REGISTRY
+
+from adet.layers import conv_with_kaiming_uniform
+from ..poolers import TopPooler
+from .attn_predictor import ATTPredictor
+
+
+class SeqConvs(nn.Module):
+ def __init__(self, conv_dim, roi_size):
+ super().__init__()
+
+ height = roi_size[0]
+ downsample_level = math.log2(height) - 2
+ assert math.isclose(downsample_level, int(downsample_level))
+ downsample_level = int(downsample_level)
+
+ conv_block = conv_with_kaiming_uniform(
+ norm="BN", activation=True)
+ convs = []
+ for i in range(downsample_level):
+ convs.append(conv_block(
+ conv_dim, conv_dim, 3, stride=(2, 1)))
+ convs.append(nn.Conv2d(conv_dim, conv_dim, kernel_size=(4, 1), bias=False))
+ self.convs = nn.Sequential(*convs)
+
+ def forward(self, x):
+ return self.convs(x)
+
+
+class RNNPredictor(nn.Module):
+ def __init__(self, cfg):
+ super(RNNPredictor, self).__init__()
+ # fmt: off
+ self.voc_size = cfg.MODEL.BATEXT.VOC_SIZE
+ conv_dim = cfg.MODEL.BATEXT.CONV_DIM
+ roi_size = cfg.MODEL.BATEXT.POOLER_RESOLUTION
+ # fmt: on
+
+ self.convs = SeqConvs(conv_dim, roi_size)
+ self.rnn = nn.LSTM(conv_dim, conv_dim, num_layers=1, bidirectional=True)
+ self.clf = nn.Linear(conv_dim * 2, self.voc_size + 1)
+ self.recognition_loss_fn = build_recognition_loss_fn()
+
+ def forward(self, x, targets=None):
+ # check empty
+ if x.size(0) == 0:
+ return x.new_zeros((x.size(2), 0, self.voc_size))
+ x = self.convs(x).squeeze(dim=2) # NxCxW
+ x = x.permute(2, 0, 1) # WxNxC
+ x, _ = self.rnn(x)
+ preds = self.clf(x)
+
+ if self.training:
+ rec_loss = self.recognition_loss_fn(preds, targets, self.voc_size)
+ return preds, rec_loss
+ else:
+ # (W, N, C) -> (N, W, C)
+ _, preds = preds.permute(1, 0, 2).max(dim=-1)
+ return preds, None
+
+### CoordConv
+class MaskHead(nn.Module):
+ def __init__(self, cfg):
+ super(MaskHead, self).__init__()
+
+ conv_dim = cfg.MODEL.BATEXT.CONV_DIM
+
+ conv_block = conv_with_kaiming_uniform(
+ norm="BN", activation=True)
+ convs = []
+ convs.append(conv_block(258, conv_dim, 3, 1))
+ for i in range(3):
+ convs.append(conv_block(
+ conv_dim, conv_dim, 3, 1))
+ self.mask_convs = nn.Sequential(*convs)
+
+ def forward(self, features):
+ x_range = torch.linspace(-1, 1, features.shape[-1], device=features.device)
+ y_range = torch.linspace(-1, 1, features.shape[-2], device=features.device)
+ y, x = torch.meshgrid(y_range, x_range)
+ y = y.expand([features.shape[0], 1, -1, -1])
+ x = x.expand([features.shape[0], 1, -1, -1])
+ coord_feat = torch.cat([x, y], 1)
+ ins_features = torch.cat([features, coord_feat], dim=1)
+ mask_features = self.mask_convs(ins_features)
+ return mask_features
+
+def build_recognizer(cfg, type):
+ if type == 'rnn':
+ return RNNPredictor(cfg)
+ if type == 'attn':
+ return ATTPredictor(cfg)
+ else:
+ raise NotImplementedError("{} is not a valid recognizer".format(type))
+
+
+def ctc_loss(preds, targets, voc_size):
+ # prepare targets
+ target_lengths = (targets != voc_size).long().sum(dim=-1)
+ trimmed_targets = [t[:l] for t, l in zip(targets, target_lengths)]
+ targets = torch.cat(trimmed_targets)
+
+ x = F.log_softmax(preds, dim=-1)
+ input_lengths = torch.full((x.size(1),), x.size(0), dtype=torch.long)
+ return F.ctc_loss(
+ x, targets, input_lengths, target_lengths,
+ blank=voc_size, zero_infinity=True
+ )
+
+
+def build_recognition_loss_fn(rec_type="ctc"):
+ if rec_type == "ctc":
+ return ctc_loss
+ else:
+ raise NotImplementedError("{} is not a valid recognition loss".format(rec_type))
+
+
+@ROI_HEADS_REGISTRY.register()
+class TextHead(nn.Module):
+ """
+ TextHead performs text region alignment and recognition.
+
+ It is a simplified ROIHeads, only ground truth RoIs are
+ used during training.
+ """
+ def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
+ """
+ Args:
+ in_channels (int): number of channels of the input feature
+ """
+ super(TextHead, self).__init__()
+ # fmt: off
+ pooler_resolution = cfg.MODEL.BATEXT.POOLER_RESOLUTION
+ pooler_scales = cfg.MODEL.BATEXT.POOLER_SCALES
+ sampling_ratio = cfg.MODEL.BATEXT.SAMPLING_RATIO
+ conv_dim = cfg.MODEL.BATEXT.CONV_DIM
+ num_conv = cfg.MODEL.BATEXT.NUM_CONV
+ canonical_size = cfg.MODEL.BATEXT.CANONICAL_SIZE
+ self.in_features = cfg.MODEL.BATEXT.IN_FEATURES
+ self.voc_size = cfg.MODEL.BATEXT.VOC_SIZE
+ recognizer = cfg.MODEL.BATEXT.RECOGNIZER
+ self.top_size = cfg.MODEL.TOP_MODULE.DIM
+ self.coordconv = cfg.MODEL.BATEXT.USE_COORDCONV
+ self.aet = cfg.MODEL.BATEXT.USE_AET
+ # fmt: on
+
+ self.pooler = TopPooler(
+ output_size=pooler_resolution,
+ scales=pooler_scales,
+ sampling_ratio=sampling_ratio,
+ pooler_type="BezierAlign",
+ canonical_box_size=canonical_size,
+ canonical_level=3,
+ assign_crit="bezier")
+
+ conv_block = conv_with_kaiming_uniform(
+ norm="BN", activation=True)
+ tower = []
+ for i in range(num_conv):
+ tower.append(
+ conv_block(conv_dim, conv_dim, 3, 1))
+ self.tower = nn.Sequential(*tower)
+
+ if self.coordconv:
+ self.mask_head = MaskHead(cfg)
+
+ self.recognizer = build_recognizer(cfg, recognizer)
+
+ def forward(self, images, features, proposals, targets=None):
+ """
+ see detectron2.modeling.ROIHeads
+ """
+ del images
+ features = [features[f] for f in self.in_features]
+
+ if self.coordconv:
+ mask_features = []
+ for i in range(len(features)):
+ mask_feat = self.mask_head(features[i])
+ all_feat = mask_feat + features[i]
+ mask_features.append(all_feat)
+ features = mask_features
+
+ if self.training:
+ beziers = [p.beziers for p in targets]
+ if not self.aet:
+ targets = torch.cat([x.text for x in targets], dim=0)
+ else:
+ beziers2 = [p.top_feat for p in proposals]
+ for k in range(len(targets)):
+ rec_assign = [int(torch.argmin(torch.abs(beziers[k] - beziers2[k][i]).sum(dim=1))) for i in range(len(beziers2[k]))]
+ targets[k] = torch.cat([targets[k].text, targets[k].text[rec_assign]], dim = 0)
+ targets = torch.cat([x for x in targets], dim = 0)
+ cat_beziers = []
+ for ix in range(len(beziers)):
+ cat_beziers.append(cat((beziers[ix], beziers2[ix]), dim=0))
+ beziers = cat_beziers
+ else:
+ beziers = [p.top_feat for p in proposals]
+ bezier_features = self.pooler(features, beziers)
+ bezier_features = self.tower(bezier_features)
+
+ # TODO: move this part to recognizer
+ if self.training:
+ preds, rec_loss = self.recognizer(bezier_features, targets)
+ rec_loss *= 0.05
+ losses = {'rec_loss': rec_loss}
+ return None, losses
+ else:
+ if bezier_features.size(0) == 0:
+ for box in proposals:
+ box.beziers = box.top_feat
+ box.recs = box.top_feat
+ return proposals, {}
+ preds, _ = self.recognizer(bezier_features, targets)
+ start_ind = 0
+ for proposals_per_im in proposals:
+ end_ind = start_ind + len(proposals_per_im)
+ proposals_per_im.recs = preds[start_ind:end_ind]
+ proposals_per_im.beziers = proposals_per_im.top_feat
+ start_ind = end_ind
+ return proposals, {}
diff --git a/AdelaiDet/adet/modeling/solov2/__init__.py b/AdelaiDet/adet/modeling/solov2/__init__.py
new file mode 100755
index 0000000..40eac44
--- /dev/null
+++ b/AdelaiDet/adet/modeling/solov2/__init__.py
@@ -0,0 +1 @@
+from .solov2 import SOLOv2
diff --git a/AdelaiDet/adet/modeling/solov2/loss.py b/AdelaiDet/adet/modeling/solov2/loss.py
new file mode 100755
index 0000000..e049fe0
--- /dev/null
+++ b/AdelaiDet/adet/modeling/solov2/loss.py
@@ -0,0 +1,127 @@
+import torch
+from torch import nn
+import torch.nn.functional as F
+from fvcore.nn import sigmoid_focal_loss_jit
+
+
+def dice_loss(input, target):
+ input = input.contiguous().view(input.size()[0], -1)
+ target = target.contiguous().view(target.size()[0], -1).float()
+
+ a = torch.sum(input * target, 1)
+ b = torch.sum(input * input, 1) + 0.001
+ c = torch.sum(target * target, 1) + 0.001
+ d = (2 * a) / (b + c)
+ return 1 - d
+
+
+def reduce_loss(loss, reduction):
+ """Reduce loss as specified.
+ Args:
+ loss (Tensor): Elementwise loss tensor.
+ reduction (str): Options are "none", "mean" and "sum".
+ Return:
+ Tensor: Reduced loss tensor.
+ """
+ reduction_enum = F._Reduction.get_enum(reduction)
+ # none: 0, elementwise_mean:1, sum: 2
+ if reduction_enum == 0:
+ return loss
+ elif reduction_enum == 1:
+ return loss.mean()
+ elif reduction_enum == 2:
+ return loss.sum()
+
+
+def weight_reduce_loss(loss, weight=None, reduction='mean', avg_factor=None):
+ """Apply element-wise weight and reduce loss.
+ Args:
+ loss (Tensor): Element-wise loss.
+ weight (Tensor): Element-wise weights.
+ reduction (str): Same as built-in losses of PyTorch.
+ avg_factor (float): Avarage factor when computing the mean of losses.
+ Returns:
+ Tensor: Processed loss values.
+ """
+ # if weight is specified, apply element-wise weight
+ if weight is not None:
+ loss = loss * weight
+
+ # if avg_factor is not specified, just reduce the loss
+ if avg_factor is None:
+ loss = reduce_loss(loss, reduction)
+ else:
+ # if reduction is mean, then average the loss by avg_factor
+ if reduction == 'mean':
+ loss = loss.sum() / avg_factor
+ # if reduction is 'none', then do nothing, otherwise raise an error
+ elif reduction != 'none':
+ raise ValueError('avg_factor can not be used with reduction="sum"')
+ return loss
+
+
+def sigmoid_focal_loss(pred,
+ target,
+ weight=None,
+ gamma=2.0,
+ alpha=0.25,
+ reduction='mean',
+ avg_factor=None):
+ # Function.apply does not accept keyword arguments, so the decorator
+ # "weighted_loss" is not applicable
+ loss = sigmoid_focal_loss_jit(pred, target, gamma=gamma, alpha=alpha)
+ if weight is not None:
+ if weight.shape != loss.shape:
+ if weight.size(0) == loss.size(0):
+ # For most cases, weight is of shape (num_priors, ),
+ # which means it does not have the second axis num_class
+ weight = weight.view(-1, 1)
+ else:
+ # Sometimes, weight per anchor per class is also needed. e.g.
+ # in FSAF. But it may be flattened of shape
+ # (num_priors x num_class, ), while loss is still of shape
+ # (num_priors, num_class).
+ assert weight.numel() == loss.numel()
+ weight = weight.view(loss.size(0), -1)
+ assert weight.ndim == loss.ndim
+ loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
+ return loss
+
+
+class FocalLoss(nn.Module):
+
+ def __init__(self,
+ use_sigmoid=True,
+ gamma=2.0,
+ alpha=0.25,
+ reduction='mean',
+ loss_weight=1.0):
+ super(FocalLoss, self).__init__()
+ assert use_sigmoid is True, 'Only sigmoid focal loss supported now.'
+ self.use_sigmoid = use_sigmoid
+ self.gamma = gamma
+ self.alpha = alpha
+ self.reduction = reduction
+ self.loss_weight = loss_weight
+
+ def forward(self,
+ pred,
+ target,
+ weight=None,
+ avg_factor=None,
+ reduction_override=None):
+ assert reduction_override in (None, 'none', 'mean', 'sum')
+ reduction = (
+ reduction_override if reduction_override else self.reduction)
+ if self.use_sigmoid:
+ loss_cls = self.loss_weight * sigmoid_focal_loss(
+ pred,
+ target,
+ weight,
+ gamma=self.gamma,
+ alpha=self.alpha,
+ reduction=reduction,
+ avg_factor=avg_factor)
+ else:
+ raise NotImplementedError
+ return loss_cls
diff --git a/AdelaiDet/adet/modeling/solov2/solov2.py b/AdelaiDet/adet/modeling/solov2/solov2.py
new file mode 100755
index 0000000..dea9c62
--- /dev/null
+++ b/AdelaiDet/adet/modeling/solov2/solov2.py
@@ -0,0 +1,949 @@
+# -*- coding: utf-8 -*-
+import logging
+import math
+from typing import List
+
+import torch
+import torch.nn.functional as F
+from torch import nn
+
+from detectron2.layers import ShapeSpec, batched_nms, cat, paste_masks_in_image
+from detectron2.modeling.anchor_generator import DefaultAnchorGenerator
+from detectron2.modeling.backbone import build_backbone
+from detectron2.modeling.box_regression import Box2BoxTransform
+from detectron2.modeling.meta_arch.build import META_ARCH_REGISTRY
+from detectron2.structures import Boxes, ImageList, Instances
+from detectron2.utils.logger import log_first_n
+from fvcore.nn import sigmoid_focal_loss_jit
+
+from .utils import imrescale, center_of_mass, point_nms, mask_nms, matrix_nms
+from .loss import dice_loss, FocalLoss
+
+__all__ = ["SOLOv2"]
+
+# projection 2D point to 1D value
+PROJECTION=10000
+
+@META_ARCH_REGISTRY.register()
+class SOLOv2(nn.Module):
+ """
+ SOLOv2 model. Creates FPN backbone, instance branch for kernels and categories prediction,
+ mask branch for unified mask features.
+ Calculates and applies proper losses to class and masks.
+ """
+
+ def __init__(self, cfg):
+ super().__init__()
+
+ # get the device of the model
+ self.device = torch.device(cfg.MODEL.DEVICE)
+
+ self.scale_ranges = cfg.MODEL.SOLOV2.FPN_SCALE_RANGES
+ self.strides = cfg.MODEL.SOLOV2.FPN_INSTANCE_STRIDES
+ self.sigma = cfg.MODEL.SOLOV2.SIGMA
+ # Instance parameters.
+ self.num_classes = cfg.MODEL.SOLOV2.NUM_CLASSES
+ self.num_kernels = cfg.MODEL.SOLOV2.NUM_KERNELS
+ self.num_grids = cfg.MODEL.SOLOV2.NUM_GRIDS
+
+ self.instance_in_features = cfg.MODEL.SOLOV2.INSTANCE_IN_FEATURES
+ self.instance_strides = cfg.MODEL.SOLOV2.FPN_INSTANCE_STRIDES
+ self.instance_in_channels = cfg.MODEL.SOLOV2.INSTANCE_IN_CHANNELS # = fpn.
+ self.instance_channels = cfg.MODEL.SOLOV2.INSTANCE_CHANNELS
+
+ # Mask parameters.
+ self.mask_on = cfg.MODEL.MASK_ON
+ self.mask_in_features = cfg.MODEL.SOLOV2.MASK_IN_FEATURES
+ self.mask_in_channels = cfg.MODEL.SOLOV2.MASK_IN_CHANNELS
+ self.mask_channels = cfg.MODEL.SOLOV2.MASK_CHANNELS
+ self.num_masks = cfg.MODEL.SOLOV2.NUM_MASKS
+
+ # Inference parameters.
+ self.max_before_nms = cfg.MODEL.SOLOV2.NMS_PRE
+ self.score_threshold = cfg.MODEL.SOLOV2.SCORE_THR
+ self.update_threshold = cfg.MODEL.SOLOV2.UPDATE_THR
+ self.mask_threshold = cfg.MODEL.SOLOV2.MASK_THR
+ self.max_per_img = cfg.MODEL.SOLOV2.MAX_PER_IMG
+ self.nms_kernel = cfg.MODEL.SOLOV2.NMS_KERNEL
+ self.nms_sigma = cfg.MODEL.SOLOV2.NMS_SIGMA
+ self.nms_type = cfg.MODEL.SOLOV2.NMS_TYPE
+ self.prompt = cfg.MODEL.SOLOV2.PROMPT
+ self.eval_pseudo_label = cfg.MODEL.SOLOV2.EVAL_PSEUDO_LABEL
+
+ # build the backbone.
+ self.backbone = build_backbone(cfg)
+ backbone_shape = self.backbone.output_shape()
+
+ # build the ins head.
+ instance_shapes = [backbone_shape[f] for f in self.instance_in_features]
+ self.ins_head = SOLOv2InsHead(cfg, instance_shapes)
+
+ # build the mask head.
+ mask_shapes = [backbone_shape[f] for f in self.mask_in_features]
+ self.mask_head = SOLOv2MaskHead(cfg, mask_shapes)
+
+ # loss
+ self.ins_loss_weight = cfg.MODEL.SOLOV2.LOSS.DICE_WEIGHT
+ self.focal_loss_alpha = cfg.MODEL.SOLOV2.LOSS.FOCAL_ALPHA
+ self.focal_loss_gamma = cfg.MODEL.SOLOV2.LOSS.FOCAL_GAMMA
+ self.focal_loss_weight = cfg.MODEL.SOLOV2.LOSS.FOCAL_WEIGHT
+
+ # image transform
+ pixel_mean = torch.Tensor(cfg.MODEL.PIXEL_MEAN).to(self.device).view(3, 1, 1)
+ pixel_std = torch.Tensor(cfg.MODEL.PIXEL_STD).to(self.device).view(3, 1, 1)
+ self.normalizer = lambda x: (x - pixel_mean) / pixel_std
+ self.to(self.device)
+
+ def forward(self, batched_inputs):
+ """
+ Args:
+ batched_inputs: a list, batched outputs of :class:`DetectionTransform` .
+ Each item in the list contains the inputs for one image.
+ For now, each item in the list is a dict that contains:
+ image: Tensor, image in (C, H, W) format.
+ instances: Instances
+ Other information that's included in the original dicts, such as:
+ "height", "width" (int): the output resolution of the model, used in inference.
+ See :meth:`postprocess` for details.
+ Returns:
+ losses (dict[str: Tensor]): mapping from a named loss to a tensor
+ storing the loss. Used during training only.
+ """
+ images = self.preprocess_image(batched_inputs)
+
+ if "instances" in batched_inputs[0]:
+ gt_instances = [x["instances"].to(self.device) for x in batched_inputs]
+ elif "targets" in batched_inputs[0]:
+ log_first_n(
+ logging.WARN, "'targets' in the model inputs is now renamed to 'instances'!", n=10
+ )
+ gt_instances = [x["targets"].to(self.device) for x in batched_inputs]
+ else:
+ gt_instances = None
+
+ features = self.backbone(images.tensor)
+
+ # ins branch
+ ins_features = [features[f] for f in self.instance_in_features]
+ ins_features = self.split_feats(ins_features)
+ cate_pred, kernel_pred = self.ins_head(ins_features)
+
+ # mask branch
+ mask_features = [features[f] for f in self.mask_in_features]
+ mask_pred = self.mask_head(mask_features)
+
+ if self.training:
+ """
+ get_ground_truth.
+ return loss and so on.
+ """
+ mask_feat_size = mask_pred.size()[-2:]
+ targets = self.get_ground_truth(gt_instances, mask_feat_size)
+ losses = self.loss(cate_pred, kernel_pred, mask_pred, targets)
+ return losses
+
+ else:
+ if self.prompt == "none":
+ # point nms.
+ cate_pred = [point_nms(cate_p.sigmoid(), kernel=2).permute(0, 2, 3, 1) for cate_p in cate_pred]
+
+ # do inference for results.
+ results = self.inference(cate_pred, kernel_pred, mask_pred, images.image_sizes, batched_inputs, cate_pred)
+
+ else:
+ """
+ Implementation for PointWSSIS (https://arxiv.org/abs/2303.15062),
+ Weakly Semi-Supervised Instance Segmentation with Point Labels.
+ """
+ # do inference with prompt.
+ targets = self.get_ground_truth(gt_instances, mask_pred.size()[-2:])
+ points = [t.unsqueeze(0) for target in targets[5] for t in target]
+
+ # prompt -> image-level labels : removing misclassified proposals
+ if self.prompt == "cls":
+ # point nms.
+ cate_pred = [point_nms(cate_p.sigmoid(), kernel=2).permute(0, 2, 3, 1)
+ for cate_p in cate_pred]
+
+ cate_gt = []
+ for cat in targets[1]:
+ for c in cat:
+ c_onehot = torch.nn.functional.one_hot(c, num_classes=self.num_classes+1)
+ c_onehot = c_onehot[:, :, :self.num_classes].unsqueeze(0).float()
+ cate_gt.append(c_onehot)
+
+ cate_pred = [cat_pred * cat_gt for cat_pred, cat_gt in zip(cate_pred, cate_gt)] # score filtering with gt cls labels
+
+ results = self.inference(cate_pred, kernel_pred, mask_pred, images.image_sizes, batched_inputs, points)
+
+ # prompt -> point labels : removing false-positive and false-negative proposals
+ elif self.prompt == "point":
+ point_encodings_flatten = [t.unsqueeze(0) for target in targets[4] for t in target ]
+
+ cate_gt = []
+ for cat in targets[1]:
+ for c in cat:
+ c_onehot = torch.nn.functional.one_hot(c, num_classes=self.num_classes+1)
+ c_onehot = c_onehot[:, :, :self.num_classes].unsqueeze(0).float()
+ cate_gt.append(c_onehot)
+
+ point_encodings = []
+ for cate_p, cate_g, point in zip(cate_pred, cate_gt, point_encodings_flatten):
+ # cate_p : [1, C, S, S]
+ # cate_g : [1, S, S, C]
+ # point : [1, S, S, C]
+ for p, c_nonzero in zip(point[cate_g.nonzero(as_tuple=True)], cate_g.nonzero()):
+ cy = float(p // PROJECTION) / float(PROJECTION)
+ cx = float(p % PROJECTION) / float(PROJECTION)
+ point_encodings.append((c_nonzero[0].item(), cy, cx, c_nonzero[3].item()))
+
+ point_encodings = list(set(point_encodings))
+
+ new_cate_gt = [torch.zeros_like(gt) for gt in cate_gt]
+ for p_encoding in point_encodings:
+
+ max_score, max_fpn_level, max_idx = -1, 0, (0,0,0,0)
+ for fpn_level, (cate_p, cate_g, kernel_p) in enumerate(zip(cate_pred, cate_gt, kernel_pred)):
+ cate_p = cate_p.sigmoid().permute(0, 2, 3, 1)
+ s = cate_p.size(2) # grid size
+
+ idx = tuple(map(int, [
+ p_encoding[0],
+ max(0, min(s-1, int( p_encoding[1] // (1. / s) ))),
+ max(0, min(s-1, int( p_encoding[2] // (1. / s) ))),
+ p_encoding[3],
+ ]))
+
+ # obtaining kernels decoded from the point
+ kernel = kernel_p.permute(0,2,3,1)[idx[:-1]]
+ kernel = kernel[None, :, None, None]
+
+ # from point to mask
+ seg_pred = F.conv2d(mask_pred, kernel, stride=1).squeeze(0).sigmoid()
+ seg_mask = seg_pred > self.mask_threshold
+ sum_mask = seg_mask.sum((1, 2)).float()
+ seg_score = (seg_pred * seg_mask.float()).sum((1, 2)) / (sum_mask+1e-8)
+
+ score = seg_score * cate_p[idx]
+
+ # adaptive pyramid selection: get max-scoring fpn-level
+ if score > max_score:
+ max_score = score
+ max_fpn_level = fpn_level
+ max_idx = idx
+
+ if max_score > 0:
+ new_cate_gt[max_fpn_level][max_idx] = 1.
+
+ results = self.inference(new_cate_gt, kernel_pred, mask_pred, images.image_sizes, batched_inputs, points)
+
+ # prompt -> point labels including instance size information
+ elif self.prompt == "point_with_size":
+ cate_gt = []
+ for cat in targets[1]:
+ for c in cat:
+ c_onehot = torch.nn.functional.one_hot(c, num_classes=self.num_classes+1)
+ c_onehot = c_onehot[:, :, :self.num_classes].unsqueeze(0).float()
+ cate_gt.append(c_onehot)
+
+ results = self.inference(cate_gt, kernel_pred, mask_pred, images.image_sizes, batched_inputs, points)
+ else:
+ raise "[ASSERT] MODEL.SOLOV2.PROMPT can be none, cls, point, point_with_size"
+
+ return results
+
+ def preprocess_image(self, batched_inputs):
+ """
+ Normalize, pad and batch the input images.
+ """
+ images = [x["image"].to(self.device) for x in batched_inputs]
+ images = [self.normalizer(x) for x in images]
+ images = ImageList.from_tensors(images, self.backbone.size_divisibility)
+ return images
+
+ @torch.no_grad()
+ def get_ground_truth(self, gt_instances, mask_feat_size=None):
+ ins_label_list, cate_label_list, ins_ind_label_list, grid_order_list, cate_point_encoding_list, cate_point_original_list = [], [], [], [], [], []
+ for img_idx in range(len(gt_instances)):
+ cur_ins_label_list, cur_cate_label_list, \
+ cur_ins_ind_label_list, cur_grid_order_list, cur_cate_point_encoding_list, cur_cate_point_original_list = \
+ self.get_ground_truth_single(img_idx, gt_instances,
+ mask_feat_size=mask_feat_size)
+ ins_label_list.append(cur_ins_label_list)
+ cate_label_list.append(cur_cate_label_list)
+ ins_ind_label_list.append(cur_ins_ind_label_list)
+ grid_order_list.append(cur_grid_order_list)
+ cate_point_encoding_list.append(cur_cate_point_encoding_list)
+ cate_point_original_list.append(cur_cate_point_original_list)
+
+ return ins_label_list, cate_label_list, ins_ind_label_list, grid_order_list, cate_point_encoding_list, cate_point_original_list
+
+ def get_ground_truth_single(self, img_idx, gt_instances, mask_feat_size):
+ gt_bboxes_raw = gt_instances[img_idx].gt_boxes.tensor
+ gt_labels_raw = gt_instances[img_idx].gt_classes
+ gt_masks_raw = gt_instances[img_idx].gt_masks.tensor
+
+ ins_label_list = []
+ cate_label_list = []
+ ins_ind_label_list = []
+ grid_order_list = []
+ cate_point_encoding_list = []
+ cate_point_original_list = []
+
+ # handling unlabeled data
+ if gt_labels_raw.shape[0] == 0:
+ for num_grid in self.num_grids:
+ ins_label = torch.zeros([0, mask_feat_size[0], mask_feat_size[1]], dtype=torch.uint8, device=self.device)
+ cate_label = torch.zeros([num_grid, num_grid], dtype=torch.int64, device=self.device)
+ cate_label = torch.fill_(cate_label, self.num_classes)
+ ins_ind_label = torch.zeros([num_grid ** 2], dtype=torch.bool, device=self.device)
+ cate_point_encoding = torch.zeros([num_grid, num_grid, self.num_classes], dtype=torch.int64, device=self.device)
+ cate_point_original = torch.zeros([num_grid, num_grid, self.num_classes], dtype=torch.int64, device=self.device)
+
+ ins_label_list.append(ins_label)
+ cate_label_list.append(cate_label)
+ ins_ind_label_list.append(ins_ind_label)
+ grid_order_list.append([])
+ cate_point_encoding_list.append(cate_point_encoding)
+ cate_point_original_list.append(cate_point_original)
+
+ return ins_label_list, cate_label_list, ins_ind_label_list, grid_order_list, cate_point_encoding_list, cate_point_original_list
+
+ device = gt_labels_raw[0].device
+
+ # ins
+ gt_areas = torch.sqrt((gt_bboxes_raw[:, 2] - gt_bboxes_raw[:, 0]) * (
+ gt_bboxes_raw[:, 3] - gt_bboxes_raw[:, 1]))
+
+ for (lower_bound, upper_bound), stride, num_grid \
+ in zip(self.scale_ranges, self.strides, self.num_grids):
+
+ hit_indices = ((gt_areas >= lower_bound) & (gt_areas < upper_bound)).nonzero().flatten()
+ num_ins = len(hit_indices)
+
+ ins_label = []
+ grid_order = []
+ cate_label = torch.zeros([num_grid, num_grid], dtype=torch.int64, device=device)
+ cate_label = torch.fill_(cate_label, self.num_classes)
+ ins_ind_label = torch.zeros([num_grid ** 2], dtype=torch.bool, device=device)
+ cate_point_encoding = torch.zeros([num_grid, num_grid, self.num_classes], dtype=torch.int64, device=device)
+ cate_point_original = torch.zeros([num_grid, num_grid, self.num_classes], dtype=torch.int64, device=device)
+
+ if num_ins == 0:
+ ins_label = torch.zeros([0, mask_feat_size[0], mask_feat_size[1]], dtype=torch.uint8, device=device)
+ ins_label_list.append(ins_label)
+ cate_label_list.append(cate_label)
+ ins_ind_label_list.append(ins_ind_label)
+ grid_order_list.append([])
+ cate_point_encoding_list.append(cate_point_encoding)
+ cate_point_original_list.append(cate_point_original)
+ continue
+
+ gt_bboxes = gt_bboxes_raw[hit_indices]
+ gt_labels = gt_labels_raw[hit_indices]
+ gt_masks = gt_masks_raw[hit_indices, ...]
+
+ half_ws = 0.5 * (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * self.sigma
+ half_hs = 0.5 * (gt_bboxes[:, 3] - gt_bboxes[:, 1]) * self.sigma
+
+ # mass center
+ mh, mw = gt_masks.shape[1:]
+ center_ws, center_hs = center_of_mass(gt_masks)
+ valid_mask_flags = gt_masks.sum(dim=-1).sum(dim=-1) > 0
+
+ output_stride = 4
+ gt_masks = gt_masks.permute(1, 2, 0).to(dtype=torch.uint8).cpu().numpy()
+ gt_masks = imrescale(gt_masks, scale=1./output_stride)
+ if len(gt_masks.shape) == 2:
+ gt_masks = gt_masks[..., None]
+ gt_masks = torch.from_numpy(gt_masks).to(dtype=torch.uint8, device=device).permute(2, 0, 1)
+
+ for seg_mask, gt_label, half_h, half_w, center_h, center_w, valid_mask_flag in zip(gt_masks, gt_labels, half_hs, half_ws, center_hs, center_ws, valid_mask_flags):
+ if not valid_mask_flag:
+ continue
+ upsampled_size = (mask_feat_size[0] * 4, mask_feat_size[1] * 4)
+ coord_w = int((center_w / upsampled_size[1]) // (1. / num_grid))
+ coord_h = int((center_h / upsampled_size[0]) // (1. / num_grid))
+
+ # left, top, right, down -> box for positive region
+ if not self.training and self.prompt == 'point':
+ # point positive region (one-pixel positive sample assignment)
+ top_box = max(0, int(((center_h) / upsampled_size[0]) // (1. / num_grid)))
+ down_box = min(num_grid - 1, int(((center_h) / upsampled_size[0]) // (1. / num_grid)))
+ left_box = max(0, int(((center_w) / upsampled_size[1]) // (1. / num_grid)))
+ right_box = min(num_grid - 1, int(((center_w) / upsampled_size[1]) // (1. / num_grid)))
+
+ else: # box positive region (widen positive sample assignment)
+ top_box = max(0, int(((center_h - half_h) / upsampled_size[0]) // (1. / num_grid)))
+ down_box = min(num_grid - 1, int(((center_h + half_h) / upsampled_size[0]) // (1. / num_grid)))
+ left_box = max(0, int(((center_w - half_w) / upsampled_size[1]) // (1. / num_grid)))
+ right_box = min(num_grid - 1, int(((center_w + half_w) / upsampled_size[1]) // (1. / num_grid)))
+
+ top = max(top_box, coord_h-1)
+ down = min(down_box, coord_h+1)
+ left = max(coord_w-1, left_box)
+ right = min(right_box, coord_w+1)
+
+ cate_label[top:(down+1), left:(right+1)] = gt_label
+
+ enc_ch = center_h / upsampled_size[0] * PROJECTION
+ enc_cw = center_w / upsampled_size[1] * PROJECTION
+ cate_point_encoding[top:(down+1), left:(right+1)] = int(enc_ch) * PROJECTION + int(enc_cw)
+
+ ori_ch = center_h / mh * PROJECTION
+ ori_cw = center_w / mw * PROJECTION
+ cate_point_original[top:(down+1), left:(right+1)] = int(ori_ch) * PROJECTION + int(ori_cw)
+
+ for i in range(top, down+1):
+ for j in range(left, right+1):
+ label = int(i * num_grid + j)
+
+ cur_ins_label = torch.zeros([mask_feat_size[0], mask_feat_size[1]], dtype=torch.uint8,
+ device=device)
+ cur_ins_label[:seg_mask.shape[0], :seg_mask.shape[1]] = seg_mask
+ ins_label.append(cur_ins_label)
+ ins_ind_label[label] = True
+ grid_order.append(label)
+ if len(ins_label) == 0:
+ ins_label = torch.zeros([0, mask_feat_size[0], mask_feat_size[1]], dtype=torch.uint8, device=device)
+ else:
+ ins_label = torch.stack(ins_label, 0)
+ ins_label_list.append(ins_label)
+ cate_label_list.append(cate_label)
+ ins_ind_label_list.append(ins_ind_label)
+ grid_order_list.append(grid_order)
+ cate_point_encoding_list.append(cate_point_encoding)
+ cate_point_original_list.append(cate_point_original)
+
+ return ins_label_list, cate_label_list, ins_ind_label_list, grid_order_list, cate_point_encoding_list, cate_point_original_list
+
+
+ def loss(self, cate_preds, kernel_preds, ins_pred, targets):
+ pass
+ ins_label_list, cate_label_list, ins_ind_label_list, grid_order_list = targets[:4]
+ # ins
+ ins_labels = [torch.cat([ins_labels_level_img
+ for ins_labels_level_img in ins_labels_level], 0)
+ for ins_labels_level in zip(*ins_label_list)]
+
+ kernel_preds = [[kernel_preds_level_img.view(kernel_preds_level_img.shape[0], -1)[:, grid_orders_level_img]
+ for kernel_preds_level_img, grid_orders_level_img in
+ zip(kernel_preds_level, grid_orders_level)]
+ for kernel_preds_level, grid_orders_level in zip(kernel_preds, zip(*grid_order_list))]
+ # generate masks
+ ins_pred_list = []
+ for b_kernel_pred in kernel_preds:
+ b_mask_pred = []
+ for idx, kernel_pred in enumerate(b_kernel_pred):
+
+ if kernel_pred.size()[-1] == 0:
+ continue
+ cur_ins_pred = ins_pred[idx, ...]
+ H, W = cur_ins_pred.shape[-2:]
+ N, I = kernel_pred.shape
+ cur_ins_pred = cur_ins_pred.unsqueeze(0)
+ kernel_pred = kernel_pred.permute(1, 0).view(I, -1, 1, 1)
+ cur_ins_pred = F.conv2d(cur_ins_pred, kernel_pred, stride=1).view(-1, H, W)
+ b_mask_pred.append(cur_ins_pred)
+ if len(b_mask_pred) == 0:
+ b_mask_pred = None
+ else:
+ b_mask_pred = torch.cat(b_mask_pred, 0)
+ ins_pred_list.append(b_mask_pred)
+
+ ins_ind_labels = [
+ torch.cat([ins_ind_labels_level_img.flatten()
+ for ins_ind_labels_level_img in ins_ind_labels_level])
+ for ins_ind_labels_level in zip(*ins_ind_label_list)
+ ]
+ flatten_ins_ind_labels = torch.cat(ins_ind_labels)
+
+ num_ins = flatten_ins_ind_labels.sum()
+
+ # dice loss
+ loss_ins = []
+ for input, target in zip(ins_pred_list, ins_labels):
+ if input is None:
+ continue
+ input = torch.sigmoid(input)
+ loss_ins.append(dice_loss(input, target))
+
+ loss_ins_mean = torch.cat(loss_ins).mean()
+ loss_ins = loss_ins_mean * self.ins_loss_weight
+
+ # cate
+ cate_labels = [
+ torch.cat([cate_labels_level_img.flatten()
+ for cate_labels_level_img in cate_labels_level])
+ for cate_labels_level in zip(*cate_label_list)
+ ]
+ flatten_cate_labels = torch.cat(cate_labels)
+
+ cate_preds = [
+ cate_pred.permute(0, 2, 3, 1).reshape(-1, self.num_classes)
+ for cate_pred in cate_preds
+ ]
+ flatten_cate_preds = torch.cat(cate_preds)
+
+ # prepare one_hot
+ pos_inds = torch.nonzero(flatten_cate_labels != self.num_classes).squeeze(1)
+
+ flatten_cate_labels_oh = torch.zeros_like(flatten_cate_preds)
+ flatten_cate_labels_oh[pos_inds, flatten_cate_labels[pos_inds]] = 1
+
+ loss_cate = self.focal_loss_weight * sigmoid_focal_loss_jit(flatten_cate_preds, flatten_cate_labels_oh,
+ gamma=self.focal_loss_gamma,
+ alpha=self.focal_loss_alpha,
+ reduction="sum") / (num_ins + 1)
+ return {'loss_ins': loss_ins,
+ 'loss_cate': loss_cate}
+
+ @staticmethod
+ def split_feats(feats):
+ if len(feats) == 5:
+ return (F.interpolate(feats[0], scale_factor=0.5, mode='bilinear'),
+ feats[1],
+ feats[2],
+ feats[3],
+ F.interpolate(feats[4], size=feats[3].shape[-2:], mode='bilinear'))
+
+ elif len(feats) == 4:
+ return (F.interpolate(feats[0], scale_factor=0.5, mode='bilinear'),
+ feats[1],
+ feats[2],
+ feats[3])
+
+ elif len(feats) == 3:
+ return (F.interpolate(feats[0], scale_factor=0.5, mode='bilinear'),
+ feats[1],
+ feats[2])
+
+ elif len(feats) == 2:
+ return (F.interpolate(feats[0], scale_factor=0.5, mode='bilinear'),
+ feats[1])
+
+ elif len(feats) == 1:
+ return (F.interpolate(feats[0], scale_factor=0.5, mode='bilinear'), )
+
+
+ def inference(self, pred_cates, pred_kernels, pred_masks, cur_sizes, images, points):
+ assert len(pred_cates) == len(pred_kernels)
+
+ results = []
+ num_ins_levels = len(pred_cates)
+ for img_idx in range(len(images)):
+ # image size.
+ ori_img = images[img_idx]
+ height, width = ori_img["height"], ori_img["width"]
+ ori_size = (height, width)
+
+ # prediction.
+ pred_cate = [pred_cates[i][img_idx].view(-1, self.num_classes).detach()
+ for i in range(num_ins_levels)]
+ pred_kernel = [pred_kernels[i][img_idx].permute(1, 2, 0).view(-1, self.num_kernels).detach()
+ for i in range(num_ins_levels)]
+ pred_mask = pred_masks[img_idx, ...].unsqueeze(0)
+ point = [points[i][img_idx].view(-1, self.num_classes).detach()
+ for i in range(num_ins_levels)] # beom
+
+ pred_cate = torch.cat(pred_cate, dim=0)
+ pred_kernel = torch.cat(pred_kernel, dim=0)
+ point = torch.cat(point, dim=0) # beom
+
+ # inference for single image.
+ result = self.inference_single_image(pred_cate, pred_kernel, pred_mask,
+ cur_sizes[img_idx], ori_size, point)
+ results.append({"instances": result})
+ return results
+
+ def inference_single_image(
+ self, cate_preds, kernel_preds, seg_preds, cur_size, ori_size, points
+ ):
+ # overall info.
+ h, w = cur_size
+ f_h, f_w = seg_preds.size()[-2:]
+ ratio = math.ceil(h/f_h)
+ upsampled_size_out = (int(f_h*ratio), int(f_w*ratio))
+
+ # process.
+ inds = (cate_preds > self.score_threshold)
+ cate_scores = cate_preds[inds]
+ points = points[inds]
+
+ if len(cate_scores) == 0:
+ results = Instances(ori_size)
+ results.scores = torch.tensor([])
+ results.pred_classes = torch.tensor([])
+ results.pred_masks = torch.tensor([])
+ results.pred_boxes = Boxes(torch.tensor([]))
+ return results
+
+ # cate_labels & kernel_preds
+ inds = inds.nonzero()
+ cate_labels = inds[:, 1]
+ kernel_preds = kernel_preds[inds[:, 0]]
+
+ # trans vector.
+ size_trans = cate_labels.new_tensor(self.num_grids).pow(2).cumsum(0)
+ strides = kernel_preds.new_ones(size_trans[-1])
+
+ n_stage = len(self.num_grids)
+ strides[:size_trans[0]] *= self.instance_strides[0]
+ for ind_ in range(1, n_stage):
+ strides[size_trans[ind_ - 1]:size_trans[ind_]] *= self.instance_strides[ind_]
+ strides = strides[inds[:, 0]]
+
+ # mask encoding.
+ N, I = kernel_preds.shape
+ kernel_preds = kernel_preds.view(N, I, 1, 1)
+ seg_preds = F.conv2d(seg_preds, kernel_preds, stride=1).squeeze(0).sigmoid()
+
+ # mask.
+ seg_masks = seg_preds > self.mask_threshold
+ sum_masks = seg_masks.sum((1, 2)).float()
+
+ # filter.
+ keep = sum_masks > strides
+ if keep.sum() == 0:
+ results = Instances(ori_size)
+ results.scores = torch.tensor([])
+ results.pred_classes = torch.tensor([])
+ results.pred_masks = torch.tensor([])
+ results.pred_boxes = Boxes(torch.tensor([]))
+ return results
+
+ seg_masks = seg_masks[keep, ...]
+ seg_preds = seg_preds[keep, ...]
+ sum_masks = sum_masks[keep]
+ cate_scores = cate_scores[keep]
+ cate_labels = cate_labels[keep]
+ points = points[keep]
+
+ # maskness.
+ seg_scores = (seg_preds * seg_masks.float()).sum((1, 2)) / sum_masks
+ cate_scores *= seg_scores
+
+ # sort and keep top nms_pre
+ sort_inds = torch.argsort(cate_scores, descending=True)
+ if len(sort_inds) > self.max_before_nms:
+ sort_inds = sort_inds[:self.max_before_nms]
+ seg_masks = seg_masks[sort_inds, :, :]
+ seg_preds = seg_preds[sort_inds, :, :]
+ sum_masks = sum_masks[sort_inds]
+ cate_scores = cate_scores[sort_inds]
+ cate_labels = cate_labels[sort_inds]
+ points = points[sort_inds]
+
+ if self.nms_type == "matrix":
+ # matrix nms & filter.
+ cate_scores = matrix_nms(cate_labels, seg_masks, sum_masks, cate_scores,
+ sigma=self.nms_sigma, kernel=self.nms_kernel)
+ keep = cate_scores >= self.update_threshold
+ elif self.nms_type == "mask":
+ # original mask nms.
+ keep = mask_nms(cate_labels, seg_masks, sum_masks, cate_scores,
+ nms_thr=self.mask_threshold)
+ else:
+ raise NotImplementedError
+
+ if keep.sum() == 0:
+ results = Instances(ori_size)
+ results.scores = torch.tensor([])
+ results.pred_classes = torch.tensor([])
+ results.pred_masks = torch.tensor([])
+ results.pred_boxes = Boxes(torch.tensor([]))
+ return results
+
+ seg_preds = seg_preds[keep, :, :]
+ cate_scores = cate_scores[keep]
+ cate_labels = cate_labels[keep]
+ points = points[keep]
+
+ # sort and keep top_k
+ sort_inds = torch.argsort(cate_scores, descending=True)
+ if len(sort_inds) > self.max_per_img:
+ sort_inds = sort_inds[:self.max_per_img]
+ seg_preds = seg_preds[sort_inds, :, :]
+ cate_scores = cate_scores[sort_inds]
+ cate_labels = cate_labels[sort_inds]
+ points = points[sort_inds]
+
+ # reshape to original size.
+ seg_preds = F.interpolate(seg_preds.unsqueeze(0),
+ size=upsampled_size_out,
+ mode='bilinear')[:, :, :h, :w]
+ seg_masks = F.interpolate(seg_preds,
+ size=ori_size,
+ mode='bilinear').squeeze(0)
+ seg_masks = seg_masks > self.mask_threshold
+
+ if self.eval_pseudo_label:
+ # set all confidence scores to 1.0
+ cate_scores = torch.ones_like(cate_scores)
+
+ results = Instances(ori_size)
+ results.pred_classes = cate_labels
+ results.scores = cate_scores
+ results.pred_masks = seg_masks
+
+ # get bbox from mask
+ pred_boxes = torch.zeros(seg_masks.size(0), 4)
+ for i in range(seg_masks.size(0)):
+
+ if self.prompt == "none":
+ mask = seg_masks[i].squeeze()
+ ys, xs = torch.where(mask)
+ try:
+ pred_boxes[i] = torch.tensor([xs.min(), ys.min(), xs.max(), ys.max()]).float()
+ except:
+ pred_boxes[i] = torch.tensor([0, 0, 0, 0]).float()
+
+ else:
+ # saving point coordinate for each segment output
+ cy = points[i] // PROJECTION
+ cx = points[i] % PROJECTION
+ pred_boxes[i] = torch.tensor([cy, cx, 0, 0]).float()
+
+ results.pred_boxes = Boxes(pred_boxes)
+
+ return results
+
+
+class SOLOv2InsHead(nn.Module):
+ def __init__(self, cfg, input_shape: List[ShapeSpec]):
+ """
+ SOLOv2 Instance Head.
+ """
+ super().__init__()
+ # fmt: off
+ self.num_classes = cfg.MODEL.SOLOV2.NUM_CLASSES
+ self.num_kernels = cfg.MODEL.SOLOV2.NUM_KERNELS
+ self.num_grids = cfg.MODEL.SOLOV2.NUM_GRIDS
+ self.instance_in_features = cfg.MODEL.SOLOV2.INSTANCE_IN_FEATURES
+ self.instance_strides = cfg.MODEL.SOLOV2.FPN_INSTANCE_STRIDES
+ self.instance_in_channels = cfg.MODEL.SOLOV2.INSTANCE_IN_CHANNELS # = fpn.
+ self.instance_channels = cfg.MODEL.SOLOV2.INSTANCE_CHANNELS
+ # Convolutions to use in the towers
+ self.type_dcn = cfg.MODEL.SOLOV2.TYPE_DCN
+ self.num_levels = len(self.instance_in_features)
+ assert self.num_levels == len(self.instance_strides), \
+ print("Strides should match the features.")
+ # fmt: on
+
+ head_configs = {"cate": (cfg.MODEL.SOLOV2.NUM_INSTANCE_CONVS,
+ cfg.MODEL.SOLOV2.USE_DCN_IN_INSTANCE,
+ False),
+ "kernel": (cfg.MODEL.SOLOV2.NUM_INSTANCE_CONVS,
+ cfg.MODEL.SOLOV2.USE_DCN_IN_INSTANCE,
+ cfg.MODEL.SOLOV2.USE_COORD_CONV)
+ }
+
+ norm = None if cfg.MODEL.SOLOV2.NORM == "none" else cfg.MODEL.SOLOV2.NORM
+ in_channels = [s.channels for s in input_shape]
+ assert len(set(in_channels)) == 1, \
+ print("Each level must have the same channel!")
+ in_channels = in_channels[0]
+ assert in_channels == cfg.MODEL.SOLOV2.INSTANCE_IN_CHANNELS, \
+ print("In channels should equal to tower in channels!")
+
+ for head in head_configs:
+ tower = []
+ num_convs, use_deformable, use_coord = head_configs[head]
+ for i in range(num_convs):
+ conv_func = nn.Conv2d
+ if i == 0:
+ if use_coord:
+ chn = self.instance_in_channels + 2
+ else:
+ chn = self.instance_in_channels
+ else:
+ chn = self.instance_channels
+
+ tower.append(conv_func(
+ chn, self.instance_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=norm is None
+ ))
+ if norm == "GN":
+ tower.append(nn.GroupNorm(32, self.instance_channels))
+ tower.append(nn.ReLU(inplace=True))
+ self.add_module('{}_tower'.format(head),
+ nn.Sequential(*tower))
+
+ self.cate_pred = nn.Conv2d(
+ self.instance_channels, self.num_classes,
+ kernel_size=3, stride=1, padding=1
+ )
+ self.kernel_pred = nn.Conv2d(
+ self.instance_channels, self.num_kernels,
+ kernel_size=3, stride=1, padding=1
+ )
+
+ for modules in [
+ self.cate_tower, self.kernel_tower,
+ self.cate_pred, self.kernel_pred,
+ ]:
+ for l in modules.modules():
+ if isinstance(l, nn.Conv2d):
+ torch.nn.init.normal_(l.weight, std=0.01)
+ if l.bias is not None:
+ nn.init.constant_(l.bias, 0)
+
+ # initialize the bias for focal loss
+ prior_prob = cfg.MODEL.SOLOV2.PRIOR_PROB
+ bias_value = -math.log((1 - prior_prob) / prior_prob)
+ torch.nn.init.constant_(self.cate_pred.bias, bias_value)
+
+ def forward(self, features):
+ """
+ Arguments:
+ features (list[Tensor]): FPN feature map tensors in high to low resolution.
+ Each tensor in the list correspond to different feature levels.
+
+ Returns:
+ pass
+ """
+ cate_pred = []
+ kernel_pred = []
+
+ for idx, feature in enumerate(features):
+ ins_kernel_feat = feature
+ # concat coord
+ x_range = torch.linspace(-1, 1, ins_kernel_feat.shape[-1], device=ins_kernel_feat.device)
+ y_range = torch.linspace(-1, 1, ins_kernel_feat.shape[-2], device=ins_kernel_feat.device)
+ y, x = torch.meshgrid(y_range, x_range)
+ y = y.expand([ins_kernel_feat.shape[0], 1, -1, -1])
+ x = x.expand([ins_kernel_feat.shape[0], 1, -1, -1])
+ coord_feat = torch.cat([x, y], 1)
+ ins_kernel_feat = torch.cat([ins_kernel_feat, coord_feat], 1)
+
+ # individual feature.
+ kernel_feat = ins_kernel_feat
+ seg_num_grid = self.num_grids[idx]
+ kernel_feat = F.interpolate(kernel_feat, size=seg_num_grid, mode='bilinear')
+ cate_feat = kernel_feat[:, :-2, :, :]
+
+ # kernel
+ kernel_feat = self.kernel_tower(kernel_feat)
+ kernel_pred.append(self.kernel_pred(kernel_feat))
+
+ # cate
+ cate_feat = self.cate_tower(cate_feat)
+ cate_pred.append(self.cate_pred(cate_feat))
+ return cate_pred, kernel_pred
+
+
+class SOLOv2MaskHead(nn.Module):
+ def __init__(self, cfg, input_shape: List[ShapeSpec]):
+ """
+ SOLOv2 Mask Head.
+ """
+ super().__init__()
+ # fmt: off
+ self.mask_on = cfg.MODEL.MASK_ON
+ self.num_masks = cfg.MODEL.SOLOV2.NUM_MASKS
+ self.mask_in_features = cfg.MODEL.SOLOV2.MASK_IN_FEATURES
+ self.mask_in_channels = cfg.MODEL.SOLOV2.MASK_IN_CHANNELS
+ self.mask_channels = cfg.MODEL.SOLOV2.MASK_CHANNELS
+ self.num_levels = len(input_shape)
+ assert self.num_levels == len(self.mask_in_features), \
+ print("Input shape should match the features.")
+ # fmt: on
+ norm = None if cfg.MODEL.SOLOV2.NORM == "none" else cfg.MODEL.SOLOV2.NORM
+
+ self.convs_all_levels = nn.ModuleList()
+ for i in range(self.num_levels):
+ convs_per_level = nn.Sequential()
+ if i == 0:
+ conv_tower = list()
+ conv_tower.append(nn.Conv2d(
+ self.mask_in_channels, self.mask_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=norm is None
+ ))
+ if norm == "GN":
+ conv_tower.append(nn.GroupNorm(32, self.mask_channels))
+ conv_tower.append(nn.ReLU(inplace=False))
+ convs_per_level.add_module('conv' + str(i), nn.Sequential(*conv_tower))
+ self.convs_all_levels.append(convs_per_level)
+ continue
+
+ for j in range(i):
+ if j == 0:
+ chn = self.mask_in_channels + 2 if i == 3 else self.mask_in_channels
+ conv_tower = list()
+ conv_tower.append(nn.Conv2d(
+ chn, self.mask_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=norm is None
+ ))
+ if norm == "GN":
+ conv_tower.append(nn.GroupNorm(32, self.mask_channels))
+ conv_tower.append(nn.ReLU(inplace=False))
+ convs_per_level.add_module('conv' + str(j), nn.Sequential(*conv_tower))
+ upsample_tower = nn.Upsample(
+ scale_factor=2, mode='bilinear', align_corners=False)
+ convs_per_level.add_module(
+ 'upsample' + str(j), upsample_tower)
+ continue
+ conv_tower = list()
+ conv_tower.append(nn.Conv2d(
+ self.mask_channels, self.mask_channels,
+ kernel_size=3, stride=1,
+ padding=1, bias=norm is None
+ ))
+ if norm == "GN":
+ conv_tower.append(nn.GroupNorm(32, self.mask_channels))
+ conv_tower.append(nn.ReLU(inplace=False))
+ convs_per_level.add_module('conv' + str(j), nn.Sequential(*conv_tower))
+ upsample_tower = nn.Upsample(
+ scale_factor=2, mode='bilinear', align_corners=False)
+ convs_per_level.add_module('upsample' + str(j), upsample_tower)
+
+ self.convs_all_levels.append(convs_per_level)
+
+ self.conv_pred = nn.Sequential(
+ nn.Conv2d(
+ self.mask_channels, self.num_masks,
+ kernel_size=1, stride=1,
+ padding=0, bias=norm is None),
+ nn.GroupNorm(32, self.num_masks),
+ nn.ReLU(inplace=True)
+ )
+
+ for modules in [self.convs_all_levels, self.conv_pred]:
+ for l in modules.modules():
+ if isinstance(l, nn.Conv2d):
+ torch.nn.init.normal_(l.weight, std=0.01)
+ if l.bias is not None:
+ nn.init.constant_(l.bias, 0)
+
+ def forward(self, features):
+ """
+ Arguments:
+ features (list[Tensor]): FPN feature map tensors in high to low resolution.
+ Each tensor in the list correspond to different feature levels.
+
+ Returns:
+ pass
+ """
+ assert len(features) == self.num_levels, \
+ print("The number of input features should be equal to the supposed level.")
+
+ # bottom features first.
+ feature_add_all_level = self.convs_all_levels[0](features[0])
+ for i in range(1, self.num_levels):
+ mask_feat = features[i]
+ if i == 3: # add for coord.
+ x_range = torch.linspace(-1, 1, mask_feat.shape[-1], device=mask_feat.device)
+ y_range = torch.linspace(-1, 1, mask_feat.shape[-2], device=mask_feat.device)
+ y, x = torch.meshgrid(y_range, x_range)
+ y = y.expand([mask_feat.shape[0], 1, -1, -1])
+ x = x.expand([mask_feat.shape[0], 1, -1, -1])
+ coord_feat = torch.cat([x, y], 1)
+ mask_feat = torch.cat([mask_feat, coord_feat], 1)
+ # add for top features.
+ feature_add_all_level = feature_add_all_level + self.convs_all_levels[i](mask_feat)
+
+ mask_pred = self.conv_pred(feature_add_all_level)
+ return mask_pred
diff --git a/AdelaiDet/adet/modeling/solov2/utils.py b/AdelaiDet/adet/modeling/solov2/utils.py
new file mode 100755
index 0000000..a0a7f8b
--- /dev/null
+++ b/AdelaiDet/adet/modeling/solov2/utils.py
@@ -0,0 +1,212 @@
+import cv2
+import torch
+import torch.nn.functional as F
+
+
+def _scale_size(size, scale):
+ """Rescale a size by a ratio.
+ Args:
+ size (tuple[int]): (w, h).
+ scale (float): Scaling factor.
+ Returns:
+ tuple[int]: scaled size.
+ """
+ w, h = size
+ return int(w * float(scale) + 0.5), int(h * float(scale) + 0.5)
+
+
+interp_codes = {
+ 'nearest': cv2.INTER_NEAREST,
+ 'bilinear': cv2.INTER_LINEAR,
+ 'bicubic': cv2.INTER_CUBIC,
+ 'area': cv2.INTER_AREA,
+ 'lanczos': cv2.INTER_LANCZOS4
+}
+
+
+def imresize(img,
+ size,
+ return_scale=False,
+ interpolation='bilinear',
+ out=None):
+ """Resize image to a given size.
+ Args:
+ img (ndarray): The input image.
+ size (tuple[int]): Target size (w, h).
+ return_scale (bool): Whether to return `w_scale` and `h_scale`.
+ interpolation (str): Interpolation method, accepted values are
+ "nearest", "bilinear", "bicubic", "area", "lanczos".
+ out (ndarray): The output destination.
+ Returns:
+ tuple | ndarray: (`resized_img`, `w_scale`, `h_scale`) or
+ `resized_img`.
+ """
+ h, w = img.shape[:2]
+ resized_img = cv2.resize(
+ img, size, dst=out, interpolation=interp_codes[interpolation])
+ if not return_scale:
+ return resized_img
+ else:
+ w_scale = size[0] / w
+ h_scale = size[1] / h
+ return resized_img, w_scale, h_scale
+
+
+def imresize_like(img, dst_img, return_scale=False, interpolation='bilinear'):
+ """Resize image to the same size of a given image.
+ Args:
+ img (ndarray): The input image.
+ dst_img (ndarray): The target image.
+ return_scale (bool): Whether to return `w_scale` and `h_scale`.
+ interpolation (str): Same as :func:`resize`.
+ Returns:
+ tuple or ndarray: (`resized_img`, `w_scale`, `h_scale`) or
+ `resized_img`.
+ """
+ h, w = dst_img.shape[:2]
+ return imresize(img, (w, h), return_scale, interpolation)
+
+
+def rescale_size(old_size, scale, return_scale=False):
+ """Calculate the new size to be rescaled to.
+ Args:
+ old_size (tuple[int]): The old size (w, h) of image.
+ scale (float | tuple[int]): The scaling factor or maximum size.
+ If it is a float number, then the image will be rescaled by this
+ factor, else if it is a tuple of 2 integers, then the image will
+ be rescaled as large as possible within the scale.
+ return_scale (bool): Whether to return the scaling factor besides the
+ rescaled image size.
+ Returns:
+ tuple[int]: The new rescaled image size.
+ """
+ w, h = old_size
+ if isinstance(scale, (float, int)):
+ if scale <= 0:
+ raise ValueError(f'Invalid scale {scale}, must be positive.')
+ scale_factor = scale
+ elif isinstance(scale, tuple):
+ max_long_edge = max(scale)
+ max_short_edge = min(scale)
+ scale_factor = min(max_long_edge / max(h, w),
+ max_short_edge / min(h, w))
+ else:
+ raise TypeError(
+ f'Scale must be a number or tuple of int, but got {type(scale)}')
+
+ new_size = _scale_size((w, h), scale_factor)
+
+ if return_scale:
+ return new_size, scale_factor
+ else:
+ return new_size
+
+
+def imrescale(img, scale, return_scale=False, interpolation='bilinear'):
+ """Resize image while keeping the aspect ratio.
+ Args:
+ img (ndarray): The input image.
+ scale (float | tuple[int]): The scaling factor or maximum size.
+ If it is a float number, then the image will be rescaled by this
+ factor, else if it is a tuple of 2 integers, then the image will
+ be rescaled as large as possible within the scale.
+ return_scale (bool): Whether to return the scaling factor besides the
+ rescaled image.
+ interpolation (str): Same as :func:`resize`.
+ Returns:
+ ndarray: The rescaled image.
+ """
+ h, w = img.shape[:2]
+ new_size, scale_factor = rescale_size((w, h), scale, return_scale=True)
+ rescaled_img = imresize(img, new_size, interpolation=interpolation)
+ if return_scale:
+ return rescaled_img, scale_factor
+ else:
+ return rescaled_img
+
+def center_of_mass(bitmasks):
+ _, h, w = bitmasks.size()
+
+ ys = torch.arange(0, h, dtype=torch.float32, device=bitmasks.device)
+ xs = torch.arange(0, w, dtype=torch.float32, device=bitmasks.device)
+
+ m00 = bitmasks.sum(dim=-1).sum(dim=-1).clamp(min=1e-6)
+ m10 = (bitmasks * xs).sum(dim=-1).sum(dim=-1)
+ m01 = (bitmasks * ys[:, None]).sum(dim=-1).sum(dim=-1)
+ center_x = m10 / m00
+ center_y = m01 / m00
+ return center_x, center_y
+
+def point_nms(heat, kernel=2):
+ # kernel must be 2
+ hmax = F.max_pool2d(heat, (kernel, kernel), stride=1, padding=1)
+ keep = (hmax[:, :, :-1, :-1] == heat).float()
+ return heat * keep
+
+def matrix_nms(cate_labels, seg_masks, sum_masks, cate_scores, sigma=2.0, kernel='gaussian'):
+ n_samples = len(cate_labels)
+ if n_samples == 0:
+ return []
+
+ seg_masks = seg_masks.reshape(n_samples, -1).float()
+ # inter.
+ inter_matrix = torch.mm(seg_masks, seg_masks.transpose(1, 0))
+ # union.
+ sum_masks_x = sum_masks.expand(n_samples, n_samples)
+ # iou.
+ iou_matrix = (inter_matrix / (sum_masks_x + sum_masks_x.transpose(1, 0) - inter_matrix)).triu(diagonal=1)
+ # label_specific matrix.
+ cate_labels_x = cate_labels.expand(n_samples, n_samples)
+ label_matrix = (cate_labels_x == cate_labels_x.transpose(1, 0)).float().triu(diagonal=1)
+
+ # IoU compensation
+ compensate_iou, _ = (iou_matrix * label_matrix).max(0)
+ compensate_iou = compensate_iou.expand(n_samples, n_samples).transpose(1, 0)
+
+ # IoU decay / soft nms
+ delay_iou = iou_matrix * label_matrix
+
+ # matrix nms
+ if kernel == 'linear':
+ delay_matrix = (1 - delay_iou) / (1 - compensate_iou)
+ delay_coefficient, _ = delay_matrix.min(0)
+ else:
+ delay_matrix = torch.exp(-1 * sigma * (delay_iou ** 2))
+ compensate_matrix = torch.exp(-1 * sigma * (compensate_iou ** 2))
+ delay_coefficient, _ = (delay_matrix / compensate_matrix).min(0)
+
+ # update the score.
+ cate_scores_update = cate_scores * delay_coefficient
+
+ return cate_scores_update
+
+
+def mask_nms(cate_labels, seg_masks, sum_masks, cate_scores, nms_thr=0.5):
+ n_samples = len(cate_scores)
+ if n_samples == 0:
+ return []
+
+ keep = seg_masks.new_ones(cate_scores.shape)
+ seg_masks = seg_masks.float()
+
+ for i in range(n_samples - 1):
+ if not keep[i]:
+ continue
+ mask_i = seg_masks[i]
+ label_i = cate_labels[i]
+ for j in range(i + 1, n_samples, 1):
+ if not keep[j]:
+ continue
+ mask_j = seg_masks[j]
+ label_j = cate_labels[j]
+ if label_i != label_j:
+ continue
+ # overlaps
+ inter = (mask_i * mask_j).sum()
+ union = sum_masks[i] + sum_masks[j] - inter
+ if union > 0:
+ if inter / union > nms_thr:
+ keep[j] = False
+ else:
+ keep[j] = False
+ return keep
diff --git a/AdelaiDet/adet/structures/__init__.py b/AdelaiDet/adet/structures/__init__.py
new file mode 100755
index 0000000..fb84eb0
--- /dev/null
+++ b/AdelaiDet/adet/structures/__init__.py
@@ -0,0 +1 @@
+from .beziers import Beziers
\ No newline at end of file
diff --git a/AdelaiDet/adet/structures/beziers.py b/AdelaiDet/adet/structures/beziers.py
new file mode 100755
index 0000000..2f222a5
--- /dev/null
+++ b/AdelaiDet/adet/structures/beziers.py
@@ -0,0 +1,44 @@
+from typing import Union
+import torch
+
+
+class Beziers:
+ """
+ This structure stores a list of bezier curves as a Nx16 torch.Tensor.
+ It will support some common methods about bezier shapes
+ (`area`, `clip`, `nonempty`, etc),
+ and also behaves like a Tensor
+ (support indexing, `to(device)`, `.device`, and iteration over all boxes)
+
+ Attributes:
+ tensor (torch.Tensor): float matrix of Nx4. Each row is (x1, y1, x2, y2).
+ """
+
+ def __init__(self, tensor: torch.Tensor):
+ """
+ Args:
+ tensor (Tensor[float]): a Nx4 matrix. Each row is (x1, y1, x2, y2).
+ """
+ device = tensor.device if isinstance(tensor, torch.Tensor) else torch.device("cpu")
+ tensor = torch.as_tensor(tensor, dtype=torch.float32, device=device)
+ if tensor.numel() == 0:
+ # Use reshape, so we don't end up creating a new tensor that does not depend on
+ # the inputs (and consequently confuses jit)
+ tensor = tensor.reshape((0, 16)).to(dtype=torch.float32, device=device)
+ assert tensor.dim() == 2 and tensor.size(-1) == 16, tensor.size()
+
+ self.tensor = tensor
+
+ def to(self, device: str) -> "Beziers":
+ return Beziers(self.tensor.to(device))
+
+ def __getitem__(self, item: Union[int, slice, torch.BoolTensor]) -> "Beziers":
+ """
+ Returns:
+ Beziers: Create a new :class:`Beziers` by indexing.
+ """
+ if isinstance(item, int):
+ return Beziers(self.tensor[item].view(1, -1))
+ b = self.tensor[item]
+ assert b.dim() == 2, "Indexing on Boxes with {} failed to return a matrix!".format(item)
+ return Beziers(b)
\ No newline at end of file
diff --git a/AdelaiDet/adet/utils/__init__.py b/AdelaiDet/adet/utils/__init__.py
new file mode 100755
index 0000000..e69de29
diff --git a/AdelaiDet/adet/utils/comm.py b/AdelaiDet/adet/utils/comm.py
new file mode 100755
index 0000000..78f2f32
--- /dev/null
+++ b/AdelaiDet/adet/utils/comm.py
@@ -0,0 +1,103 @@
+import torch
+import torch.nn.functional as F
+import torch.distributed as dist
+
+from detectron2.utils.comm import get_world_size
+
+
+def reduce_sum(tensor):
+ world_size = get_world_size()
+ if world_size < 2:
+ return tensor
+ tensor = tensor.clone()
+ dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
+ return tensor
+
+
+def reduce_mean(tensor):
+ num_gpus = get_world_size()
+ total = reduce_sum(tensor)
+ return total.float() / num_gpus
+
+
+def aligned_bilinear(tensor, factor):
+ assert tensor.dim() == 4
+ assert factor >= 1
+ assert int(factor) == factor
+
+ if factor == 1:
+ return tensor
+
+ h, w = tensor.size()[2:]
+ tensor = F.pad(tensor, pad=(0, 1, 0, 1), mode="replicate")
+ oh = factor * h + 1
+ ow = factor * w + 1
+ tensor = F.interpolate(
+ tensor, size=(oh, ow),
+ mode='bilinear',
+ align_corners=True
+ )
+ tensor = F.pad(
+ tensor, pad=(factor // 2, 0, factor // 2, 0),
+ mode="replicate"
+ )
+
+ return tensor[:, :, :oh - 1, :ow - 1]
+
+
+def compute_locations(h, w, stride, device):
+ shifts_x = torch.arange(
+ 0, w * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shifts_y = torch.arange(
+ 0, h * stride, step=stride,
+ dtype=torch.float32, device=device
+ )
+ shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
+ shift_x = shift_x.reshape(-1)
+ shift_y = shift_y.reshape(-1)
+ locations = torch.stack((shift_x, shift_y), dim=1) + stride // 2
+ return locations
+
+
+def compute_ious(pred, target):
+ """
+ Args:
+ pred: Nx4 predicted bounding boxes
+ target: Nx4 target bounding boxes
+ Both are in the form of FCOS prediction (l, t, r, b)
+ """
+ pred_left = pred[:, 0]
+ pred_top = pred[:, 1]
+ pred_right = pred[:, 2]
+ pred_bottom = pred[:, 3]
+
+ target_left = target[:, 0]
+ target_top = target[:, 1]
+ target_right = target[:, 2]
+ target_bottom = target[:, 3]
+
+ target_aera = (target_left + target_right) * \
+ (target_top + target_bottom)
+ pred_aera = (pred_left + pred_right) * \
+ (pred_top + pred_bottom)
+
+ w_intersect = torch.min(pred_left, target_left) + \
+ torch.min(pred_right, target_right)
+ h_intersect = torch.min(pred_bottom, target_bottom) + \
+ torch.min(pred_top, target_top)
+
+ g_w_intersect = torch.max(pred_left, target_left) + \
+ torch.max(pred_right, target_right)
+ g_h_intersect = torch.max(pred_bottom, target_bottom) + \
+ torch.max(pred_top, target_top)
+ ac_uion = g_w_intersect * g_h_intersect
+
+ area_intersect = w_intersect * h_intersect
+ area_union = target_aera + pred_aera - area_intersect
+
+ ious = (area_intersect + 1.0) / (area_union + 1.0)
+ gious = ious - (ac_uion - area_union) / ac_uion
+
+ return ious, gious
diff --git a/AdelaiDet/adet/utils/measures.py b/AdelaiDet/adet/utils/measures.py
new file mode 100755
index 0000000..b99a4be
--- /dev/null
+++ b/AdelaiDet/adet/utils/measures.py
@@ -0,0 +1,192 @@
+# coding: utf-8
+# Adapted from https://github.com/ShichenLiu/CondenseNet/blob/master/utils.py
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+
+import operator
+
+from functools import reduce
+
+
+def get_num_gen(gen):
+ return sum(1 for x in gen)
+
+
+def is_pruned(layer):
+ try:
+ layer.mask
+ return True
+ except AttributeError:
+ return False
+
+
+def is_leaf(model):
+ return get_num_gen(model.children()) == 0
+
+
+def get_layer_info(layer):
+ layer_str = str(layer)
+ type_name = layer_str[:layer_str.find('(')].strip()
+ return type_name
+
+
+def get_layer_param(model):
+ return sum([reduce(operator.mul, i.size(), 1) for i in model.parameters()])
+
+
+### The input batch size should be 1 to call this function
+def measure_layer(layer, *args):
+ global count_ops, count_params
+
+ for x in args:
+ delta_ops = 0
+ delta_params = 0
+ multi_add = 1
+ type_name = get_layer_info(layer)
+
+ ### ops_conv
+ if type_name in ['Conv2d']:
+ out_h = int((x.size()[2] + 2 * layer.padding[0] / layer.dilation[0] - layer.kernel_size[0]) /
+ layer.stride[0] + 1)
+ out_w = int((x.size()[3] + 2 * layer.padding[1] / layer.dilation[1] - layer.kernel_size[1]) /
+ layer.stride[1] + 1)
+ delta_ops = layer.in_channels * layer.out_channels * layer.kernel_size[0] * layer.kernel_size[1] * out_h * out_w / layer.groups * multi_add
+ delta_params = get_layer_param(layer)
+
+ elif type_name in ['ConvTranspose2d']:
+ _, _, in_h, in_w = x.size()
+ out_h = int((in_h-1)*layer.stride[0] - 2 * layer.padding[0] + layer.kernel_size[0] + layer.output_padding[0])
+ out_w = int((in_w-1)*layer.stride[1] - 2 * layer.padding[1] + layer.kernel_size[1] + layer.output_padding[1])
+ delta_ops = layer.in_channels * layer.out_channels * layer.kernel_size[0] * \
+ layer.kernel_size[1] * out_h * out_w / layer.groups * multi_add
+ delta_params = get_layer_param(layer)
+
+ ### ops_learned_conv
+ elif type_name in ['LearnedGroupConv']:
+ measure_layer(layer.relu, x)
+ measure_layer(layer.norm, x)
+ conv = layer.conv
+ out_h = int((x.size()[2] + 2 * conv.padding[0] - conv.kernel_size[0]) /
+ conv.stride[0] + 1)
+ out_w = int((x.size()[3] + 2 * conv.padding[1] - conv.kernel_size[1]) /
+ conv.stride[1] + 1)
+ delta_ops = conv.in_channels * conv.out_channels * conv.kernel_size[0] * conv.kernel_size[1] * out_h * out_w / layer.condense_factor * multi_add
+ delta_params = get_layer_param(conv) / layer.condense_factor
+
+ ### ops_nonlinearity
+ elif type_name in ['ReLU', 'ReLU6']:
+ delta_ops = x.numel()
+ delta_params = get_layer_param(layer)
+
+ ### ops_pooling
+ elif type_name in ['AvgPool2d', 'MaxPool2d']:
+ in_w = x.size()[2]
+ kernel_ops = layer.kernel_size * layer.kernel_size
+ out_w = int((in_w + 2 * layer.padding - layer.kernel_size) / layer.stride + 1)
+ out_h = int((in_w + 2 * layer.padding - layer.kernel_size) / layer.stride + 1)
+ delta_ops = x.size()[0] * x.size()[1] * out_w * out_h * kernel_ops
+ delta_params = get_layer_param(layer)
+
+ elif type_name in ['LastLevelMaxPool']:
+ pass
+
+ elif type_name in ['AdaptiveAvgPool2d']:
+ delta_ops = x.size()[0] * x.size()[1] * x.size()[2] * x.size()[3]
+ delta_params = get_layer_param(layer)
+
+ elif type_name in ['ZeroPad2d', 'RetinaNetPostProcessor']:
+ pass
+ #delta_ops = x.size()[0] * x.size()[1] * x.size()[2] * x.size()[3]
+ #delta_params = get_layer_param(layer)
+
+ ### ops_linear
+ elif type_name in ['Linear']:
+ weight_ops = layer.weight.numel() * multi_add
+ bias_ops = layer.bias.numel()
+ delta_ops = x.size()[0] * (weight_ops + bias_ops)
+ delta_params = get_layer_param(layer)
+
+ ### ops_nothing
+ elif type_name in ['BatchNorm2d', 'Dropout2d', 'DropChannel', 'Dropout', 'FrozenBatchNorm2d', 'GroupNorm']:
+ delta_params = get_layer_param(layer)
+
+ elif type_name in ['SumTwo']:
+ delta_ops = x.numel()
+
+ elif type_name in ['AggregateCell']:
+ if not layer.pre_transform:
+ delta_ops = 2 * x.numel() # twice for each input
+ else:
+ measure_layer(layer.branch_1, x)
+ measure_layer(layer.branch_2, x)
+ delta_params = get_layer_param(layer)
+
+ elif type_name in ['Identity', 'Zero']:
+ pass
+
+ elif type_name in ['Scale']:
+ delta_params = get_layer_param(layer)
+ delta_ops = x.numel()
+
+ elif type_name in ['FCOSPostProcessor', 'RPNPostProcessor', 'KeypointPostProcessor',
+ 'ROIAlign', 'PostProcessor', 'KeypointRCNNPredictor',
+ 'NaiveSyncBatchNorm', 'Upsample', 'Sequential']:
+ pass
+
+ elif type_name in ['DeformConv']:
+ # don't count bilinear
+ offset_conv = list(layer.parameters())[0]
+ delta_ops = reduce(operator.mul, offset_conv.size(), x.size()[2] * x.size()[3])
+ out_h = int((x.size()[2] + 2 * layer.padding[0] / layer.dilation[0]
+ - layer.kernel_size[0]) / layer.stride[0] + 1)
+ out_w = int((x.size()[3] + 2 * layer.padding[1] / layer.dilation[1]
+ - layer.kernel_size[1]) / layer.stride[1] + 1)
+ delta_ops += layer.in_channels * layer.out_channels * layer.kernel_size[0] * layer.kernel_size[1] * out_h * out_w / layer.groups * multi_add
+ delta_params = get_layer_param(layer)
+
+ ### unknown layer type
+ else:
+ raise TypeError('unknown layer type: %s' % type_name)
+
+ count_ops += delta_ops
+ count_params += delta_params
+ return
+
+
+def measure_model(model, x):
+ global count_ops, count_params
+ count_ops = 0
+ count_params = 0
+
+ def should_measure(x):
+ return is_leaf(x) or is_pruned(x)
+
+ def modify_forward(model):
+ for child in model.children():
+ if should_measure(child):
+ def new_forward(m):
+ def lambda_forward(*args):
+ measure_layer(m, *args)
+ return m.old_forward(*args)
+ return lambda_forward
+ child.old_forward = child.forward
+ child.forward = new_forward(child)
+ else:
+ modify_forward(child)
+
+ def restore_forward(model):
+ for child in model.children():
+ # leaf node
+ if is_leaf(child) and hasattr(child, 'old_forward'):
+ child.forward = child.old_forward
+ child.old_forward = None
+ else:
+ restore_forward(child)
+
+ modify_forward(model)
+ out = model.forward(x)
+ restore_forward(model)
+
+ return out, count_ops, count_params
diff --git a/AdelaiDet/adet/utils/visualizer.py b/AdelaiDet/adet/utils/visualizer.py
new file mode 100755
index 0000000..8b81286
--- /dev/null
+++ b/AdelaiDet/adet/utils/visualizer.py
@@ -0,0 +1,161 @@
+import numpy as np
+import pickle
+from detectron2.utils.visualizer import Visualizer
+import matplotlib.colors as mplc
+import matplotlib.font_manager as mfm
+
+class TextVisualizer(Visualizer):
+ def __init__(self, image, metadata, instance_mode, cfg):
+ Visualizer.__init__(self, image, metadata, instance_mode=instance_mode)
+ self.voc_size = cfg.MODEL.BATEXT.VOC_SIZE
+ self.use_customer_dictionary = cfg.MODEL.BATEXT.CUSTOM_DICT
+ if not self.use_customer_dictionary:
+ self.CTLABELS = [' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_','`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','{','|','}','~']
+ else:
+ with open(self.use_customer_dictionary, 'rb') as fp:
+ self.CTLABELS = pickle.load(fp)
+ assert(int(self.voc_size - 1) == len(self.CTLABELS)), "voc_size is not matched dictionary size, got {} and {}.".format(int(self.voc_size - 1), len(self.CTLABELS))
+
+ def draw_instance_predictions(self, predictions):
+ beziers = predictions.beziers.numpy()
+ scores = predictions.scores.tolist()
+ recs = predictions.recs
+
+ self.overlay_instances(beziers, recs, scores)
+
+ return self.output
+
+ def _bezier_to_poly(self, bezier):
+ # bezier to polygon
+ u = np.linspace(0, 1, 20)
+ bezier = bezier.reshape(2, 4, 2).transpose(0, 2, 1).reshape(4, 4)
+ points = np.outer((1 - u) ** 3, bezier[:, 0]) \
+ + np.outer(3 * u * ((1 - u) ** 2), bezier[:, 1]) \
+ + np.outer(3 * (u ** 2) * (1 - u), bezier[:, 2]) \
+ + np.outer(u ** 3, bezier[:, 3])
+ points = np.concatenate((points[:, :2], points[:, 2:]), axis=0)
+
+ return points
+
+ def _decode_recognition(self, rec):
+ s = ''
+ for c in rec:
+ c = int(c)
+ if c < self.voc_size - 1:
+ if self.voc_size == 96:
+ s += self.CTLABELS[c]
+ else:
+ s += str(chr(self.CTLABELS[c]))
+ elif c == self.voc_size -1:
+ s += u'口'
+ return s
+
+ def _ctc_decode_recognition(self, rec):
+ # ctc decoding
+ last_char = False
+ s = ''
+ for c in rec:
+ c = int(c)
+ if c < self.voc_size - 1:
+ if last_char != c:
+ if self.voc_size == 96:
+ s += self.CTLABELS[c]
+ last_char = c
+ else:
+ s += str(chr(self.CTLABELS[c]))
+ last_char = c
+ elif c == self.voc_size -1:
+ s += u'口'
+ else:
+ last_char = False
+ return s
+
+ def overlay_instances(self, beziers, recs, scores, alpha=0.5):
+ color = (0.1, 0.2, 0.5)
+
+ for bezier, rec, score in zip(beziers, recs, scores):
+ polygon = self._bezier_to_poly(bezier)
+ self.draw_polygon(polygon, color, alpha=alpha)
+
+ # draw text in the top left corner
+ text = self._decode_recognition(rec)
+ text = "{:.3f}: {}".format(score, text)
+ lighter_color = self._change_color_brightness(color, brightness_factor=0.7)
+ text_pos = polygon[0]
+ horiz_align = "left"
+ font_size = self._default_font_size
+
+ self.draw_text(
+ text,
+ text_pos,
+ color=lighter_color,
+ horizontal_alignment=horiz_align,
+ font_size=font_size,
+ draw_chinese=False if self.voc_size == 96 else True
+ )
+
+
+ def draw_text(
+ self,
+ text,
+ position,
+ *,
+ font_size=None,
+ color="g",
+ horizontal_alignment="center",
+ rotation=0,
+ draw_chinese=False
+ ):
+ """
+ Args:
+ text (str): class label
+ position (tuple): a tuple of the x and y coordinates to place text on image.
+ font_size (int, optional): font of the text. If not provided, a font size
+ proportional to the image width is calculated and used.
+ color: color of the text. Refer to `matplotlib.colors` for full list
+ of formats that are accepted.
+ horizontal_alignment (str): see `matplotlib.text.Text`
+ rotation: rotation angle in degrees CCW
+ Returns:
+ output (VisImage): image object with text drawn.
+ """
+ if not font_size:
+ font_size = self._default_font_size
+
+ # since the text background is dark, we don't want the text to be dark
+ color = np.maximum(list(mplc.to_rgb(color)), 0.2)
+ color[np.argmax(color)] = max(0.8, np.max(color))
+
+ x, y = position
+ if draw_chinese:
+ font_path = "./simsun.ttc"
+ prop = mfm.FontProperties(fname=font_path)
+ self.output.ax.text(
+ x,
+ y,
+ text,
+ size=font_size * self.output.scale,
+ family="sans-serif",
+ bbox={"facecolor": "black", "alpha": 0.8, "pad": 0.7, "edgecolor": "none"},
+ verticalalignment="top",
+ horizontalalignment=horizontal_alignment,
+ color=color,
+ zorder=10,
+ rotation=rotation,
+ fontproperties=prop
+ )
+ else:
+ self.output.ax.text(
+ x,
+ y,
+ text,
+ size=font_size * self.output.scale,
+ family="sans-serif",
+ bbox={"facecolor": "black", "alpha": 0.8, "pad": 0.7, "edgecolor": "none"},
+ verticalalignment="top",
+ horizontalalignment=horizontal_alignment,
+ color=color,
+ zorder=10,
+ rotation=rotation,
+ )
+ return self.output
\ No newline at end of file
diff --git a/AdelaiDet/configs/BAText/Base-BAText.yaml b/AdelaiDet/configs/BAText/Base-BAText.yaml
new file mode 100755
index 0000000..25bc95b
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Base-BAText.yaml
@@ -0,0 +1,33 @@
+MODEL:
+ META_ARCHITECTURE: "OneStageRCNN"
+ BACKBONE:
+ NAME: "build_fcos_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res2", "res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ PROPOSAL_GENERATOR:
+ NAME: "BAText"
+ FCOS:
+ NMS_TH: 0.5
+ THRESH_WITH_CTR: False
+ USE_SCALE: False
+ NUM_CLASSES: 1
+ INFERENCE_TH_TRAIN: 0.7
+ INFERENCE_TH_TEST: 0.45
+ ROI_HEADS:
+ NAME: "TextHead"
+ IOU_THRESHOLDS: [0.5]
+SOLVER:
+ CLIP_GRADIENTS:
+ ENABLED: True
+INPUT:
+ HFLIP_TRAIN: False
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800, 832, 864, 896)
+ MAX_SIZE_TRAIN: 1600
+ MIN_SIZE_TEST: 1000
+ MAX_SIZE_TEST: 1824
+ CROP:
+ ENABLED: True
+ CROP_INSTANCE: False
+ SIZE: [0.1, 0.1]
\ No newline at end of file
diff --git a/AdelaiDet/configs/BAText/CTW1500/Base-CTW1500.yaml b/AdelaiDet/configs/BAText/CTW1500/Base-CTW1500.yaml
new file mode 100755
index 0000000..0021fd5
--- /dev/null
+++ b/AdelaiDet/configs/BAText/CTW1500/Base-CTW1500.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../Base-BAText.yaml"
+MODEL:
+ BATEXT:
+ POOLER_RESOLUTION: (8,128)
+ NUM_CHARS: 100
+ FCOS:
+ INFERENCE_TH_TEST: 0.6
+DATASETS:
+ TRAIN: ("ctw1500_word_train",)
+ TEST: ("ctw1500_word_test",)
+INPUT:
+ MIN_SIZE_TEST: 800
+ MAX_SIZE_TEST: 1024
diff --git a/AdelaiDet/configs/BAText/CTW1500/attn_R_50.yaml b/AdelaiDet/configs/BAText/CTW1500/attn_R_50.yaml
new file mode 100755
index 0000000..258cf1e
--- /dev/null
+++ b/AdelaiDet/configs/BAText/CTW1500/attn_R_50.yaml
@@ -0,0 +1,16 @@
+_BASE_: "Base-CTW1500.yaml"
+MODEL:
+ WEIGHTS: "weights/batext/pretrain_attn_R_50.pth"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn" # "attn" "rnn"
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.001
+ STEPS: (80000,)
+ MAX_ITER: 120000
+ CHECKPOINT_PERIOD: 10000
+TEST:
+ EVAL_PERIOD: 10000
+OUTPUT_DIR: "output/batext/ctw1500/attn_R_50"
diff --git a/AdelaiDet/configs/BAText/CTW1500/v2_attn_R_50.yaml b/AdelaiDet/configs/BAText/CTW1500/v2_attn_R_50.yaml
new file mode 100755
index 0000000..ddcd73a
--- /dev/null
+++ b/AdelaiDet/configs/BAText/CTW1500/v2_attn_R_50.yaml
@@ -0,0 +1,28 @@
+_BASE_: "Base-CTW1500.yaml"
+MODEL:
+ WEIGHTS: "model_v2_pretrain.pth"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+ FCOS:
+ # Best e2e: 0.5; Best det: 0.3
+ INFERENCE_TH_TEST: 0.5
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.001
+ STEPS: (80000, )
+ MAX_ITER: 100000
+ CHECKPOINT_PERIOD: 10000
+TEST:
+ EVAL_PERIOD: 10000
+OUTPUT_DIR: "output/batext/ctw1500/v2_attn_R_50"
diff --git a/AdelaiDet/configs/BAText/ICDAR2015/Base-ic15.yaml b/AdelaiDet/configs/BAText/ICDAR2015/Base-ic15.yaml
new file mode 100755
index 0000000..2bcd36c
--- /dev/null
+++ b/AdelaiDet/configs/BAText/ICDAR2015/Base-ic15.yaml
@@ -0,0 +1,4 @@
+_BASE_: "../Base-BAText.yaml"
+DATASETS:
+ TRAIN: ("icdar2015_train",)
+ TEST: ("icdar2015_test",)
\ No newline at end of file
diff --git a/AdelaiDet/configs/BAText/ICDAR2015/v1_attn_R_50.yaml b/AdelaiDet/configs/BAText/ICDAR2015/v1_attn_R_50.yaml
new file mode 100755
index 0000000..cddc052
--- /dev/null
+++ b/AdelaiDet/configs/BAText/ICDAR2015/v1_attn_R_50.yaml
@@ -0,0 +1,20 @@
+_BASE_: "Base-ic15.yaml"
+MODEL:
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+SOLVER:
+ IMS_PER_BATCH: 4
+ BASE_LR: 0.001
+ MAX_ITER: 5500
+ CHECKPOINT_PERIOD: 500
+INPUT:
+ MIN_SIZE_TRAIN: (980, 1044, 1108, 1172, 1236, 1300, 1364, 1428, 1492)
+ MAX_SIZE_TRAIN: 2900
+ MIN_SIZE_TEST: 2000
+ MAX_SIZE_TEST: 4000
+ IS_ROTATE: True
+TEST:
+ EVAL_PERIOD: 500
+OUTPUT_DIR: "output/batext/ic15/v1_attn_R_50"
diff --git a/AdelaiDet/configs/BAText/ICDAR2015/v2_attn_R_50.yaml b/AdelaiDet/configs/BAText/ICDAR2015/v2_attn_R_50.yaml
new file mode 100755
index 0000000..e93ae62
--- /dev/null
+++ b/AdelaiDet/configs/BAText/ICDAR2015/v2_attn_R_50.yaml
@@ -0,0 +1,33 @@
+_BASE_: "Base-ic15.yaml"
+MODEL:
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+ POOLER_RESOLUTION: (16, 64)
+ FCOS:
+ INFERENCE_TH_TEST: 0.4
+ NMS_TH: 0.4
+SOLVER:
+ IMS_PER_BATCH: 4
+ BASE_LR: 0.001
+ MAX_ITER: 5500
+ CHECKPOINT_PERIOD: 500
+INPUT:
+ MIN_SIZE_TRAIN: (980, 1044, 1108, 1172, 1236, 1300, 1364, 1428, 1492)
+ MAX_SIZE_TRAIN: 2900
+ MIN_SIZE_TEST: 2000
+ MAX_SIZE_TEST: 4000
+ IS_ROTATE: True
+TEST:
+ EVAL_PERIOD: 500
+OUTPUT_DIR: "output/batext/ic15/v2_attn_R_50"
diff --git a/AdelaiDet/configs/BAText/Pretrain/Base-Chn-Pretrain.yaml b/AdelaiDet/configs/BAText/Pretrain/Base-Chn-Pretrain.yaml
new file mode 100755
index 0000000..0eeeb89
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/Base-Chn-Pretrain.yaml
@@ -0,0 +1,4 @@
+_BASE_: "../Base-BAText.yaml"
+DATASETS:
+ TRAIN: ("chnsyn_train", "rects_train", "rects_val", "lsvt_train", "art_train", )
+ TEST: ("rects_test", )
diff --git a/AdelaiDet/configs/BAText/Pretrain/Base-Pretrain-ic15.yaml b/AdelaiDet/configs/BAText/Pretrain/Base-Pretrain-ic15.yaml
new file mode 100755
index 0000000..bd69190
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/Base-Pretrain-ic15.yaml
@@ -0,0 +1,4 @@
+_BASE_: "../Base-BAText.yaml"
+DATASETS:
+ TRAIN: ("mltbezier_word_train", "totaltext_train", "syntext1_train", "syntext2_train", "icdar2013_train", "icdar2015_train")
+ TEST: ("icdar2015_test",)
\ No newline at end of file
diff --git a/AdelaiDet/configs/BAText/Pretrain/Base-Pretrain.yaml b/AdelaiDet/configs/BAText/Pretrain/Base-Pretrain.yaml
new file mode 100755
index 0000000..e25a216
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/Base-Pretrain.yaml
@@ -0,0 +1,4 @@
+_BASE_: "../Base-BAText.yaml"
+DATASETS:
+ TRAIN: ("mltbezier_word_train", "totaltext_train", "syntext1_train", "syntext2_train",)
+ TEST: ("totaltext_val",)
\ No newline at end of file
diff --git a/AdelaiDet/configs/BAText/Pretrain/attn_R_50.yaml b/AdelaiDet/configs/BAText/Pretrain/attn_R_50.yaml
new file mode 100755
index 0000000..d318c31
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/attn_R_50.yaml
@@ -0,0 +1,16 @@
+_BASE_: "Base-Pretrain.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.01
+ STEPS: (160000, 220000)
+ MAX_ITER: 260000
+ CHECKPOINT_PERIOD: 20000
+TEST:
+ EVAL_PERIOD: 20000
+OUTPUT_DIR: "output/batext/pretrain/attn_R_50"
diff --git a/AdelaiDet/configs/BAText/Pretrain/v1_ic15_attn_R_50.yaml b/AdelaiDet/configs/BAText/Pretrain/v1_ic15_attn_R_50.yaml
new file mode 100755
index 0000000..98a16ac
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/v1_ic15_attn_R_50.yaml
@@ -0,0 +1,19 @@
+_BASE_: "Base-Pretrain-ic15.yaml"
+MODEL:
+ WEIGHTS: "https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ POOLER_RESOLUTION: (16, 64)
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.01
+ STEPS: (160000, 220000)
+ MAX_ITER: 260000
+ CHECKPOINT_PERIOD: 5000
+TEST:
+ EVAL_PERIOD: 20000
+INPUT:
+ IS_ROTATE: True
+OUTPUT_DIR: "output/batext/pretrain/v1_ic15_attn_R_50"
diff --git a/AdelaiDet/configs/BAText/Pretrain/v2_attn_R_50.yaml b/AdelaiDet/configs/BAText/Pretrain/v2_attn_R_50.yaml
new file mode 100755
index 0000000..5eb7753
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/v2_attn_R_50.yaml
@@ -0,0 +1,26 @@
+_BASE_: "Base-Pretrain.yaml"
+MODEL:
+ WEIGHTS: "https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.01
+ STEPS: (160000, 220000)
+ MAX_ITER: 260000
+ CHECKPOINT_PERIOD: 20000
+TEST:
+ EVAL_PERIOD: 20000
+OUTPUT_DIR: "output/batext/pretrain/v2_attn_R_50"
+
diff --git a/AdelaiDet/configs/BAText/Pretrain/v2_chn_attn_R_50.yaml b/AdelaiDet/configs/BAText/Pretrain/v2_chn_attn_R_50.yaml
new file mode 100755
index 0000000..e3e01c2
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/v2_chn_attn_R_50.yaml
@@ -0,0 +1,29 @@
+_BASE_: "Base-Chn-Pretrain.yaml"
+MODEL:
+ WEIGHTS: "https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+ VOC_SIZE: 5462
+ CUSTOM_DICT: "chn_cls_list"
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.01
+ STEPS: (160000, 220000)
+ MAX_ITER: 260000
+ CHECKPOINT_PERIOD: 10000
+INPUT:
+ CROP:
+ ENABLED: False
+OUTPUT_DIR: "output/batext/chn_pretrain/v2_attn_R_50"
+
diff --git a/AdelaiDet/configs/BAText/Pretrain/v2_ic15_attn_R_50.yaml b/AdelaiDet/configs/BAText/Pretrain/v2_ic15_attn_R_50.yaml
new file mode 100755
index 0000000..2a1a14b
--- /dev/null
+++ b/AdelaiDet/configs/BAText/Pretrain/v2_ic15_attn_R_50.yaml
@@ -0,0 +1,29 @@
+_BASE_: "Base-Pretrain-ic15.yaml"
+MODEL:
+ WEIGHTS: "https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+ POOLER_RESOLUTION: (16, 64)
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.01
+ STEPS: (160000, 220000)
+ MAX_ITER: 260000
+ CHECKPOINT_PERIOD: 20000
+TEST:
+ EVAL_PERIOD: 20000
+INPUT:
+ IS_ROTATE: True
+OUTPUT_DIR: "output/batext/pretrain/v2_ic15_attn_R_50"
+
diff --git a/AdelaiDet/configs/BAText/README.md b/AdelaiDet/configs/BAText/README.md
new file mode 100755
index 0000000..2fb5ee1
--- /dev/null
+++ b/AdelaiDet/configs/BAText/README.md
@@ -0,0 +1,343 @@
+# ABCNetv1 & ABCNetv2
+[ABCNetv1](https://openaccess.thecvf.com/content_CVPR_2020/html/Liu_ABCNet_Real-Time_Scene_Text_Spotting_With_Adaptive_Bezier-Curve_Network_CVPR_2020_paper.html) is an efficient end-to-end scene text spotting framework over 10x faster than previous state of the art. It's published in IEEE Conf. Comp Vis Pattern Recogn.'2020 as an oral paper. [ABCNetv2](https://ieeexplore.ieee.org/document/9525302) is published in TPAMI.
+
+## Models
+### Experimental resutls on CTW1500:
+
+Name | inf. time | e2e-None-hmean | e2e-Full-hmean | det-hmean | download
+--- |:---:|:---:|:---:|:---:|:---:
+[v1-CTW1500-finetune](CTW1500/attn_R_50.yaml) | 8.7 FPS | 53.2 | 74.7 | 84.4 | [pretrained-model](https://universityofadelaide.box.com/shared/static/okeo5pvul5v5rxqh4yg8pcf805tzj2no.pth)
+[v2-CTW1500-finetune](CTW1500/v2_attn_R_50.yaml) | 7.2 FPS | 57.7 | 75.8 | 85.0 | [finetuned-model](https://drive.google.com/file/d/12HV1dHjw1POdhOiHXPPXcGnjyp-3IuQv/view?usp=sharing)
+
+
+### Experimental resutls on TotalText:
+
+Config | inf. time | e2e-None-hmean | e2e-Full-hmean | det-hmean | download
+--- |:---------:|:---------:|:---------:|:---------:|:---:
+[v1-pretrain](Pretrain/attn_R_50.yaml) | 11.3 FPS | 58.1 | 75.5 | 80.0 | [pretrained-model](https://cloudstor.aarnet.edu.au/plus/s/dEzxhTlEumICiq0/download)
+[v1-totaltext-finetune](TotalText/attn_R_50.yaml) | 11.3 FPS | 67.1 | 81.1 | 86.0 | [finetuned-model](https://cloudstor.aarnet.edu.au/plus/s/tYsnegjTs13MwwK/download)
+[v2-pretrain](Pretrain/v2_attn_R_50.yaml) | 7.8 FPS | 63.5 | 78.4 | 83.7 | [pretrained-model](https://drive.google.com/file/d/1v5C9klxBuNVBaLVxZRCy1MYnwEu0F25q/view?usp=sharing)
+[v2-totaltext-finetune](TotalText/v2_attn_R_50.yaml) | 7.7 FPS | 71.8 | 83.4 | 87.2 | [finetuned-model](https://drive.google.com/file/d/1jR5-A-7ITvjdSx3kWVE9bMgh_biMsqcR/view?usp=sharing)
+
+### Experimental resutls on [ICDAR2015](https://rrc.cvc.uab.es/?ch=4):
+
+Name | e2e-None | e2e-Generic | e2e-Weak | e2e-Strong | det-hmean | download
+--- |:---:|:---:|:---:|:---:|:---:|:---:
+[v1-icdar2015-pretrain](Pretrain/v1_ic15_attn_R_50.yaml) | 38.0 | 50.8 | 59.0 | 65.8 | 83.2 | [pretrained-model](https://drive.google.com/file/d/1MZab_ftY8qGCurW1rwZBx5ftquZgcf4e/view?usp=sharing)
+[v1-icdar2015-finetune](ICDAR2015/v1_attn_R_50.yaml) | 57.1 | 66.8 | 74.1 | 79.2 | 86.8 | [finetuned-model](https://drive.google.com/file/d/15eEctI4CqTxtcMAMcYiHIysYw3l53BGQ/view?usp=sharing)
+[v2-icdar2015-pretrain](Pretrain/v2_ic15_attn_R_50.yaml) | 59.5 | 69.0 | 75.8 | 80.8 | 86.2 | [pretrained-model](https://drive.google.com/file/d/17xIB064Jlq31z875POrw9a3aDmg04C3y/view?usp=sharing)
+[v2-icdar2015-finetune](ICDAR2015/v2_attn_R_50.yaml) | 66.3 | 73.2 | 78.8 | 83.7 | 88.2 | [finetuned-model](https://drive.google.com/file/d/1bxVxu7kX13S1_xYvCfUfomO8hSZGNZUl/view?usp=sharing)
+
+### Experimental resutls on [ReCTS](https://rrc.cvc.uab.es/?ch=12):
+
+Name | inf. time | det-recall | det-precision | det-hmean | 1 - NED | download
+--- |:---:|:---:|:---:|:---:|:---:|:---:
+[v2-Chinese-pretrained](Pretrain/v2_chn_attn_R_50.yaml) | -| - | - | - | - | [pretrained-model](https://drive.google.com/file/d/1AU8yAMNm2H8ryB7uIvp2HUHpCso7eyNH/view?usp=sharing)
+[v2-ReCTS-finetune](ReCTS/v2_chn_attn_R_50.yaml) | 8 FPS | 87.9 | 92.9 | 90.33 | 63.9 | [finetuned-model](https://drive.google.com/file/d/1YTlC5jkh6y3g1RRc_hDs4m_tcU2J20fe/view?usp=sharing)
+
+
+### Experimental resutls on [MSRA-TD500](http://www.iapr-tc11.org/mediawiki/index.php/MSRA_Text_Detection_500_Database_%28MSRA-TD500%29):
+
+Name | det-recall | det-precision | det-hmean | download
+--- |:---:|:---:|:---:|:---:
+[v2-TD500-finetune](https://github.com/aim-uofa/AdelaiDet/issues/537) | 81.9 | 89.0 | 85.3 | [finetuned-model](https://github.com/aim-uofa/AdelaiDet/issues/537)
+
+* Note the pretrained model for TD500 is the Chinese pretrained used for ReCTS. As MSRA-TD is a det. only dataset, a small amount of [modification](https://github.com/aim-uofa/AdelaiDet/issues/537) is needed.
+
+## Quick Start (ABCNetv1)
+
+### Inference with our trained Models
+
+1. Select the model and config file above, for example, `configs/BAText/CTW1500/attn_R_50.yaml`.
+2. Run the demo with
+
+```
+wget -O ctw1500_attn_R_50.pth https://universityofadelaide.box.com/shared/static/okeo5pvul5v5rxqh4yg8pcf805tzj2no.pth
+python demo/demo.py \
+ --config-file configs/BAText/CTW1500/attn_R_50.yaml \
+ --input datasets/CTW1500/ctwtest_text_image/ \
+ --opts MODEL.WEIGHTS ctw1500_attn_R_50.pth
+```
+or
+```
+wget -O tt_attn_R_50.pth https://cloudstor.aarnet.edu.au/plus/s/tYsnegjTs13MwwK/download
+python demo/demo.py \
+ --config-file configs/BAText/TotalText/attn_R_50.yaml \
+ --input datasets/totaltext/test_images/ \
+ --opts MODEL.WEIGHTS tt_attn_R_50.pth
+```
+or
+```
+# Download v1_ic15_finetuned.pth above
+python demo/demo.py \
+ --config-file configs/BAText/ICDAR2015/v1_attn_R_50.yaml \
+ --input datasets/icdar2015/test_images \
+ --opts MODEL.WEIGHTS v1_ic15_finetuned.pth
+```
+### Train Your Own Models
+
+To train a model with "train_net.py", first setup the corresponding datasets following
+[datasets/README.md](../../datasets/README.md) or using the following script:
+
+```
+cd datasets/
+wget https://drive.google.com/file/d/1we4iwZNA80q-yRoEKqB66SuTa1tPbhZu/view?usp=sharing -O totaltext.zip
+unzip totaltext.zip
+rm totaltext.zip
+wget https://drive.google.com/file/d/1ntlnlnQHZisDoS_bgDvrcrYFomw9iTZ0/view?usp=sharing -O CTW1500.zip
+unzip CTW1500.zip
+rm CTW1500.zip
+wget https://drive.google.com/file/d/1J94245rU-s7KTecNQRD3KXG04ICZhL9z/view?usp=sharing -O icdar2015.zip
+unzip icdar2015.zip
+rm icdar2015.zip
+mkdir evaluation
+cd evaluation
+wget -O gt_ctw1500.zip https://cloudstor.aarnet.edu.au/plus/s/xU3yeM3GnidiSTr/download
+wget -O gt_totaltext.zip https://cloudstor.aarnet.edu.au/plus/s/SFHvin8BLUM4cNd/download
+wget -O gt_icdar2015.zip https://drive.google.com/file/d/1wrq_-qIyb_8dhYVlDzLZTTajQzbic82Z/view?usp=sharing
+```
+
+* Note (synthetic and mlt2017 datasets need to be downloaded through [datasets/README.md](../../datasets/README.md).)
+
+You can also prepare your custom dataset following the [example scripts](https://universityofadelaide.box.com/s/phqfzpvhe0obmkvn17akn9qw47u1m44i).
+
+Pretrainining with synthetic data (For Totaltext and CTW1500):
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/Pretrain/attn_R_50.yaml \
+ --num-gpus 4 \
+ OUTPUT_DIR text_pretraining/attn_R_50
+```
+
+Pretrainining with synthetic data (For ICDAR2015):
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/Pretrain/v1_ic15_attn_R_50.yaml \
+ --num-gpus 4 \
+ OUTPUT_DIR text_pretraining/v1_ic15_attn_R_50
+```
+
+
+Finetuning on TotalText:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/TotalText/attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS text_pretraining/attn_R_50/model_final.pth
+```
+
+Finetuning on CTW1500:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/CTW1500/attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS text_pretraining/attn_R_50/model_final.pth
+```
+
+Finetuning on ICDAR2015:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/ICDAR2015/v1_attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS text_pretraining/v1_ic15_attn_R_50/model_final.pth
+```
+
+### Evaluate on Trained Model
+Download test GT [here](../../datasets/README.md) so that the directory has the following structure:
+
+```
+datasets
+|_ evaluation
+| |_ gt_totaltext.zip
+| |_ gt_ctw1500.zip
+| |_ gt_icdar2015.zip
+```
+
+Producing both (w/wo lexion) e2e and detection results on CTW1500:
+```
+wget -O ctw1500_attn_R_50.pth https://universityofadelaide.box.com/shared/static/okeo5pvul5v5rxqh4yg8pcf805tzj2no.pth
+python tools/train_net.py \
+ --config-file configs/BAText/CTW1500/attn_R_50.yaml \
+ --eval-only \
+ MODEL.WEIGHTS ctw1500_attn_R_50.pth
+```
+or Totaltext:
+```
+wget -O tt_attn_R_50.pth https://cloudstor.aarnet.edu.au/plus/s/tYsnegjTs13MwwK/download
+python tools/train_net.py \
+ --config-file configs/BAText/TotalText/attn_R_50.yaml \
+ --eval-only \
+ MODEL.WEIGHTS tt_attn_R_50.pth
+```
+or ICDAR2015:
+```
+# Download v1_ic15_finetuned.pth above
+# MODEL.BATEXT.EVAL_TYPE: 3: Strong, 2: Weak, 1: Generic
+python tools/train_net.py \
+ --config-file configs/BAText/ICDAR2015/v1_attn_R_50.yaml \
+ --num-gpus 4 \
+ --eval-only \
+ MODEL.WEIGHTS v1_ic15_finetuned.pth \
+ MODEL.BATEXT.EVAL_TYPE 3
+```
+
+You can also evalute the json result file offline following the [evaluation_example_scripts](https://universityofadelaide.box.com/shared/static/e3yha5080jzvjuyfeayprnkbu265t3hr.zip), including an example of how to evaluate on a custom dataset. If you want to measure the ***inference time***, please change --num-gpus to 1.
+
+### Standalone BezierAlign Warping
+If you are insteresting in warping a curved instance into a rectangular format independantly, please refer to the example script [here](https://github.com/Yuliang-Liu/bezier_curve_text_spotting#bezieralign-example).
+
+## Quick Start (ABCNetv2)
+The datasets and the basic training details (learning rate, iterations, etc.) used for ABCNetv2 are exactly the same as ABCNet v1. Please following above to prepare the training and evaluation data. If you are interesting in text spotting quantization, please refer to the [patch](https://github.com/aim-uofa/model-quantization/blob/master/doc/detectron2.md#text-spotting).
+
+### Demo
+* For CTW1500
+```
+# Download model_v2_ctw1500.pth above
+python demo/demo.py \
+ --config-file configs/BAText/CTW1500/v2_attn_R_50.yaml \
+ --input datasets/CTW1500/ctwtest_text_image/ \
+ --opts MODEL.WEIGHTS model_v2_ctw1500.pth
+```
+* For TotalText
+```
+# Download model_v2_totaltext.pth above
+python demo/demo.py \
+ --config-file configs/BAText/TotalText/v2_attn_R_50.yaml \
+ --input datasets/totaltext/test_images/ \
+ --opts MODEL.WEIGHTS model_v2_totaltext.pth
+```
+* For ICDAR2015
+```
+# Download ic15_finetuned.pth above
+python demo/demo.py \
+ --config-file configs/BAText/ICDAR2015/v2_attn_R_50.yaml \
+ --input datasets/icdar2015/test_images/ \
+ --opts MODEL.WEIGHTS ic15_finetuned.pth
+```
+* For ReCTS (Chinese)
+```
+# Download model_v2_rects.pth above
+wget https://drive.google.com/file/d/1dcR__ZgV_JOfpp8Vde4FR3bSR-QnrHVo/view?usp=sharing -O simsun.ttc
+wget https://drive.google.com/file/d/1wqkX2VAy48yte19q1Yn5IVjdMVpLzYVo/view?usp=sharing -O chn_cls_list
+python demo/demo.py \
+ --config-file configs/BAText/ReCTS/v2_chn_attn_R_50.yaml \
+ --input datasets/ReCTS/ReCTS_test_images/ \
+ --opts MODEL.WEIGHTS model_v2_rects.pth
+```
+
+### Train
+Training ABCNetv2 using 4 V100.
+* Pretrainining with synthetic data (for TotalText and CTW1500):
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/Pretrain/v2_attn_R_50.yaml \
+ --num-gpus 4 \
+ OUTPUT_DIR text_pretraining/v2_attn_R_50
+```
+* Pretrainining with synthetic data (for ICDAR2015):
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/Pretrain/v2_ic15_attn_R_50.yaml \
+ --num-gpus 4 \
+ OUTPUT_DIR text_pretraining/v2_ic15_attn_R_50
+```
+* Pretrainining with synthetic data (for ReCTS):
+```
+wget https://drive.google.com/file/d/1wqkX2VAy48yte19q1Yn5IVjdMVpLzYVo/view?usp=sharing -O chn_cls_list
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/Pretrain/v2_chn_attn_R_50.yaml \
+ --num-gpus 4 \
+ OUTPUT_DIR text_pretraining/v2_chn_attn_R_50
+```
+* Finetuning on TotalText:
+```
+# Download model_v2_pretrain.pth above or using your own pretrained model
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/TotalText/v2_attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS text_pretraining/v2_attn_R_50/model_final.pth
+```
+* Finetuning on CTW1500:
+```
+# Download model_v2_pretrain.pth above or using your own pretrained model
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/CTW1500/v2_attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS text_pretraining/v2_attn_R_50/model_final.pth
+```
+* Finetuning on ICDAR2015:
+```
+# Download ic15_pretrained.pth above or using your own pretrained model
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/ICDAR2015/v2_attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS ic15_pretrained.pth
+```
+* Finetuning on ReCTS:
+```
+# Download model_v2_chn_pretrain.pth or using your own pretrained model
+wget https://drive.google.com/file/d/1XOtlUz9lxh2HV5Gmu3alb5WKZafFn-0_/view?usp=sharing -O model_v2_chn_pretrain.pth
+wget https://drive.google.com/file/d/1wqkX2VAy48yte19q1Yn5IVjdMVpLzYVo/view?usp=sharing -O chn_cls_list
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BAText/ReCTS/v2_chn_attn_R_50.yaml \
+ --num-gpus 4 \
+ MODEL.WEIGHTS model_v2_chn_pretrain.pth
+```
+### Evaluation
+* Evaluate on CTW1500:
+```
+# Download model_v2_ctw1500.pth above
+python tools/train_net.py \
+ --config-file configs/BAText/CTW1500/v2_attn_R_50.yaml \
+ --eval-only \
+ MODEL.WEIGHTS model_v2_ctw1500.pth
+```
+* Evaluate on Totaltext:
+```
+# Download model_v2_totaltext.pth above
+python tools/train_net.py \
+ --config-file configs/BAText/TotalText/v2_attn_R_50.yaml \
+ --eval-only \
+ MODEL.WEIGHTS model_v2_totaltext.pth
+```
+* Evaluate on ICDAR2015:
+```
+# Download ic15_finetuned.pth above
+# MODEL.BATEXT.EVAL_TYPE: 3: Strong, 2: Weak, 1: Generic
+python tools/train_net.py \
+ --config-file configs/BAText/ICDAR2015/v2_attn_R_50.yaml \
+ --num-gpus 4 \
+ --eval-only \
+ MODEL.WEIGHTS ic15_finetuned.pth \
+ MODEL.BATEXT.EVAL_TYPE 3
+```
+* Evaluate on ReCTS:
+
+ReCTS does not provide annotations for the test set, you may need to submit the results using the predicted json file in the [official website](https://rrc.cvc.uab.es/?ch=12).
+
+
+# BibTeX
+
+```BibTeX
+@inproceedings{liu2020abcnet,
+ title = {{ABCNet}: Real-time Scene Text Spotting with Adaptive Bezier-Curve Network},
+ author = {Liu, Yuliang and Chen, Hao and Shen, Chunhua and He, Tong and Jin, Lianwen and Wang, Liangwei},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2020}
+}
+@ARTICLE{9525302,
+ author={Liu, Yuliang and Shen, Chunhua and Jin, Lianwen and He, Tong and Chen, Peng and Liu, Chongyu and Chen, Hao},
+ journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
+ title={ABCNet v2: Adaptive Bezier-Curve Network for Real-time End-to-end Text Spotting},
+ year={2021},
+ volume={},
+ number={},
+ pages={1-1},
+ doi={10.1109/TPAMI.2021.3107437}}
+```
+
diff --git a/AdelaiDet/configs/BAText/ReCTS/Base-ReCTS.yaml b/AdelaiDet/configs/BAText/ReCTS/Base-ReCTS.yaml
new file mode 100755
index 0000000..b56c864
--- /dev/null
+++ b/AdelaiDet/configs/BAText/ReCTS/Base-ReCTS.yaml
@@ -0,0 +1,4 @@
+_BASE_: "../Base-BAText.yaml"
+DATASETS:
+ TRAIN: ("rects_train", "rects_val")
+ TEST: ("rects_test",)
diff --git a/AdelaiDet/configs/BAText/ReCTS/v2_chn_attn_R_50.yaml b/AdelaiDet/configs/BAText/ReCTS/v2_chn_attn_R_50.yaml
new file mode 100755
index 0000000..f7a68d5
--- /dev/null
+++ b/AdelaiDet/configs/BAText/ReCTS/v2_chn_attn_R_50.yaml
@@ -0,0 +1,29 @@
+_BASE_: "Base-ReCTS.yaml"
+MODEL:
+ WEIGHTS: "https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+ VOC_SIZE: 5462
+ CUSTOM_DICT: "chn_cls_list"
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.001
+ STEPS: (140000, 160000)
+ MAX_ITER: 180000
+ CHECKPOINT_PERIOD: 10000
+INPUT:
+ CROP:
+ ENABLED: False
+OUTPUT_DIR: "output/batext/rects/v2_attn_R_50"
+
diff --git a/AdelaiDet/configs/BAText/TotalText/Base-TotalText.yaml b/AdelaiDet/configs/BAText/TotalText/Base-TotalText.yaml
new file mode 100755
index 0000000..b84ce78
--- /dev/null
+++ b/AdelaiDet/configs/BAText/TotalText/Base-TotalText.yaml
@@ -0,0 +1,4 @@
+_BASE_: "../Base-BAText.yaml"
+DATASETS:
+ TRAIN: ("totaltext_train",)
+ TEST: ("totaltext_val",)
\ No newline at end of file
diff --git a/AdelaiDet/configs/BAText/TotalText/attn_R_50.yaml b/AdelaiDet/configs/BAText/TotalText/attn_R_50.yaml
new file mode 100755
index 0000000..6be64bd
--- /dev/null
+++ b/AdelaiDet/configs/BAText/TotalText/attn_R_50.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-TotalText.yaml"
+MODEL:
+ WEIGHTS: "weights/batext/pretrain_attn_R_50.pth"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn" # "attn" "rnn"
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.001
+ MAX_ITER: 5000
+ CHECKPOINT_PERIOD: 1000
+TEST:
+ EVAL_PERIOD: 1000
+OUTPUT_DIR: "output/batext/totaltext/attn_R_50"
diff --git a/AdelaiDet/configs/BAText/TotalText/v2_attn_R_50.yaml b/AdelaiDet/configs/BAText/TotalText/v2_attn_R_50.yaml
new file mode 100755
index 0000000..0da3ccc
--- /dev/null
+++ b/AdelaiDet/configs/BAText/TotalText/v2_attn_R_50.yaml
@@ -0,0 +1,27 @@
+_BASE_: "Base-TotalText.yaml"
+MODEL:
+ WEIGHTS: "https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ BiFPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ OUT_CHANNELS: 256
+ NUM_REPEATS: 2
+ NORM: "SyncBN"
+ RESNETS:
+ DEPTH: 50
+ BATEXT:
+ RECOGNIZER: "attn"
+ USE_COORDCONV: True
+ USE_AET: True
+ FCOS:
+ # Best e2e: 0.5; Best det: 0.4
+ INFERENCE_TH_TEST: 0.5
+SOLVER:
+ IMS_PER_BATCH: 8
+ BASE_LR: 0.001
+ MAX_ITER: 5000
+ CHECKPOINT_PERIOD: 1000
+TEST:
+ EVAL_PERIOD: 1000
+OUTPUT_DIR: "output/batext/pretrain/v2_attn_R_50"
diff --git a/AdelaiDet/configs/BlendMask/550_R_50_1x.yaml b/AdelaiDet/configs/BlendMask/550_R_50_1x.yaml
new file mode 100755
index 0000000..4e609eb
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/550_R_50_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-550.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+OUTPUT_DIR: "output/blendmask/550_R_50_1x"
diff --git a/AdelaiDet/configs/BlendMask/550_R_50_3x.yaml b/AdelaiDet/configs/BlendMask/550_R_50_3x.yaml
new file mode 100755
index 0000000..ca5b575
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/550_R_50_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-550.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/blendmask/550_R_50_3x"
diff --git a/AdelaiDet/configs/BlendMask/550_R_50_dcni3_5x.yaml b/AdelaiDet/configs/BlendMask/550_R_50_dcni3_5x.yaml
new file mode 100755
index 0000000..d2a9501
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/550_R_50_dcni3_5x.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-550.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ DEFORM_MODULATED: True
+ DEFORM_INTERVAL: 3
+INPUT:
+ MIN_SIZE_TRAIN: (440, 594)
+ MIN_SIZE_TRAIN_SAMPLING: "range"
+ MAX_SIZE_TRAIN: 990
+ CROP:
+ ENABLED: True
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/blendmask/550_R_50_dcni3_5x"
diff --git a/AdelaiDet/configs/BlendMask/Base-550.yaml b/AdelaiDet/configs/BlendMask/Base-550.yaml
new file mode 100755
index 0000000..3acc65d
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Base-550.yaml
@@ -0,0 +1,17 @@
+_BASE_: "Base-BlendMask.yaml"
+MODEL:
+ FCOS:
+ TOP_LEVELS: 1
+ IN_FEATURES: ["p3", "p4", "p5", "p6"]
+ FPN_STRIDES: [8, 16, 32, 64]
+ SIZES_OF_INTEREST: [64, 128, 256]
+ NUM_SHARE_CONVS: 3
+ NUM_CLS_CONVS: 0
+ NUM_BOX_CONVS: 0
+ BASIS_MODULE:
+ NUM_CONVS: 2
+INPUT:
+ MIN_SIZE_TRAIN: (440, 462, 484, 506, 528, 550)
+ MAX_SIZE_TRAIN: 916
+ MIN_SIZE_TEST: 550
+ MAX_SIZE_TEST: 916
diff --git a/AdelaiDet/configs/BlendMask/Base-BlendMask.yaml b/AdelaiDet/configs/BlendMask/Base-BlendMask.yaml
new file mode 100755
index 0000000..da455b9
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Base-BlendMask.yaml
@@ -0,0 +1,29 @@
+MODEL:
+ META_ARCHITECTURE: "BlendMask"
+ MASK_ON: True
+ BACKBONE:
+ NAME: "build_fcos_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ PROPOSAL_GENERATOR:
+ NAME: "FCOS"
+ BASIS_MODULE:
+ LOSS_ON: True
+ PANOPTIC_FPN:
+ COMBINE:
+ ENABLED: False
+ FCOS:
+ THRESH_WITH_CTR: True
+ USE_SCALE: False
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.01 # Note that RetinaNet uses a different default learning rate
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
diff --git a/AdelaiDet/configs/BlendMask/Base-RT.yaml b/AdelaiDet/configs/BlendMask/Base-RT.yaml
new file mode 100755
index 0000000..572a7a6
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Base-RT.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-BlendMask.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
\ No newline at end of file
diff --git a/AdelaiDet/configs/BlendMask/DLA_34_syncbn_4x.yaml b/AdelaiDet/configs/BlendMask/DLA_34_syncbn_4x.yaml
new file mode 100755
index 0000000..cd6588c
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/DLA_34_syncbn_4x.yaml
@@ -0,0 +1,12 @@
+_BASE_: "Base-RT.yaml"
+MODEL:
+ BACKBONE:
+ NAME: "build_fcos_dla_fpn_backbone"
+ FREEZE_AT: -1
+ WEIGHTS: "http://dl.yf.io/dla/models/imagenet/dla34-ba72cf86.pth"
+ DLA:
+ CONV_BODY: "DLA34"
+ NORM: "SyncBN"
+ FPN:
+ IN_FEATURES: ["level3", "level4", "level5"]
+OUTPUT_DIR: "output/blendmask/DLA_34_syncbn_4x"
diff --git a/AdelaiDet/configs/BlendMask/Panoptic/Base-Panoptic.yaml b/AdelaiDet/configs/BlendMask/Panoptic/Base-Panoptic.yaml
new file mode 100755
index 0000000..ffd7ff0
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Panoptic/Base-Panoptic.yaml
@@ -0,0 +1,16 @@
+_BASE_: "../Base-BlendMask.yaml"
+MODEL:
+ RESNETS:
+ OUT_FEATURES: ["res2", "res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ SEM_SEG_HEAD:
+ LOSS_WEIGHT: 0.5
+ PANOPTIC_FPN:
+ COMBINE:
+ ENABLED: True
+ INSTANCES_CONFIDENCE_THRESH: 0.45
+ OVERLAP_THRESH: 0.4
+DATASETS:
+ TRAIN: ("coco_2017_train_panoptic_separated",)
+ TEST: ("coco_2017_val_panoptic_separated",)
diff --git a/AdelaiDet/configs/BlendMask/Panoptic/R_101_3x.yaml b/AdelaiDet/configs/BlendMask/Panoptic/R_101_3x.yaml
new file mode 100755
index 0000000..c5afd60
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Panoptic/R_101_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-Panoptic.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/panoptic/blendmask/R_101_3x"
diff --git a/AdelaiDet/configs/BlendMask/Panoptic/R_101_dcni3_5x.yaml b/AdelaiDet/configs/BlendMask/Panoptic/R_101_dcni3_5x.yaml
new file mode 100755
index 0000000..45bc89c
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Panoptic/R_101_dcni3_5x.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-Panoptic.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ DEFORM_MODULATED: True
+ DEFORM_INTERVAL: 3
+SOLVER:
+ STEPS: (280000, 360000)
+ MAX_ITER: 400000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 864)
+ MIN_SIZE_TRAIN_SAMPLING: "range"
+ MAX_SIZE_TRAIN: 1333
+ CROP:
+ ENABLED: True
+OUTPUT_DIR: "output/panoptic/blendmask/R_101_dcni3_5x"
diff --git a/AdelaiDet/configs/BlendMask/Panoptic/R_50_1x.yaml b/AdelaiDet/configs/BlendMask/Panoptic/R_50_1x.yaml
new file mode 100755
index 0000000..24f9e99
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Panoptic/R_50_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-Panoptic.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+OUTPUT_DIR: "output/panoptic/blendmask/R_50_1x"
diff --git a/AdelaiDet/configs/BlendMask/Panoptic/R_50_3x.yaml b/AdelaiDet/configs/BlendMask/Panoptic/R_50_3x.yaml
new file mode 100755
index 0000000..9de5502
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Panoptic/R_50_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-Panoptic.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/panoptic/blendmask/R_50_3x"
diff --git a/AdelaiDet/configs/BlendMask/Panoptic/R_50_dcni3_5x.yaml b/AdelaiDet/configs/BlendMask/Panoptic/R_50_dcni3_5x.yaml
new file mode 100755
index 0000000..b928f1d
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Panoptic/R_50_dcni3_5x.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-Panoptic.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ DEFORM_MODULATED: True
+ DEFORM_INTERVAL: 3
+SOLVER:
+ STEPS: (280000, 360000)
+ MAX_ITER: 400000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 864)
+ MIN_SIZE_TRAIN_SAMPLING: "range"
+ MAX_SIZE_TRAIN: 1440
+ CROP:
+ ENABLED: True
+OUTPUT_DIR: "output/panoptic/blendmask/R_50_dcni3_5x"
diff --git a/AdelaiDet/configs/BlendMask/Person/Base-Person.yaml b/AdelaiDet/configs/BlendMask/Person/Base-Person.yaml
new file mode 100755
index 0000000..f7c8be0
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Person/Base-Person.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-BlendMask.yaml"
+MODEL:
+ BASIS_MODULE:
+ NUM_CLASSES: 1
+ FCOS:
+ NUM_CLASSES: 1
+DATASETS:
+ TRAIN: ("pic_person_train",)
+ TEST: ("pic_person_val",)
diff --git a/AdelaiDet/configs/BlendMask/Person/R_50_1x.yaml b/AdelaiDet/configs/BlendMask/Person/R_50_1x.yaml
new file mode 100755
index 0000000..0b044c6
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/Person/R_50_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-Person.yaml"
+MODEL:
+ WEIGHTS: "https://cloudstor.aarnet.edu.au/plus/s/9u1cG2zXvEva5SM/download#R_50_3x.pth"
+ RESNETS:
+ DEPTH: 50
+OUTPUT_DIR: "output/person/blendmask/R_50_1x"
diff --git a/AdelaiDet/configs/BlendMask/README.md b/AdelaiDet/configs/BlendMask/README.md
new file mode 100755
index 0000000..6454bd1
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/README.md
@@ -0,0 +1,93 @@
+
+# BlendMask: Top-Down Meets Bottom-Up for Instance Segmentation
+
+ BlendMask: Top-Down Meets Bottom-Up for Instance Segmentation;
+ Hao Chen, Kunyang Sun, Zhi Tian, Chunhua Shen, Yongming Huang, and Youliang Yan;
+ In: Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR), 2020.
+
+[[`Paper`](https://arxiv.org/abs/2001.00309)] [[`BibTeX`](#citing-blendmask)]
+
+This project contains training BlendMask for instance segmentation and panoptic segmentation on COCO and configs for segmenting persons on PIC.
+
+## Quick Start
+
+### Demo
+
+```
+wget -O blendmask_r101_dcni3_5x.pth https://cloudstor.aarnet.edu.au/plus/s/vbnKnQtaGlw8TKv/download
+python demo/demo.py \
+ --config-file configs/BlendMask/R_101_dcni3_5x.yaml \
+ --input datasets/coco/val2017/000000005992.jpg \
+ --confidence-threshold 0.35 \
+ --opts MODEL.WEIGHTS blendmask_r101_dcni3_5x.pth
+```
+
+### Training and evaluation
+
+To train a model with "train_net.py", first
+setup the corresponding datasets following
+[datasets/README.md](https://github.com/facebookresearch/detectron2/blob/master/datasets/README.md),
+
+Then follow [these steps](https://github.com/aim-uofa/AdelaiDet/blob/master/datasets/README.md#blendmask-instance-detection) to generate blendmask format annotations for instance segmentation.
+
+then run:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BlendMask/R_50_1x.yaml \
+ --num-gpus 4 \
+ OUTPUT_DIR training_dir/blendmask_R_50_1x
+```
+To evaluate the model after training, run:
+
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BlendMask/R_50_1x.yaml \
+ --eval-only \
+ --num-gpus 4 \
+ OUTPUT_DIR training_dir/blendmask_R_50_1x \
+ MODEL.WEIGHTS training_dir/blendmask_R_50_1x/model_final.pth
+```
+
+## Models
+### COCO Instance Segmentation Baselines
+
+Model | Name | inf. time | box AP | mask AP | download
+--- |:---|:---:|:---:|:---:|:--:|
+Mask R-CNN |[R_50_1x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml) | 13 FPS | 38.6 | 35.2 |
+BlendMask |[R_50_1x](R_50_1x.yaml) | 14 FPS | 39.9 | 35.8 | [model](https://cloudstor.aarnet.edu.au/plus/s/zoxXPnr6Hw3OJgK/download)
+Mask R-CNN |[R_50_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml) | 13 FPS | 41.0 | 37.2 |
+BlendMask |[R_50_3x](R_50_3x.yaml) | 14 FPS | 42.7 | 37.8 | [model](https://cloudstor.aarnet.edu.au/plus/s/ZnaInHFEKst6mvg/download)
+Mask R-CNN |[R_101_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_FPN_3x.yaml) | 10 FPS | 42.9 | 38.6 |
+BlendMask |[R_101_3x](R_101_3x.yaml) | 11 FPS | 44.8 | 39.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/e4fXrliAcMtyEBy/download)
+BlendMask |[R_101_dcni3_5x](R_101_dcni3_5x.yaml) | 10 FPS | 46.8 | 41.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/vbnKnQtaGlw8TKv/download)
+
+### BlendMask Real-time Models
+
+Model | Name | inf. time | box AP | mask AP | download
+--- |:---|:---:|:---:|:---:|:---:
+Mask R-CNN |[550_R_50_3x](../RCNN/550_R_50_FPN_3x.yaml) | 16 FPS | 39.1 | 35.3 |
+BlendMask |[550_R_50_3x](550_R_50_3x.yaml) | 28 FPS | 38.7 | 34.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/R3Qintf7N8UCiIt/download)
+BlendMask |[RT_R_50_4x_syncbn_shtw](RT_R_50_4x_syncbn_shtw.yaml) | 31 FPS | 39.3 | 35.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/iNAQQmfOJlTaBRk/download)
+BlendMask |[RT_R_50_4x_bn-head_syncbn_shtw](RT_R_50_4x_bn-head_syncbn_shtw.yaml) | 31 FPS | 39.3 | 35.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/hI15l4ChWFqWvHp/download)
+BlendMask |[DLA_34_4x](DLA_34_syncbn_4x.yaml) | 32 FPS | 40.8 | 36.3 | [model](https://cloudstor.aarnet.edu.au/plus/s/JO2xPUGMSbUkKFZ/download)
+
+### COCO Panoptic Segmentation Baselines with BlendMask
+Model | Name | PQ | PQTh | PQSt | download
+--- |:---|:---:|:---:|:---:|:---:
+Panoptic FPN |[R_50_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_3x.yaml) | 41.5 | 48.3 | 31.2 |
+BlendMask |[R_50_3x](Panoptic/R_50_3x.yaml) | 42.5 | 49.5 | 32.0 | [model](https://cloudstor.aarnet.edu.au/plus/s/oDgi0826JOJXCr5/download)
+Panoptic FPN |[R_101_3x](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/panoptic_fpn_R_101_3x.yaml) | 43.0 | 49.7 | 32.9 |
+BlendMask |[R_101_3x](Panoptic/R_101_3x.yaml) | 44.3 | 51.6 | 33.2 | [model](https://cloudstor.aarnet.edu.au/plus/s/u6gZwj06MWDEkYe/download)
+BlendMask |[R_101_dcni3_5x](Panoptic/R_101_dcni3_5x.yaml) | 46.0 | 52.9 | 35.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/Jwp41WEzDdrhWsN/download)
+
+# Citing BlendMask
+If you use BlendMask in your research or wish to refer to the baseline results, please use the following BibTeX entries.
+```BibTeX
+@inproceedings{chen2020blendmask,
+ title = {{BlendMask}: Top-Down Meets Bottom-Up for Instance Segmentation},
+ author = {Chen, Hao and Sun, Kunyang and Tian, Zhi and Shen, Chunhua and Huang, Yongming and Yan, Youliang},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2020}
+}
+```
diff --git a/AdelaiDet/configs/BlendMask/RT_R_50_4x_bn-head_syncbn_shtw.yaml b/AdelaiDet/configs/BlendMask/RT_R_50_4x_bn-head_syncbn_shtw.yaml
new file mode 100755
index 0000000..a3bc210
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/RT_R_50_4x_bn-head_syncbn_shtw.yaml
@@ -0,0 +1,5 @@
+_BASE_: "RT_R_50_4x_syncbn_shtw.yaml"
+MODEL:
+ FCOS:
+ NORM: "SyncBN"
+OUTPUT_DIR: "output/blendmask/RT_R_50_4x_bn-head_syncbn_shtw"
diff --git a/AdelaiDet/configs/BlendMask/RT_R_50_4x_syncbn_shtw.yaml b/AdelaiDet/configs/BlendMask/RT_R_50_4x_syncbn_shtw.yaml
new file mode 100755
index 0000000..a984161
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/RT_R_50_4x_syncbn_shtw.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-RT.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ NORM: "SyncBN"
+ BACKBONE:
+ FREEZE_AT: -1
+ FCOS:
+ NUM_SHARE_CONVS: 3
+ NUM_CLS_CONVS: 0
+ NUM_BOX_CONVS: 0
+ BASIS_MODULE:
+ NUM_CONVS: 2
+OUTPUT_DIR: "output/blendmask/RT_R_50_4x_syncbn_shtw"
diff --git a/AdelaiDet/configs/BlendMask/R_101_3x.yaml b/AdelaiDet/configs/BlendMask/R_101_3x.yaml
new file mode 100755
index 0000000..835e629
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/R_101_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-BlendMask.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/blendmask/R_101_3x"
diff --git a/AdelaiDet/configs/BlendMask/R_101_dcni3_5x.yaml b/AdelaiDet/configs/BlendMask/R_101_dcni3_5x.yaml
new file mode 100755
index 0000000..b89b93f
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/R_101_dcni3_5x.yaml
@@ -0,0 +1,20 @@
+_BASE_: "Base-BlendMask.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ DEFORM_MODULATED: True
+ DEFORM_INTERVAL: 3
+SOLVER:
+ STEPS: (280000, 360000)
+ MAX_ITER: 400000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 864)
+ MIN_SIZE_TRAIN_SAMPLING: "range"
+ MAX_SIZE_TRAIN: 1440
+ CROP:
+ ENABLED: True
+TEST:
+ EVAL_PERIOD: 20000
+OUTPUT_DIR: "output/blendmask/R_101_dcni3_5x"
diff --git a/AdelaiDet/configs/BlendMask/R_50_1x.yaml b/AdelaiDet/configs/BlendMask/R_50_1x.yaml
new file mode 100755
index 0000000..646430a
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/R_50_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-BlendMask.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+OUTPUT_DIR: "output/blendmask/R_50_1x"
diff --git a/AdelaiDet/configs/BlendMask/R_50_3x.yaml b/AdelaiDet/configs/BlendMask/R_50_3x.yaml
new file mode 100755
index 0000000..f4acd8d
--- /dev/null
+++ b/AdelaiDet/configs/BlendMask/R_50_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-BlendMask.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/blendmask/R_50_3x"
diff --git a/AdelaiDet/configs/BoxInst/Base-BoxInst.yaml b/AdelaiDet/configs/BoxInst/Base-BoxInst.yaml
new file mode 100755
index 0000000..0ae6fea
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/Base-BoxInst.yaml
@@ -0,0 +1,35 @@
+MODEL:
+ META_ARCHITECTURE: "CondInst"
+ MASK_ON: True
+ BACKBONE:
+ NAME: "build_fcos_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ PROPOSAL_GENERATOR:
+ NAME: "FCOS"
+ FCOS:
+ THRESH_WITH_CTR: True
+ USE_SCALE: True
+ CONDINST:
+ TOPK_PROPOSALS_PER_IM: 64
+ MASK_BRANCH:
+ OUT_CHANNELS: 16
+ BOXINST:
+ ENABLED: True
+ BOTTOM_PIXELS_REMOVED: 10
+ PAIRWISE:
+ SIZE: 3
+ DILATION: 2
+ COLOR_THRESH: 0.3
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.01
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
diff --git a/AdelaiDet/configs/BoxInst/MS_R_101_1x.yaml b/AdelaiDet/configs/BoxInst/MS_R_101_1x.yaml
new file mode 100755
index 0000000..a3ab902
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_101_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+OUTPUT_DIR: "output/boxinst_MS_R_101_1x"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_101_3x.yaml b/AdelaiDet/configs/BoxInst/MS_R_101_3x.yaml
new file mode 100755
index 0000000..eb2b32c
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_101_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/boxinst_MS_R_101_3x"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_101_BiFPN_3x.yaml b/AdelaiDet/configs/BoxInst/MS_R_101_BiFPN_3x.yaml
new file mode 100755
index 0000000..e2cdca1
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_101_BiFPN_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 101
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/boxinst_MS_R_101_3x_bifpn"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_101_BiFPN_dcni3_3x.yaml b/AdelaiDet/configs/BoxInst/MS_R_101_BiFPN_dcni3_3x.yaml
new file mode 100755
index 0000000..d9a1665
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_101_BiFPN_dcni3_3x.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 101
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ DEFORM_MODULATED: True
+ DEFORM_INTERVAL: 3
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/boxinst_MS_R_101_BiFPN_dcni3_3x"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_50_1x.yaml b/AdelaiDet/configs/BoxInst/MS_R_50_1x.yaml
new file mode 100755
index 0000000..0a0839f
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_50_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+OUTPUT_DIR: "output/boxinst_MS_R_50_1x"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_50_3x.yaml b/AdelaiDet/configs/BoxInst/MS_R_50_3x.yaml
new file mode 100755
index 0000000..2764947
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_50_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/boxinst_MS_R_50_3x"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_50_BiFPN_1x.yaml b/AdelaiDet/configs/BoxInst/MS_R_50_BiFPN_1x.yaml
new file mode 100755
index 0000000..8f9f689
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_50_BiFPN_1x.yaml
@@ -0,0 +1,12 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 50
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+OUTPUT_DIR: "output/boxinst_MS_R_50_1x_bifpn"
diff --git a/AdelaiDet/configs/BoxInst/MS_R_50_BiFPN_3x.yaml b/AdelaiDet/configs/BoxInst/MS_R_50_BiFPN_3x.yaml
new file mode 100755
index 0000000..7888f2b
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/MS_R_50_BiFPN_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-BoxInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 50
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/boxinst_MS_R_50_3x_bifpn"
diff --git a/AdelaiDet/configs/BoxInst/README.md b/AdelaiDet/configs/BoxInst/README.md
new file mode 100755
index 0000000..93cd716
--- /dev/null
+++ b/AdelaiDet/configs/BoxInst/README.md
@@ -0,0 +1,71 @@
+# BoxInst: High-Performance Instance Segmentation with Box Annotations
+
+ BoxInst: High-Performance Instance Segmentation with Box Annotations;
+ Zhi Tian, Chunhua Shen, Xinlong Wang and Hao Chen;
+ In: Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR), 2021.
+ arXiv preprint arXiv:2012.02310
+
+[[`Paper`](https://arxiv.org/abs/2012.02310)] [[`BibTeX`](#citing-boxinst)] [[`Video Demo`](https://www.youtube.com/watch?v=NuF8NAYf5L8)]
+
+
+# Installation & Quick Start
+First, follow the [default instruction](../../README.md#Installation) to install the project and [datasets/README.md](https://github.com/facebookresearch/detectron2/blob/master/datasets/README.md)
+set up the datasets (e.g., MS-COCO).
+
+For demo, run the following command lines:
+```
+wget https://cloudstor.aarnet.edu.au/plus/s/Aabn3BEuq4HKiNK/download -O BoxInst_MS_R_50_3x.pth
+python demo/demo.py \
+ --config-file configs/BoxInst/MS_R_50_3x.yaml \
+ --input input1.jpg input2.jpg \
+ --opts MODEL.WEIGHTS BoxInst_MS_R_50_3x.pth
+```
+
+For training on COCO, run:
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BoxInst/MS_R_50_1x.yaml \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/BoxInst_MS_R_50_1x
+```
+
+For evaluation on COCO, run:
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/BoxInst/MS_R_50_1x.yaml \
+ --eval-only \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/BoxInst_MS_R_50_1x \
+ MODEL.WEIGHTS training_dir/BoxInst_MS_R_50_1x/model_final.pth
+```
+
+
+## Models
+### COCO Instance Segmentation Baselines with [BoxInst](https://arxiv.org/abs/2012.02310)
+
+Only **box annotations** are used during training.
+
+Name | inf. time | box AP | mask AP | mask AP (test-dev)| download
+--- |:---:|:---:|:---:|:---:|:---:
+[BoxInst_MS_R_50_1x](MS_R_50_1x.yaml) | 14 FPS | 39.4 | 30.7 | - | [model](https://cloudstor.aarnet.edu.au/plus/s/odj8VwqgRT8TMsR/download)
+[BoxInst_MS_R_50_3x](MS_R_50_3x.yaml) | 14 FPS | 41.5 | 31.8 | 32.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/Aabn3BEuq4HKiNK/download)
+[BoxInst_MS_R_101_1x](MS_R_101_1x.yaml) | 11 FPS | 41.4 | 32.2 | 32.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/FiPXJJ1GdJtdO0w/download)
+[BoxInst_MS_R_101_3x](MS_R_101_3x.yaml) | 11 FPS | 43.3 | 33.0 | 33.2 | [model](https://cloudstor.aarnet.edu.au/plus/s/8CcXfqKpPFc4bJ4/download)
+[BoxInst_MS_R_101_BiFPN_3x](MS_R_101_BiFPN_3x.yaml) | 10 FPS | 45.4 | 34.1 | 33.9 | [model](https://cloudstor.aarnet.edu.au/plus/s/qGGrOlYgTqIur7B/download)
+[BoxInst_MS_R_101_BiFPN_dcni3_3x](MS_R_101_BiFPN_dcni3_3x.yaml) | 8 FPS | 46.4 | 34.8 | 35.0 | [model](https://cloudstor.aarnet.edu.au/plus/s/e8hivzBFhadkEfc/download)
+
+Disclaimer:
+- All models are trained with multi-scale data augmentation. Inference time is measured on a single NVIDIA 1080Ti with batch size 1.
+- This is a reimplementation. Thus, the numbers might be slightly different from the ones reported in our original paper.
+
+
+# Citing BoxInst
+If you use BoxInst in your research or wish to refer to the baseline results, please use the following BibTeX entries.
+```BibTeX
+@inproceedings{tian2020boxinst,
+ title = {{BoxInst}: High-Performance Instance Segmentation with Box Annotations},
+ author = {Tian, Zhi and Shen, Chunhua and Wang, Xinlong and Chen, Hao},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2021}
+}
+```
diff --git a/AdelaiDet/configs/CondInst/Base-CondInst.yaml b/AdelaiDet/configs/CondInst/Base-CondInst.yaml
new file mode 100755
index 0000000..264ba64
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/Base-CondInst.yaml
@@ -0,0 +1,26 @@
+MODEL:
+ META_ARCHITECTURE: "CondInst"
+ MASK_ON: True
+ BACKBONE:
+ NAME: "build_fcos_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ PROPOSAL_GENERATOR:
+ NAME: "FCOS"
+ FCOS:
+ THRESH_WITH_CTR: True
+ USE_SCALE: True
+ CONDINST:
+ MAX_PROPOSALS: 500
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.01
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
diff --git a/AdelaiDet/configs/CondInst/MS_R_101_1x.yaml b/AdelaiDet/configs/CondInst/MS_R_101_1x.yaml
new file mode 100755
index 0000000..e8b7304
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_101_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+OUTPUT_DIR: "output/condinst_MS_R_101_1x"
diff --git a/AdelaiDet/configs/CondInst/MS_R_101_3x.yaml b/AdelaiDet/configs/CondInst/MS_R_101_3x.yaml
new file mode 100755
index 0000000..d87efba
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_101_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_101_3x"
diff --git a/AdelaiDet/configs/CondInst/MS_R_101_3x_sem.yaml b/AdelaiDet/configs/CondInst/MS_R_101_3x_sem.yaml
new file mode 100755
index 0000000..62cb2ad
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_101_3x_sem.yaml
@@ -0,0 +1,12 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+ CONDINST:
+ MASK_BRANCH:
+ SEMANTIC_LOSS_ON: True
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_101_3x_sem"
diff --git a/AdelaiDet/configs/CondInst/MS_R_101_BiFPN_3x.yaml b/AdelaiDet/configs/CondInst/MS_R_101_BiFPN_3x.yaml
new file mode 100755
index 0000000..dfc5510
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_101_BiFPN_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 101
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_101_3x_bifpn"
diff --git a/AdelaiDet/configs/CondInst/MS_R_101_BiFPN_3x_sem.yaml b/AdelaiDet/configs/CondInst/MS_R_101_BiFPN_3x_sem.yaml
new file mode 100755
index 0000000..4d19e33
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_101_BiFPN_3x_sem.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 101
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+ CONDINST:
+ MASK_BRANCH:
+ SEMANTIC_LOSS_ON: True
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_101_3x_bifpn_sem"
diff --git a/AdelaiDet/configs/CondInst/MS_R_50_1x.yaml b/AdelaiDet/configs/CondInst/MS_R_50_1x.yaml
new file mode 100755
index 0000000..5271f7f
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_50_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+OUTPUT_DIR: "output/condinst_MS_R_50_1x"
diff --git a/AdelaiDet/configs/CondInst/MS_R_50_3x.yaml b/AdelaiDet/configs/CondInst/MS_R_50_3x.yaml
new file mode 100755
index 0000000..fd115a6
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_50_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_50_3x"
diff --git a/AdelaiDet/configs/CondInst/MS_R_50_3x_sem.yaml b/AdelaiDet/configs/CondInst/MS_R_50_3x_sem.yaml
new file mode 100755
index 0000000..0a8f9fb
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_50_3x_sem.yaml
@@ -0,0 +1,12 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ CONDINST:
+ MASK_BRANCH:
+ SEMANTIC_LOSS_ON: True
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_50_3x_sem"
diff --git a/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_1x.yaml b/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_1x.yaml
new file mode 100755
index 0000000..ffb1890
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_1x.yaml
@@ -0,0 +1,12 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 50
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+OUTPUT_DIR: "output/condinst_MS_R_50_1x_bifpn"
diff --git a/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_3x.yaml b/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_3x.yaml
new file mode 100755
index 0000000..a8c4f33
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 50
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_50_3x_bifpn"
diff --git a/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_3x_sem.yaml b/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_3x_sem.yaml
new file mode 100755
index 0000000..2fec59d
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/MS_R_50_BiFPN_3x_sem.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-CondInst.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ NAME: "build_fcos_resnet_bifpn_backbone"
+ RESNETS:
+ DEPTH: 50
+ BiFPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ OUT_CHANNELS: 160
+ NORM: "SyncBN"
+ CONDINST:
+ MASK_BRANCH:
+ SEMANTIC_LOSS_ON: True
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/condinst_MS_R_50_3x_bifpn_sem"
diff --git a/AdelaiDet/configs/CondInst/README.md b/AdelaiDet/configs/CondInst/README.md
new file mode 100755
index 0000000..9541392
--- /dev/null
+++ b/AdelaiDet/configs/CondInst/README.md
@@ -0,0 +1,86 @@
+# Conditional Convolutions for Instance Segmentation (Oral)
+
+ Conditional Convolutions for Instance Segmentation;
+ Zhi Tian, Chunhua Shen and Hao Chen;
+ In: Proc. European Conference on Computer Vision (ECCV), 2020.
+ arXiv preprint arXiv:2003.05664
+
+[[`Paper`](https://arxiv.org/abs/2003.05664)] [[`BibTeX`](#citing-condinst)]
+
+
+# Installation & Quick Start
+First, follow the [default instruction](../../README.md#Installation) to install the project and [datasets/README.md](https://github.com/facebookresearch/detectron2/blob/master/datasets/README.md)
+set up the datasets (e.g., MS-COCO).
+
+For demo, run the following command lines:
+```
+wget https://cloudstor.aarnet.edu.au/plus/s/M8nNxSR5iNP4qyO/download -O CondInst_MS_R_101_3x_sem.pth
+python demo/demo.py \
+ --config-file configs/CondInst/MS_R_101_3x_sem.yaml \
+ --input input1.jpg input2.jpg \
+ --opts MODEL.WEIGHTS CondInst_MS_R_101_3x_sem.pth
+```
+
+For training on COCO, run:
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/CondInst/MS_R_50_1x.yaml \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/CondInst_MS_R_50_1x
+```
+
+For evaluation on COCO, run:
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/CondInst/MS_R_50_1x.yaml \
+ --eval-only \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/CondInst_MS_R_50_1x \
+ MODEL.WEIGHTS training_dir/CondInst_MS_R_50_1x/model_final.pth
+```
+
+
+## Models
+### COCO Instance Segmentation Baselines with [CondInst](https://arxiv.org/abs/2003.05664)
+
+Name | inf. time | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:
+[CondInst_MS_R_50_1x](MS_R_50_1x.yaml) | 14 FPS | 39.7 | 35.7 | [model](https://cloudstor.aarnet.edu.au/plus/s/Trx1r4tLJja7sLT/download)
+[CondInst_MS_R_50_3x](MS_R_50_3x.yaml) | 14 FPS | 41.9 | 37.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/T3OGVBiaSVLvo5E/download)
+[CondInst_MS_R_101_3x](MS_R_101_3x.yaml) | 11 FPS | 43.3 | 38.6 | [model](https://cloudstor.aarnet.edu.au/plus/s/vWLiYm8OnrTSUD2/download)
+
+With an auxiliary semantic segmentation task (set `MODEL.CONDINST.MASK_BRANCH.SEMANTIC_LOSS_ON = True` to enable it):
+
+Name | inf. time | box AP | mask AP | mask AP (test-dev) | download
+--- |:---:|:---:|:---:|:---:|:---:
+[CondInst_MS_R_50_3x_sem](MS_R_50_3x_sem.yaml) | 14 FPS | 42.6 | 38.2 | 38.7 | [model](https://cloudstor.aarnet.edu.au/plus/s/75Ag8VvC6WedVNh/download)
+[CondInst_MS_R_101_3x_sem](MS_R_101_3x_sem.yaml) | 11 FPS | 44.6 | 39.8 | 40.1 | [model](https://cloudstor.aarnet.edu.au/plus/s/M8nNxSR5iNP4qyO/download)
+
+With BiFPN:
+
+Name | inf. time | box AP | mask AP | download
+--- |:---:|:---:|:---:|:---:
+[CondInst_MS_R_50_BiFPN_1x](MS_R_50_BiFPN_1x.yaml) | 13 FPS | 42.5 | 37.3 | [model](https://cloudstor.aarnet.edu.au/plus/s/RyCG82WhTop99j2/download)
+[CondInst_MS_R_50_BiFPN_3x](MS_R_50_BiFPN_3x.yaml) | 13 FPS | 44.3 | 38.9 | [model](https://cloudstor.aarnet.edu.au/plus/s/W9ZCcxJF0P5NhJQ/download)
+[CondInst_MS_R_50_BiFPN_3x_sem](MS_R_50_BiFPN_3x_sem.yaml) | 13 FPS | 44.7 | 39.4 | [model](https://cloudstor.aarnet.edu.au/plus/s/9cAHjZtdaAGnb2Q/download)
+[CondInst_MS_R_101_BiFPN_3x](MS_R_101_BiFPN_3x.yaml) | 10 FPS | 45.3 | 39.6 | [model](https://cloudstor.aarnet.edu.au/plus/s/HyB0O0D7hfpUC2n/download)
+[CondInst_MS_R_101_BiFPN_3x_sem](MS_R_101_BiFPN_3x_sem.yaml) | 10 FPS | 45.7 | 40.2 | [model](https://cloudstor.aarnet.edu.au/plus/s/2p1ashxl54Su8vv/download)
+
+
+*Disclaimer:*
+
+- All models are trained with multi-scale data augmentation. Inference time is measured on a single NVIDIA 1080Ti with batch size 1.
+- The final mask's resolution is 1/4 of the input image (i.e., `MODEL.CONDINST.MASK_OUT_STRIDE = 4`, which is enough on MS-COCO and different from our original paper. In the paper, we used `MODEL.CONDINST.MASK_OUT_STRIDE = 2`. If you want high-resolution mask results, please reduce it.
+- This is a reimplementation. Thus, the numbers are slightly different from our original paper (within 0.1% in mask AP).
+
+
+# Citing CondInst
+If you use CondInst in your research or wish to refer to the baseline results, please use the following BibTeX entries.
+```BibTeX
+@inproceedings{tian2020conditional,
+ title = {Conditional Convolutions for Instance Segmentation},
+ author = {Tian, Zhi and Shen, Chunhua and Chen, Hao},
+ booktitle = {Proc. Eur. Conf. Computer Vision (ECCV)},
+ year = {2020}
+}
+```
diff --git a/AdelaiDet/configs/DenseCL/FCOS_R50_1x.yaml b/AdelaiDet/configs/DenseCL/FCOS_R50_1x.yaml
new file mode 100755
index 0000000..bdcd931
--- /dev/null
+++ b/AdelaiDet/configs/DenseCL/FCOS_R50_1x.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../FCOS-Detection/Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ FREEZE_AT: 0
+ RESNETS:
+ DEPTH: 50
+ NORM: "SyncBN"
+ FPN:
+ NORM: "SyncBN"
+TEST:
+ PRECISE_BN:
+ ENABLED: True
diff --git a/AdelaiDet/configs/DenseCL/FCOS_R50_1x_DenseCL.yaml b/AdelaiDet/configs/DenseCL/FCOS_R50_1x_DenseCL.yaml
new file mode 100755
index 0000000..36b3180
--- /dev/null
+++ b/AdelaiDet/configs/DenseCL/FCOS_R50_1x_DenseCL.yaml
@@ -0,0 +1,9 @@
+_BASE_: "FCOS_R50_1x.yaml"
+MODEL:
+ PIXEL_MEAN: [123.675, 116.280, 103.530]
+ PIXEL_STD: [58.395, 57.120, 57.375]
+ WEIGHTS: "See Instructions"
+ RESNETS:
+ STRIDE_IN_1X1: False
+INPUT:
+ FORMAT: "RGB"
diff --git a/AdelaiDet/configs/DenseCL/README.md b/AdelaiDet/configs/DenseCL/README.md
new file mode 100755
index 0000000..cbf1307
--- /dev/null
+++ b/AdelaiDet/configs/DenseCL/README.md
@@ -0,0 +1,92 @@
+# Dense Contrastive Learning for Self-Supervised Visual Pre-Training
+
+Here we provide instructions and results for applying DenseCL pre-trained models to AdelaiDet. Please refer to [https://git.io/DenseCL
+](https://git.io/DenseCL
+) for the pre-training code.
+
+> [**Dense Contrastive Learning for Self-Supervised Visual Pre-Training**](https://arxiv.org/abs/2011.09157),
+> Xinlong Wang, Rufeng Zhang, Chunhua Shen, Tao Kong, Lei Li
+> In: Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR), 2021, **Oral**
+> *arXiv preprint ([arXiv 2011.09157](https://arxiv.org/abs/2011.09157))*
+
+
+# Installation
+First, follow the [default instruction](../../README.md#Installation) to install the project and [datasets/README.md](https://github.com/facebookresearch/detectron2/blob/master/datasets/README.md)
+set up the datasets (e.g., MS-COCO).
+
+
+# DenseCL Pre-trained Models
+pre-train method | pre-train dataset | backbone | #epoch | Link
+--- |:---:|:---:|:---:|:---:
+DenseCL | COCO | ResNet-50 | 800 | [download](https://cloudstor.aarnet.edu.au/plus/s/W5oDyYB218xz625/download)
+DenseCL | COCO | ResNet-50 | 1600 | [download](https://cloudstor.aarnet.edu.au/plus/s/3GapXiWuVAzdKwJ/download)
+DenseCL | ImageNet | ResNet-50 | 200 | [download](https://cloudstor.aarnet.edu.au/plus/s/hdAg5RYm8NNM2QP/download)
+DenseCL | ImageNet | ResNet-101 | 200 | [download](https://cloudstor.aarnet.edu.au/plus/s/4sugyvuBOiMXXnC/download)
+
+
+# Usage
+
+## Download the pre-trained model
+```
+PRETRAIN_DIR=./
+wget https://cloudstor.aarnet.edu.au/plus/s/hdAg5RYm8NNM2QP/download -O ${PRETRAIN_DIR}/densecl_r50_imagenet_200ep.pkl
+```
+
+## Convert it to detectron2's format
+Use [convert-pretrain-to-detectron2.py](https://github.com/WXinlong/DenseCL/blob/main/benchmarks/detection/convert-pretrain-to-detectron2.py) to convert the pre-trained backbone weights:
+```
+WEIGHT_FILE=${PRETRAIN_DIR}/densecl_r50_imagenet_200ep.pth
+OUTPUT_FILE=${PRETRAIN_DIR}/densecl_r50_imagenet_200ep.pkl
+python convert-pretrain-to-detectron2.py ${WEIGHT_FILE} ${OUTPUT_FILE}
+```
+
+## Train the downstream models
+
+For training a SOLOv2, run:
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/DenseCL/SOLOv2_R50_1x_DenseCL.yaml \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/SOLOv2_R50_1x_DenseCL \
+ MODEL.WEIGHTS ${PRETRAIN_DIR}/densecl_r50_imagenet_200ep.pkl
+```
+
+For training a FCOS, run:
+```
+OMP_NUM_THREADS=1 python tools/train_net.py \
+ --config-file configs/DenseCL/FCOS_R50_1x_DenseCL.yaml \
+ --num-gpus 8 \
+ OUTPUT_DIR training_dir/FCOS_R50_1x_DenseCL \
+ MODEL.WEIGHTS ${PRETRAIN_DIR}/densecl_r50_imagenet_200ep.pkl
+```
+
+
+# Performance
+## SOLOv2 on COCO Instance Segmentation
+
+pre-train method | pre-train dataset | mask AP |
+--- |:---:|:---:|
+Supervised | ImageNet | 35.2
+MoCo-v2 | ImageNet | 35.2
+DenseCL | ImageNet | 35.7 (+0.5)
+
+## FCOS on COCO Object Detection
+
+pre-train method | pre-train dataset | box AP |
+--- |:---:|:---:|
+Supervised | ImageNet | 39.9
+MoCo-v2 | ImageNet | 40.3
+DenseCL | ImageNet | 40.9 (+1.0)
+
+
+
+# Citation
+Please consider citing our paper in your publications if the project helps your research. BibTeX reference is as follows.
+```BibTeX
+@inproceedings{wang2020densecl,
+ title = {Dense Contrastive Learning for Self-Supervised Visual Pre-Training},
+ author = {Wang, Xinlong and Zhang, Rufeng and Shen, Chunhua and Kong, Tao and Li, Lei},
+ booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)},
+ year = {2021}
+}
+```
diff --git a/AdelaiDet/configs/DenseCL/SOLOv2_R50_1x.yaml b/AdelaiDet/configs/DenseCL/SOLOv2_R50_1x.yaml
new file mode 100755
index 0000000..eb9db49
--- /dev/null
+++ b/AdelaiDet/configs/DenseCL/SOLOv2_R50_1x.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../SOLOv2/Base-SOLOv2.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ BACKBONE:
+ FREEZE_AT: 0
+ RESNETS:
+ DEPTH: 50
+ NORM: "SyncBN"
+ FPN:
+ NORM: "SyncBN"
+TEST:
+ PRECISE_BN:
+ ENABLED: True
diff --git a/AdelaiDet/configs/DenseCL/SOLOv2_R50_1x_DenseCL.yaml b/AdelaiDet/configs/DenseCL/SOLOv2_R50_1x_DenseCL.yaml
new file mode 100755
index 0000000..9b6ceb0
--- /dev/null
+++ b/AdelaiDet/configs/DenseCL/SOLOv2_R50_1x_DenseCL.yaml
@@ -0,0 +1,9 @@
+_BASE_: "SOLOv2_R50_1x.yaml"
+MODEL:
+ PIXEL_MEAN: [123.675, 116.280, 103.530]
+ PIXEL_STD: [58.395, 57.120, 57.375]
+ WEIGHTS: "See Instructions"
+ RESNETS:
+ STRIDE_IN_1X1: False
+INPUT:
+ FORMAT: "RGB"
diff --git a/AdelaiDet/configs/FCOS-Detection/Base-FCOS.yaml b/AdelaiDet/configs/FCOS-Detection/Base-FCOS.yaml
new file mode 100755
index 0000000..11539a3
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/Base-FCOS.yaml
@@ -0,0 +1,21 @@
+MODEL:
+ META_ARCHITECTURE: "OneStageDetector"
+ BACKBONE:
+ NAME: "build_fcos_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ PROPOSAL_GENERATOR:
+ NAME: "FCOS"
+ # PIXEL_MEAN: [102.9801, 115.9465, 122.7717]
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.01 # Note that RetinaNet uses a different default learning rate
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
diff --git a/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn.yaml b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn.yaml
new file mode 100755
index 0000000..a19af65
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn.yaml
@@ -0,0 +1,25 @@
+_BASE_: "../Base-FCOS.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ BACKBONE:
+ NAME: "build_fcos_dla_fpn_backbone"
+ FREEZE_AT: -1
+ WEIGHTS: "http://dl.yf.io/dla/models/imagenet/dla34-ba72cf86.pth"
+ DLA:
+ CONV_BODY: "DLA34"
+ NORM: "SyncBN"
+ FPN:
+ IN_FEATURES: ["level3", "level4", "level5"]
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
+OUTPUT_DIR: "output/fcos/FCOS_RT_MS_DLA_34_4x_syncbn"
diff --git a/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_bn_head.yaml b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_bn_head.yaml
new file mode 100755
index 0000000..c3a58ad
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_bn_head.yaml
@@ -0,0 +1,26 @@
+_BASE_: "../Base-FCOS.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ BACKBONE:
+ NAME: "build_fcos_dla_fpn_backbone"
+ FREEZE_AT: -1
+ WEIGHTS: "http://dl.yf.io/dla/models/imagenet/dla34-ba72cf86.pth"
+ DLA:
+ CONV_BODY: "DLA34"
+ NORM: "SyncBN"
+ FPN:
+ IN_FEATURES: ["level3", "level4", "level5"]
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
+OUTPUT_DIR: "output/fcos/FCOS_RT_MS_DLA_34_4x_syncbn_bn_head"
diff --git a/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers.yaml b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers.yaml
new file mode 100755
index 0000000..84db2d1
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers.yaml
@@ -0,0 +1,28 @@
+_BASE_: "../Base-FCOS.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ BACKBONE:
+ NAME: "build_fcos_dla_fpn_backbone"
+ FREEZE_AT: -1
+ WEIGHTS: "http://dl.yf.io/dla/models/imagenet/dla34-ba72cf86.pth"
+ DLA:
+ CONV_BODY: "DLA34"
+ NORM: "SyncBN"
+ FPN:
+ IN_FEATURES: ["level3", "level4", "level5"]
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+ NUM_SHARE_CONVS: 4
+ NUM_BOX_CONVS: 0
+ NUM_CLS_CONVS: 0
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
+OUTPUT_DIR: "output/fcos/FCOS_RT_MS_DLA_34_4x_syncbn_shared_towers"
diff --git a/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers_bn_head.yaml b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers_bn_head.yaml
new file mode 100755
index 0000000..773116a
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers_bn_head.yaml
@@ -0,0 +1,29 @@
+_BASE_: "../Base-FCOS.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ BACKBONE:
+ NAME: "build_fcos_dla_fpn_backbone"
+ FREEZE_AT: -1
+ WEIGHTS: "http://dl.yf.io/dla/models/imagenet/dla34-ba72cf86.pth"
+ DLA:
+ CONV_BODY: "DLA34"
+ NORM: "SyncBN"
+ FPN:
+ IN_FEATURES: ["level3", "level4", "level5"]
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+ NUM_SHARE_CONVS: 4
+ NUM_BOX_CONVS: 0
+ NUM_CLS_CONVS: 0
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
+OUTPUT_DIR: "output/fcos/FCOS_RT_MS_DLA_34_4x_syncbn_shared_towers_bn_head"
diff --git a/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_R_50_4x_syncbn.yaml b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_R_50_4x_syncbn.yaml
new file mode 100755
index 0000000..5f7a90e
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_R_50_4x_syncbn.yaml
@@ -0,0 +1,20 @@
+_BASE_: "../Base-FCOS.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ NORM: "SyncBN"
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
+OUTPUT_DIR: "output/fcos/FCOS_RT_MS_R_50_4x_syncbn"
diff --git a/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_R_50_4x_syncbn_bn_head.yaml b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_R_50_4x_syncbn_bn_head.yaml
new file mode 100755
index 0000000..97ff613
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/FCOS_RT/MS_R_50_4x_syncbn_bn_head.yaml
@@ -0,0 +1,21 @@
+_BASE_: "../Base-FCOS.yaml"
+INPUT:
+ MIN_SIZE_TRAIN: (256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608)
+ MAX_SIZE_TRAIN: 900
+ MAX_SIZE_TEST: 736
+ MIN_SIZE_TEST: 512
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ NORM: "SyncBN"
+ FCOS:
+ TOP_LEVELS: 0
+ SIZES_OF_INTEREST: [64, 128]
+ FPN_STRIDES: [8, 16, 32]
+ IN_FEATURES: ['p3', 'p4', 'p5']
+ NORM: "SyncBN"
+SOLVER:
+ STEPS: (300000, 340000)
+ MAX_ITER: 360000
+OUTPUT_DIR: "output/fcos/FCOS_RT_MS_R_50_4x_syncbn_bn_head"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_R_101_2x.yaml b/AdelaiDet/configs/FCOS-Detection/MS_R_101_2x.yaml
new file mode 100755
index 0000000..7ff14a8
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_R_101_2x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/R_101_2x"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_R_101_2x_iou.yaml b/AdelaiDet/configs/FCOS-Detection/MS_R_101_2x_iou.yaml
new file mode 100755
index 0000000..51199f5
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_R_101_2x_iou.yaml
@@ -0,0 +1,11 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+ FCOS:
+ BOX_QUALITY: "iou"
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/MS_R_101_2x_iou"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_R_50_2x.yaml b/AdelaiDet/configs/FCOS-Detection/MS_R_50_2x.yaml
new file mode 100755
index 0000000..d7184cb
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_R_50_2x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/R_50_2x"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_R_50_2x_iou.yaml b/AdelaiDet/configs/FCOS-Detection/MS_R_50_2x_iou.yaml
new file mode 100755
index 0000000..417a50f
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_R_50_2x_iou.yaml
@@ -0,0 +1,11 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ FCOS:
+ BOX_QUALITY: "iou"
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/MS_R_50_2x_iou"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x.yaml b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x.yaml
new file mode 100755
index 0000000..6e63ad5
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x.yaml
@@ -0,0 +1,13 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/X_101_2x"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2.yaml b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2.yaml
new file mode 100755
index 0000000..087c7b8
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2.yaml
@@ -0,0 +1,17 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+ DEFORM_ON_PER_STAGE: [False, False, True, True] # on Res4, Res5
+ DEFORM_MODULATED: True
+ FCOS:
+ USE_DEFORMABLE: True
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/MS_X_101_2x_dcnv2"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2_iou.yaml b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2_iou.yaml
new file mode 100755
index 0000000..5e76bd8
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_dcnv2_iou.yaml
@@ -0,0 +1,18 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+ DEFORM_ON_PER_STAGE: [False, False, True, True] # on Res4, Res5
+ DEFORM_MODULATED: True
+ FCOS:
+ USE_DEFORMABLE: True
+ BOX_QUALITY: "iou"
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/MS_X_101_2x_dcnv2_iou"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_iou.yaml b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_iou.yaml
new file mode 100755
index 0000000..91eff62
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_X_101_32x8d_2x_iou.yaml
@@ -0,0 +1,15 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+ FCOS:
+ BOX_QUALITY: "iou"
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/X_101_2x_iou"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_X_101_64x4d_2x.yaml b/AdelaiDet/configs/FCOS-Detection/MS_X_101_64x4d_2x.yaml
new file mode 100755
index 0000000..230698a
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_X_101_64x4d_2x.yaml
@@ -0,0 +1,13 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "catalog://ImageNetPretrained/FAIR/X-101-64x4d"
+ PIXEL_STD: [1.0, 1.0, 1.0]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 64
+ WIDTH_PER_GROUP: 4
+ DEPTH: 101
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/MS_X_101_64x4d_2x"
diff --git a/AdelaiDet/configs/FCOS-Detection/MS_X_101_64x4d_2x_dcnv2.yaml b/AdelaiDet/configs/FCOS-Detection/MS_X_101_64x4d_2x_dcnv2.yaml
new file mode 100755
index 0000000..41faeb3
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/MS_X_101_64x4d_2x_dcnv2.yaml
@@ -0,0 +1,17 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "catalog://ImageNetPretrained/FAIR/X-101-64x4d"
+ PIXEL_STD: [1.0, 1.0, 1.0]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 64
+ WIDTH_PER_GROUP: 4
+ DEPTH: 101
+ DEFORM_ON_PER_STAGE: [False, False, True, True] # on Res4, Res5
+ DEFORM_MODULATED: True
+ FCOS:
+ USE_DEFORMABLE: True
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000
+OUTPUT_DIR: "output/fcos/MS_X_101_64x4d_2x_dcnv2"
diff --git a/AdelaiDet/configs/FCOS-Detection/README.md b/AdelaiDet/configs/FCOS-Detection/README.md
new file mode 100755
index 0000000..2b7fb3d
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/README.md
@@ -0,0 +1,83 @@
+# FCOS: Fully Convolutional One-Stage Object Detection
+
+ FCOS: Fully Convolutional One-Stage Object Detection;
+ Zhi Tian, Chunhua Shen, Hao Chen, and Tong He;
+ In: Proc. Int. Conf. Computer Vision (ICCV), 2019.
+[arXiv preprint arXiv:1904.01355](https://arxiv.org/abs/1904.01355)
+
+ FCOS: A Simple and Strong Anchor-free Object Detector;
+ Zhi Tian, Chunhua Shen, Hao Chen, and Tong He;
+ IEEE T. Pattern Analysis and Machine Intelligence (TPAMI), 2021.
+[arXiv preprint arXiv:2006.09214](https://arxiv.org/abs/2006.09214)
+
+
+[`BibTeX`](#citing-fcos)
+
+# Installation & Quick Start
+No special setup needed. The [default instruction](../../README.md#Installation) would work.
+
+## Models
+### COCO Object Detecton Baselines with [FCOS](https://arxiv.org/abs/1904.01355)
+
+Name | inf. time | box AP | box AP (test-dev) | download
+--- |:---:|:---:|:---:|:---:
+[FCOS_R_50_1x](R_50_1x.yaml) | 16 FPS | 38.7 | [38.8](https://gist.github.com/tianzhi0549/1c8d115efaf1e49a4f390cce63ca69ca) | [model](https://cloudstor.aarnet.edu.au/plus/s/glqFc13cCoEyHYy/download)
+[FCOS_MS_R_50_2x](MS_R_50_2x.yaml) | 16 FPS | 41.0 | [41.4](https://gist.github.com/tianzhi0549/3ca076c2125891312dbf5ce932469e76) | [model](https://cloudstor.aarnet.edu.au/plus/s/reA6HVaGX47yKGV/download)
+[FCOS_MS_R_101_2x](MS_R_101_2x.yaml) | 12 FPS | 43.1 | [43.2](https://gist.github.com/tianzhi0549/d97994a5b72980ba94de25737d2d40cb) | [model](https://cloudstor.aarnet.edu.au/plus/s/M3UOT6JcyHy2QW1/download)
+[FCOS_MS_X_101_32x8d_2x](MS_X_101_32x8d_2x.yaml) | 6.6 FPS | 43.9 | [44.1](https://gist.github.com/tianzhi0549/3135d6e0fad24b07cc685fef660c5363) | [model](https://cloudstor.aarnet.edu.au/plus/s/R7H00WeWKZG45pP/download)
+[FCOS_MS_X_101_64x4d_2x](MS_X_101_64x4d_2x.yaml) | 6.1 FPS | 44.7 | [44.8](https://gist.github.com/tianzhi0549/b68f6500ec24e6b263c12c345a7b5c7b) | [model](https://cloudstor.aarnet.edu.au/plus/s/XOLUCzqKYckNII7/download)
+[FCOS_MS_X_101_32x8d_dcnv2_2x](MS_X_101_32x8d_2x_dcnv2.yaml) | 4.6 FPS | 46.6 | [46.6](https://gist.github.com/tianzhi0549/316e8feaa17bf0341e2effa485fb41c0) | [model](https://cloudstor.aarnet.edu.au/plus/s/TDsnYK8OXDTrafF/download)
+
+The following models use IoU (instead of "center-ness") to predict the box quality (setting `MODEL.FCOS.BOX_QUALITY = "iou"`).
+
+Name | inf. time | box AP | download
+--- |:---:|:---:|:---:
+[FCOS_R_50_1x_iou](R_50_1x_iou.yaml) | 16 FPS | 39.4 | [model](https://cloudstor.aarnet.edu.au/plus/s/LE6u0koeu0YlalU/download)
+[FCOS_MS_R_50_2x_iou](MS_R_50_2x_iou.yaml) | 16 FPS | 41.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/Qx7HeA0XCr2y6UW/download)
+[FCOS_MS_R_101_2x_iou](MS_R_101_2x_iou.yaml) | 12 FPS | 43.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/JJhntIKrvaS51et/download)
+[FCOS_MS_X_101_32x8d_2x_iou](MS_X_101_32x8d_2x_iou.yaml) | 6.6 FPS | 44.5 | [model](https://cloudstor.aarnet.edu.au/plus/s/iYAmR0pDwIme3ac/download)
+[FCOS_MS_X_101_32x8d_2x_dcnv2_iou](MS_X_101_32x8d_2x_dcnv2_iou.yaml) | 4.6 FPS | 47.4 | [model](https://cloudstor.aarnet.edu.au/plus/s/Ols6N8tarxUVEdF/download)
+
+*"MS": the models are trained with multi-scale data augmentation.*
+
+### FCOS Real-time Models
+
+Name | inf. time | box AP | box AP (test-dev) | download
+--- |:---:|:---:|:---:|:---:
+[FCOS_RT_MS_DLA_34_4x_shtw](FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers.yaml) | 52 FPS | 39.1 | [39.2](https://gist.github.com/tianzhi0549/9f56ceaec77e2eb4170b6cd18da2856c) | [model](https://cloudstor.aarnet.edu.au/plus/s/4vc3XwQezyhNvnB/download)
+[FCOS_RT_MS_DLA_34_4x](FCOS_RT/MS_DLA_34_4x_syncbn.yaml) | 46 FPS | 40.3 | [40.3](https://gist.github.com/tianzhi0549/338d8614beafe21b7af4dc5defc37d95) | [model](https://cloudstor.aarnet.edu.au/plus/s/zNPNyTkizaOOsUQ/download)
+[FCOS_RT_MS_R_50_4x](FCOS_RT/MS_R_50_4x_syncbn.yaml) | 38 FPS | 40.2 | [40.2](https://gist.github.com/tianzhi0549/5c7892831d9c03d615214a66e3af19f4) | [model](https://cloudstor.aarnet.edu.au/plus/s/TlnlXUr6lNNSyoZ/download)
+
+If you prefer BN in FCOS heads, please try the following models.
+
+Name | inf. time | box AP | box AP (test-dev) | download
+--- |:---:|:---:|:---:|:---:
+[FCOS_RT_MS_DLA_34_4x_shtw_bn](FCOS_RT/MS_DLA_34_4x_syncbn_shared_towers_bn_head.yaml) | 52 FPS | 38.9 | [39.1](https://gist.github.com/tianzhi0549/d87298bb7beb7c926a355708d05e9a0c) | [model](https://cloudstor.aarnet.edu.au/plus/s/rdmHHSs4oCg7l7U/download)
+[FCOS_RT_MS_DLA_34_4x_bn](FCOS_RT/MS_DLA_34_4x_syncbn_bn_head.yaml) | 48 FPS | 39.4 | [39.9](https://gist.github.com/tianzhi0549/6a7053943c96111134a81f3141d1b9b5) | [model](https://cloudstor.aarnet.edu.au/plus/s/T5httPVo1VndbD4/download)
+[FCOS_RT_MS_R_50_4x_bn](FCOS_RT/MS_R_50_4x_syncbn_bn_head.yaml) | 40 FPS | 39.3 | [39.7](https://gist.github.com/tianzhi0549/35869c1d00688b4d60cc8f7e7d91c94d) | [model](https://cloudstor.aarnet.edu.au/plus/s/dHNUNs0YxVhZAmg/download)
+
+*Inference time is measured on a NVIDIA 1080Ti with batch size 1. Real-time models use shorter side 512 for inference.*
+
+*Disclaimer:*
+
+If the number of foreground samples is small or unstable, please set [`MODEL.FCOS.LOSS_NORMALIZER_CLS`](https://github.com/aim-uofa/AdelaiDet/blob/586bf2d6d4a4d662956203675a108f79d7d0f3ce/adet/config/defaults.py#L47) to `"moving_fg"`, which is more stable than normalizing the loss with the number of foreground samples in this case.
+
+
+# Citing FCOS
+If you use FCOS in your research or wish to refer to the baseline results, please use the following BibTeX entries.
+```BibTeX
+@inproceedings{tian2019fcos,
+ title = {{FCOS}: Fully Convolutional One-Stage Object Detection},
+ author = {Tian, Zhi and Shen, Chunhua and Chen, Hao and He, Tong},
+ booktitle = {Proc. Int. Conf. Computer Vision (ICCV)},
+ year = {2019}
+}
+```
+```BibTeX
+@article{tian2021fcos,
+ title = {{FCOS}: A Simple and Strong Anchor-free Object Detector},
+ author = {Tian, Zhi and Shen, Chunhua and Chen, Hao and He, Tong},
+ journal = {IEEE T. Pattern Analysis and Machine Intelligence (TPAMI)},
+ year = {2021}
+}
+```
diff --git a/AdelaiDet/configs/FCOS-Detection/R_50_1x.yaml b/AdelaiDet/configs/FCOS-Detection/R_50_1x.yaml
new file mode 100755
index 0000000..9a1675e
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/R_50_1x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+INPUT:
+ MIN_SIZE_TRAIN: (800,)
+OUTPUT_DIR: "output/fcos/R_50_1x"
diff --git a/AdelaiDet/configs/FCOS-Detection/R_50_1x_iou.yaml b/AdelaiDet/configs/FCOS-Detection/R_50_1x_iou.yaml
new file mode 100755
index 0000000..520cb25
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/R_50_1x_iou.yaml
@@ -0,0 +1,10 @@
+_BASE_: "Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+ FCOS:
+ BOX_QUALITY: "iou"
+INPUT:
+ MIN_SIZE_TRAIN: (800,)
+OUTPUT_DIR: "output/fcos/R_50_1x_iou"
diff --git a/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_39_3x.yaml b/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_39_3x.yaml
new file mode 100755
index 0000000..f748bf1
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_39_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "../Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "https://www.dropbox.com/s/q98pypf96rhtd8y/vovnet39_ese_detectron2.pth?dl=1"
+ BACKBONE:
+ NAME: "build_fcos_vovnet_fpn_backbone"
+ FREEZE_AT: 0
+ VOVNET:
+ CONV_BODY : "V-39-eSE"
+ OUT_FEATURES: ["stage3", "stage4", "stage5"]
+ FPN:
+ IN_FEATURES: ["stage3", "stage4", "stage5"]
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/fcos/V_39_ms_3x"
diff --git a/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_57_3x.yaml b/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_57_3x.yaml
new file mode 100755
index 0000000..fd700e8
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_57_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "../Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "https://www.dropbox.com/s/8xl0cb3jj51f45a/vovnet57_ese_detectron2.pth?dl=1"
+ BACKBONE:
+ NAME: "build_fcos_vovnet_fpn_backbone"
+ FREEZE_AT: 0
+ VOVNET:
+ CONV_BODY : "V-57-eSE"
+ OUT_FEATURES: ["stage3", "stage4", "stage5"]
+ FPN:
+ IN_FEATURES: ["stage3", "stage4", "stage5"]
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/fcos/V_57_ms_3x"
diff --git a/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_99_3x.yaml b/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_99_3x.yaml
new file mode 100755
index 0000000..699a05f
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/vovnet/MS_V_99_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "../Base-FCOS.yaml"
+MODEL:
+ WEIGHTS: "https://www.dropbox.com/s/1mlv31coewx8trd/vovnet99_ese_detectron2.pth?dl=1"
+ BACKBONE:
+ NAME: "build_fcos_vovnet_fpn_backbone"
+ FREEZE_AT: 0
+ VOVNET:
+ CONV_BODY : "V-99-eSE"
+ OUT_FEATURES: ["stage3", "stage4", "stage5"]
+ FPN:
+ IN_FEATURES: ["stage3", "stage4", "stage5"]
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+OUTPUT_DIR: "output/fcos/V_99_ms_3x"
diff --git a/AdelaiDet/configs/FCOS-Detection/vovnet/README.md b/AdelaiDet/configs/FCOS-Detection/vovnet/README.md
new file mode 100755
index 0000000..a333553
--- /dev/null
+++ b/AdelaiDet/configs/FCOS-Detection/vovnet/README.md
@@ -0,0 +1,59 @@
+# [VoVNet-v2](https://github.com/youngwanLEE/CenterMask) backbone networks in [FCOS](https://github.com/aim-uofa/adet)
+**Efficient Backbone Network for Object Detection and Segmentation**\
+Youngwan Lee
+
+
+[[`vovnet-detectron2`](https://github.com/youngwanLEE/vovnet-detectron2)][[`CenterMask(code)`](https://github.com/youngwanLEE/CenterMask)] [[`VoVNet-v1(arxiv)`](https://arxiv.org/abs/1904.09730)] [[`VoVNet-v2(arxiv)`](https://arxiv.org/abs/1911.06667)] [[`BibTeX`](#CitingVoVNet)]
+
+
+
+
+
+
+## How to Cite
+```
+@article{kim2023devil,
+ title={The Devil is in the Points: Weakly Semi-Supervised Instance Segmentation via Point-Guided Mask Representation},
+ author={Kim, Beomyoung and Jeong, Joonhyun and Han, Dongyoon and Hwang, Sung Ju},
+ journal={arXiv preprint arXiv:2303.15062},
+ year={2023}
+}
+```
+
+## License
+```
+PointWSSIS
+Copyright (c) 2023-present NAVER Cloud Corp.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```
diff --git a/detectron2/.circleci/config.yml b/detectron2/.circleci/config.yml
new file mode 100755
index 0000000..9a2148c
--- /dev/null
+++ b/detectron2/.circleci/config.yml
@@ -0,0 +1,271 @@
+version: 2.1
+
+# -------------------------------------------------------------------------------------
+# Environments to run the jobs in
+# -------------------------------------------------------------------------------------
+cpu: &cpu
+ machine:
+ image: ubuntu-2004:202107-02
+ resource_class: medium
+
+gpu: &gpu
+ machine:
+ # NOTE: use a cuda version that's supported by all our pytorch versions
+ image: ubuntu-1604-cuda-11.1:202012-01
+ resource_class: gpu.nvidia.small
+
+windows-cpu: &windows_cpu
+ machine:
+ resource_class: windows.medium
+ image: windows-server-2019-vs2019:stable
+ shell: powershell.exe
+
+# windows-gpu: &windows_gpu
+# machine:
+# resource_class: windows.gpu.nvidia.medium
+# image: windows-server-2019-nvidia:stable
+
+version_parameters: &version_parameters
+ parameters:
+ pytorch_version:
+ type: string
+ torchvision_version:
+ type: string
+ pytorch_index:
+ type: string
+ # use test wheels index to have access to RC wheels
+ # https://download.pytorch.org/whl/test/torch_test.html
+ default: "https://download.pytorch.org/whl/torch_stable.html"
+ python_version: # NOTE: only affect linux
+ type: string
+ default: '3.8.6'
+
+ environment:
+ PYTORCH_VERSION: << parameters.pytorch_version >>
+ TORCHVISION_VERSION: << parameters.torchvision_version >>
+ PYTORCH_INDEX: << parameters.pytorch_index >>
+ PYTHON_VERSION: << parameters.python_version>>
+ # point datasets to ~/.torch so it's cached in CI
+ DETECTRON2_DATASETS: ~/.torch/datasets
+
+# -------------------------------------------------------------------------------------
+# Re-usable commands
+# -------------------------------------------------------------------------------------
+# install_nvidia_driver: &install_nvidia_driver
+# - run:
+# name: Install nvidia driver
+# working_directory: ~/
+# command: |
+# wget -q 'https://s3.amazonaws.com/ossci-linux/nvidia_driver/NVIDIA-Linux-x86_64-430.40.run'
+# sudo /bin/bash ./NVIDIA-Linux-x86_64-430.40.run -s --no-drm
+# nvidia-smi
+
+add_ssh_keys: &add_ssh_keys
+ # https://circleci.com/docs/2.0/add-ssh-key/
+ - add_ssh_keys:
+ fingerprints:
+ - "e4:13:f2:22:d4:49:e8:e4:57:5a:ac:20:2f:3f:1f:ca"
+
+install_python: &install_python
+ - run:
+ name: Install Python
+ working_directory: ~/
+ command: |
+ # upgrade pyenv
+ cd /opt/circleci/.pyenv/plugins/python-build/../.. && git pull && cd -
+ pyenv install -s $PYTHON_VERSION
+ pyenv global $PYTHON_VERSION
+ python --version
+ which python
+ pip install --upgrade pip
+
+setup_venv: &setup_venv
+ - run:
+ name: Setup Virtual Env
+ working_directory: ~/
+ command: |
+ python -m venv ~/venv
+ echo ". ~/venv/bin/activate" >> $BASH_ENV
+ . ~/venv/bin/activate
+ python --version
+ which python
+ which pip
+ pip install --upgrade pip
+
+setup_venv_win: &setup_venv_win
+ - run:
+ name: Setup Virtual Env for Windows
+ command: |
+ pip install virtualenv
+ python -m virtualenv env
+ .\env\Scripts\activate
+ python --version
+ which python
+ which pip
+
+install_linux_dep: &install_linux_dep
+ - run:
+ name: Install Dependencies
+ command: |
+ # disable crash coredump, so unittests fail fast
+ sudo systemctl stop apport.service
+ # install from github to get latest; install iopath first since fvcore depends on it
+ pip install --progress-bar off -U 'git+https://github.com/facebookresearch/iopath'
+ pip install --progress-bar off -U 'git+https://github.com/facebookresearch/fvcore'
+ # Don't use pytest-xdist: cuda tests are unstable under multi-process workers.
+ # Don't use opencv 4.7.0.68: https://github.com/opencv/opencv-python/issues/765
+ pip install --progress-bar off ninja opencv-python-headless!=4.7.0.68 pytest tensorboard pycocotools onnx
+ pip install --progress-bar off torch==$PYTORCH_VERSION -f $PYTORCH_INDEX
+ if [[ "$TORCHVISION_VERSION" == "master" ]]; then
+ pip install git+https://github.com/pytorch/vision.git
+ else
+ pip install --progress-bar off torchvision==$TORCHVISION_VERSION -f $PYTORCH_INDEX
+ fi
+
+ python -c 'import torch; print("CUDA:", torch.cuda.is_available())'
+ gcc --version
+
+install_detectron2: &install_detectron2
+ - run:
+ name: Install Detectron2
+ command: |
+ # Remove first, in case it's in the CI cache
+ pip uninstall -y detectron2
+
+ pip install --progress-bar off -e .[all]
+ python -m detectron2.utils.collect_env
+ ./datasets/prepare_for_tests.sh
+
+run_unittests: &run_unittests
+ - run:
+ name: Run Unit Tests
+ command: |
+ pytest -sv --durations=15 tests # parallel causes some random failures
+
+uninstall_tests: &uninstall_tests
+ - run:
+ name: Run Tests After Uninstalling
+ command: |
+ pip uninstall -y detectron2
+ # Remove built binaries
+ rm -rf build/ detectron2/*.so
+ # Tests that code is importable without installation
+ PYTHONPATH=. ./.circleci/import-tests.sh
+
+
+# -------------------------------------------------------------------------------------
+# Jobs to run
+# -------------------------------------------------------------------------------------
+jobs:
+ linux_cpu_tests:
+ <<: *cpu
+ <<: *version_parameters
+
+ working_directory: ~/detectron2
+
+ steps:
+ - checkout
+
+ # Cache the venv directory that contains python, dependencies, and checkpoints
+ # Refresh the key when dependencies should be updated (e.g. when pytorch releases)
+ - restore_cache:
+ keys:
+ - cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
+
+ - <<: *install_python
+ - <<: *install_linux_dep
+ - <<: *install_detectron2
+ - <<: *run_unittests
+ - <<: *uninstall_tests
+
+ - save_cache:
+ paths:
+ - /opt/circleci/.pyenv
+ - ~/.torch
+ key: cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
+
+
+ linux_gpu_tests:
+ <<: *gpu
+ <<: *version_parameters
+
+ working_directory: ~/detectron2
+
+ steps:
+ - checkout
+
+ - restore_cache:
+ keys:
+ - cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
+
+ - <<: *install_python
+ - <<: *install_linux_dep
+ - <<: *install_detectron2
+ - <<: *run_unittests
+ - <<: *uninstall_tests
+
+ - save_cache:
+ paths:
+ - /opt/circleci/.pyenv
+ - ~/.torch
+ key: cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
+
+ windows_cpu_build:
+ <<: *windows_cpu
+ <<: *version_parameters
+ steps:
+ - <<: *add_ssh_keys
+ - checkout
+ - <<: *setup_venv_win
+
+ # Cache the env directory that contains dependencies
+ - restore_cache:
+ keys:
+ - cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210404
+
+ - run:
+ name: Install Dependencies
+ command: |
+ pip install certifi --ignore-installed # required on windows to workaround some cert issue
+ pip install numpy cython # required on windows before pycocotools
+ pip install opencv-python-headless pytest-xdist pycocotools tensorboard onnx
+ pip install -U git+https://github.com/facebookresearch/iopath
+ pip install -U git+https://github.com/facebookresearch/fvcore
+ pip install torch==$env:PYTORCH_VERSION torchvision==$env:TORCHVISION_VERSION -f $env:PYTORCH_INDEX
+
+ - save_cache:
+ paths:
+ - env
+ key: cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210404
+
+ - <<: *install_detectron2
+ # TODO: unittest fails for now
+
+workflows:
+ version: 2
+ regular_test:
+ jobs:
+ - linux_cpu_tests:
+ name: linux_cpu_tests_pytorch1.10
+ pytorch_version: '1.10.0+cpu'
+ torchvision_version: '0.11.1+cpu'
+ - linux_gpu_tests:
+ name: linux_gpu_tests_pytorch1.8
+ pytorch_version: '1.8.1+cu111'
+ torchvision_version: '0.9.1+cu111'
+ - linux_gpu_tests:
+ name: linux_gpu_tests_pytorch1.9
+ pytorch_version: '1.9+cu111'
+ torchvision_version: '0.10+cu111'
+ - linux_gpu_tests:
+ name: linux_gpu_tests_pytorch1.10
+ pytorch_version: '1.10+cu111'
+ torchvision_version: '0.11.1+cu111'
+ - linux_gpu_tests:
+ name: linux_gpu_tests_pytorch1.10_python39
+ pytorch_version: '1.10+cu111'
+ torchvision_version: '0.11.1+cu111'
+ python_version: '3.9.6'
+ - windows_cpu_build:
+ pytorch_version: '1.10+cpu'
+ torchvision_version: '0.11.1+cpu'
diff --git a/detectron2/.circleci/import-tests.sh b/detectron2/.circleci/import-tests.sh
new file mode 100755
index 0000000..8e8deb6
--- /dev/null
+++ b/detectron2/.circleci/import-tests.sh
@@ -0,0 +1,16 @@
+#!/bin/bash -e
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+# Test that import works without building detectron2.
+
+# Check that _C is not importable
+python -c "from detectron2 import _C" > /dev/null 2>&1 && {
+ echo "This test should be run without building detectron2."
+ exit 1
+}
+
+# Check that other modules are still importable, even when _C is not importable
+python -c "from detectron2 import modeling"
+python -c "from detectron2 import modeling, data"
+python -c "from detectron2 import evaluation, export, checkpoint"
+python -c "from detectron2 import utils, engine"
diff --git a/detectron2/.clang-format b/detectron2/.clang-format
new file mode 100755
index 0000000..39b1b3d
--- /dev/null
+++ b/detectron2/.clang-format
@@ -0,0 +1,85 @@
+AccessModifierOffset: -1
+AlignAfterOpenBracket: AlwaysBreak
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: true
+AlignOperands: false
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ForEachMacros: [ FOR_EACH, FOR_EACH_R, FOR_EACH_RANGE, ]
+IncludeCategories:
+ - Regex: '^<.*\.h(pp)?>'
+ Priority: 1
+ - Regex: '^<.*'
+ Priority: 2
+ - Regex: '.*'
+ Priority: 3
+IndentCaseLabels: true
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: false
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 8
+UseTab: Never
diff --git a/detectron2/.flake8 b/detectron2/.flake8
new file mode 100755
index 0000000..28881e4
--- /dev/null
+++ b/detectron2/.flake8
@@ -0,0 +1,15 @@
+# This is an example .flake8 config, used when developing *Black* itself.
+# Keep in sync with setup.cfg which is used for source packages.
+
+[flake8]
+ignore = W503, E203, E221, C901, C408, E741, C407, B017, F811, C101, EXE001, EXE002
+max-line-length = 100
+max-complexity = 18
+select = B,C,E,F,W,T4,B9
+exclude = build
+per-file-ignores =
+ **/__init__.py:F401,F403,E402
+ **/configs/**.py:F401,E402
+ configs/**.py:F401,E402
+ **/tests/config/**.py:F401,E402
+ tests/config/**.py:F401,E402
diff --git a/detectron2/.github/CODE_OF_CONDUCT.md b/detectron2/.github/CODE_OF_CONDUCT.md
new file mode 100755
index 0000000..0f7ad8b
--- /dev/null
+++ b/detectron2/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,5 @@
+# Code of Conduct
+
+Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
+Please read the [full text](https://code.fb.com/codeofconduct/)
+so that you can understand what actions will and will not be tolerated.
diff --git a/detectron2/.github/CONTRIBUTING.md b/detectron2/.github/CONTRIBUTING.md
new file mode 100755
index 0000000..9bab709
--- /dev/null
+++ b/detectron2/.github/CONTRIBUTING.md
@@ -0,0 +1,68 @@
+# Contributing to detectron2
+
+## Issues
+We use GitHub issues to track public bugs and questions.
+Please make sure to follow one of the
+[issue templates](https://github.com/facebookresearch/detectron2/issues/new/choose)
+when reporting any issues.
+
+Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
+disclosure of security bugs. In those cases, please go through the process
+outlined on that page and do not file a public issue.
+
+## Pull Requests
+We actively welcome pull requests.
+
+However, if you're adding any significant features (e.g. > 50 lines), please
+make sure to discuss with maintainers about your motivation and proposals in an issue
+before sending a PR. This is to save your time so you don't spend time on a PR that we'll not accept.
+
+We do not always accept new features, and we take the following
+factors into consideration:
+
+1. Whether the same feature can be achieved without modifying detectron2.
+ Detectron2 is designed so that you can implement many extensions from the outside, e.g.
+ those in [projects](https://github.com/facebookresearch/detectron2/tree/master/projects).
+ * If some part of detectron2 is not extensible enough, you can also bring up a more general issue to
+ improve it. Such feature request may be useful to more users.
+2. Whether the feature is potentially useful to a large audience (e.g. an impactful detection paper, a popular dataset,
+ a significant speedup, a widely useful utility),
+ or only to a small portion of users (e.g., a less-known paper, an improvement not in the object
+ detection field, a trick that's not very popular in the community, code to handle a non-standard type of data)
+ * Adoption of additional models, datasets, new task are by default not added to detectron2 before they
+ receive significant popularity in the community.
+ We sometimes accept such features in `projects/`, or as a link in `projects/README.md`.
+3. Whether the proposed solution has a good design / interface. This can be discussed in the issue prior to PRs, or
+ in the form of a draft PR.
+4. Whether the proposed solution adds extra mental/practical overhead to users who don't
+ need such feature.
+5. Whether the proposed solution breaks existing APIs.
+
+To add a feature to an existing function/class `Func`, there are always two approaches:
+(1) add new arguments to `Func`; (2) write a new `Func_with_new_feature`.
+To meet the above criteria, we often prefer approach (2), because:
+
+1. It does not involve modifying or potentially breaking existing code.
+2. It does not add overhead to users who do not need the new feature.
+3. Adding new arguments to a function/class is not scalable w.r.t. all the possible new research ideas in the future.
+
+When sending a PR, please do:
+
+1. If a PR contains multiple orthogonal changes, split it to several PRs.
+2. If you've added code that should be tested, add tests.
+3. For PRs that need experiments (e.g. adding a new model or new methods),
+ you don't need to update model zoo, but do provide experiment results in the description of the PR.
+4. If APIs are changed, update the documentation.
+5. We use the [Google style docstrings](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html) in python.
+6. Make sure your code lints with `./dev/linter.sh`.
+
+
+## Contributor License Agreement ("CLA")
+In order to accept your pull request, we need you to submit a CLA. You only need
+to do this once to work on any of Facebook's open source projects.
+
+Complete your CLA here:
+
+## License
+By contributing to detectron2, you agree that your contributions will be licensed
+under the LICENSE file in the root directory of this source tree.
diff --git a/detectron2/.github/Detectron2-Logo-Horz.svg b/detectron2/.github/Detectron2-Logo-Horz.svg
new file mode 100755
index 0000000..eb2d643
--- /dev/null
+++ b/detectron2/.github/Detectron2-Logo-Horz.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/detectron2/.github/ISSUE_TEMPLATE.md b/detectron2/.github/ISSUE_TEMPLATE.md
new file mode 100755
index 0000000..5e8aaa2
--- /dev/null
+++ b/detectron2/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,5 @@
+
+Please select an issue template from
+https://github.com/facebookresearch/detectron2/issues/new/choose .
+
+Otherwise your issue will be closed.
diff --git a/detectron2/.github/ISSUE_TEMPLATE/bugs.md b/detectron2/.github/ISSUE_TEMPLATE/bugs.md
new file mode 100755
index 0000000..d0235c7
--- /dev/null
+++ b/detectron2/.github/ISSUE_TEMPLATE/bugs.md
@@ -0,0 +1,38 @@
+---
+name: "🐛 Bugs"
+about: Report bugs in detectron2
+title: Please read & provide the following
+
+---
+
+## Instructions To Reproduce the 🐛 Bug:
+1. Full runnable code or full changes you made:
+```
+If making changes to the project itself, please use output of the following command:
+git rev-parse HEAD; git diff
+
+
+```
+2. What exact command you run:
+3. __Full logs__ or other relevant observations:
+```
+
+```
+4. please simplify the steps as much as possible so they do not require additional resources to
+ run, such as a private dataset.
+
+## Expected behavior:
+
+If there are no obvious error in "full logs" provided above,
+please tell us the expected behavior.
+
+## Environment:
+
+Provide your environment information using the following command:
+```
+wget -nc -q https://github.com/facebookresearch/detectron2/raw/main/detectron2/utils/collect_env.py && python collect_env.py
+```
+
+If your issue looks like an installation issue / environment issue,
+please first try to solve it yourself with the instructions in
+https://detectron2.readthedocs.io/tutorials/install.html#common-installation-issues
diff --git a/detectron2/.github/ISSUE_TEMPLATE/config.yml b/detectron2/.github/ISSUE_TEMPLATE/config.yml
new file mode 100755
index 0000000..c60c2e1
--- /dev/null
+++ b/detectron2/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,17 @@
+# require an issue template to be chosen
+blank_issues_enabled: false
+
+contact_links:
+ - name: How-To / All Other Questions
+ url: https://github.com/facebookresearch/detectron2/discussions
+ about: Use "github discussions" for community support on general questions that don't belong to the above issue categories
+ - name: Detectron2 Documentation
+ url: https://detectron2.readthedocs.io/index.html
+ about: Check if your question is answered in tutorials or API docs
+
+# Unexpected behaviors & bugs are split to two templates.
+# When they are one template, users think "it's not a bug" and don't choose the template.
+#
+# But the file name is still "unexpected-problems-bugs.md" so that old references
+# to this issue template still works.
+# It's ok since this template should be a superset of "bugs.md" (unexpected behaviors is a superset of bugs)
diff --git a/detectron2/.github/ISSUE_TEMPLATE/documentation.md b/detectron2/.github/ISSUE_TEMPLATE/documentation.md
new file mode 100755
index 0000000..88214d6
--- /dev/null
+++ b/detectron2/.github/ISSUE_TEMPLATE/documentation.md
@@ -0,0 +1,14 @@
+---
+name: "\U0001F4DA Documentation Issue"
+about: Report a problem about existing documentation, comments, website or tutorials.
+labels: documentation
+
+---
+
+## 📚 Documentation Issue
+
+This issue category is for problems about existing documentation, not for asking how-to questions.
+
+* Provide a link to an existing documentation/comment/tutorial:
+
+* How should the above documentation/comment/tutorial improve:
diff --git a/detectron2/.github/ISSUE_TEMPLATE/feature-request.md b/detectron2/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100755
index 0000000..03a1e93
--- /dev/null
+++ b/detectron2/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,31 @@
+---
+name: "\U0001F680Feature Request"
+about: Suggest an improvement or new feature
+labels: enhancement
+
+---
+
+## 🚀 Feature
+A clear and concise description of the feature proposal.
+
+## Motivation & Examples
+
+Tell us why the feature is useful.
+
+Describe what the feature would look like, if it is implemented.
+Best demonstrated using **code examples** in addition to words.
+
+## Note
+
+We only consider adding new features if they are relevant to many users.
+
+If you request implementation of research papers -- we only consider papers that have enough significance and prevalance in the object detection field.
+
+We do not take requests for most projects in the `projects/` directory, because they are research code release that is mainly for other researchers to reproduce results.
+
+"Make X faster/accurate" is not a valid feature request. "Implement a concrete feature that can make X faster/accurate" can be a valid feature request.
+
+Instead of adding features inside detectron2,
+you can implement many features by [extending detectron2](https://detectron2.readthedocs.io/tutorials/extend.html).
+The [projects/](https://github.com/facebookresearch/detectron2/tree/main/projects/) directory contains many of such examples.
+
diff --git a/detectron2/.github/ISSUE_TEMPLATE/unexpected-problems-bugs.md b/detectron2/.github/ISSUE_TEMPLATE/unexpected-problems-bugs.md
new file mode 100755
index 0000000..5db8f22
--- /dev/null
+++ b/detectron2/.github/ISSUE_TEMPLATE/unexpected-problems-bugs.md
@@ -0,0 +1,44 @@
+---
+name: "😩 Unexpected behaviors"
+about: Report unexpected behaviors when using detectron2
+title: Please read & provide the following
+
+---
+
+If you do not know the root cause of the problem, please post according to this template:
+
+## Instructions To Reproduce the Issue:
+
+Check https://stackoverflow.com/help/minimal-reproducible-example for how to ask good questions.
+Simplify the steps to reproduce the issue using suggestions from the above link, and provide them below:
+
+1. Full runnable code or full changes you made:
+```
+If making changes to the project itself, please use output of the following command:
+git rev-parse HEAD; git diff
+
+
+```
+2. What exact command you run:
+3. __Full logs__ or other relevant observations:
+```
+
+```
+
+## Expected behavior:
+
+If there are no obvious crash in "full logs" provided above,
+please tell us the expected behavior.
+
+If you expect a model to converge / work better, we do not help with such issues, unless
+a model fails to reproduce the results in detectron2 model zoo, or proves existence of bugs.
+
+## Environment:
+
+Paste the output of the following command:
+```
+wget -nc -nv https://github.com/facebookresearch/detectron2/raw/main/detectron2/utils/collect_env.py && python collect_env.py
+```
+
+If your issue looks like an installation issue / environment issue,
+please first check common issues in https://detectron2.readthedocs.io/tutorials/install.html#common-installation-issues
diff --git a/detectron2/.github/pull_request_template.md b/detectron2/.github/pull_request_template.md
new file mode 100755
index 0000000..d71729b
--- /dev/null
+++ b/detectron2/.github/pull_request_template.md
@@ -0,0 +1,10 @@
+Thanks for your contribution!
+
+If you're sending a large PR (e.g., >100 lines),
+please open an issue first about the feature / bug, and indicate how you want to contribute.
+
+We do not always accept features.
+See https://detectron2.readthedocs.io/notes/contributing.html#pull-requests about how we handle PRs.
+
+Before submitting a PR, please run `dev/linter.sh` to lint the code.
+
diff --git a/detectron2/.github/workflows/check-template.yml b/detectron2/.github/workflows/check-template.yml
new file mode 100755
index 0000000..3caed9d
--- /dev/null
+++ b/detectron2/.github/workflows/check-template.yml
@@ -0,0 +1,86 @@
+name: Check issue template
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ check-template:
+ runs-on: ubuntu-latest
+ # comment this out when testing with https://github.com/nektos/act
+ if: ${{ github.repository_owner == 'facebookresearch' }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/github-script@v3
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ // Arguments available:
+ // - github: A pre-authenticated octokit/rest.js client
+ // - context: An object containing the context of the workflow run
+ // - core: A reference to the @actions/core package
+ // - io: A reference to the @actions/io package
+ const fs = require('fs');
+ const editDistance = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/levenshtein.js`).getEditDistance
+ issue = await github.issues.get({
+ owner: context.issue.owner,
+ repo: context.issue.repo,
+ issue_number: context.issue.number,
+ });
+ const hasLabel = issue.data.labels.length > 0;
+ if (hasLabel || issue.state === "closed") {
+ // don't require template on them
+ core.debug("Issue " + issue.data.title + " was skipped.");
+ return;
+ }
+
+ sameAsTemplate = function(filename, body) {
+ let tmpl = fs.readFileSync(`.github/ISSUE_TEMPLATE/${filename}`, 'utf8');
+ tmpl = tmpl.toLowerCase().split("---").slice(2).join("").trim();
+ tmpl = tmpl.replace(/(\r\n|\n|\r)/gm, "");
+ let bodyr = body.replace(/(\r\n|\n|\r)/gm, "");
+ let dist = editDistance(tmpl, bodyr);
+ return dist < 8;
+ };
+
+ checkFail = async function(msg) {
+ core.info("Processing '" + issue.data.title + "' with message: " + msg);
+ await github.issues.addLabels({
+ owner: context.issue.owner,
+ repo: context.issue.repo,
+ issue_number: context.issue.number,
+ labels: ["needs-more-info"],
+ });
+ await github.issues.createComment({
+ owner: context.issue.owner,
+ repo: context.issue.repo,
+ issue_number: context.issue.number,
+ body: msg,
+ });
+ };
+
+ const body = issue.data.body.toLowerCase().trim();
+
+ if (sameAsTemplate("bugs.md", body) || sameAsTemplate("unexpected-problems-bugs.md", body)) {
+ await checkFail(`
+ We found that not enough information is provided about this issue.
+ Please provide details following the [issue template](https://github.com/facebookresearch/detectron2/issues/new/choose).`)
+ return;
+ }
+
+ const hasInstructions = body.indexOf("reproduce") != -1;
+ const hasEnvironment = (body.indexOf("environment") != -1) || (body.indexOf("colab") != -1) || (body.indexOf("docker") != -1);
+ if (hasInstructions && hasEnvironment) {
+ core.debug("Issue " + issue.data.title + " follows template.");
+ return;
+ }
+
+ let message = "You've chosen to report an unexpected problem or bug. Unless you already know the root cause of it, please include details about it by filling the [issue template](https://github.com/facebookresearch/detectron2/issues/new/choose).\n";
+ message += "The following information is missing: ";
+ if (!hasInstructions) {
+ message += "\"Instructions To Reproduce the Issue and __Full__ Logs\"; ";
+ }
+ if (!hasEnvironment) {
+ message += "\"Your Environment\"; ";
+ }
+ await checkFail(message);
diff --git a/detectron2/.github/workflows/levenshtein.js b/detectron2/.github/workflows/levenshtein.js
new file mode 100755
index 0000000..67a5e36
--- /dev/null
+++ b/detectron2/.github/workflows/levenshtein.js
@@ -0,0 +1,44 @@
+/*
+Copyright (c) 2011 Andrei Mackenzie
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+// Compute the edit distance between the two given strings
+exports.getEditDistance = function(a, b){
+ if(a.length == 0) return b.length;
+ if(b.length == 0) return a.length;
+
+ var matrix = [];
+
+ // increment along the first column of each row
+ var i;
+ for(i = 0; i <= b.length; i++){
+ matrix[i] = [i];
+ }
+
+ // increment each column in the first row
+ var j;
+ for(j = 0; j <= a.length; j++){
+ matrix[0][j] = j;
+ }
+
+ // Fill in the rest of the matrix
+ for(i = 1; i <= b.length; i++){
+ for(j = 1; j <= a.length; j++){
+ if(b.charAt(i-1) == a.charAt(j-1)){
+ matrix[i][j] = matrix[i-1][j-1];
+ } else {
+ matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
+ Math.min(matrix[i][j-1] + 1, // insertion
+ matrix[i-1][j] + 1)); // deletion
+ }
+ }
+ }
+
+ return matrix[b.length][a.length];
+};
diff --git a/detectron2/.github/workflows/needs-reply.yml b/detectron2/.github/workflows/needs-reply.yml
new file mode 100755
index 0000000..4affabd
--- /dev/null
+++ b/detectron2/.github/workflows/needs-reply.yml
@@ -0,0 +1,98 @@
+name: Close/Lock issues after inactivity
+
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+jobs:
+ close-issues-needs-more-info:
+ runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'facebookresearch' }}
+ steps:
+ - name: Close old issues that need reply
+ uses: actions/github-script@v3
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ # Modified from https://github.com/dwieeb/needs-reply
+ script: |
+ // Arguments available:
+ // - github: A pre-authenticated octokit/rest.js client
+ // - context: An object containing the context of the workflow run
+ // - core: A reference to the @actions/core package
+ // - io: A reference to the @actions/io package
+ const kLabelToCheck = "needs-more-info";
+ const kInvalidLabel = "invalid/unrelated";
+ const kDaysBeforeClose = 7;
+ const kMessage = "Requested information was not provided in 7 days, so we're closing this issue.\n\nPlease open new issue if information becomes available. Otherwise, use [github discussions](https://github.com/facebookresearch/detectron2/discussions) for free-form discussions."
+
+ issues = await github.issues.listForRepo({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'open',
+ labels: kLabelToCheck,
+ sort: 'updated',
+ direction: 'asc',
+ per_page: 30,
+ page: 1,
+ });
+ issues = issues.data;
+ if (issues.length === 0) {
+ core.info('No more issues found to process. Exiting.');
+ return;
+ }
+ for (const issue of issues) {
+ if (!!issue.pull_request)
+ continue;
+ core.info(`Processing issue #${issue.number}`);
+
+ let updatedAt = new Date(issue.updated_at).getTime();
+ const numComments = issue.comments;
+ const comments = await github.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ per_page: 30,
+ page: Math.floor((numComments - 1) / 30) + 1, // the last page
+ });
+ const lastComments = comments.data
+ .map(l => new Date(l.created_at).getTime())
+ .sort();
+ if (lastComments.length > 0) {
+ updatedAt = lastComments[lastComments.length - 1];
+ }
+
+ const now = new Date().getTime();
+ const daysSinceUpdated = (now - updatedAt) / 1000 / 60 / 60 / 24;
+
+ if (daysSinceUpdated < kDaysBeforeClose) {
+ core.info(`Skipping #${issue.number} because it has been updated in the last ${daysSinceUpdated} days`);
+ continue;
+ }
+ core.info(`Closing #${issue.number} because it has not been updated in the last ${daysSinceUpdated} days`);
+ await github.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ body: kMessage,
+ });
+ const newLabels = numComments <= 2 ? [kInvalidLabel, kLabelToCheck] : issue.labels;
+ await github.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ labels: newLabels,
+ state: 'closed',
+ });
+ }
+
+ lock-issues-after-closed:
+ runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'facebookresearch' }}
+ steps:
+ - name: Lock closed issues that have no activity for a while
+ uses: dessant/lock-threads@v2
+ with:
+ github-token: ${{ github.token }}
+ issue-lock-inactive-days: '300'
+ process-only: 'issues'
+ issue-exclude-labels: 'enhancement,bug,documentation'
diff --git a/detectron2/.github/workflows/remove-needs-reply.yml b/detectron2/.github/workflows/remove-needs-reply.yml
new file mode 100755
index 0000000..1f000b2
--- /dev/null
+++ b/detectron2/.github/workflows/remove-needs-reply.yml
@@ -0,0 +1,25 @@
+name: Remove needs-more-info label
+
+on:
+ issue_comment:
+ types: [created]
+ issues:
+ types: [edited]
+
+jobs:
+ remove-needs-more-info-label:
+ runs-on: ubuntu-latest
+ # 1. issue_comment events could include PR comment, filter them out
+ # 2. Only trigger action if event was produced by the original author
+ if: ${{ !github.event.issue.pull_request && github.event.sender.login == github.event.issue.user.login }}
+ steps:
+ - name: Remove needs-more-info label
+ uses: octokit/request-action@v2.x
+ continue-on-error: true
+ with:
+ route: DELETE /repos/:repository/issues/:issue/labels/:label
+ repository: ${{ github.repository }}
+ issue: ${{ github.event.issue.number }}
+ label: needs-more-info
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/detectron2/.github/workflows/workflow.yml b/detectron2/.github/workflows/workflow.yml
new file mode 100755
index 0000000..3de246c
--- /dev/null
+++ b/detectron2/.github/workflows/workflow.yml
@@ -0,0 +1,81 @@
+name: CI
+on: [push, pull_request]
+
+# Run linter with github actions for quick feedbacks.
+# Run macos tests with github actions. Linux (CPU & GPU) tests currently runs on CircleCI
+jobs:
+ linter:
+ runs-on: ubuntu-latest
+ # run on PRs, or commits to facebookresearch (not internal)
+ if: ${{ github.repository_owner == 'facebookresearch' || github.event_name == 'pull_request' }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.9
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.9
+ - name: Install dependencies
+ # flake8-bugbear flake8-comprehensions are useful but not available internally
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install flake8==3.8.1 isort==4.3.21
+ python -m pip install black==22.3.0
+ flake8 --version
+ - name: Lint
+ run: |
+ echo "Running isort"
+ isort -c -sp .
+ echo "Running black"
+ black -l 100 --check .
+ echo "Running flake8"
+ flake8 .
+
+ macos_tests:
+ runs-on: macos-latest
+ # run on PRs, or commits to facebookresearch (not internal)
+ if: ${{ github.repository_owner == 'facebookresearch' || github.event_name == 'pull_request' }}
+ strategy:
+ fail-fast: false
+ matrix:
+ torch: ["1.8", "1.9", "1.10"]
+ include:
+ - torch: "1.8"
+ torchvision: 0.9
+ - torch: "1.9"
+ torchvision: "0.10"
+ - torch: "1.10"
+ torchvision: "0.11.1"
+ env:
+ # point datasets to ~/.torch so it's cached by CI
+ DETECTRON2_DATASETS: ~/.torch/datasets
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.8
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ ${{ env.pythonLocation }}/lib/python3.8/site-packages
+ ~/.torch
+ key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20220119
+
+ - name: Install dependencies
+ run: |
+ python -m pip install -U pip
+ python -m pip install ninja opencv-python-headless onnx pytest-xdist
+ python -m pip install torch==${{matrix.torch}} torchvision==${{matrix.torchvision}} -f https://download.pytorch.org/whl/torch_stable.html
+ # install from github to get latest; install iopath first since fvcore depends on it
+ python -m pip install -U 'git+https://github.com/facebookresearch/iopath'
+ python -m pip install -U 'git+https://github.com/facebookresearch/fvcore'
+
+ - name: Build and install
+ run: |
+ CC=clang CXX=clang++ python -m pip install -e .[all]
+ python -m detectron2.utils.collect_env
+ ./datasets/prepare_for_tests.sh
+ - name: Run unittests
+ run: python -m pytest -n 4 --durations=15 -sv tests/
diff --git a/detectron2/.gitignore b/detectron2/.gitignore
new file mode 100755
index 0000000..9953d9b
--- /dev/null
+++ b/detectron2/.gitignore
@@ -0,0 +1,53 @@
+# output dir
+output
+instant_test_output
+inference_test_output
+
+
+*.png
+*.json
+*.diff
+*.jpg
+!/projects/DensePose/doc/images/*.jpg
+
+# compilation and distribution
+__pycache__
+_ext
+*.pyc
+*.pyd
+*.so
+*.dll
+*.egg-info/
+build/
+dist/
+wheels/
+
+# pytorch/python/numpy formats
+*.pth
+*.pkl
+*.npy
+*.ts
+model_ts*.txt
+
+# ipython/jupyter notebooks
+*.ipynb
+**/.ipynb_checkpoints/
+
+# Editor temporaries
+*.swn
+*.swo
+*.swp
+*~
+
+# editor settings
+.idea
+.vscode
+_darcs
+
+# project dirs
+/detectron2/model_zoo/configs
+/datasets/*
+!/datasets/*.*
+/projects/*/datasets
+/models
+/snippet
diff --git a/detectron2/GETTING_STARTED.md b/detectron2/GETTING_STARTED.md
new file mode 100755
index 0000000..404b0c8
--- /dev/null
+++ b/detectron2/GETTING_STARTED.md
@@ -0,0 +1,79 @@
+## Getting Started with Detectron2
+
+This document provides a brief intro of the usage of builtin command-line tools in detectron2.
+
+For a tutorial that involves actual coding with the API,
+see our [Colab Notebook](https://colab.research.google.com/drive/16jcaJoc6bCFAQ96jDe2HwtXj7BMD_-m5)
+which covers how to run inference with an
+existing model, and how to train a builtin model on a custom dataset.
+
+
+### Inference Demo with Pre-trained Models
+
+1. Pick a model and its config file from
+ [model zoo](MODEL_ZOO.md),
+ for example, `mask_rcnn_R_50_FPN_3x.yaml`.
+2. We provide `demo.py` that is able to demo builtin configs. Run it with:
+```
+cd demo/
+python demo.py --config-file ../configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml \
+ --input input1.jpg input2.jpg \
+ [--other-options]
+ --opts MODEL.WEIGHTS detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl
+```
+The configs are made for training, therefore we need to specify `MODEL.WEIGHTS` to a model from model zoo for evaluation.
+This command will run the inference and show visualizations in an OpenCV window.
+
+For details of the command line arguments, see `demo.py -h` or look at its source code
+to understand its behavior. Some common arguments are:
+* To run __on your webcam__, replace `--input files` with `--webcam`.
+* To run __on a video__, replace `--input files` with `--video-input video.mp4`.
+* To run __on cpu__, add `MODEL.DEVICE cpu` after `--opts`.
+* To save outputs to a directory (for images) or a file (for webcam or video), use `--output`.
+
+
+### Training & Evaluation in Command Line
+
+We provide two scripts in "tools/plain_train_net.py" and "tools/train_net.py",
+that are made to train all the configs provided in detectron2. You may want to
+use it as a reference to write your own training script.
+
+Compared to "train_net.py", "plain_train_net.py" supports fewer default
+features. It also includes fewer abstraction, therefore is easier to add custom
+logic.
+
+To train a model with "train_net.py", first
+setup the corresponding datasets following
+[datasets/README.md](./datasets/README.md),
+then run:
+```
+cd tools/
+./train_net.py --num-gpus 8 \
+ --config-file ../configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
+```
+
+The configs are made for 8-GPU training.
+To train on 1 GPU, you may need to [change some parameters](https://arxiv.org/abs/1706.02677), e.g.:
+```
+./train_net.py \
+ --config-file ../configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml \
+ --num-gpus 1 SOLVER.IMS_PER_BATCH 2 SOLVER.BASE_LR 0.0025
+```
+
+To evaluate a model's performance, use
+```
+./train_net.py \
+ --config-file ../configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml \
+ --eval-only MODEL.WEIGHTS /path/to/checkpoint_file
+```
+For more options, see `./train_net.py -h`.
+
+### Use Detectron2 APIs in Your Code
+
+See our [Colab Notebook](https://colab.research.google.com/drive/16jcaJoc6bCFAQ96jDe2HwtXj7BMD_-m5)
+to learn how to use detectron2 APIs to:
+1. run inference with an existing model
+2. train a builtin model on a custom dataset
+
+See [detectron2/projects](https://github.com/facebookresearch/detectron2/tree/main/projects)
+for more ways to build your project on detectron2.
diff --git a/detectron2/INSTALL.md b/detectron2/INSTALL.md
new file mode 100755
index 0000000..f522e6f
--- /dev/null
+++ b/detectron2/INSTALL.md
@@ -0,0 +1,261 @@
+## Installation
+
+### Requirements
+- Linux or macOS with Python ≥ 3.7
+- PyTorch ≥ 1.8 and [torchvision](https://github.com/pytorch/vision/) that matches the PyTorch installation.
+ Install them together at [pytorch.org](https://pytorch.org) to make sure of this
+- OpenCV is optional but needed by demo and visualization
+
+
+### Build Detectron2 from Source
+
+gcc & g++ ≥ 5.4 are required. [ninja](https://ninja-build.org/) is optional but recommended for faster build.
+After having them, run:
+```
+python -m pip install 'git+https://github.com/facebookresearch/detectron2.git'
+# (add --user if you don't have permission)
+
+# Or, to install it from a local clone:
+git clone https://github.com/facebookresearch/detectron2.git
+python -m pip install -e detectron2
+
+# On macOS, you may need to prepend the above commands with a few environment variables:
+CC=clang CXX=clang++ ARCHFLAGS="-arch x86_64" python -m pip install ...
+```
+
+To __rebuild__ detectron2 that's built from a local clone, use `rm -rf build/ **/*.so` to clean the
+old build first. You often need to rebuild detectron2 after reinstalling PyTorch.
+
+### Install Pre-Built Detectron2 (Linux only)
+
+Choose from this table to install [v0.6 (Oct 2021)](https://github.com/facebookresearch/detectron2/releases):
+
+
+
+Note that:
+1. The pre-built packages have to be used with corresponding version of CUDA and the official package of PyTorch.
+ Otherwise, please build detectron2 from source.
+2. New packages are released every few months. Therefore, packages may not contain latest features in the main
+ branch and may not be compatible with the main branch of a research project that uses detectron2
+ (e.g. those in [projects](projects)).
+
+### Common Installation Issues
+
+Click each issue for its solutions:
+
+
+
+Undefined symbols that looks like "TH..","at::Tensor...","torch..."
+
+
+
+This usually happens when detectron2 or torchvision is not
+compiled with the version of PyTorch you're running.
+
+If the error comes from a pre-built torchvision, uninstall torchvision and pytorch and reinstall them
+following [pytorch.org](http://pytorch.org). So the versions will match.
+
+If the error comes from a pre-built detectron2, check [release notes](https://github.com/facebookresearch/detectron2/releases),
+uninstall and reinstall the correct pre-built detectron2 that matches pytorch version.
+
+If the error comes from detectron2 or torchvision that you built manually from source,
+remove files you built (`build/`, `**/*.so`) and rebuild it so it can pick up the version of pytorch currently in your environment.
+
+If the above instructions do not resolve this problem, please provide an environment (e.g. a dockerfile) that can reproduce the issue.
+
+
+
+
+Missing torch dynamic libraries, OR segmentation fault immediately when using detectron2.
+
+This usually happens when detectron2 or torchvision is not
+compiled with the version of PyTorch you're running. See the previous common issue for the solution.
+
+
+
+
+Undefined C++ symbols (e.g. "GLIBCXX..") or C++ symbols not found.
+
+
+Usually it's because the library is compiled with a newer C++ compiler but run with an old C++ runtime.
+
+This often happens with old anaconda.
+It may help to run `conda update libgcc` to upgrade its runtime.
+
+The fundamental solution is to avoid the mismatch, either by compiling using older version of C++
+compiler, or run the code with proper C++ runtime.
+To run the code with a specific C++ runtime, you can use environment variable `LD_PRELOAD=/path/to/libstdc++.so`.
+
+
+
+
+
+"nvcc not found" or "Not compiled with GPU support" or "Detectron2 CUDA Compiler: not available".
+
+
+CUDA is not found when building detectron2.
+You should make sure
+
+```
+python -c 'import torch; from torch.utils.cpp_extension import CUDA_HOME; print(torch.cuda.is_available(), CUDA_HOME)'
+```
+
+print `(True, a directory with cuda)` at the time you build detectron2.
+
+Most models can run inference (but not training) without GPU support. To use CPUs, set `MODEL.DEVICE='cpu'` in the config.
+
+
+
+
+"invalid device function" or "no kernel image is available for execution".
+
+
+Two possibilities:
+
+* You build detectron2 with one version of CUDA but run it with a different version.
+
+ To check whether it is the case,
+ use `python -m detectron2.utils.collect_env` to find out inconsistent CUDA versions.
+ In the output of this command, you should expect "Detectron2 CUDA Compiler", "CUDA_HOME", "PyTorch built with - CUDA"
+ to contain cuda libraries of the same version.
+
+ When they are inconsistent,
+ you need to either install a different build of PyTorch (or build by yourself)
+ to match your local CUDA installation, or install a different version of CUDA to match PyTorch.
+
+* PyTorch/torchvision/Detectron2 is not built for the correct GPU SM architecture (aka. compute capability).
+
+ The architecture included by PyTorch/detectron2/torchvision is available in the "architecture flags" in
+ `python -m detectron2.utils.collect_env`. It must include
+ the architecture of your GPU, which can be found at [developer.nvidia.com/cuda-gpus](https://developer.nvidia.com/cuda-gpus).
+
+ If you're using pre-built PyTorch/detectron2/torchvision, they have included support for most popular GPUs already.
+ If not supported, you need to build them from source.
+
+ When building detectron2/torchvision from source, they detect the GPU device and build for only the device.
+ This means the compiled code may not work on a different GPU device.
+ To recompile them for the correct architecture, remove all installed/compiled files,
+ and rebuild them with the `TORCH_CUDA_ARCH_LIST` environment variable set properly.
+ For example, `export TORCH_CUDA_ARCH_LIST="6.0;7.0"` makes it compile for both P100s and V100s.
+
+
+
+
+Undefined CUDA symbols; Cannot open libcudart.so
+
+
+The version of NVCC you use to build detectron2 or torchvision does
+not match the version of CUDA you are running with.
+This often happens when using anaconda's CUDA runtime.
+
+Use `python -m detectron2.utils.collect_env` to find out inconsistent CUDA versions.
+In the output of this command, you should expect "Detectron2 CUDA Compiler", "CUDA_HOME", "PyTorch built with - CUDA"
+to contain cuda libraries of the same version.
+
+When they are inconsistent,
+you need to either install a different build of PyTorch (or build by yourself)
+to match your local CUDA installation, or install a different version of CUDA to match PyTorch.
+
+
+
+
+
+C++ compilation errors from NVCC / NVRTC, or "Unsupported gpu architecture"
+
+
+A few possibilities:
+
+1. Local CUDA/NVCC version has to match the CUDA version of your PyTorch. Both can be found in `python collect_env.py`
+ (download from [here](./detectron2/utils/collect_env.py)).
+ When they are inconsistent, you need to either install a different build of PyTorch (or build by yourself)
+ to match your local CUDA installation, or install a different version of CUDA to match PyTorch.
+
+2. Local CUDA/NVCC version shall support the SM architecture (a.k.a. compute capability) of your GPU.
+ The capability of your GPU can be found at [developer.nvidia.com/cuda-gpus](https://developer.nvidia.com/cuda-gpus).
+ The capability supported by NVCC is listed at [here](https://gist.github.com/ax3l/9489132).
+ If your NVCC version is too old, this can be workaround by setting environment variable
+ `TORCH_CUDA_ARCH_LIST` to a lower, supported capability.
+
+3. The combination of NVCC and GCC you use is incompatible. You need to change one of their versions.
+ See [here](https://gist.github.com/ax3l/9489132) for some valid combinations.
+ Notably, CUDA<=10.1.105 doesn't support GCC>7.3.
+
+ The CUDA/GCC version used by PyTorch can be found by `print(torch.__config__.show())`.
+
+
+
+
+
+
+"ImportError: cannot import name '_C'".
+
+
+Please build and install detectron2 following the instructions above.
+
+Or, if you are running code from detectron2's root directory, `cd` to a different one.
+Otherwise you may not import the code that you installed.
+
+
+
+
+
+Any issue on windows.
+
+
+
+Detectron2 is continuously built on windows with [CircleCI](https://app.circleci.com/pipelines/github/facebookresearch/detectron2?branch=main).
+However we do not provide official support for it.
+PRs that improves code compatibility on windows are welcome.
+
+
+
+
+ONNX conversion segfault after some "TraceWarning".
+
+
+The ONNX package is compiled with a too old compiler.
+
+Please build and install ONNX from its source code using a compiler
+whose version is closer to what's used by PyTorch (available in `torch.__config__.show()`).
+
+
+
+
+
+"library not found for -lstdc++" on older version of MacOS
+
+
+
+See [this stackoverflow answer](https://stackoverflow.com/questions/56083725/macos-build-issues-lstdc-not-found-while-building-python-package).
+
+
+
+
+### Installation inside specific environments:
+
+* __Colab__: see our [Colab Tutorial](https://colab.research.google.com/drive/16jcaJoc6bCFAQ96jDe2HwtXj7BMD_-m5)
+ which has step-by-step instructions.
+
+* __Docker__: The official [Dockerfile](docker) installs detectron2 with a few simple commands.
diff --git a/detectron2/LICENSE b/detectron2/LICENSE
new file mode 100755
index 0000000..cd1b070
--- /dev/null
+++ b/detectron2/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by
+the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all
+other entities that control, are controlled by, or are under common
+control with that entity. For the purposes of this definition,
+"control" means (i) the power, direct or indirect, to cause the
+direction or management of such entity, whether by contract or
+otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity
+exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications,
+including but not limited to software source code, documentation
+source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical
+transformation or translation of a Source form, including but
+not limited to compiled object code, generated documentation,
+and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or
+Object form, made available under the License, as indicated by a
+copyright notice that is included in or attached to the work
+(an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object
+form, that is based on (or derived from) the Work and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship. For the purposes
+of this License, Derivative Works shall not include works that remain
+separable from, or merely link (or bind by name) to the interfaces of,
+the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including
+the original version of the Work and any modifications or additions
+to that Work or Derivative Works thereof, that is intentionally
+submitted to Licensor for inclusion in the Work by the copyright owner
+or by an individual or Legal Entity authorized to submit on behalf of
+the copyright owner. For the purposes of this definition, "submitted"
+means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems,
+and issue tracking systems that are managed by, or on behalf of, the
+Licensor for the purpose of discussing and improving the Work, but
+excluding communication that is conspicuously marked or otherwise
+designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity
+on behalf of whom a Contribution has been received by Licensor and
+subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the
+Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+(except as stated in this section) patent license to make, have made,
+use, offer to sell, sell, import, and otherwise transfer the Work,
+where such license applies only to those patent claims licensable
+by such Contributor that are necessarily infringed by their
+Contribution(s) alone or by combination of their Contribution(s)
+with the Work to which such Contribution(s) was submitted. If You
+institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work
+or a Contribution incorporated within the Work constitutes direct
+or contributory patent infringement, then any patent licenses
+granted to You under this License for that Work shall terminate
+as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+Work or Derivative Works thereof in any medium, with or without
+modifications, and in Source or Object form, provided that You
+meet the following conditions:
+
+(a) You must give any other recipients of the Work or
+Derivative Works a copy of this License; and
+
+(b) You must cause any modified files to carry prominent notices
+stating that You changed the files; and
+
+(c) You must retain, in the Source form of any Derivative Works
+that You distribute, all copyright, patent, trademark, and
+attribution notices from the Source form of the Work,
+excluding those notices that do not pertain to any part of
+the Derivative Works; and
+
+(d) If the Work includes a "NOTICE" text file as part of its
+distribution, then any Derivative Works that You distribute must
+include a readable copy of the attribution notices contained
+within such NOTICE file, excluding those notices that do not
+pertain to any part of the Derivative Works, in at least one
+of the following places: within a NOTICE text file distributed
+as part of the Derivative Works; within the Source form or
+documentation, if provided along with the Derivative Works; or,
+within a display generated by the Derivative Works, if and
+wherever such third-party notices normally appear. The contents
+of the NOTICE file are for informational purposes only and
+do not modify the License. You may add Your own attribution
+notices within Derivative Works that You distribute, alongside
+or as an addendum to the NOTICE text from the Work, provided
+that such additional attribution notices cannot be construed
+as modifying the License.
+
+You may add Your own copyright statement to Your modifications and
+may provide additional or different license terms and conditions
+for use, reproduction, or distribution of Your modifications, or
+for any such Derivative Works as a whole, provided Your use,
+reproduction, and distribution of the Work otherwise complies with
+the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work
+by You to the Licensor shall be under the terms and conditions of
+this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify
+the terms of any separate license agreement you may have executed
+with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor,
+except as required for reasonable and customary use in describing the
+origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+agreed to in writing, Licensor provides the Work (and each
+Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied, including, without limitation, any warranties or conditions
+of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+PARTICULAR PURPOSE. You are solely responsible for determining the
+appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+whether in tort (including negligence), contract, or otherwise,
+unless required by applicable law (such as deliberate and grossly
+negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special,
+incidental, or consequential damages of any character arising as a
+result of this License or out of the use or inability to use the
+Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses), even if such Contributor
+has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+the Work or Derivative Works thereof, You may choose to offer,
+and charge a fee for, acceptance of support, warranty, indemnity,
+or other liability obligations and/or rights consistent with this
+License. However, in accepting such obligations, You may act only
+on Your own behalf and on Your sole responsibility, not on behalf
+of any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason
+of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+To apply the Apache License to your work, attach the following
+boilerplate notice, with the fields enclosed by brackets "[]"
+replaced with your own identifying information. (Don't include
+the brackets!) The text should be enclosed in the appropriate
+comment syntax for the file format. We also recommend that a
+file or class name and description of purpose be included on the
+same "printed page" as the copyright notice for easier
+identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/detectron2/MODEL_ZOO.md b/detectron2/MODEL_ZOO.md
new file mode 100755
index 0000000..69db272
--- /dev/null
+++ b/detectron2/MODEL_ZOO.md
@@ -0,0 +1,1052 @@
+# Detectron2 Model Zoo and Baselines
+
+## Introduction
+
+This file documents a large collection of baselines trained
+with detectron2 in Sep-Oct, 2019.
+All numbers were obtained on [Big Basin](https://engineering.fb.com/data-center-engineering/introducing-big-basin-our-next-generation-ai-hardware/)
+servers with 8 NVIDIA V100 GPUs & NVLink. The speed numbers are periodically updated with latest PyTorch/CUDA/cuDNN versions.
+You can access these models from code using [detectron2.model_zoo](https://detectron2.readthedocs.io/modules/model_zoo.html) APIs.
+
+In addition to these official baseline models, you can find more models in [projects/](projects/).
+
+#### How to Read the Tables
+* The "Name" column contains a link to the config file. Models can be reproduced using `tools/train_net.py` with the corresponding yaml config file,
+ or `tools/lazyconfig_train_net.py` for python config files.
+* Training speed is averaged across the entire training.
+ We keep updating the speed with latest version of detectron2/pytorch/etc.,
+ so they might be different from the `metrics` file.
+ Training speed for multi-machine jobs is not provided.
+* Inference speed is measured by `tools/train_net.py --eval-only`, or [inference_on_dataset()](https://detectron2.readthedocs.io/modules/evaluation.html#detectron2.evaluation.inference_on_dataset),
+ with batch size 1 in detectron2 directly.
+ Measuring it with custom code may introduce other overhead.
+ Actual deployment in production should in general be faster than the given inference
+ speed due to more optimizations.
+* The *model id* column is provided for ease of reference.
+ To check downloaded file integrity, any model on this page contains its md5 prefix in its file name.
+* Training curves and other statistics can be found in `metrics` for each model.
+
+#### Common Settings for COCO Models
+* All COCO models were trained on `train2017` and evaluated on `val2017`.
+* The default settings are __not directly comparable__ with Detectron's standard settings.
+ For example, our default training data augmentation uses scale jittering in addition to horizontal flipping.
+
+ To make fair comparisons with Detectron's settings, see
+ [Detectron1-Comparisons](configs/Detectron1-Comparisons/) for accuracy comparison,
+ and [benchmarks](https://detectron2.readthedocs.io/notes/benchmarks.html)
+ for speed comparison.
+* For Faster/Mask R-CNN, we provide baselines based on __3 different backbone combinations__:
+ * __FPN__: Use a ResNet+FPN backbone with standard conv and FC heads for mask and box prediction,
+ respectively. It obtains the best
+ speed/accuracy tradeoff, but the other two are still useful for research.
+ * __C4__: Use a ResNet conv4 backbone with conv5 head. The original baseline in the Faster R-CNN paper.
+ * __DC5__ (Dilated-C5): Use a ResNet conv5 backbone with dilations in conv5, and standard conv and FC heads
+ for mask and box prediction, respectively.
+ This is used by the Deformable ConvNet paper.
+* Most models are trained with the 3x schedule (~37 COCO epochs).
+ Although 1x models are heavily under-trained, we provide some ResNet-50 models with the 1x (~12 COCO epochs)
+ training schedule for comparison when doing quick research iteration.
+
+#### ImageNet Pretrained Models
+
+It's common to initialize from backbone models pre-trained on ImageNet classification tasks. The following backbone models are available:
+
+* [R-50.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/MSRA/R-50.pkl): converted copy of [MSRA's original ResNet-50](https://github.com/KaimingHe/deep-residual-networks) model.
+* [R-101.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/MSRA/R-101.pkl): converted copy of [MSRA's original ResNet-101](https://github.com/KaimingHe/deep-residual-networks) model.
+* [X-101-32x8d.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/FAIR/X-101-32x8d.pkl): ResNeXt-101-32x8d model trained with Caffe2 at FB.
+* [R-50.pkl (torchvision)](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/torchvision/R-50.pkl): converted copy of [torchvision's ResNet-50](https://pytorch.org/docs/stable/torchvision/models.html#torchvision.models.resnet50) model.
+ More details can be found in [the conversion script](tools/convert-torchvision-to-d2.py).
+
+Note that the above models have __different__ format from those provided in Detectron: we do not fuse BatchNorm into an affine layer.
+Pretrained models in Detectron's format can still be used. For example:
+* [X-152-32x8d-IN5k.pkl](https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/25093814/X-152-32x8d-IN5k.pkl):
+ ResNeXt-152-32x8d model trained on ImageNet-5k with Caffe2 at FB (see ResNeXt paper for details on ImageNet-5k).
+* [R-50-GN.pkl](https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/47261647/R-50-GN.pkl):
+ ResNet-50 with Group Normalization.
+* [R-101-GN.pkl](https://dl.fbaipublicfiles.com/detectron/ImageNetPretrained/47592356/R-101-GN.pkl):
+ ResNet-101 with Group Normalization.
+
+These models require slightly different settings regarding normalization and architecture. See the model zoo configs for reference.
+
+#### License
+
+All models available for download through this document are licensed under the
+[Creative Commons Attribution-ShareAlike 3.0 license](https://creativecommons.org/licenses/by-sa/3.0/).
+
+### COCO Object Detection Baselines
+
+#### Faster R-CNN:
+
+
+
+
+
+
+
+#### New baselines using Large-Scale Jitter and Longer Training Schedule
+
+The following baselines of COCO Instance Segmentation with Mask R-CNN are generated
+using a longer training schedule and large-scale jitter as described in Google's
+[Simple Copy-Paste Data Augmentation](https://arxiv.org/pdf/2012.07177.pdf) paper. These
+models are trained from scratch using random initialization. These baselines exceed the
+previous Mask R-CNN baselines.
+
+In the following table, one epoch consists of training on 118000 COCO images.
+
+
+
+
+### LVIS Instance Segmentation Baselines with Mask R-CNN
+
+Mask R-CNN baselines on the [LVIS dataset](https://lvisdataset.org), v0.5.
+These baselines are described in Table 3(c) of the [LVIS paper](https://arxiv.org/abs/1908.03195).
+
+NOTE: the 1x schedule here has the same amount of __iterations__ as the COCO 1x baselines.
+They are roughly 24 epochs of LVISv0.5 data.
+The final results of these configs have large variance across different runs.
+
+
+
+
+
+
+
+Ablations for normalization methods, and a few models trained from scratch following [Rethinking ImageNet Pre-training](https://arxiv.org/abs/1811.08883).
+(Note: The baseline uses `2fc` head while the others use [`4conv1fc` head](https://arxiv.org/abs/1803.08494))
+
+
+
+
diff --git a/detectron2/README.md b/detectron2/README.md
new file mode 100755
index 0000000..75db3c5
--- /dev/null
+++ b/detectron2/README.md
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+Detectron2 is Facebook AI Research's next generation library
+that provides state-of-the-art detection and segmentation algorithms.
+It is the successor of
+[Detectron](https://github.com/facebookresearch/Detectron/)
+and [maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark/).
+It supports a number of computer vision research projects and production applications in Facebook.
+
+
+
+
+
+
+## Learn More about Detectron2
+
+Explain Like I’m 5: Detectron2 | Using Machine Learning with Detectron2
+:-------------------------:|:-------------------------:
+[![Explain Like I’m 5: Detectron2](https://img.youtube.com/vi/1oq1Ye7dFqc/0.jpg)](https://www.youtube.com/watch?v=1oq1Ye7dFqc) | [![Using Machine Learning with Detectron2](https://img.youtube.com/vi/eUSgtfK4ivk/0.jpg)](https://www.youtube.com/watch?v=eUSgtfK4ivk)
+
+## What's New
+* Includes new capabilities such as panoptic segmentation, Densepose, Cascade R-CNN, rotated bounding boxes, PointRend,
+ DeepLab, ViTDet, MViTv2 etc.
+* Used as a library to support building [research projects](projects/) on top of it.
+* Models can be exported to TorchScript format or Caffe2 format for deployment.
+* It [trains much faster](https://detectron2.readthedocs.io/notes/benchmarks.html).
+
+See our [blog post](https://ai.facebook.com/blog/-detectron2-a-pytorch-based-modular-object-detection-library-/)
+to see more demos and learn about detectron2.
+
+## Installation
+
+See [installation instructions](https://detectron2.readthedocs.io/tutorials/install.html).
+
+## Getting Started
+
+See [Getting Started with Detectron2](https://detectron2.readthedocs.io/tutorials/getting_started.html),
+and the [Colab Notebook](https://colab.research.google.com/drive/16jcaJoc6bCFAQ96jDe2HwtXj7BMD_-m5)
+to learn about basic usage.
+
+Learn more at our [documentation](https://detectron2.readthedocs.org).
+And see [projects/](projects/) for some projects that are built on top of detectron2.
+
+## Model Zoo and Baselines
+
+We provide a large set of baseline results and trained models available for download in the [Detectron2 Model Zoo](MODEL_ZOO.md).
+
+## License
+
+Detectron2 is released under the [Apache 2.0 license](LICENSE).
+
+## Citing Detectron2
+
+If you use Detectron2 in your research or wish to refer to the baseline results published in the [Model Zoo](MODEL_ZOO.md), please use the following BibTeX entry.
+
+```BibTeX
+@misc{wu2019detectron2,
+ author = {Yuxin Wu and Alexander Kirillov and Francisco Massa and
+ Wan-Yen Lo and Ross Girshick},
+ title = {Detectron2},
+ howpublished = {\url{https://github.com/facebookresearch/detectron2}},
+ year = {2019}
+}
+```
diff --git a/detectron2/configs/Base-RCNN-C4.yaml b/detectron2/configs/Base-RCNN-C4.yaml
new file mode 100755
index 0000000..fbf34a0
--- /dev/null
+++ b/detectron2/configs/Base-RCNN-C4.yaml
@@ -0,0 +1,18 @@
+MODEL:
+ META_ARCHITECTURE: "GeneralizedRCNN"
+ RPN:
+ PRE_NMS_TOPK_TEST: 6000
+ POST_NMS_TOPK_TEST: 1000
+ ROI_HEADS:
+ NAME: "Res5ROIHeads"
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.02
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+VERSION: 2
diff --git a/detectron2/configs/Base-RCNN-DilatedC5.yaml b/detectron2/configs/Base-RCNN-DilatedC5.yaml
new file mode 100755
index 0000000..c0d6d16
--- /dev/null
+++ b/detectron2/configs/Base-RCNN-DilatedC5.yaml
@@ -0,0 +1,31 @@
+MODEL:
+ META_ARCHITECTURE: "GeneralizedRCNN"
+ RESNETS:
+ OUT_FEATURES: ["res5"]
+ RES5_DILATION: 2
+ RPN:
+ IN_FEATURES: ["res5"]
+ PRE_NMS_TOPK_TEST: 6000
+ POST_NMS_TOPK_TEST: 1000
+ ROI_HEADS:
+ NAME: "StandardROIHeads"
+ IN_FEATURES: ["res5"]
+ ROI_BOX_HEAD:
+ NAME: "FastRCNNConvFCHead"
+ NUM_FC: 2
+ POOLER_RESOLUTION: 7
+ ROI_MASK_HEAD:
+ NAME: "MaskRCNNConvUpsampleHead"
+ NUM_CONV: 4
+ POOLER_RESOLUTION: 14
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.02
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+VERSION: 2
diff --git a/detectron2/configs/Base-RCNN-FPN.yaml b/detectron2/configs/Base-RCNN-FPN.yaml
new file mode 100755
index 0000000..3e020f2
--- /dev/null
+++ b/detectron2/configs/Base-RCNN-FPN.yaml
@@ -0,0 +1,42 @@
+MODEL:
+ META_ARCHITECTURE: "GeneralizedRCNN"
+ BACKBONE:
+ NAME: "build_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res2", "res3", "res4", "res5"]
+ FPN:
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ ANCHOR_GENERATOR:
+ SIZES: [[32], [64], [128], [256], [512]] # One size for each in feature map
+ ASPECT_RATIOS: [[0.5, 1.0, 2.0]] # Three aspect ratios (same for all in feature maps)
+ RPN:
+ IN_FEATURES: ["p2", "p3", "p4", "p5", "p6"]
+ PRE_NMS_TOPK_TRAIN: 2000 # Per FPN level
+ PRE_NMS_TOPK_TEST: 1000 # Per FPN level
+ # Detectron1 uses 2000 proposals per-batch,
+ # (See "modeling/rpn/rpn_outputs.py" for details of this legacy issue)
+ # which is approximately 1000 proposals per-image since the default batch size for FPN is 2.
+ POST_NMS_TOPK_TRAIN: 1000
+ POST_NMS_TOPK_TEST: 1000
+ ROI_HEADS:
+ NAME: "StandardROIHeads"
+ IN_FEATURES: ["p2", "p3", "p4", "p5"]
+ ROI_BOX_HEAD:
+ NAME: "FastRCNNConvFCHead"
+ NUM_FC: 2
+ POOLER_RESOLUTION: 7
+ ROI_MASK_HEAD:
+ NAME: "MaskRCNNConvUpsampleHead"
+ NUM_CONV: 4
+ POOLER_RESOLUTION: 14
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.02
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+VERSION: 2
diff --git a/detectron2/configs/Base-RetinaNet.yaml b/detectron2/configs/Base-RetinaNet.yaml
new file mode 100755
index 0000000..8b45b98
--- /dev/null
+++ b/detectron2/configs/Base-RetinaNet.yaml
@@ -0,0 +1,25 @@
+MODEL:
+ META_ARCHITECTURE: "RetinaNet"
+ BACKBONE:
+ NAME: "build_retinanet_resnet_fpn_backbone"
+ RESNETS:
+ OUT_FEATURES: ["res3", "res4", "res5"]
+ ANCHOR_GENERATOR:
+ SIZES: !!python/object/apply:eval ["[[x, x * 2**(1.0/3), x * 2**(2.0/3) ] for x in [32, 64, 128, 256, 512 ]]"]
+ FPN:
+ IN_FEATURES: ["res3", "res4", "res5"]
+ RETINANET:
+ IOU_THRESHOLDS: [0.4, 0.5]
+ IOU_LABELS: [0, -1, 1]
+ SMOOTH_L1_LOSS_BETA: 0.0
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ TEST: ("coco_2017_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.01 # Note that RetinaNet uses a different default learning rate
+ STEPS: (60000, 80000)
+ MAX_ITER: 90000
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+VERSION: 2
diff --git a/detectron2/configs/COCO-Detection/fast_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/COCO-Detection/fast_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..773ac10
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/fast_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,17 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ LOAD_PROPOSALS: True
+ RESNETS:
+ DEPTH: 50
+ PROPOSAL_GENERATOR:
+ NAME: "PrecomputedProposals"
+DATASETS:
+ TRAIN: ("coco_2017_train",)
+ PROPOSAL_FILES_TRAIN: ("detectron2://COCO-Detection/rpn_R_50_FPN_1x/137258492/coco_2017_train_box_proposals_21bc3a.pkl", )
+ TEST: ("coco_2017_val",)
+ PROPOSAL_FILES_TEST: ("detectron2://COCO-Detection/rpn_R_50_FPN_1x/137258492/coco_2017_val_box_proposals_ee0dad.pkl", )
+DATALOADER:
+ # proposals are part of the dataset_dicts, and take a lot of RAM
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_101_C4_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_101_C4_3x.yaml
new file mode 100755
index 0000000..db142cd
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_101_C4_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_101_DC5_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_101_DC5_3x.yaml
new file mode 100755
index 0000000..bceb6b3
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_101_DC5_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-DilatedC5.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_101_FPN_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_101_FPN_3x.yaml
new file mode 100755
index 0000000..57a098f
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_101_FPN_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_50_C4_1x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_C4_1x.yaml
new file mode 100755
index 0000000..f961301
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_C4_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_50_C4_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_C4_3x.yaml
new file mode 100755
index 0000000..bc51bce
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_C4_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_50_DC5_1x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_DC5_1x.yaml
new file mode 100755
index 0000000..0fe96f5
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_DC5_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "../Base-RCNN-DilatedC5.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_50_DC5_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_DC5_3x.yaml
new file mode 100755
index 0000000..33fadeb
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_DC5_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-DilatedC5.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..3262019
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml
new file mode 100755
index 0000000..4139518
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/faster_rcnn_X_101_32x8d_FPN_3x.yaml b/detectron2/configs/COCO-Detection/faster_rcnn_X_101_32x8d_FPN_3x.yaml
new file mode 100755
index 0000000..9c9b5ab
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/faster_rcnn_X_101_32x8d_FPN_3x.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ MASK_ON: False
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/fcos_R_50_FPN_1x.py b/detectron2/configs/COCO-Detection/fcos_R_50_FPN_1x.py
new file mode 100755
index 0000000..86f83c6
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/fcos_R_50_FPN_1x.py
@@ -0,0 +1,11 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco import dataloader
+from ..common.models.fcos import model
+from ..common.train import train
+
+dataloader.train.mapper.use_instance_mask = False
+optimizer.lr = 0.01
+
+model.backbone.bottom_up.freeze_at = 2
+train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
diff --git a/detectron2/configs/COCO-Detection/retinanet_R_101_FPN_3x.yaml b/detectron2/configs/COCO-Detection/retinanet_R_101_FPN_3x.yaml
new file mode 100755
index 0000000..4abb1b9
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/retinanet_R_101_FPN_3x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "../Base-RetinaNet.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_1x.py b/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_1x.py
new file mode 100755
index 0000000..43057a8
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_1x.py
@@ -0,0 +1,11 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco import dataloader
+from ..common.models.retinanet import model
+from ..common.train import train
+
+dataloader.train.mapper.use_instance_mask = False
+model.backbone.bottom_up.freeze_at = 2
+optimizer.lr = 0.01
+
+train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
diff --git a/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_1x.yaml b/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..4a24ce3
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_1x.yaml
@@ -0,0 +1,5 @@
+_BASE_: "../Base-RetinaNet.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_3x.yaml b/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_3x.yaml
new file mode 100755
index 0000000..3b5412d
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/retinanet_R_50_FPN_3x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "../Base-RetinaNet.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Detection/rpn_R_50_C4_1x.yaml b/detectron2/configs/COCO-Detection/rpn_R_50_C4_1x.yaml
new file mode 100755
index 0000000..e048211
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/rpn_R_50_C4_1x.yaml
@@ -0,0 +1,10 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ META_ARCHITECTURE: "ProposalNetwork"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+ RPN:
+ PRE_NMS_TOPK_TEST: 12000
+ POST_NMS_TOPK_TEST: 2000
diff --git a/detectron2/configs/COCO-Detection/rpn_R_50_FPN_1x.yaml b/detectron2/configs/COCO-Detection/rpn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..dc9c952
--- /dev/null
+++ b/detectron2/configs/COCO-Detection/rpn_R_50_FPN_1x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "ProposalNetwork"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+ RPN:
+ POST_NMS_TOPK_TEST: 2000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_C4_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_C4_3x.yaml
new file mode 100755
index 0000000..1a94cc4
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_C4_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_DC5_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_DC5_3x.yaml
new file mode 100755
index 0000000..67b70cf
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_DC5_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-DilatedC5.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_FPN_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_FPN_3x.yaml
new file mode 100755
index 0000000..1935a30
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_101_FPN_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_1x.py b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_1x.py
new file mode 100755
index 0000000..22016be
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_1x.py
@@ -0,0 +1,8 @@
+from ..common.train import train
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco import dataloader
+from ..common.models.mask_rcnn_c4 import model
+
+model.backbone.freeze_at = 2
+train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_1x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_1x.yaml
new file mode 100755
index 0000000..a9aeb4e
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_3x.yaml
new file mode 100755
index 0000000..38ed867
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_C4_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_1x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_1x.yaml
new file mode 100755
index 0000000..b13eefa
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "../Base-RCNN-DilatedC5.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_3x.yaml
new file mode 100755
index 0000000..d401016
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-DilatedC5.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.py b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.py
new file mode 100755
index 0000000..40844dd
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.py
@@ -0,0 +1,8 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco import dataloader
+from ..common.models.mask_rcnn_fpn import model
+from ..common.train import train
+
+model.backbone.bottom_up.freeze_at = 2
+train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..d50fb86
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,6 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x_giou.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x_giou.yaml
new file mode 100755
index 0000000..bec680e
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x_giou.yaml
@@ -0,0 +1,12 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ RPN:
+ BBOX_REG_LOSS_TYPE: "giou"
+ BBOX_REG_LOSS_WEIGHT: 2.0
+ ROI_BOX_HEAD:
+ BBOX_REG_LOSS_TYPE: "giou"
+ BBOX_REG_LOSS_WEIGHT: 10.0
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml
new file mode 100755
index 0000000..be7d06b
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml
@@ -0,0 +1,9 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml
new file mode 100755
index 0000000..d14c63f
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ MASK_ON: True
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_regnetx_4gf_dds_fpn_1x.py b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_regnetx_4gf_dds_fpn_1x.py
new file mode 100755
index 0000000..d7bbdd7
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_regnetx_4gf_dds_fpn_1x.py
@@ -0,0 +1,34 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco import dataloader
+from ..common.models.mask_rcnn_fpn import model
+from ..common.train import train
+
+from detectron2.config import LazyCall as L
+from detectron2.modeling.backbone import RegNet
+from detectron2.modeling.backbone.regnet import SimpleStem, ResBottleneckBlock
+
+
+# Replace default ResNet with RegNetX-4GF from the DDS paper. Config source:
+# https://github.com/facebookresearch/pycls/blob/2c152a6e5d913e898cca4f0a758f41e6b976714d/configs/dds_baselines/regnetx/RegNetX-4.0GF_dds_8gpu.yaml#L4-L9 # noqa
+model.backbone.bottom_up = L(RegNet)(
+ stem_class=SimpleStem,
+ stem_width=32,
+ block_class=ResBottleneckBlock,
+ depth=23,
+ w_a=38.65,
+ w_0=96,
+ w_m=2.43,
+ group_width=40,
+ freeze_at=2,
+ norm="FrozenBN",
+ out_features=["s1", "s2", "s3", "s4"],
+)
+model.pixel_std = [57.375, 57.120, 58.395]
+
+optimizer.weight_decay = 5e-5
+train.init_checkpoint = (
+ "https://dl.fbaipublicfiles.com/pycls/dds_baselines/160906383/RegNetX-4.0GF_dds_8gpu.pyth"
+)
+# RegNets benefit from enabling cudnn benchmark mode
+train.cudnn_benchmark = True
diff --git a/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_regnety_4gf_dds_fpn_1x.py b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_regnety_4gf_dds_fpn_1x.py
new file mode 100755
index 0000000..72c6b7a
--- /dev/null
+++ b/detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_regnety_4gf_dds_fpn_1x.py
@@ -0,0 +1,35 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco import dataloader
+from ..common.models.mask_rcnn_fpn import model
+from ..common.train import train
+
+from detectron2.config import LazyCall as L
+from detectron2.modeling.backbone import RegNet
+from detectron2.modeling.backbone.regnet import SimpleStem, ResBottleneckBlock
+
+
+# Replace default ResNet with RegNetY-4GF from the DDS paper. Config source:
+# https://github.com/facebookresearch/pycls/blob/2c152a6e5d913e898cca4f0a758f41e6b976714d/configs/dds_baselines/regnety/RegNetY-4.0GF_dds_8gpu.yaml#L4-L10 # noqa
+model.backbone.bottom_up = L(RegNet)(
+ stem_class=SimpleStem,
+ stem_width=32,
+ block_class=ResBottleneckBlock,
+ depth=22,
+ w_a=31.41,
+ w_0=96,
+ w_m=2.24,
+ group_width=64,
+ se_ratio=0.25,
+ freeze_at=2,
+ norm="FrozenBN",
+ out_features=["s1", "s2", "s3", "s4"],
+)
+model.pixel_std = [57.375, 57.120, 58.395]
+
+optimizer.weight_decay = 5e-5
+train.init_checkpoint = (
+ "https://dl.fbaipublicfiles.com/pycls/dds_baselines/160906838/RegNetY-4.0GF_dds_8gpu.pyth"
+)
+# RegNets benefit from enabling cudnn benchmark mode
+train.cudnn_benchmark = True
diff --git a/detectron2/configs/COCO-Keypoints/Base-Keypoint-RCNN-FPN.yaml b/detectron2/configs/COCO-Keypoints/Base-Keypoint-RCNN-FPN.yaml
new file mode 100755
index 0000000..4e03944
--- /dev/null
+++ b/detectron2/configs/COCO-Keypoints/Base-Keypoint-RCNN-FPN.yaml
@@ -0,0 +1,15 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ KEYPOINT_ON: True
+ ROI_HEADS:
+ NUM_CLASSES: 1
+ ROI_BOX_HEAD:
+ SMOOTH_L1_BETA: 0.5 # Keypoint AP degrades (though box AP improves) when using plain L1 loss
+ RPN:
+ # Detectron1 uses 2000 proposals per-batch, but this option is per-image in detectron2.
+ # 1000 proposals per-image is found to hurt box AP.
+ # Therefore we increase it to 1500 per-image.
+ POST_NMS_TOPK_TRAIN: 1500
+DATASETS:
+ TRAIN: ("keypoints_coco_2017_train",)
+ TEST: ("keypoints_coco_2017_val",)
diff --git a/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_101_FPN_3x.yaml b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_101_FPN_3x.yaml
new file mode 100755
index 0000000..9309535
--- /dev/null
+++ b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_101_FPN_3x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "Base-Keypoint-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.py b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.py
new file mode 100755
index 0000000..1aad53b
--- /dev/null
+++ b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.py
@@ -0,0 +1,8 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco_keypoint import dataloader
+from ..common.models.keypoint_rcnn_fpn import model
+from ..common.train import train
+
+model.backbone.bottom_up.freeze_at = 2
+train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
diff --git a/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..7bf85cf
--- /dev/null
+++ b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,5 @@
+_BASE_: "Base-Keypoint-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml
new file mode 100755
index 0000000..a07f243
--- /dev/null
+++ b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "Base-Keypoint-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-Keypoints/keypoint_rcnn_X_101_32x8d_FPN_3x.yaml b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_X_101_32x8d_FPN_3x.yaml
new file mode 100755
index 0000000..d4bfa20
--- /dev/null
+++ b/detectron2/configs/COCO-Keypoints/keypoint_rcnn_X_101_32x8d_FPN_3x.yaml
@@ -0,0 +1,12 @@
+_BASE_: "Base-Keypoint-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-PanopticSegmentation/Base-Panoptic-FPN.yaml b/detectron2/configs/COCO-PanopticSegmentation/Base-Panoptic-FPN.yaml
new file mode 100755
index 0000000..f00d54b
--- /dev/null
+++ b/detectron2/configs/COCO-PanopticSegmentation/Base-Panoptic-FPN.yaml
@@ -0,0 +1,11 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "PanopticFPN"
+ MASK_ON: True
+ SEM_SEG_HEAD:
+ LOSS_WEIGHT: 0.5
+DATASETS:
+ TRAIN: ("coco_2017_train_panoptic_separated",)
+ TEST: ("coco_2017_val_panoptic_separated",)
+DATALOADER:
+ FILTER_EMPTY_ANNOTATIONS: False
diff --git a/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_101_3x.yaml b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_101_3x.yaml
new file mode 100755
index 0000000..0e01f6f
--- /dev/null
+++ b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_101_3x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "Base-Panoptic-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ RESNETS:
+ DEPTH: 101
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_1x.py b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_1x.py
new file mode 100755
index 0000000..40cf181
--- /dev/null
+++ b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_1x.py
@@ -0,0 +1,8 @@
+from ..common.optim import SGD as optimizer
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.data.coco_panoptic_separated import dataloader
+from ..common.models.panoptic_fpn import model
+from ..common.train import train
+
+model.backbone.bottom_up.freeze_at = 2
+train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
diff --git a/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_1x.yaml b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_1x.yaml
new file mode 100755
index 0000000..6afa2c1
--- /dev/null
+++ b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_1x.yaml
@@ -0,0 +1,5 @@
+_BASE_: "Base-Panoptic-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
diff --git a/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_3x.yaml b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_3x.yaml
new file mode 100755
index 0000000..b956b3f
--- /dev/null
+++ b/detectron2/configs/COCO-PanopticSegmentation/panoptic_fpn_R_50_3x.yaml
@@ -0,0 +1,8 @@
+_BASE_: "Base-Panoptic-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/Cityscapes/mask_rcnn_R_50_FPN.yaml b/detectron2/configs/Cityscapes/mask_rcnn_R_50_FPN.yaml
new file mode 100755
index 0000000..1a7aaeb
--- /dev/null
+++ b/detectron2/configs/Cityscapes/mask_rcnn_R_50_FPN.yaml
@@ -0,0 +1,27 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ # WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ # For better, more stable performance initialize from COCO
+ WEIGHTS: "detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl"
+ MASK_ON: True
+ ROI_HEADS:
+ NUM_CLASSES: 8
+# This is similar to the setting used in Mask R-CNN paper, Appendix A
+# But there are some differences, e.g., we did not initialize the output
+# layer using the corresponding classes from COCO
+INPUT:
+ MIN_SIZE_TRAIN: (800, 832, 864, 896, 928, 960, 992, 1024)
+ MIN_SIZE_TRAIN_SAMPLING: "choice"
+ MIN_SIZE_TEST: 1024
+ MAX_SIZE_TRAIN: 2048
+ MAX_SIZE_TEST: 2048
+DATASETS:
+ TRAIN: ("cityscapes_fine_instance_seg_train",)
+ TEST: ("cityscapes_fine_instance_seg_val",)
+SOLVER:
+ BASE_LR: 0.01
+ STEPS: (18000,)
+ MAX_ITER: 24000
+ IMS_PER_BATCH: 8
+TEST:
+ EVAL_PERIOD: 8000
diff --git a/detectron2/configs/Detectron1-Comparisons/README.md b/detectron2/configs/Detectron1-Comparisons/README.md
new file mode 100755
index 0000000..924fd00
--- /dev/null
+++ b/detectron2/configs/Detectron1-Comparisons/README.md
@@ -0,0 +1,84 @@
+
+Detectron2 model zoo's experimental settings and a few implementation details are different from Detectron.
+
+The differences in implementation details are shared in
+[Compatibility with Other Libraries](../../docs/notes/compatibility.md).
+
+The differences in model zoo's experimental settings include:
+* Use scale augmentation during training. This improves AP with lower training cost.
+* Use L1 loss instead of smooth L1 loss for simplicity. This sometimes improves box AP but may
+ affect other AP.
+* Use `POOLER_SAMPLING_RATIO=0` instead of 2. This does not significantly affect AP.
+* Use `ROIAlignV2`. This does not significantly affect AP.
+
+In this directory, we provide a few configs that __do not__ have the above changes.
+They mimic Detectron's behavior as close as possible,
+and provide a fair comparison of accuracy and speed against Detectron.
+
+
+
+
+
+
+## Comparisons:
+
+* Faster R-CNN: Detectron's AP is 36.7, similar to ours.
+* Keypoint R-CNN: Detectron's AP is box 53.6, keypoint 64.2. Fixing a Detectron's
+ [bug](https://github.com/facebookresearch/Detectron/issues/459) lead to a drop in box AP, and can be
+ compensated back by some parameter tuning.
+* Mask R-CNN: Detectron's AP is box 37.7, mask 33.9. We're 1 AP better in mask AP, due to more correct implementation.
+ See [this article](https://ppwwyyxx.com/blog/2021/Where-are-Pixels/) for details.
+
+For speed comparison, see [benchmarks](https://detectron2.readthedocs.io/notes/benchmarks.html).
diff --git a/detectron2/configs/Detectron1-Comparisons/faster_rcnn_R_50_FPN_noaug_1x.yaml b/detectron2/configs/Detectron1-Comparisons/faster_rcnn_R_50_FPN_noaug_1x.yaml
new file mode 100755
index 0000000..6ce77f1
--- /dev/null
+++ b/detectron2/configs/Detectron1-Comparisons/faster_rcnn_R_50_FPN_noaug_1x.yaml
@@ -0,0 +1,17 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+ # Detectron1 uses smooth L1 loss with some magic beta values.
+ # The defaults are changed to L1 loss in Detectron2.
+ RPN:
+ SMOOTH_L1_BETA: 0.1111
+ ROI_BOX_HEAD:
+ SMOOTH_L1_BETA: 1.0
+ POOLER_SAMPLING_RATIO: 2
+ POOLER_TYPE: "ROIAlign"
+INPUT:
+ # no scale augmentation
+ MIN_SIZE_TRAIN: (800, )
diff --git a/detectron2/configs/Detectron1-Comparisons/keypoint_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/Detectron1-Comparisons/keypoint_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..aacf868
--- /dev/null
+++ b/detectron2/configs/Detectron1-Comparisons/keypoint_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,27 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ KEYPOINT_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NUM_CLASSES: 1
+ ROI_KEYPOINT_HEAD:
+ POOLER_RESOLUTION: 14
+ POOLER_SAMPLING_RATIO: 2
+ POOLER_TYPE: "ROIAlign"
+ # Detectron1 uses smooth L1 loss with some magic beta values.
+ # The defaults are changed to L1 loss in Detectron2.
+ ROI_BOX_HEAD:
+ SMOOTH_L1_BETA: 1.0
+ POOLER_SAMPLING_RATIO: 2
+ POOLER_TYPE: "ROIAlign"
+ RPN:
+ SMOOTH_L1_BETA: 0.1111
+ # Detectron1 uses 2000 proposals per-batch, but this option is per-image in detectron2
+ # 1000 proposals per-image is found to hurt box AP.
+ # Therefore we increase it to 1500 per-image.
+ POST_NMS_TOPK_TRAIN: 1500
+DATASETS:
+ TRAIN: ("keypoints_coco_2017_train",)
+ TEST: ("keypoints_coco_2017_val",)
diff --git a/detectron2/configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml b/detectron2/configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml
new file mode 100755
index 0000000..4ea86a8
--- /dev/null
+++ b/detectron2/configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml
@@ -0,0 +1,20 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ # Detectron1 uses smooth L1 loss with some magic beta values.
+ # The defaults are changed to L1 loss in Detectron2.
+ RPN:
+ SMOOTH_L1_BETA: 0.1111
+ ROI_BOX_HEAD:
+ SMOOTH_L1_BETA: 1.0
+ POOLER_SAMPLING_RATIO: 2
+ POOLER_TYPE: "ROIAlign"
+ ROI_MASK_HEAD:
+ POOLER_SAMPLING_RATIO: 2
+ POOLER_TYPE: "ROIAlign"
+INPUT:
+ # no scale augmentation
+ MIN_SIZE_TRAIN: (800, )
diff --git a/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_R_101_FPN_1x.yaml b/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_R_101_FPN_1x.yaml
new file mode 100755
index 0000000..f0c3a1b
--- /dev/null
+++ b/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_R_101_FPN_1x.yaml
@@ -0,0 +1,19 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 101
+ ROI_HEADS:
+ NUM_CLASSES: 1230
+ SCORE_THRESH_TEST: 0.0001
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+DATASETS:
+ TRAIN: ("lvis_v0.5_train",)
+ TEST: ("lvis_v0.5_val",)
+TEST:
+ DETECTIONS_PER_IMAGE: 300 # LVIS allows up to 300
+DATALOADER:
+ SAMPLER_TRAIN: "RepeatFactorTrainingSampler"
+ REPEAT_THRESHOLD: 0.001
diff --git a/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..64b4caa
--- /dev/null
+++ b/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,19 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NUM_CLASSES: 1230
+ SCORE_THRESH_TEST: 0.0001
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+DATASETS:
+ TRAIN: ("lvis_v0.5_train",)
+ TEST: ("lvis_v0.5_val",)
+TEST:
+ DETECTIONS_PER_IMAGE: 300 # LVIS allows up to 300
+DATALOADER:
+ SAMPLER_TRAIN: "RepeatFactorTrainingSampler"
+ REPEAT_THRESHOLD: 0.001
diff --git a/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_1x.yaml b/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_1x.yaml
new file mode 100755
index 0000000..c8b822c
--- /dev/null
+++ b/detectron2/configs/LVISv0.5-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_1x.yaml
@@ -0,0 +1,23 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ MASK_ON: True
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+ ROI_HEADS:
+ NUM_CLASSES: 1230
+ SCORE_THRESH_TEST: 0.0001
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+DATASETS:
+ TRAIN: ("lvis_v0.5_train",)
+ TEST: ("lvis_v0.5_val",)
+TEST:
+ DETECTIONS_PER_IMAGE: 300 # LVIS allows up to 300
+DATALOADER:
+ SAMPLER_TRAIN: "RepeatFactorTrainingSampler"
+ REPEAT_THRESHOLD: 0.001
diff --git a/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_R_101_FPN_1x.yaml b/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_R_101_FPN_1x.yaml
new file mode 100755
index 0000000..ca4dd97
--- /dev/null
+++ b/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_R_101_FPN_1x.yaml
@@ -0,0 +1,22 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-101.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 101
+ ROI_HEADS:
+ NUM_CLASSES: 1203
+ SCORE_THRESH_TEST: 0.0001
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+DATASETS:
+ TRAIN: ("lvis_v1_train",)
+ TEST: ("lvis_v1_val",)
+TEST:
+ DETECTIONS_PER_IMAGE: 300 # LVIS allows up to 300
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000 # 180000 * 16 / 100000 ~ 28.8 epochs
+DATALOADER:
+ SAMPLER_TRAIN: "RepeatFactorTrainingSampler"
+ REPEAT_THRESHOLD: 0.001
diff --git a/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..f313295
--- /dev/null
+++ b/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,22 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NUM_CLASSES: 1203
+ SCORE_THRESH_TEST: 0.0001
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+DATASETS:
+ TRAIN: ("lvis_v1_train",)
+ TEST: ("lvis_v1_val",)
+TEST:
+ DETECTIONS_PER_IMAGE: 300 # LVIS allows up to 300
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000 # 180000 * 16 / 100000 ~ 28.8 epochs
+DATALOADER:
+ SAMPLER_TRAIN: "RepeatFactorTrainingSampler"
+ REPEAT_THRESHOLD: 0.001
diff --git a/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_1x.yaml b/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_1x.yaml
new file mode 100755
index 0000000..f6528f7
--- /dev/null
+++ b/detectron2/configs/LVISv1-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_1x.yaml
@@ -0,0 +1,26 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/FAIR/X-101-32x8d.pkl"
+ PIXEL_STD: [57.375, 57.120, 58.395]
+ MASK_ON: True
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 101
+ ROI_HEADS:
+ NUM_CLASSES: 1203
+ SCORE_THRESH_TEST: 0.0001
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+DATASETS:
+ TRAIN: ("lvis_v1_train",)
+ TEST: ("lvis_v1_val",)
+SOLVER:
+ STEPS: (120000, 160000)
+ MAX_ITER: 180000 # 180000 * 16 / 100000 ~ 28.8 epochs
+TEST:
+ DETECTIONS_PER_IMAGE: 300 # LVIS allows up to 300
+DATALOADER:
+ SAMPLER_TRAIN: "RepeatFactorTrainingSampler"
+ REPEAT_THRESHOLD: 0.001
diff --git a/detectron2/configs/Misc/cascade_mask_rcnn_R_50_FPN_1x.yaml b/detectron2/configs/Misc/cascade_mask_rcnn_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..abb33b6
--- /dev/null
+++ b/detectron2/configs/Misc/cascade_mask_rcnn_R_50_FPN_1x.yaml
@@ -0,0 +1,12 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NAME: CascadeROIHeads
+ ROI_BOX_HEAD:
+ CLS_AGNOSTIC_BBOX_REG: True
+ RPN:
+ POST_NMS_TOPK_TRAIN: 2000
diff --git a/detectron2/configs/Misc/cascade_mask_rcnn_R_50_FPN_3x.yaml b/detectron2/configs/Misc/cascade_mask_rcnn_R_50_FPN_3x.yaml
new file mode 100755
index 0000000..e2201ad
--- /dev/null
+++ b/detectron2/configs/Misc/cascade_mask_rcnn_R_50_FPN_3x.yaml
@@ -0,0 +1,15 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NAME: CascadeROIHeads
+ ROI_BOX_HEAD:
+ CLS_AGNOSTIC_BBOX_REG: True
+ RPN:
+ POST_NMS_TOPK_TRAIN: 2000
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/Misc/cascade_mask_rcnn_X_152_32x8d_FPN_IN5k_gn_dconv.yaml b/detectron2/configs/Misc/cascade_mask_rcnn_X_152_32x8d_FPN_IN5k_gn_dconv.yaml
new file mode 100755
index 0000000..fc117f6
--- /dev/null
+++ b/detectron2/configs/Misc/cascade_mask_rcnn_X_152_32x8d_FPN_IN5k_gn_dconv.yaml
@@ -0,0 +1,36 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ MASK_ON: True
+ WEIGHTS: "catalog://ImageNetPretrained/FAIR/X-152-32x8d-IN5k"
+ RESNETS:
+ STRIDE_IN_1X1: False # this is a C2 model
+ NUM_GROUPS: 32
+ WIDTH_PER_GROUP: 8
+ DEPTH: 152
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ ROI_HEADS:
+ NAME: "CascadeROIHeads"
+ ROI_BOX_HEAD:
+ NAME: "FastRCNNConvFCHead"
+ NUM_CONV: 4
+ NUM_FC: 1
+ NORM: "GN"
+ CLS_AGNOSTIC_BBOX_REG: True
+ ROI_MASK_HEAD:
+ NUM_CONV: 8
+ NORM: "GN"
+ RPN:
+ POST_NMS_TOPK_TRAIN: 2000
+SOLVER:
+ IMS_PER_BATCH: 128
+ STEPS: (35000, 45000)
+ MAX_ITER: 50000
+ BASE_LR: 0.16
+INPUT:
+ MIN_SIZE_TRAIN: (640, 864)
+ MIN_SIZE_TRAIN_SAMPLING: "range"
+ MAX_SIZE_TRAIN: 1440
+ CROP:
+ ENABLED: True
+TEST:
+ EVAL_PERIOD: 2500
diff --git a/detectron2/configs/Misc/mask_rcnn_R_50_FPN_1x_cls_agnostic.yaml b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_1x_cls_agnostic.yaml
new file mode 100755
index 0000000..4c3b767
--- /dev/null
+++ b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_1x_cls_agnostic.yaml
@@ -0,0 +1,10 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_BOX_HEAD:
+ CLS_AGNOSTIC_BBOX_REG: True
+ ROI_MASK_HEAD:
+ CLS_AGNOSTIC_MASK: True
diff --git a/detectron2/configs/Misc/mask_rcnn_R_50_FPN_1x_dconv_c3-c5.yaml b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_1x_dconv_c3-c5.yaml
new file mode 100755
index 0000000..04ff988
--- /dev/null
+++ b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_1x_dconv_c3-c5.yaml
@@ -0,0 +1,8 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ DEFORM_ON_PER_STAGE: [False, True, True, True] # on Res3,Res4,Res5
+ DEFORM_MODULATED: False
diff --git a/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_dconv_c3-c5.yaml b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_dconv_c3-c5.yaml
new file mode 100755
index 0000000..68c0ca5
--- /dev/null
+++ b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_dconv_c3-c5.yaml
@@ -0,0 +1,11 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ DEFORM_ON_PER_STAGE: [False, True, True, True] # on Res3,Res4,Res5
+ DEFORM_MODULATED: False
+SOLVER:
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_gn.yaml b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_gn.yaml
new file mode 100755
index 0000000..74d274e
--- /dev/null
+++ b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_gn.yaml
@@ -0,0 +1,21 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "catalog://ImageNetPretrained/FAIR/R-50-GN"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ NORM: "GN"
+ STRIDE_IN_1X1: False
+ FPN:
+ NORM: "GN"
+ ROI_BOX_HEAD:
+ NAME: "FastRCNNConvFCHead"
+ NUM_CONV: 4
+ NUM_FC: 1
+ NORM: "GN"
+ ROI_MASK_HEAD:
+ NORM: "GN"
+SOLVER:
+ # 3x schedule
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
diff --git a/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_syncbn.yaml b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_syncbn.yaml
new file mode 100755
index 0000000..11ebb07
--- /dev/null
+++ b/detectron2/configs/Misc/mask_rcnn_R_50_FPN_3x_syncbn.yaml
@@ -0,0 +1,24 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ NORM: "SyncBN"
+ STRIDE_IN_1X1: True
+ FPN:
+ NORM: "SyncBN"
+ ROI_BOX_HEAD:
+ NAME: "FastRCNNConvFCHead"
+ NUM_CONV: 4
+ NUM_FC: 1
+ NORM: "SyncBN"
+ ROI_MASK_HEAD:
+ NORM: "SyncBN"
+SOLVER:
+ # 3x schedule
+ STEPS: (210000, 250000)
+ MAX_ITER: 270000
+TEST:
+ PRECISE_BN:
+ ENABLED: True
diff --git a/detectron2/configs/Misc/mmdet_mask_rcnn_R_50_FPN_1x.py b/detectron2/configs/Misc/mmdet_mask_rcnn_R_50_FPN_1x.py
new file mode 100755
index 0000000..bdd49a4
--- /dev/null
+++ b/detectron2/configs/Misc/mmdet_mask_rcnn_R_50_FPN_1x.py
@@ -0,0 +1,152 @@
+# An example config to train a mmdetection model using detectron2.
+
+from ..common.data.coco import dataloader
+from ..common.coco_schedule import lr_multiplier_1x as lr_multiplier
+from ..common.optim import SGD as optimizer
+from ..common.train import train
+from ..common.data.constants import constants
+
+from detectron2.modeling.mmdet_wrapper import MMDetDetector
+from detectron2.config import LazyCall as L
+
+model = L(MMDetDetector)(
+ detector=dict(
+ type="MaskRCNN",
+ pretrained="torchvision://resnet50",
+ backbone=dict(
+ type="ResNet",
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ frozen_stages=1,
+ norm_cfg=dict(type="BN", requires_grad=True),
+ norm_eval=True,
+ style="pytorch",
+ ),
+ neck=dict(type="FPN", in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5),
+ rpn_head=dict(
+ type="RPNHead",
+ in_channels=256,
+ feat_channels=256,
+ anchor_generator=dict(
+ type="AnchorGenerator",
+ scales=[8],
+ ratios=[0.5, 1.0, 2.0],
+ strides=[4, 8, 16, 32, 64],
+ ),
+ bbox_coder=dict(
+ type="DeltaXYWHBBoxCoder",
+ target_means=[0.0, 0.0, 0.0, 0.0],
+ target_stds=[1.0, 1.0, 1.0, 1.0],
+ ),
+ loss_cls=dict(type="CrossEntropyLoss", use_sigmoid=True, loss_weight=1.0),
+ loss_bbox=dict(type="L1Loss", loss_weight=1.0),
+ ),
+ roi_head=dict(
+ type="StandardRoIHead",
+ bbox_roi_extractor=dict(
+ type="SingleRoIExtractor",
+ roi_layer=dict(type="RoIAlign", output_size=7, sampling_ratio=0),
+ out_channels=256,
+ featmap_strides=[4, 8, 16, 32],
+ ),
+ bbox_head=dict(
+ type="Shared2FCBBoxHead",
+ in_channels=256,
+ fc_out_channels=1024,
+ roi_feat_size=7,
+ num_classes=80,
+ bbox_coder=dict(
+ type="DeltaXYWHBBoxCoder",
+ target_means=[0.0, 0.0, 0.0, 0.0],
+ target_stds=[0.1, 0.1, 0.2, 0.2],
+ ),
+ reg_class_agnostic=False,
+ loss_cls=dict(type="CrossEntropyLoss", use_sigmoid=False, loss_weight=1.0),
+ loss_bbox=dict(type="L1Loss", loss_weight=1.0),
+ ),
+ mask_roi_extractor=dict(
+ type="SingleRoIExtractor",
+ roi_layer=dict(type="RoIAlign", output_size=14, sampling_ratio=0),
+ out_channels=256,
+ featmap_strides=[4, 8, 16, 32],
+ ),
+ mask_head=dict(
+ type="FCNMaskHead",
+ num_convs=4,
+ in_channels=256,
+ conv_out_channels=256,
+ num_classes=80,
+ loss_mask=dict(type="CrossEntropyLoss", use_mask=True, loss_weight=1.0),
+ ),
+ ),
+ # model training and testing settings
+ train_cfg=dict(
+ rpn=dict(
+ assigner=dict(
+ type="MaxIoUAssigner",
+ pos_iou_thr=0.7,
+ neg_iou_thr=0.3,
+ min_pos_iou=0.3,
+ match_low_quality=True,
+ ignore_iof_thr=-1,
+ ),
+ sampler=dict(
+ type="RandomSampler",
+ num=256,
+ pos_fraction=0.5,
+ neg_pos_ub=-1,
+ add_gt_as_proposals=False,
+ ),
+ allowed_border=-1,
+ pos_weight=-1,
+ debug=False,
+ ),
+ rpn_proposal=dict(
+ nms_pre=2000,
+ max_per_img=1000,
+ nms=dict(type="nms", iou_threshold=0.7),
+ min_bbox_size=0,
+ ),
+ rcnn=dict(
+ assigner=dict(
+ type="MaxIoUAssigner",
+ pos_iou_thr=0.5,
+ neg_iou_thr=0.5,
+ min_pos_iou=0.5,
+ match_low_quality=True,
+ ignore_iof_thr=-1,
+ ),
+ sampler=dict(
+ type="RandomSampler",
+ num=512,
+ pos_fraction=0.25,
+ neg_pos_ub=-1,
+ add_gt_as_proposals=True,
+ ),
+ mask_size=28,
+ pos_weight=-1,
+ debug=False,
+ ),
+ ),
+ test_cfg=dict(
+ rpn=dict(
+ nms_pre=1000,
+ max_per_img=1000,
+ nms=dict(type="nms", iou_threshold=0.7),
+ min_bbox_size=0,
+ ),
+ rcnn=dict(
+ score_thr=0.05,
+ nms=dict(type="nms", iou_threshold=0.5),
+ max_per_img=100,
+ mask_thr_binary=0.5,
+ ),
+ ),
+ ),
+ pixel_mean=constants.imagenet_rgb256_mean,
+ pixel_std=constants.imagenet_rgb256_std,
+)
+
+dataloader.train.mapper.image_format = "RGB" # torchvision pretrained model
+train.init_checkpoint = None # pretrained model is loaded inside backbone
diff --git a/detectron2/configs/Misc/panoptic_fpn_R_101_dconv_cascade_gn_3x.yaml b/detectron2/configs/Misc/panoptic_fpn_R_101_dconv_cascade_gn_3x.yaml
new file mode 100755
index 0000000..34016ce
--- /dev/null
+++ b/detectron2/configs/Misc/panoptic_fpn_R_101_dconv_cascade_gn_3x.yaml
@@ -0,0 +1,26 @@
+# A large PanopticFPN for demo purposes.
+# Use GN on backbone to support semantic seg.
+# Use Cascade + Deform Conv to improve localization.
+_BASE_: "../COCO-PanopticSegmentation/Base-Panoptic-FPN.yaml"
+MODEL:
+ WEIGHTS: "catalog://ImageNetPretrained/FAIR/R-101-GN"
+ RESNETS:
+ DEPTH: 101
+ NORM: "GN"
+ DEFORM_ON_PER_STAGE: [False, True, True, True]
+ STRIDE_IN_1X1: False
+ FPN:
+ NORM: "GN"
+ ROI_HEADS:
+ NAME: CascadeROIHeads
+ ROI_BOX_HEAD:
+ CLS_AGNOSTIC_BBOX_REG: True
+ ROI_MASK_HEAD:
+ NORM: "GN"
+ RPN:
+ POST_NMS_TOPK_TRAIN: 2000
+SOLVER:
+ STEPS: (105000, 125000)
+ MAX_ITER: 135000
+ IMS_PER_BATCH: 32
+ BASE_LR: 0.04
diff --git a/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_3x_gn.yaml b/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_3x_gn.yaml
new file mode 100755
index 0000000..f340028
--- /dev/null
+++ b/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_3x_gn.yaml
@@ -0,0 +1,13 @@
+_BASE_: "mask_rcnn_R_50_FPN_3x_gn.yaml"
+MODEL:
+ # Train from random initialization.
+ WEIGHTS: ""
+ # It makes sense to divide by STD when training from scratch
+ # But it seems to make no difference on the results and C2's models didn't do this.
+ # So we keep things consistent with C2.
+ # PIXEL_STD: [57.375, 57.12, 58.395]
+ MASK_ON: True
+ BACKBONE:
+ FREEZE_AT: 0
+# NOTE: Please refer to Rethinking ImageNet Pre-training https://arxiv.org/abs/1811.08883
+# to learn what you need for training from scratch.
diff --git a/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_9x_gn.yaml b/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_9x_gn.yaml
new file mode 100755
index 0000000..d90c9ff
--- /dev/null
+++ b/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_9x_gn.yaml
@@ -0,0 +1,19 @@
+_BASE_: "mask_rcnn_R_50_FPN_3x_gn.yaml"
+MODEL:
+ PIXEL_STD: [57.375, 57.12, 58.395]
+ WEIGHTS: ""
+ MASK_ON: True
+ RESNETS:
+ STRIDE_IN_1X1: False
+ BACKBONE:
+ FREEZE_AT: 0
+SOLVER:
+ # 9x schedule
+ IMS_PER_BATCH: 64 # 4x the standard
+ STEPS: (187500, 197500) # last 60/4==15k and last 20/4==5k
+ MAX_ITER: 202500 # 90k * 9 / 4
+ BASE_LR: 0.08
+TEST:
+ EVAL_PERIOD: 2500
+# NOTE: Please refer to Rethinking ImageNet Pre-training https://arxiv.org/abs/1811.08883
+# to learn what you need for training from scratch.
diff --git a/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_9x_syncbn.yaml b/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_9x_syncbn.yaml
new file mode 100755
index 0000000..60d4e42
--- /dev/null
+++ b/detectron2/configs/Misc/scratch_mask_rcnn_R_50_FPN_9x_syncbn.yaml
@@ -0,0 +1,19 @@
+_BASE_: "mask_rcnn_R_50_FPN_3x_syncbn.yaml"
+MODEL:
+ PIXEL_STD: [57.375, 57.12, 58.395]
+ WEIGHTS: ""
+ MASK_ON: True
+ RESNETS:
+ STRIDE_IN_1X1: False
+ BACKBONE:
+ FREEZE_AT: 0
+SOLVER:
+ # 9x schedule
+ IMS_PER_BATCH: 64 # 4x the standard
+ STEPS: (187500, 197500) # last 60/4==15k and last 20/4==5k
+ MAX_ITER: 202500 # 90k * 9 / 4
+ BASE_LR: 0.08
+TEST:
+ EVAL_PERIOD: 2500
+# NOTE: Please refer to Rethinking ImageNet Pre-training https://arxiv.org/abs/1811.08883
+# to learn what you need for training from scratch.
diff --git a/detectron2/configs/Misc/semantic_R_50_FPN_1x.yaml b/detectron2/configs/Misc/semantic_R_50_FPN_1x.yaml
new file mode 100755
index 0000000..ac256e1
--- /dev/null
+++ b/detectron2/configs/Misc/semantic_R_50_FPN_1x.yaml
@@ -0,0 +1,11 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "SemanticSegmentor"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+DATASETS:
+ TRAIN: ("coco_2017_train_panoptic_stuffonly",)
+ TEST: ("coco_2017_val_panoptic_stuffonly",)
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
diff --git a/detectron2/configs/Misc/torchvision_imagenet_R_50.py b/detectron2/configs/Misc/torchvision_imagenet_R_50.py
new file mode 100755
index 0000000..0d75305
--- /dev/null
+++ b/detectron2/configs/Misc/torchvision_imagenet_R_50.py
@@ -0,0 +1,150 @@
+"""
+An example config file to train a ImageNet classifier with detectron2.
+Model and dataloader both come from torchvision.
+This shows how to use detectron2 as a general engine for any new models and tasks.
+
+To run, use the following command:
+
+python tools/lazyconfig_train_net.py --config-file configs/Misc/torchvision_imagenet_R_50.py \
+ --num-gpus 8 dataloader.train.dataset.root=/path/to/imagenet/
+
+"""
+
+
+import torch
+from torch import nn
+from torch.nn import functional as F
+from omegaconf import OmegaConf
+import torchvision
+from torchvision.transforms import transforms as T
+from torchvision.models.resnet import ResNet, Bottleneck
+from fvcore.common.param_scheduler import MultiStepParamScheduler
+
+from detectron2.solver import WarmupParamScheduler
+from detectron2.solver.build import get_default_optimizer_params
+from detectron2.config import LazyCall as L
+from detectron2.model_zoo import get_config
+from detectron2.data.samplers import TrainingSampler, InferenceSampler
+from detectron2.evaluation import DatasetEvaluator
+from detectron2.utils import comm
+
+
+"""
+Note: Here we put reusable code (models, evaluation, data) together with configs just as a
+proof-of-concept, to easily demonstrate what's needed to train a ImageNet classifier in detectron2.
+Writing code in configs offers extreme flexibility but is often not a good engineering practice.
+In practice, you might want to put code in your project and import them instead.
+"""
+
+
+def build_data_loader(dataset, batch_size, num_workers, training=True):
+ return torch.utils.data.DataLoader(
+ dataset,
+ sampler=(TrainingSampler if training else InferenceSampler)(len(dataset)),
+ batch_size=batch_size,
+ num_workers=num_workers,
+ pin_memory=True,
+ )
+
+
+class ClassificationNet(nn.Module):
+ def __init__(self, model: nn.Module):
+ super().__init__()
+ self.model = model
+
+ @property
+ def device(self):
+ return list(self.model.parameters())[0].device
+
+ def forward(self, inputs):
+ image, label = inputs
+ pred = self.model(image.to(self.device))
+ if self.training:
+ label = label.to(self.device)
+ return F.cross_entropy(pred, label)
+ else:
+ return pred
+
+
+class ClassificationAcc(DatasetEvaluator):
+ def reset(self):
+ self.corr = self.total = 0
+
+ def process(self, inputs, outputs):
+ image, label = inputs
+ self.corr += (outputs.argmax(dim=1).cpu() == label.cpu()).sum().item()
+ self.total += len(label)
+
+ def evaluate(self):
+ all_corr_total = comm.all_gather([self.corr, self.total])
+ corr = sum(x[0] for x in all_corr_total)
+ total = sum(x[1] for x in all_corr_total)
+ return {"accuracy": corr / total}
+
+
+# --- End of code that could be in a project and be imported
+
+
+dataloader = OmegaConf.create()
+dataloader.train = L(build_data_loader)(
+ dataset=L(torchvision.datasets.ImageNet)(
+ root="/path/to/imagenet",
+ split="train",
+ transform=L(T.Compose)(
+ transforms=[
+ L(T.RandomResizedCrop)(size=224),
+ L(T.RandomHorizontalFlip)(),
+ T.ToTensor(),
+ L(T.Normalize)(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
+ ]
+ ),
+ ),
+ batch_size=256 // 8,
+ num_workers=4,
+ training=True,
+)
+
+dataloader.test = L(build_data_loader)(
+ dataset=L(torchvision.datasets.ImageNet)(
+ root="${...train.dataset.root}",
+ split="val",
+ transform=L(T.Compose)(
+ transforms=[
+ L(T.Resize)(size=256),
+ L(T.CenterCrop)(size=224),
+ T.ToTensor(),
+ L(T.Normalize)(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
+ ]
+ ),
+ ),
+ batch_size=256 // 8,
+ num_workers=4,
+ training=False,
+)
+
+dataloader.evaluator = L(ClassificationAcc)()
+
+model = L(ClassificationNet)(
+ model=(ResNet)(block=Bottleneck, layers=[3, 4, 6, 3], zero_init_residual=True)
+)
+
+
+optimizer = L(torch.optim.SGD)(
+ params=L(get_default_optimizer_params)(),
+ lr=0.1,
+ momentum=0.9,
+ weight_decay=1e-4,
+)
+
+lr_multiplier = L(WarmupParamScheduler)(
+ scheduler=L(MultiStepParamScheduler)(
+ values=[1.0, 0.1, 0.01, 0.001], milestones=[30, 60, 90, 100]
+ ),
+ warmup_length=1 / 100,
+ warmup_factor=0.1,
+)
+
+
+train = get_config("common/train.py").train
+train.init_checkpoint = None
+train.max_iter = 100 * 1281167 // 256
diff --git a/detectron2/configs/PascalVOC-Detection/faster_rcnn_R_50_C4.yaml b/detectron2/configs/PascalVOC-Detection/faster_rcnn_R_50_C4.yaml
new file mode 100755
index 0000000..ea2a6ba
--- /dev/null
+++ b/detectron2/configs/PascalVOC-Detection/faster_rcnn_R_50_C4.yaml
@@ -0,0 +1,18 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NUM_CLASSES: 20
+INPUT:
+ MIN_SIZE_TRAIN: (480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800)
+ MIN_SIZE_TEST: 800
+DATASETS:
+ TRAIN: ('voc_2007_trainval', 'voc_2012_trainval')
+ TEST: ('voc_2007_test',)
+SOLVER:
+ STEPS: (12000, 16000)
+ MAX_ITER: 18000 # 17.4 epochs
+ WARMUP_ITERS: 100
diff --git a/detectron2/configs/PascalVOC-Detection/faster_rcnn_R_50_FPN.yaml b/detectron2/configs/PascalVOC-Detection/faster_rcnn_R_50_FPN.yaml
new file mode 100755
index 0000000..e554cab
--- /dev/null
+++ b/detectron2/configs/PascalVOC-Detection/faster_rcnn_R_50_FPN.yaml
@@ -0,0 +1,18 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: False
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ NUM_CLASSES: 20
+INPUT:
+ MIN_SIZE_TRAIN: (480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800)
+ MIN_SIZE_TEST: 800
+DATASETS:
+ TRAIN: ('voc_2007_trainval', 'voc_2012_trainval')
+ TEST: ('voc_2007_test',)
+SOLVER:
+ STEPS: (12000, 16000)
+ MAX_ITER: 18000 # 17.4 epochs
+ WARMUP_ITERS: 100
diff --git a/detectron2/configs/common/README.md b/detectron2/configs/common/README.md
new file mode 100755
index 0000000..912cc29
--- /dev/null
+++ b/detectron2/configs/common/README.md
@@ -0,0 +1,6 @@
+This directory provides definitions for a few common models, dataloaders, scheduler,
+and optimizers that are often used in training.
+The definition of these objects are provided in the form of lazy instantiation:
+their arguments can be edited by users before constructing the objects.
+
+They can be imported, or loaded by `model_zoo.get_config` API in users' own configs.
diff --git a/detectron2/configs/common/coco_schedule.py b/detectron2/configs/common/coco_schedule.py
new file mode 100755
index 0000000..355e66a
--- /dev/null
+++ b/detectron2/configs/common/coco_schedule.py
@@ -0,0 +1,47 @@
+from fvcore.common.param_scheduler import MultiStepParamScheduler
+
+from detectron2.config import LazyCall as L
+from detectron2.solver import WarmupParamScheduler
+
+
+def default_X_scheduler(num_X):
+ """
+ Returns the config for a default multi-step LR scheduler such as "1x", "3x",
+ commonly referred to in papers, where every 1x has the total length of 1440k
+ training images (~12 COCO epochs). LR is decayed twice at the end of training
+ following the strategy defined in "Rethinking ImageNet Pretraining", Sec 4.
+
+ Args:
+ num_X: a positive real number
+
+ Returns:
+ DictConfig: configs that define the multiplier for LR during training
+ """
+ # total number of iterations assuming 16 batch size, using 1440000/16=90000
+ total_steps_16bs = num_X * 90000
+
+ if num_X <= 2:
+ scheduler = L(MultiStepParamScheduler)(
+ values=[1.0, 0.1, 0.01],
+ # note that scheduler is scale-invariant. This is equivalent to
+ # milestones=[6, 8, 9]
+ milestones=[60000, 80000, 90000],
+ )
+ else:
+ scheduler = L(MultiStepParamScheduler)(
+ values=[1.0, 0.1, 0.01],
+ milestones=[total_steps_16bs - 60000, total_steps_16bs - 20000, total_steps_16bs],
+ )
+ return L(WarmupParamScheduler)(
+ scheduler=scheduler,
+ warmup_length=1000 / total_steps_16bs,
+ warmup_method="linear",
+ warmup_factor=0.001,
+ )
+
+
+lr_multiplier_1x = default_X_scheduler(1)
+lr_multiplier_2x = default_X_scheduler(2)
+lr_multiplier_3x = default_X_scheduler(3)
+lr_multiplier_6x = default_X_scheduler(6)
+lr_multiplier_9x = default_X_scheduler(9)
diff --git a/detectron2/configs/common/data/coco.py b/detectron2/configs/common/data/coco.py
new file mode 100755
index 0000000..703c438
--- /dev/null
+++ b/detectron2/configs/common/data/coco.py
@@ -0,0 +1,48 @@
+from omegaconf import OmegaConf
+
+import detectron2.data.transforms as T
+from detectron2.config import LazyCall as L
+from detectron2.data import (
+ DatasetMapper,
+ build_detection_test_loader,
+ build_detection_train_loader,
+ get_detection_dataset_dicts,
+)
+from detectron2.evaluation import COCOEvaluator
+
+dataloader = OmegaConf.create()
+
+dataloader.train = L(build_detection_train_loader)(
+ dataset=L(get_detection_dataset_dicts)(names="coco_2017_train"),
+ mapper=L(DatasetMapper)(
+ is_train=True,
+ augmentations=[
+ L(T.ResizeShortestEdge)(
+ short_edge_length=(640, 672, 704, 736, 768, 800),
+ sample_style="choice",
+ max_size=1333,
+ ),
+ L(T.RandomFlip)(horizontal=True),
+ ],
+ image_format="BGR",
+ use_instance_mask=True,
+ ),
+ total_batch_size=16,
+ num_workers=4,
+)
+
+dataloader.test = L(build_detection_test_loader)(
+ dataset=L(get_detection_dataset_dicts)(names="coco_2017_val", filter_empty=False),
+ mapper=L(DatasetMapper)(
+ is_train=False,
+ augmentations=[
+ L(T.ResizeShortestEdge)(short_edge_length=800, max_size=1333),
+ ],
+ image_format="${...train.mapper.image_format}",
+ ),
+ num_workers=4,
+)
+
+dataloader.evaluator = L(COCOEvaluator)(
+ dataset_name="${..test.dataset.names}",
+)
diff --git a/detectron2/configs/common/data/coco_keypoint.py b/detectron2/configs/common/data/coco_keypoint.py
new file mode 100755
index 0000000..b4ceb06
--- /dev/null
+++ b/detectron2/configs/common/data/coco_keypoint.py
@@ -0,0 +1,13 @@
+from detectron2.data.detection_utils import create_keypoint_hflip_indices
+
+from .coco import dataloader
+
+dataloader.train.dataset.min_keypoints = 1
+dataloader.train.dataset.names = "keypoints_coco_2017_train"
+dataloader.test.dataset.names = "keypoints_coco_2017_val"
+
+dataloader.train.mapper.update(
+ use_instance_mask=False,
+ use_keypoint=True,
+ keypoint_hflip_indices=create_keypoint_hflip_indices(dataloader.train.dataset.names),
+)
diff --git a/detectron2/configs/common/data/coco_panoptic_separated.py b/detectron2/configs/common/data/coco_panoptic_separated.py
new file mode 100755
index 0000000..5ccbc77
--- /dev/null
+++ b/detectron2/configs/common/data/coco_panoptic_separated.py
@@ -0,0 +1,26 @@
+from detectron2.config import LazyCall as L
+from detectron2.evaluation import (
+ COCOEvaluator,
+ COCOPanopticEvaluator,
+ DatasetEvaluators,
+ SemSegEvaluator,
+)
+
+from .coco import dataloader
+
+dataloader.train.dataset.names = "coco_2017_train_panoptic_separated"
+dataloader.train.dataset.filter_empty = False
+dataloader.test.dataset.names = "coco_2017_val_panoptic_separated"
+
+
+dataloader.evaluator = [
+ L(COCOEvaluator)(
+ dataset_name="${...test.dataset.names}",
+ ),
+ L(SemSegEvaluator)(
+ dataset_name="${...test.dataset.names}",
+ ),
+ L(COCOPanopticEvaluator)(
+ dataset_name="${...test.dataset.names}",
+ ),
+]
diff --git a/detectron2/configs/common/data/constants.py b/detectron2/configs/common/data/constants.py
new file mode 100755
index 0000000..be11cb5
--- /dev/null
+++ b/detectron2/configs/common/data/constants.py
@@ -0,0 +1,9 @@
+constants = dict(
+ imagenet_rgb256_mean=[123.675, 116.28, 103.53],
+ imagenet_rgb256_std=[58.395, 57.12, 57.375],
+ imagenet_bgr256_mean=[103.530, 116.280, 123.675],
+ # When using pre-trained models in Detectron1 or any MSRA models,
+ # std has been absorbed into its conv1 weights, so the std needs to be set 1.
+ # Otherwise, you can use [57.375, 57.120, 58.395] (ImageNet std)
+ imagenet_bgr256_std=[1.0, 1.0, 1.0],
+)
diff --git a/detectron2/configs/common/models/cascade_rcnn.py b/detectron2/configs/common/models/cascade_rcnn.py
new file mode 100755
index 0000000..c7372a8
--- /dev/null
+++ b/detectron2/configs/common/models/cascade_rcnn.py
@@ -0,0 +1,36 @@
+from detectron2.config import LazyCall as L
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.box_regression import Box2BoxTransform
+from detectron2.modeling.matcher import Matcher
+from detectron2.modeling.roi_heads import FastRCNNOutputLayers, FastRCNNConvFCHead, CascadeROIHeads
+
+from .mask_rcnn_fpn import model
+
+# arguments that don't exist for Cascade R-CNN
+[model.roi_heads.pop(k) for k in ["box_head", "box_predictor", "proposal_matcher"]]
+
+model.roi_heads.update(
+ _target_=CascadeROIHeads,
+ box_heads=[
+ L(FastRCNNConvFCHead)(
+ input_shape=ShapeSpec(channels=256, height=7, width=7),
+ conv_dims=[],
+ fc_dims=[1024, 1024],
+ )
+ for k in range(3)
+ ],
+ box_predictors=[
+ L(FastRCNNOutputLayers)(
+ input_shape=ShapeSpec(channels=1024),
+ test_score_thresh=0.05,
+ box2box_transform=L(Box2BoxTransform)(weights=(w1, w1, w2, w2)),
+ cls_agnostic_bbox_reg=True,
+ num_classes="${...num_classes}",
+ )
+ for (w1, w2) in [(10, 5), (20, 10), (30, 15)]
+ ],
+ proposal_matchers=[
+ L(Matcher)(thresholds=[th], labels=[0, 1], allow_low_quality_matches=False)
+ for th in [0.5, 0.6, 0.7]
+ ],
+)
diff --git a/detectron2/configs/common/models/fcos.py b/detectron2/configs/common/models/fcos.py
new file mode 100755
index 0000000..1c75202
--- /dev/null
+++ b/detectron2/configs/common/models/fcos.py
@@ -0,0 +1,23 @@
+from detectron2.modeling.meta_arch.fcos import FCOS, FCOSHead
+
+from .retinanet import model
+
+model._target_ = FCOS
+
+del model.anchor_generator
+del model.box2box_transform
+del model.anchor_matcher
+del model.input_format
+
+# Use P5 instead of C5 to compute P6/P7
+# (Sec 2.2 of https://arxiv.org/abs/2006.09214)
+model.backbone.top_block.in_feature = "p5"
+model.backbone.top_block.in_channels = 256
+
+# New score threshold determined based on sqrt(cls_score * centerness)
+model.test_score_thresh = 0.2
+model.test_nms_thresh = 0.6
+
+model.head._target_ = FCOSHead
+del model.head.num_anchors
+model.head.norm = "GN"
diff --git a/detectron2/configs/common/models/keypoint_rcnn_fpn.py b/detectron2/configs/common/models/keypoint_rcnn_fpn.py
new file mode 100755
index 0000000..56b3994
--- /dev/null
+++ b/detectron2/configs/common/models/keypoint_rcnn_fpn.py
@@ -0,0 +1,33 @@
+from detectron2.config import LazyCall as L
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.poolers import ROIPooler
+from detectron2.modeling.roi_heads import KRCNNConvDeconvUpsampleHead
+
+from .mask_rcnn_fpn import model
+
+[model.roi_heads.pop(x) for x in ["mask_in_features", "mask_pooler", "mask_head"]]
+
+model.roi_heads.update(
+ num_classes=1,
+ keypoint_in_features=["p2", "p3", "p4", "p5"],
+ keypoint_pooler=L(ROIPooler)(
+ output_size=14,
+ scales=(1.0 / 4, 1.0 / 8, 1.0 / 16, 1.0 / 32),
+ sampling_ratio=0,
+ pooler_type="ROIAlignV2",
+ ),
+ keypoint_head=L(KRCNNConvDeconvUpsampleHead)(
+ input_shape=ShapeSpec(channels=256, width=14, height=14),
+ num_keypoints=17,
+ conv_dims=[512] * 8,
+ loss_normalizer="visible",
+ ),
+)
+
+# Detectron1 uses 2000 proposals per-batch, but this option is per-image in detectron2.
+# 1000 proposals per-image is found to hurt box AP.
+# Therefore we increase it to 1500 per-image.
+model.proposal_generator.post_nms_topk = (1500, 1000)
+
+# Keypoint AP degrades (though box AP improves) when using plain L1 loss
+model.roi_heads.box_predictor.smooth_l1_beta = 0.5
diff --git a/detectron2/configs/common/models/mask_rcnn_c4.py b/detectron2/configs/common/models/mask_rcnn_c4.py
new file mode 100755
index 0000000..902d5b1
--- /dev/null
+++ b/detectron2/configs/common/models/mask_rcnn_c4.py
@@ -0,0 +1,90 @@
+from detectron2.config import LazyCall as L
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.meta_arch import GeneralizedRCNN
+from detectron2.modeling.anchor_generator import DefaultAnchorGenerator
+from detectron2.modeling.backbone import BasicStem, BottleneckBlock, ResNet
+from detectron2.modeling.box_regression import Box2BoxTransform
+from detectron2.modeling.matcher import Matcher
+from detectron2.modeling.poolers import ROIPooler
+from detectron2.modeling.proposal_generator import RPN, StandardRPNHead
+from detectron2.modeling.roi_heads import (
+ FastRCNNOutputLayers,
+ MaskRCNNConvUpsampleHead,
+ Res5ROIHeads,
+)
+
+from ..data.constants import constants
+
+model = L(GeneralizedRCNN)(
+ backbone=L(ResNet)(
+ stem=L(BasicStem)(in_channels=3, out_channels=64, norm="FrozenBN"),
+ stages=L(ResNet.make_default_stages)(
+ depth=50,
+ stride_in_1x1=True,
+ norm="FrozenBN",
+ ),
+ out_features=["res4"],
+ ),
+ proposal_generator=L(RPN)(
+ in_features=["res4"],
+ head=L(StandardRPNHead)(in_channels=1024, num_anchors=15),
+ anchor_generator=L(DefaultAnchorGenerator)(
+ sizes=[[32, 64, 128, 256, 512]],
+ aspect_ratios=[0.5, 1.0, 2.0],
+ strides=[16],
+ offset=0.0,
+ ),
+ anchor_matcher=L(Matcher)(
+ thresholds=[0.3, 0.7], labels=[0, -1, 1], allow_low_quality_matches=True
+ ),
+ box2box_transform=L(Box2BoxTransform)(weights=[1.0, 1.0, 1.0, 1.0]),
+ batch_size_per_image=256,
+ positive_fraction=0.5,
+ pre_nms_topk=(12000, 6000),
+ post_nms_topk=(2000, 1000),
+ nms_thresh=0.7,
+ ),
+ roi_heads=L(Res5ROIHeads)(
+ num_classes=80,
+ batch_size_per_image=512,
+ positive_fraction=0.25,
+ proposal_matcher=L(Matcher)(
+ thresholds=[0.5], labels=[0, 1], allow_low_quality_matches=False
+ ),
+ in_features=["res4"],
+ pooler=L(ROIPooler)(
+ output_size=14,
+ scales=(1.0 / 16,),
+ sampling_ratio=0,
+ pooler_type="ROIAlignV2",
+ ),
+ res5=L(ResNet.make_stage)(
+ block_class=BottleneckBlock,
+ num_blocks=3,
+ stride_per_block=[2, 1, 1],
+ in_channels=1024,
+ bottleneck_channels=512,
+ out_channels=2048,
+ norm="FrozenBN",
+ stride_in_1x1=True,
+ ),
+ box_predictor=L(FastRCNNOutputLayers)(
+ input_shape=L(ShapeSpec)(channels="${...res5.out_channels}", height=1, width=1),
+ test_score_thresh=0.05,
+ box2box_transform=L(Box2BoxTransform)(weights=(10, 10, 5, 5)),
+ num_classes="${..num_classes}",
+ ),
+ mask_head=L(MaskRCNNConvUpsampleHead)(
+ input_shape=L(ShapeSpec)(
+ channels="${...res5.out_channels}",
+ width="${...pooler.output_size}",
+ height="${...pooler.output_size}",
+ ),
+ num_classes="${..num_classes}",
+ conv_dims=[256],
+ ),
+ ),
+ pixel_mean=constants.imagenet_bgr256_mean,
+ pixel_std=constants.imagenet_bgr256_std,
+ input_format="BGR",
+)
diff --git a/detectron2/configs/common/models/mask_rcnn_fpn.py b/detectron2/configs/common/models/mask_rcnn_fpn.py
new file mode 100755
index 0000000..5e5c501
--- /dev/null
+++ b/detectron2/configs/common/models/mask_rcnn_fpn.py
@@ -0,0 +1,95 @@
+from detectron2.config import LazyCall as L
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.meta_arch import GeneralizedRCNN
+from detectron2.modeling.anchor_generator import DefaultAnchorGenerator
+from detectron2.modeling.backbone.fpn import LastLevelMaxPool
+from detectron2.modeling.backbone import BasicStem, FPN, ResNet
+from detectron2.modeling.box_regression import Box2BoxTransform
+from detectron2.modeling.matcher import Matcher
+from detectron2.modeling.poolers import ROIPooler
+from detectron2.modeling.proposal_generator import RPN, StandardRPNHead
+from detectron2.modeling.roi_heads import (
+ StandardROIHeads,
+ FastRCNNOutputLayers,
+ MaskRCNNConvUpsampleHead,
+ FastRCNNConvFCHead,
+)
+
+from ..data.constants import constants
+
+model = L(GeneralizedRCNN)(
+ backbone=L(FPN)(
+ bottom_up=L(ResNet)(
+ stem=L(BasicStem)(in_channels=3, out_channels=64, norm="FrozenBN"),
+ stages=L(ResNet.make_default_stages)(
+ depth=50,
+ stride_in_1x1=True,
+ norm="FrozenBN",
+ ),
+ out_features=["res2", "res3", "res4", "res5"],
+ ),
+ in_features="${.bottom_up.out_features}",
+ out_channels=256,
+ top_block=L(LastLevelMaxPool)(),
+ ),
+ proposal_generator=L(RPN)(
+ in_features=["p2", "p3", "p4", "p5", "p6"],
+ head=L(StandardRPNHead)(in_channels=256, num_anchors=3),
+ anchor_generator=L(DefaultAnchorGenerator)(
+ sizes=[[32], [64], [128], [256], [512]],
+ aspect_ratios=[0.5, 1.0, 2.0],
+ strides=[4, 8, 16, 32, 64],
+ offset=0.0,
+ ),
+ anchor_matcher=L(Matcher)(
+ thresholds=[0.3, 0.7], labels=[0, -1, 1], allow_low_quality_matches=True
+ ),
+ box2box_transform=L(Box2BoxTransform)(weights=[1.0, 1.0, 1.0, 1.0]),
+ batch_size_per_image=256,
+ positive_fraction=0.5,
+ pre_nms_topk=(2000, 1000),
+ post_nms_topk=(1000, 1000),
+ nms_thresh=0.7,
+ ),
+ roi_heads=L(StandardROIHeads)(
+ num_classes=80,
+ batch_size_per_image=512,
+ positive_fraction=0.25,
+ proposal_matcher=L(Matcher)(
+ thresholds=[0.5], labels=[0, 1], allow_low_quality_matches=False
+ ),
+ box_in_features=["p2", "p3", "p4", "p5"],
+ box_pooler=L(ROIPooler)(
+ output_size=7,
+ scales=(1.0 / 4, 1.0 / 8, 1.0 / 16, 1.0 / 32),
+ sampling_ratio=0,
+ pooler_type="ROIAlignV2",
+ ),
+ box_head=L(FastRCNNConvFCHead)(
+ input_shape=ShapeSpec(channels=256, height=7, width=7),
+ conv_dims=[],
+ fc_dims=[1024, 1024],
+ ),
+ box_predictor=L(FastRCNNOutputLayers)(
+ input_shape=ShapeSpec(channels=1024),
+ test_score_thresh=0.05,
+ box2box_transform=L(Box2BoxTransform)(weights=(10, 10, 5, 5)),
+ num_classes="${..num_classes}",
+ ),
+ mask_in_features=["p2", "p3", "p4", "p5"],
+ mask_pooler=L(ROIPooler)(
+ output_size=14,
+ scales=(1.0 / 4, 1.0 / 8, 1.0 / 16, 1.0 / 32),
+ sampling_ratio=0,
+ pooler_type="ROIAlignV2",
+ ),
+ mask_head=L(MaskRCNNConvUpsampleHead)(
+ input_shape=ShapeSpec(channels=256, width=14, height=14),
+ num_classes="${..num_classes}",
+ conv_dims=[256, 256, 256, 256, 256],
+ ),
+ ),
+ pixel_mean=constants.imagenet_bgr256_mean,
+ pixel_std=constants.imagenet_bgr256_std,
+ input_format="BGR",
+)
diff --git a/detectron2/configs/common/models/mask_rcnn_vitdet.py b/detectron2/configs/common/models/mask_rcnn_vitdet.py
new file mode 100755
index 0000000..d6f5244
--- /dev/null
+++ b/detectron2/configs/common/models/mask_rcnn_vitdet.py
@@ -0,0 +1,59 @@
+from functools import partial
+import torch.nn as nn
+from detectron2.config import LazyCall as L
+from detectron2.modeling import ViT, SimpleFeaturePyramid
+from detectron2.modeling.backbone.fpn import LastLevelMaxPool
+
+from .mask_rcnn_fpn import model
+from ..data.constants import constants
+
+model.pixel_mean = constants.imagenet_rgb256_mean
+model.pixel_std = constants.imagenet_rgb256_std
+model.input_format = "RGB"
+
+# Base
+embed_dim, depth, num_heads, dp = 768, 12, 12, 0.1
+# Creates Simple Feature Pyramid from ViT backbone
+model.backbone = L(SimpleFeaturePyramid)(
+ net=L(ViT)( # Single-scale ViT backbone
+ img_size=1024,
+ patch_size=16,
+ embed_dim=embed_dim,
+ depth=depth,
+ num_heads=num_heads,
+ drop_path_rate=dp,
+ window_size=14,
+ mlp_ratio=4,
+ qkv_bias=True,
+ norm_layer=partial(nn.LayerNorm, eps=1e-6),
+ window_block_indexes=[
+ # 2, 5, 8 11 for global attention
+ 0,
+ 1,
+ 3,
+ 4,
+ 6,
+ 7,
+ 9,
+ 10,
+ ],
+ residual_block_indexes=[],
+ use_rel_pos=True,
+ out_feature="last_feat",
+ ),
+ in_feature="${.net.out_feature}",
+ out_channels=256,
+ scale_factors=(4.0, 2.0, 1.0, 0.5),
+ top_block=L(LastLevelMaxPool)(),
+ norm="LN",
+ square_pad=1024,
+)
+
+model.roi_heads.box_head.conv_norm = model.roi_heads.mask_head.conv_norm = "LN"
+
+# 2conv in RPN:
+model.proposal_generator.head.conv_dims = [-1, -1]
+
+# 4conv1fc box head
+model.roi_heads.box_head.conv_dims = [256, 256, 256, 256]
+model.roi_heads.box_head.fc_dims = [1024]
diff --git a/detectron2/configs/common/models/panoptic_fpn.py b/detectron2/configs/common/models/panoptic_fpn.py
new file mode 100755
index 0000000..88f55d2
--- /dev/null
+++ b/detectron2/configs/common/models/panoptic_fpn.py
@@ -0,0 +1,20 @@
+from detectron2.config import LazyCall as L
+from detectron2.layers import ShapeSpec
+from detectron2.modeling import PanopticFPN
+from detectron2.modeling.meta_arch.semantic_seg import SemSegFPNHead
+
+from .mask_rcnn_fpn import model
+
+model._target_ = PanopticFPN
+model.sem_seg_head = L(SemSegFPNHead)(
+ input_shape={
+ f: L(ShapeSpec)(stride=s, channels="${....backbone.out_channels}")
+ for f, s in zip(["p2", "p3", "p4", "p5"], [4, 8, 16, 32])
+ },
+ ignore_value=255,
+ num_classes=54, # COCO stuff + 1
+ conv_dims=128,
+ common_stride=4,
+ loss_weight=0.5,
+ norm="GN",
+)
diff --git a/detectron2/configs/common/models/retinanet.py b/detectron2/configs/common/models/retinanet.py
new file mode 100755
index 0000000..784e531
--- /dev/null
+++ b/detectron2/configs/common/models/retinanet.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+from detectron2.config import LazyCall as L
+from detectron2.layers import ShapeSpec
+from detectron2.modeling.meta_arch import RetinaNet
+from detectron2.modeling.anchor_generator import DefaultAnchorGenerator
+from detectron2.modeling.backbone.fpn import LastLevelP6P7
+from detectron2.modeling.backbone import BasicStem, FPN, ResNet
+from detectron2.modeling.box_regression import Box2BoxTransform
+from detectron2.modeling.matcher import Matcher
+from detectron2.modeling.meta_arch.retinanet import RetinaNetHead
+
+from ..data.constants import constants
+
+model = L(RetinaNet)(
+ backbone=L(FPN)(
+ bottom_up=L(ResNet)(
+ stem=L(BasicStem)(in_channels=3, out_channels=64, norm="FrozenBN"),
+ stages=L(ResNet.make_default_stages)(
+ depth=50,
+ stride_in_1x1=True,
+ norm="FrozenBN",
+ ),
+ out_features=["res3", "res4", "res5"],
+ ),
+ in_features=["res3", "res4", "res5"],
+ out_channels=256,
+ top_block=L(LastLevelP6P7)(in_channels=2048, out_channels="${..out_channels}"),
+ ),
+ head=L(RetinaNetHead)(
+ # Shape for each input feature map
+ input_shape=[ShapeSpec(channels=256)] * 5,
+ num_classes="${..num_classes}",
+ conv_dims=[256, 256, 256, 256],
+ prior_prob=0.01,
+ num_anchors=9,
+ ),
+ anchor_generator=L(DefaultAnchorGenerator)(
+ sizes=[[x, x * 2 ** (1.0 / 3), x * 2 ** (2.0 / 3)] for x in [32, 64, 128, 256, 512]],
+ aspect_ratios=[0.5, 1.0, 2.0],
+ strides=[8, 16, 32, 64, 128],
+ offset=0.0,
+ ),
+ box2box_transform=L(Box2BoxTransform)(weights=[1.0, 1.0, 1.0, 1.0]),
+ anchor_matcher=L(Matcher)(
+ thresholds=[0.4, 0.5], labels=[0, -1, 1], allow_low_quality_matches=True
+ ),
+ num_classes=80,
+ head_in_features=["p3", "p4", "p5", "p6", "p7"],
+ focal_loss_alpha=0.25,
+ focal_loss_gamma=2.0,
+ pixel_mean=constants.imagenet_bgr256_mean,
+ pixel_std=constants.imagenet_bgr256_std,
+ input_format="BGR",
+)
diff --git a/detectron2/configs/common/optim.py b/detectron2/configs/common/optim.py
new file mode 100755
index 0000000..6cf43e8
--- /dev/null
+++ b/detectron2/configs/common/optim.py
@@ -0,0 +1,28 @@
+import torch
+
+from detectron2.config import LazyCall as L
+from detectron2.solver.build import get_default_optimizer_params
+
+SGD = L(torch.optim.SGD)(
+ params=L(get_default_optimizer_params)(
+ # params.model is meant to be set to the model object, before instantiating
+ # the optimizer.
+ weight_decay_norm=0.0
+ ),
+ lr=0.02,
+ momentum=0.9,
+ weight_decay=1e-4,
+)
+
+
+AdamW = L(torch.optim.AdamW)(
+ params=L(get_default_optimizer_params)(
+ # params.model is meant to be set to the model object, before instantiating
+ # the optimizer.
+ base_lr="${..lr}",
+ weight_decay_norm=0.0,
+ ),
+ lr=1e-4,
+ betas=(0.9, 0.999),
+ weight_decay=0.1,
+)
diff --git a/detectron2/configs/common/train.py b/detectron2/configs/common/train.py
new file mode 100755
index 0000000..b6ed02b
--- /dev/null
+++ b/detectron2/configs/common/train.py
@@ -0,0 +1,18 @@
+# Common training-related configs that are designed for "tools/lazyconfig_train_net.py"
+# You can use your own instead, together with your own train_net.py
+train = dict(
+ output_dir="./output",
+ init_checkpoint="",
+ max_iter=90000,
+ amp=dict(enabled=False), # options for Automatic Mixed Precision
+ ddp=dict( # options for DistributedDataParallel
+ broadcast_buffers=False,
+ find_unused_parameters=False,
+ fp16_compression=False,
+ ),
+ checkpointer=dict(period=5000, max_to_keep=100), # options for PeriodicCheckpointer
+ eval_period=5000,
+ log_period=20,
+ device="cuda"
+ # ...
+)
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_100ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_100ep_LSJ.py
new file mode 100755
index 0000000..3740e9b
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_100ep_LSJ.py
@@ -0,0 +1,9 @@
+from .mask_rcnn_R_50_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+model.backbone.bottom_up.stages.depth = 101
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_200ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_200ep_LSJ.py
new file mode 100755
index 0000000..18e5f07
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_200ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_R_101_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 2 # 100ep -> 200ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 2 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_400ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_400ep_LSJ.py
new file mode 100755
index 0000000..63c54ee
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_101_FPN_400ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_R_101_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 4 # 100ep -> 400ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 4 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_100ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_100ep_LSJ.py
new file mode 100755
index 0000000..df7a2ae
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_100ep_LSJ.py
@@ -0,0 +1,72 @@
+import detectron2.data.transforms as T
+from detectron2.config.lazy import LazyCall as L
+from detectron2.layers.batch_norm import NaiveSyncBatchNorm
+from detectron2.solver import WarmupParamScheduler
+from fvcore.common.param_scheduler import MultiStepParamScheduler
+
+from ..common.data.coco import dataloader
+from ..common.models.mask_rcnn_fpn import model
+from ..common.optim import SGD as optimizer
+from ..common.train import train
+
+# train from scratch
+train.init_checkpoint = ""
+train.amp.enabled = True
+train.ddp.fp16_compression = True
+model.backbone.bottom_up.freeze_at = 0
+
+# SyncBN
+# fmt: off
+model.backbone.bottom_up.stem.norm = \
+ model.backbone.bottom_up.stages.norm = \
+ model.backbone.norm = "SyncBN"
+
+# Using NaiveSyncBatchNorm becase heads may have empty input. That is not supported by
+# torch.nn.SyncBatchNorm. We can remove this after
+# https://github.com/pytorch/pytorch/issues/36530 is fixed.
+model.roi_heads.box_head.conv_norm = \
+ model.roi_heads.mask_head.conv_norm = lambda c: NaiveSyncBatchNorm(c,
+ stats_mode="N")
+# fmt: on
+
+# 2conv in RPN:
+# https://github.com/tensorflow/tpu/blob/b24729de804fdb751b06467d3dce0637fa652060/models/official/detection/modeling/architecture/heads.py#L95-L97 # noqa: E501, B950
+model.proposal_generator.head.conv_dims = [-1, -1]
+
+# 4conv1fc box head
+model.roi_heads.box_head.conv_dims = [256, 256, 256, 256]
+model.roi_heads.box_head.fc_dims = [1024]
+
+# resize_and_crop_image in:
+# https://github.com/tensorflow/tpu/blob/b24729de804fdb751b06467d3dce0637fa652060/models/official/detection/utils/input_utils.py#L127 # noqa: E501, B950
+image_size = 1024
+dataloader.train.mapper.augmentations = [
+ L(T.ResizeScale)(
+ min_scale=0.1, max_scale=2.0, target_height=image_size, target_width=image_size
+ ),
+ L(T.FixedSizeCrop)(crop_size=(image_size, image_size)),
+ L(T.RandomFlip)(horizontal=True),
+]
+
+# recompute boxes due to cropping
+dataloader.train.mapper.recompute_boxes = True
+
+# larger batch-size.
+dataloader.train.total_batch_size = 64
+
+# Equivalent to 100 epochs.
+# 100 ep = 184375 iters * 64 images/iter / 118000 images/ep
+train.max_iter = 184375
+
+lr_multiplier = L(WarmupParamScheduler)(
+ scheduler=L(MultiStepParamScheduler)(
+ values=[1.0, 0.1, 0.01],
+ milestones=[163889, 177546],
+ num_updates=train.max_iter,
+ ),
+ warmup_length=500 / train.max_iter,
+ warmup_factor=0.067,
+)
+
+optimizer.lr = 0.1
+optimizer.weight_decay = 4e-5
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_200ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_200ep_LSJ.py
new file mode 100755
index 0000000..2a7c376
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_200ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_R_50_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 2 # 100ep -> 200ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 2 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_400ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_400ep_LSJ.py
new file mode 100755
index 0000000..97586b8
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_400ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_R_50_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 4 # 100ep -> 400ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 4 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_50ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_50ep_LSJ.py
new file mode 100755
index 0000000..2ca1ede
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_R_50_FPN_50ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_R_50_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter //= 2 # 100ep -> 50ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone // 2 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_100ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_100ep_LSJ.py
new file mode 100755
index 0000000..ef0b6d1
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_100ep_LSJ.py
@@ -0,0 +1,29 @@
+from .mask_rcnn_R_50_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+from detectron2.config import LazyCall as L
+from detectron2.modeling.backbone import RegNet
+from detectron2.modeling.backbone.regnet import SimpleStem, ResBottleneckBlock
+
+# Config source:
+# https://github.com/facebookresearch/detectron2/blob/main/configs/COCO-InstanceSegmentation/mask_rcnn_regnetx_4gf_dds_fpn_1x.py # noqa
+model.backbone.bottom_up = L(RegNet)(
+ stem_class=SimpleStem,
+ stem_width=32,
+ block_class=ResBottleneckBlock,
+ depth=23,
+ w_a=38.65,
+ w_0=96,
+ w_m=2.43,
+ group_width=40,
+ norm="SyncBN",
+ out_features=["s1", "s2", "s3", "s4"],
+)
+model.pixel_std = [57.375, 57.120, 58.395]
+
+# RegNets benefit from enabling cudnn benchmark mode
+train.cudnn_benchmark = True
diff --git a/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_200ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_200ep_LSJ.py
new file mode 100755
index 0000000..731320e
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_200ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_regnetx_4gf_dds_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 2 # 100ep -> 200ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 2 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_400ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_400ep_LSJ.py
new file mode 100755
index 0000000..8f369a2
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_regnetx_4gf_dds_FPN_400ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_regnetx_4gf_dds_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 4 # 100ep -> 400ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 4 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_100ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_100ep_LSJ.py
new file mode 100755
index 0000000..ba2c327
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_100ep_LSJ.py
@@ -0,0 +1,30 @@
+from .mask_rcnn_R_50_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+from detectron2.config import LazyCall as L
+from detectron2.modeling.backbone import RegNet
+from detectron2.modeling.backbone.regnet import SimpleStem, ResBottleneckBlock
+
+# Config source:
+# https://github.com/facebookresearch/detectron2/blob/main/configs/COCO-InstanceSegmentation/mask_rcnn_regnety_4gf_dds_fpn_1x.py # noqa
+model.backbone.bottom_up = L(RegNet)(
+ stem_class=SimpleStem,
+ stem_width=32,
+ block_class=ResBottleneckBlock,
+ depth=22,
+ w_a=31.41,
+ w_0=96,
+ w_m=2.24,
+ group_width=64,
+ se_ratio=0.25,
+ norm="SyncBN",
+ out_features=["s1", "s2", "s3", "s4"],
+)
+model.pixel_std = [57.375, 57.120, 58.395]
+
+# RegNets benefit from enabling cudnn benchmark mode
+train.cudnn_benchmark = True
diff --git a/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_200ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_200ep_LSJ.py
new file mode 100755
index 0000000..b867cc8
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_200ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_regnety_4gf_dds_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 2 # 100ep -> 200ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 2 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_400ep_LSJ.py b/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_400ep_LSJ.py
new file mode 100755
index 0000000..7b86ea8
--- /dev/null
+++ b/detectron2/configs/new_baselines/mask_rcnn_regnety_4gf_dds_FPN_400ep_LSJ.py
@@ -0,0 +1,14 @@
+from .mask_rcnn_regnety_4gf_dds_FPN_100ep_LSJ import (
+ dataloader,
+ lr_multiplier,
+ model,
+ optimizer,
+ train,
+)
+
+train.max_iter *= 4 # 100ep -> 400ep
+
+lr_multiplier.scheduler.milestones = [
+ milestone * 4 for milestone in lr_multiplier.scheduler.milestones
+]
+lr_multiplier.scheduler.num_updates = train.max_iter
diff --git a/detectron2/configs/quick_schedules/README.md b/detectron2/configs/quick_schedules/README.md
new file mode 100755
index 0000000..4e6c82e
--- /dev/null
+++ b/detectron2/configs/quick_schedules/README.md
@@ -0,0 +1,8 @@
+These are quick configs for performance or accuracy regression tracking purposes.
+
+* `*instance_test.yaml`: can train on 2 GPUs. They are used to test whether the training can
+ successfully finish. They are not expected to produce reasonable training results.
+* `*inference_acc_test.yaml`: They should be run using `--eval-only`. They run inference using pre-trained models and verify
+ the results are as expected.
+* `*training_acc_test.yaml`: They should be trained on 8 GPUs. They finish in about an hour and verify the training accuracy
+ is within the normal range.
diff --git a/detectron2/configs/quick_schedules/cascade_mask_rcnn_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/cascade_mask_rcnn_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..fc5a411
--- /dev/null
+++ b/detectron2/configs/quick_schedules/cascade_mask_rcnn_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../Misc/cascade_mask_rcnn_R_50_FPN_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://Misc/cascade_mask_rcnn_R_50_FPN_3x/144998488/model_final_480dd8.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 50.18, 0.02], ["segm", "AP", 43.87, 0.02]]
diff --git a/detectron2/configs/quick_schedules/cascade_mask_rcnn_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/cascade_mask_rcnn_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..e41a0fe
--- /dev/null
+++ b/detectron2/configs/quick_schedules/cascade_mask_rcnn_R_50_FPN_instant_test.yaml
@@ -0,0 +1,11 @@
+_BASE_: "../Misc/cascade_mask_rcnn_R_50_FPN_3x.yaml"
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ TEST: ("coco_2017_val_100",)
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/fast_rcnn_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/fast_rcnn_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..a2f37e5
--- /dev/null
+++ b/detectron2/configs/quick_schedules/fast_rcnn_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-Detection/fast_rcnn_R_50_FPN_1x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-Detection/fast_rcnn_R_50_FPN_1x/137635226/model_final_e5f7ce.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 45.70, 0.02]]
diff --git a/detectron2/configs/quick_schedules/fast_rcnn_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/fast_rcnn_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..52fc0ec
--- /dev/null
+++ b/detectron2/configs/quick_schedules/fast_rcnn_R_50_FPN_instant_test.yaml
@@ -0,0 +1,15 @@
+_BASE_: "../COCO-Detection/fast_rcnn_R_50_FPN_1x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ PROPOSAL_FILES_TRAIN: ("detectron2://COCO-Detection/rpn_R_50_FPN_1x/137258492/coco_2017_val_box_proposals_ee0dad.pkl", )
+ TEST: ("coco_2017_val_100",)
+ PROPOSAL_FILES_TEST: ("detectron2://COCO-Detection/rpn_R_50_FPN_1x/137258492/coco_2017_val_box_proposals_ee0dad.pkl", )
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..14cf2aa
--- /dev/null
+++ b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x/137849621/model_final_a6e10b.pkl"
+DATASETS:
+ TEST: ("keypoints_coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 52.47, 0.02], ["keypoints", "AP", 67.36, 0.02]]
diff --git a/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..3dd209f
--- /dev/null
+++ b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_instant_test.yaml
@@ -0,0 +1,16 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ KEYPOINT_ON: True
+ ROI_HEADS:
+ NUM_CLASSES: 1
+DATASETS:
+ TRAIN: ("keypoints_coco_2017_val_100",)
+ TEST: ("keypoints_coco_2017_val_100",)
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_normalized_training_acc_test.yaml b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_normalized_training_acc_test.yaml
new file mode 100755
index 0000000..4b92392
--- /dev/null
+++ b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_normalized_training_acc_test.yaml
@@ -0,0 +1,30 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ KEYPOINT_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ BATCH_SIZE_PER_IMAGE: 256
+ NUM_CLASSES: 1
+ ROI_KEYPOINT_HEAD:
+ POOLER_RESOLUTION: 14
+ POOLER_SAMPLING_RATIO: 2
+ NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS: False
+ LOSS_WEIGHT: 4.0
+ ROI_BOX_HEAD:
+ SMOOTH_L1_BETA: 1.0 # Keypoint AP degrades when using plain L1 loss
+ RPN:
+ SMOOTH_L1_BETA: 0.2 # Keypoint AP degrades when using plain L1 loss
+DATASETS:
+ TRAIN: ("keypoints_coco_2017_val",)
+ TEST: ("keypoints_coco_2017_val",)
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+SOLVER:
+ WARMUP_FACTOR: 0.33333333
+ WARMUP_ITERS: 100
+ STEPS: (5500, 5800)
+ MAX_ITER: 6000
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 55.35, 1.0], ["keypoints", "AP", 76.91, 1.0]]
diff --git a/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_training_acc_test.yaml b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_training_acc_test.yaml
new file mode 100755
index 0000000..9bd9628
--- /dev/null
+++ b/detectron2/configs/quick_schedules/keypoint_rcnn_R_50_FPN_training_acc_test.yaml
@@ -0,0 +1,28 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ KEYPOINT_ON: True
+ RESNETS:
+ DEPTH: 50
+ ROI_HEADS:
+ BATCH_SIZE_PER_IMAGE: 256
+ NUM_CLASSES: 1
+ ROI_KEYPOINT_HEAD:
+ POOLER_RESOLUTION: 14
+ POOLER_SAMPLING_RATIO: 2
+ ROI_BOX_HEAD:
+ SMOOTH_L1_BETA: 1.0 # Keypoint AP degrades when using plain L1 loss
+ RPN:
+ SMOOTH_L1_BETA: 0.2 # Keypoint AP degrades when using plain L1 loss
+DATASETS:
+ TRAIN: ("keypoints_coco_2017_val",)
+ TEST: ("keypoints_coco_2017_val",)
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+SOLVER:
+ WARMUP_FACTOR: 0.33333333
+ WARMUP_ITERS: 100
+ STEPS: (5500, 5800)
+ MAX_ITER: 6000
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 53.5, 1.0], ["keypoints", "AP", 72.4, 1.0]]
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_GCV_instant_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_GCV_instant_test.yaml
new file mode 100755
index 0000000..ab6e698
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_GCV_instant_test.yaml
@@ -0,0 +1,18 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ TEST: ("coco_2017_val_100",)
+SOLVER:
+ BASE_LR: 0.001
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+ CLIP_GRADIENTS:
+ ENABLED: True
+ CLIP_TYPE: "value"
+ CLIP_VALUE: 1.0
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_inference_acc_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_inference_acc_test.yaml
new file mode 100755
index 0000000..b2d5b7f
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-InstanceSegmentation/mask_rcnn_R_50_C4_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_C4_3x/137849525/model_final_4ce675.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 47.37, 0.02], ["segm", "AP", 40.99, 0.02]]
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_instant_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_instant_test.yaml
new file mode 100755
index 0000000..6c4f121
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_instant_test.yaml
@@ -0,0 +1,14 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ TEST: ("coco_2017_val_100",)
+SOLVER:
+ BASE_LR: 0.001
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_training_acc_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_training_acc_test.yaml
new file mode 100755
index 0000000..f68dd8f
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_C4_training_acc_test.yaml
@@ -0,0 +1,22 @@
+_BASE_: "../Base-RCNN-C4.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ ROI_HEADS:
+ BATCH_SIZE_PER_IMAGE: 256
+ MASK_ON: True
+DATASETS:
+ TRAIN: ("coco_2017_val",)
+ TEST: ("coco_2017_val",)
+INPUT:
+ MIN_SIZE_TRAIN: (600,)
+ MAX_SIZE_TRAIN: 1000
+ MIN_SIZE_TEST: 800
+ MAX_SIZE_TEST: 1000
+SOLVER:
+ IMS_PER_BATCH: 8 # base uses 16
+ WARMUP_FACTOR: 0.33333
+ WARMUP_ITERS: 100
+ STEPS: (11000, 11600)
+ MAX_ITER: 12000
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 41.88, 0.7], ["segm", "AP", 33.79, 0.5]]
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_DC5_inference_acc_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_DC5_inference_acc_test.yaml
new file mode 100755
index 0000000..e3ce6cf
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_DC5_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_DC5_3x/137849551/model_final_84107b.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 47.44, 0.02], ["segm", "AP", 42.94, 0.02]]
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..e5454bf
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,10 @@
+_BASE_: "../COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 47.34, 0.02], ["segm", "AP", 42.67, 0.02], ["bbox_TTA", "AP", 49.11, 0.02], ["segm_TTA", "AP", 45.04, 0.02]]
+ AUG:
+ ENABLED: True
+ MIN_SIZES: (700, 800) # to save some time
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..6dbfcde
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_instant_test.yaml
@@ -0,0 +1,14 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ TEST: ("coco_2017_val_100",)
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_pred_boxes_training_acc_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_pred_boxes_training_acc_test.yaml
new file mode 100755
index 0000000..52f7876
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_pred_boxes_training_acc_test.yaml
@@ -0,0 +1,6 @@
+_BASE_: "./mask_rcnn_R_50_FPN_training_acc_test.yaml"
+MODEL:
+ ROI_BOX_HEAD:
+ TRAIN_ON_PRED_BOXES: True
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 42.6, 1.0], ["segm", "AP", 35.8, 0.8]]
diff --git a/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_training_acc_test.yaml b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_training_acc_test.yaml
new file mode 100755
index 0000000..aadae4c
--- /dev/null
+++ b/detectron2/configs/quick_schedules/mask_rcnn_R_50_FPN_training_acc_test.yaml
@@ -0,0 +1,21 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ ROI_HEADS:
+ BATCH_SIZE_PER_IMAGE: 256
+ MASK_ON: True
+DATASETS:
+ TRAIN: ("coco_2017_val",)
+ TEST: ("coco_2017_val",)
+INPUT:
+ MIN_SIZE_TRAIN: (600,)
+ MAX_SIZE_TRAIN: 1000
+ MIN_SIZE_TEST: 800
+ MAX_SIZE_TEST: 1000
+SOLVER:
+ WARMUP_FACTOR: 0.3333333
+ WARMUP_ITERS: 100
+ STEPS: (5500, 5800)
+ MAX_ITER: 6000
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 42.5, 1.0], ["segm", "AP", 35.8, 0.8]]
diff --git a/detectron2/configs/quick_schedules/panoptic_fpn_R_50_inference_acc_test.yaml b/detectron2/configs/quick_schedules/panoptic_fpn_R_50_inference_acc_test.yaml
new file mode 100755
index 0000000..70874e3
--- /dev/null
+++ b/detectron2/configs/quick_schedules/panoptic_fpn_R_50_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-PanopticSegmentation/panoptic_fpn_R_50_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-PanopticSegmentation/panoptic_fpn_R_50_3x/139514569/model_final_c10459.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100_panoptic_separated",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 46.47, 0.02], ["segm", "AP", 43.39, 0.02], ["sem_seg", "mIoU", 42.55, 0.02], ["panoptic_seg", "PQ", 38.99, 0.02]]
diff --git a/detectron2/configs/quick_schedules/panoptic_fpn_R_50_instant_test.yaml b/detectron2/configs/quick_schedules/panoptic_fpn_R_50_instant_test.yaml
new file mode 100755
index 0000000..7cdee7b
--- /dev/null
+++ b/detectron2/configs/quick_schedules/panoptic_fpn_R_50_instant_test.yaml
@@ -0,0 +1,19 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "PanopticFPN"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ SEM_SEG_HEAD:
+ LOSS_WEIGHT: 0.5
+DATASETS:
+ TRAIN: ("coco_2017_val_100_panoptic_separated",)
+ TEST: ("coco_2017_val_100_panoptic_separated",)
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 1
diff --git a/detectron2/configs/quick_schedules/panoptic_fpn_R_50_training_acc_test.yaml b/detectron2/configs/quick_schedules/panoptic_fpn_R_50_training_acc_test.yaml
new file mode 100755
index 0000000..f3bbf30
--- /dev/null
+++ b/detectron2/configs/quick_schedules/panoptic_fpn_R_50_training_acc_test.yaml
@@ -0,0 +1,20 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "PanopticFPN"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ MASK_ON: True
+ RESNETS:
+ DEPTH: 50
+ SEM_SEG_HEAD:
+ LOSS_WEIGHT: 0.5
+DATASETS:
+ TRAIN: ("coco_2017_val_panoptic_separated",)
+ TEST: ("coco_2017_val_panoptic_separated",)
+SOLVER:
+ BASE_LR: 0.01
+ WARMUP_FACTOR: 0.001
+ WARMUP_ITERS: 500
+ STEPS: (5500,)
+ MAX_ITER: 7000
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 46.70, 1.1], ["segm", "AP", 39.0, 0.7], ["sem_seg", "mIoU", 64.73, 1.3], ["panoptic_seg", "PQ", 48.13, 0.8]]
diff --git a/detectron2/configs/quick_schedules/retinanet_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/retinanet_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..cb666c1
--- /dev/null
+++ b/detectron2/configs/quick_schedules/retinanet_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-Detection/retinanet_R_50_FPN_3x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-Detection/retinanet_R_50_FPN_3x/190397829/model_final_5bd44e.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["bbox", "AP", 44.45, 0.02]]
diff --git a/detectron2/configs/quick_schedules/retinanet_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/retinanet_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..8d95c1f
--- /dev/null
+++ b/detectron2/configs/quick_schedules/retinanet_R_50_FPN_instant_test.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../COCO-Detection/retinanet_R_50_FPN_1x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ TEST: ("coco_2017_val_100",)
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/rpn_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/rpn_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..c7c3f90
--- /dev/null
+++ b/detectron2/configs/quick_schedules/rpn_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,7 @@
+_BASE_: "../COCO-Detection/rpn_R_50_FPN_1x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://COCO-Detection/rpn_R_50_FPN_1x/137258492/model_final_02ce48.pkl"
+DATASETS:
+ TEST: ("coco_2017_val_100",)
+TEST:
+ EXPECTED_RESULTS: [["box_proposals", "AR@1000", 58.16, 0.02]]
diff --git a/detectron2/configs/quick_schedules/rpn_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/rpn_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..402d432
--- /dev/null
+++ b/detectron2/configs/quick_schedules/rpn_R_50_FPN_instant_test.yaml
@@ -0,0 +1,13 @@
+_BASE_: "../COCO-Detection/rpn_R_50_FPN_1x.yaml"
+MODEL:
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+DATASETS:
+ TRAIN: ("coco_2017_val_100",)
+ TEST: ("coco_2017_val_100",)
+SOLVER:
+ STEPS: (30,)
+ MAX_ITER: 40
+ BASE_LR: 0.005
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/semantic_R_50_FPN_inference_acc_test.yaml b/detectron2/configs/quick_schedules/semantic_R_50_FPN_inference_acc_test.yaml
new file mode 100755
index 0000000..bca7498
--- /dev/null
+++ b/detectron2/configs/quick_schedules/semantic_R_50_FPN_inference_acc_test.yaml
@@ -0,0 +1,10 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "SemanticSegmentor"
+ WEIGHTS: "detectron2://semantic_R_50_FPN_1x/111802073/model_final_c18079783c55a94968edc28b7101c5f0.pkl"
+ RESNETS:
+ DEPTH: 50
+DATASETS:
+ TEST: ("coco_2017_val_100_panoptic_stuffonly",)
+TEST:
+ EXPECTED_RESULTS: [["sem_seg", "mIoU", 39.53, 0.02], ["sem_seg", "mACC", 51.50, 0.02]]
diff --git a/detectron2/configs/quick_schedules/semantic_R_50_FPN_instant_test.yaml b/detectron2/configs/quick_schedules/semantic_R_50_FPN_instant_test.yaml
new file mode 100755
index 0000000..14ab606
--- /dev/null
+++ b/detectron2/configs/quick_schedules/semantic_R_50_FPN_instant_test.yaml
@@ -0,0 +1,18 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "SemanticSegmentor"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+DATASETS:
+ TRAIN: ("coco_2017_val_100_panoptic_stuffonly",)
+ TEST: ("coco_2017_val_100_panoptic_stuffonly",)
+INPUT:
+ MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
+SOLVER:
+ BASE_LR: 0.005
+ STEPS: (30,)
+ MAX_ITER: 40
+ IMS_PER_BATCH: 4
+DATALOADER:
+ NUM_WORKERS: 2
diff --git a/detectron2/configs/quick_schedules/semantic_R_50_FPN_training_acc_test.yaml b/detectron2/configs/quick_schedules/semantic_R_50_FPN_training_acc_test.yaml
new file mode 100755
index 0000000..1f78d77
--- /dev/null
+++ b/detectron2/configs/quick_schedules/semantic_R_50_FPN_training_acc_test.yaml
@@ -0,0 +1,20 @@
+_BASE_: "../Base-RCNN-FPN.yaml"
+MODEL:
+ META_ARCHITECTURE: "SemanticSegmentor"
+ WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
+ RESNETS:
+ DEPTH: 50
+DATASETS:
+ TRAIN: ("coco_2017_val_panoptic_stuffonly",)
+ TEST: ("coco_2017_val_panoptic_stuffonly",)
+SOLVER:
+ BASE_LR: 0.01
+ WARMUP_FACTOR: 0.001
+ WARMUP_ITERS: 300
+ STEPS: (5500,)
+ MAX_ITER: 7000
+TEST:
+ EXPECTED_RESULTS: [["sem_seg", "mIoU", 76.51, 1.0], ["sem_seg", "mACC", 83.25, 1.0]]
+INPUT:
+ # no scale augmentation
+ MIN_SIZE_TRAIN: (800, )
diff --git a/detectron2/datasets/README.md b/detectron2/datasets/README.md
new file mode 100755
index 0000000..0eb44cc
--- /dev/null
+++ b/detectron2/datasets/README.md
@@ -0,0 +1,140 @@
+# Use Builtin Datasets
+
+A dataset can be used by accessing [DatasetCatalog](https://detectron2.readthedocs.io/modules/data.html#detectron2.data.DatasetCatalog)
+for its data, or [MetadataCatalog](https://detectron2.readthedocs.io/modules/data.html#detectron2.data.MetadataCatalog) for its metadata (class names, etc).
+This document explains how to setup the builtin datasets so they can be used by the above APIs.
+[Use Custom Datasets](https://detectron2.readthedocs.io/tutorials/datasets.html) gives a deeper dive on how to use `DatasetCatalog` and `MetadataCatalog`,
+and how to add new datasets to them.
+
+Detectron2 has builtin support for a few datasets.
+The datasets are assumed to exist in a directory specified by the environment variable
+`DETECTRON2_DATASETS`.
+Under this directory, detectron2 will look for datasets in the structure described below, if needed.
+```
+$DETECTRON2_DATASETS/
+ coco/
+ lvis/
+ cityscapes/
+ VOC20{07,12}/
+```
+
+You can set the location for builtin datasets by `export DETECTRON2_DATASETS=/path/to/datasets`.
+If left unset, the default is `./datasets` relative to your current working directory.
+
+The [model zoo](https://github.com/facebookresearch/detectron2/blob/master/MODEL_ZOO.md)
+contains configs and models that use these builtin datasets.
+
+## Expected dataset structure for [COCO instance/keypoint detection](https://cocodataset.org/#download):
+
+```
+coco/
+ annotations/
+ instances_{train,val}2017.json
+ person_keypoints_{train,val}2017.json
+ {train,val}2017/
+ # image files that are mentioned in the corresponding json
+```
+
+You can use the 2014 version of the dataset as well.
+
+Some of the builtin tests (`dev/run_*_tests.sh`) uses a tiny version of the COCO dataset,
+which you can download with `./datasets/prepare_for_tests.sh`.
+
+## Expected dataset structure for PanopticFPN:
+
+Extract panoptic annotations from [COCO website](https://cocodataset.org/#download)
+into the following structure:
+```
+coco/
+ annotations/
+ panoptic_{train,val}2017.json
+ panoptic_{train,val}2017/ # png annotations
+ panoptic_stuff_{train,val}2017/ # generated by the script mentioned below
+```
+
+Install panopticapi by:
+```
+pip install git+https://github.com/cocodataset/panopticapi.git
+```
+Then, run `python datasets/prepare_panoptic_fpn.py`, to extract semantic annotations from panoptic annotations.
+
+## Expected dataset structure for [LVIS instance segmentation](https://www.lvisdataset.org/dataset):
+```
+coco/
+ {train,val,test}2017/
+lvis/
+ lvis_v0.5_{train,val}.json
+ lvis_v0.5_image_info_test.json
+ lvis_v1_{train,val}.json
+ lvis_v1_image_info_test{,_challenge}.json
+```
+
+Install lvis-api by:
+```
+pip install git+https://github.com/lvis-dataset/lvis-api.git
+```
+
+To evaluate models trained on the COCO dataset using LVIS annotations,
+run `python datasets/prepare_cocofied_lvis.py` to prepare "cocofied" LVIS annotations.
+
+## Expected dataset structure for [cityscapes](https://www.cityscapes-dataset.com/downloads/):
+```
+cityscapes/
+ gtFine/
+ train/
+ aachen/
+ color.png, instanceIds.png, labelIds.png, polygons.json,
+ labelTrainIds.png
+ ...
+ val/
+ test/
+ # below are generated Cityscapes panoptic annotation
+ cityscapes_panoptic_train.json
+ cityscapes_panoptic_train/
+ cityscapes_panoptic_val.json
+ cityscapes_panoptic_val/
+ cityscapes_panoptic_test.json
+ cityscapes_panoptic_test/
+ leftImg8bit/
+ train/
+ val/
+ test/
+```
+Install cityscapes scripts by:
+```
+pip install git+https://github.com/mcordts/cityscapesScripts.git
+```
+
+Note: to create labelTrainIds.png, first prepare the above structure, then run cityscapesescript with:
+```
+CITYSCAPES_DATASET=/path/to/abovementioned/cityscapes python cityscapesscripts/preparation/createTrainIdLabelImgs.py
+```
+These files are not needed for instance segmentation.
+
+Note: to generate Cityscapes panoptic dataset, run cityscapesescript with:
+```
+CITYSCAPES_DATASET=/path/to/abovementioned/cityscapes python cityscapesscripts/preparation/createPanopticImgs.py
+```
+These files are not needed for semantic and instance segmentation.
+
+## Expected dataset structure for [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/index.html):
+```
+VOC20{07,12}/
+ Annotations/
+ ImageSets/
+ Main/
+ trainval.txt
+ test.txt
+ # train.txt or val.txt, if you use these splits
+ JPEGImages/
+```
+
+## Expected dataset structure for [ADE20k Scene Parsing](http://sceneparsing.csail.mit.edu/):
+```
+ADEChallengeData2016/
+ annotations/
+ annotations_detectron2/
+ images/
+ objectInfo150.txt
+```
+The directory `annotations_detectron2` is generated by running `python datasets/prepare_ade20k_sem_seg.py`.
diff --git a/detectron2/datasets/prepare_ade20k_sem_seg.py b/detectron2/datasets/prepare_ade20k_sem_seg.py
new file mode 100755
index 0000000..8b4a58d
--- /dev/null
+++ b/detectron2/datasets/prepare_ade20k_sem_seg.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+import numpy as np
+import os
+from pathlib import Path
+import tqdm
+from PIL import Image
+
+
+def convert(input, output):
+ img = np.asarray(Image.open(input))
+ assert img.dtype == np.uint8
+ img = img - 1 # 0 (ignore) becomes 255. others are shifted by 1
+ Image.fromarray(img).save(output)
+
+
+if __name__ == "__main__":
+ dataset_dir = Path(os.getenv("DETECTRON2_DATASETS", "datasets")) / "ADEChallengeData2016"
+ for name in ["training", "validation"]:
+ annotation_dir = dataset_dir / "annotations" / name
+ output_dir = dataset_dir / "annotations_detectron2" / name
+ output_dir.mkdir(parents=True, exist_ok=True)
+ for file in tqdm.tqdm(list(annotation_dir.iterdir())):
+ output_file = output_dir / file.name
+ convert(file, output_file)
diff --git a/detectron2/datasets/prepare_cocofied_lvis.py b/detectron2/datasets/prepare_cocofied_lvis.py
new file mode 100755
index 0000000..245c884
--- /dev/null
+++ b/detectron2/datasets/prepare_cocofied_lvis.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import copy
+import json
+import os
+from collections import defaultdict
+
+# This mapping is extracted from the official LVIS mapping:
+# https://github.com/lvis-dataset/lvis-api/blob/master/data/coco_to_synset.json
+COCO_SYNSET_CATEGORIES = [
+ {"synset": "person.n.01", "coco_cat_id": 1},
+ {"synset": "bicycle.n.01", "coco_cat_id": 2},
+ {"synset": "car.n.01", "coco_cat_id": 3},
+ {"synset": "motorcycle.n.01", "coco_cat_id": 4},
+ {"synset": "airplane.n.01", "coco_cat_id": 5},
+ {"synset": "bus.n.01", "coco_cat_id": 6},
+ {"synset": "train.n.01", "coco_cat_id": 7},
+ {"synset": "truck.n.01", "coco_cat_id": 8},
+ {"synset": "boat.n.01", "coco_cat_id": 9},
+ {"synset": "traffic_light.n.01", "coco_cat_id": 10},
+ {"synset": "fireplug.n.01", "coco_cat_id": 11},
+ {"synset": "stop_sign.n.01", "coco_cat_id": 13},
+ {"synset": "parking_meter.n.01", "coco_cat_id": 14},
+ {"synset": "bench.n.01", "coco_cat_id": 15},
+ {"synset": "bird.n.01", "coco_cat_id": 16},
+ {"synset": "cat.n.01", "coco_cat_id": 17},
+ {"synset": "dog.n.01", "coco_cat_id": 18},
+ {"synset": "horse.n.01", "coco_cat_id": 19},
+ {"synset": "sheep.n.01", "coco_cat_id": 20},
+ {"synset": "beef.n.01", "coco_cat_id": 21},
+ {"synset": "elephant.n.01", "coco_cat_id": 22},
+ {"synset": "bear.n.01", "coco_cat_id": 23},
+ {"synset": "zebra.n.01", "coco_cat_id": 24},
+ {"synset": "giraffe.n.01", "coco_cat_id": 25},
+ {"synset": "backpack.n.01", "coco_cat_id": 27},
+ {"synset": "umbrella.n.01", "coco_cat_id": 28},
+ {"synset": "bag.n.04", "coco_cat_id": 31},
+ {"synset": "necktie.n.01", "coco_cat_id": 32},
+ {"synset": "bag.n.06", "coco_cat_id": 33},
+ {"synset": "frisbee.n.01", "coco_cat_id": 34},
+ {"synset": "ski.n.01", "coco_cat_id": 35},
+ {"synset": "snowboard.n.01", "coco_cat_id": 36},
+ {"synset": "ball.n.06", "coco_cat_id": 37},
+ {"synset": "kite.n.03", "coco_cat_id": 38},
+ {"synset": "baseball_bat.n.01", "coco_cat_id": 39},
+ {"synset": "baseball_glove.n.01", "coco_cat_id": 40},
+ {"synset": "skateboard.n.01", "coco_cat_id": 41},
+ {"synset": "surfboard.n.01", "coco_cat_id": 42},
+ {"synset": "tennis_racket.n.01", "coco_cat_id": 43},
+ {"synset": "bottle.n.01", "coco_cat_id": 44},
+ {"synset": "wineglass.n.01", "coco_cat_id": 46},
+ {"synset": "cup.n.01", "coco_cat_id": 47},
+ {"synset": "fork.n.01", "coco_cat_id": 48},
+ {"synset": "knife.n.01", "coco_cat_id": 49},
+ {"synset": "spoon.n.01", "coco_cat_id": 50},
+ {"synset": "bowl.n.03", "coco_cat_id": 51},
+ {"synset": "banana.n.02", "coco_cat_id": 52},
+ {"synset": "apple.n.01", "coco_cat_id": 53},
+ {"synset": "sandwich.n.01", "coco_cat_id": 54},
+ {"synset": "orange.n.01", "coco_cat_id": 55},
+ {"synset": "broccoli.n.01", "coco_cat_id": 56},
+ {"synset": "carrot.n.01", "coco_cat_id": 57},
+ {"synset": "frank.n.02", "coco_cat_id": 58},
+ {"synset": "pizza.n.01", "coco_cat_id": 59},
+ {"synset": "doughnut.n.02", "coco_cat_id": 60},
+ {"synset": "cake.n.03", "coco_cat_id": 61},
+ {"synset": "chair.n.01", "coco_cat_id": 62},
+ {"synset": "sofa.n.01", "coco_cat_id": 63},
+ {"synset": "pot.n.04", "coco_cat_id": 64},
+ {"synset": "bed.n.01", "coco_cat_id": 65},
+ {"synset": "dining_table.n.01", "coco_cat_id": 67},
+ {"synset": "toilet.n.02", "coco_cat_id": 70},
+ {"synset": "television_receiver.n.01", "coco_cat_id": 72},
+ {"synset": "laptop.n.01", "coco_cat_id": 73},
+ {"synset": "mouse.n.04", "coco_cat_id": 74},
+ {"synset": "remote_control.n.01", "coco_cat_id": 75},
+ {"synset": "computer_keyboard.n.01", "coco_cat_id": 76},
+ {"synset": "cellular_telephone.n.01", "coco_cat_id": 77},
+ {"synset": "microwave.n.02", "coco_cat_id": 78},
+ {"synset": "oven.n.01", "coco_cat_id": 79},
+ {"synset": "toaster.n.02", "coco_cat_id": 80},
+ {"synset": "sink.n.01", "coco_cat_id": 81},
+ {"synset": "electric_refrigerator.n.01", "coco_cat_id": 82},
+ {"synset": "book.n.01", "coco_cat_id": 84},
+ {"synset": "clock.n.01", "coco_cat_id": 85},
+ {"synset": "vase.n.01", "coco_cat_id": 86},
+ {"synset": "scissors.n.01", "coco_cat_id": 87},
+ {"synset": "teddy.n.01", "coco_cat_id": 88},
+ {"synset": "hand_blower.n.01", "coco_cat_id": 89},
+ {"synset": "toothbrush.n.01", "coco_cat_id": 90},
+]
+
+
+def cocofy_lvis(input_filename, output_filename):
+ """
+ Filter LVIS instance segmentation annotations to remove all categories that are not included in
+ COCO. The new json files can be used to evaluate COCO AP using `lvis-api`. The category ids in
+ the output json are the incontiguous COCO dataset ids.
+
+ Args:
+ input_filename (str): path to the LVIS json file.
+ output_filename (str): path to the COCOfied json file.
+ """
+
+ with open(input_filename, "r") as f:
+ lvis_json = json.load(f)
+
+ lvis_annos = lvis_json.pop("annotations")
+ cocofied_lvis = copy.deepcopy(lvis_json)
+ lvis_json["annotations"] = lvis_annos
+
+ # Mapping from lvis cat id to coco cat id via synset
+ lvis_cat_id_to_synset = {cat["id"]: cat["synset"] for cat in lvis_json["categories"]}
+ synset_to_coco_cat_id = {x["synset"]: x["coco_cat_id"] for x in COCO_SYNSET_CATEGORIES}
+ # Synsets that we will keep in the dataset
+ synsets_to_keep = set(synset_to_coco_cat_id.keys())
+ coco_cat_id_with_instances = defaultdict(int)
+
+ new_annos = []
+ ann_id = 1
+ for ann in lvis_annos:
+ lvis_cat_id = ann["category_id"]
+ synset = lvis_cat_id_to_synset[lvis_cat_id]
+ if synset not in synsets_to_keep:
+ continue
+ coco_cat_id = synset_to_coco_cat_id[synset]
+ new_ann = copy.deepcopy(ann)
+ new_ann["category_id"] = coco_cat_id
+ new_ann["id"] = ann_id
+ ann_id += 1
+ new_annos.append(new_ann)
+ coco_cat_id_with_instances[coco_cat_id] += 1
+ cocofied_lvis["annotations"] = new_annos
+
+ for image in cocofied_lvis["images"]:
+ for key in ["not_exhaustive_category_ids", "neg_category_ids"]:
+ new_category_list = []
+ for lvis_cat_id in image[key]:
+ synset = lvis_cat_id_to_synset[lvis_cat_id]
+ if synset not in synsets_to_keep:
+ continue
+ coco_cat_id = synset_to_coco_cat_id[synset]
+ new_category_list.append(coco_cat_id)
+ coco_cat_id_with_instances[coco_cat_id] += 1
+ image[key] = new_category_list
+
+ coco_cat_id_with_instances = set(coco_cat_id_with_instances.keys())
+
+ new_categories = []
+ for cat in lvis_json["categories"]:
+ synset = cat["synset"]
+ if synset not in synsets_to_keep:
+ continue
+ coco_cat_id = synset_to_coco_cat_id[synset]
+ if coco_cat_id not in coco_cat_id_with_instances:
+ continue
+ new_cat = copy.deepcopy(cat)
+ new_cat["id"] = coco_cat_id
+ new_categories.append(new_cat)
+ cocofied_lvis["categories"] = new_categories
+
+ with open(output_filename, "w") as f:
+ json.dump(cocofied_lvis, f)
+ print("{} is COCOfied and stored in {}.".format(input_filename, output_filename))
+
+
+if __name__ == "__main__":
+ dataset_dir = os.path.join(os.getenv("DETECTRON2_DATASETS", "datasets"), "lvis")
+ for s in ["lvis_v0.5_train", "lvis_v0.5_val"]:
+ print("Start COCOfing {}.".format(s))
+ cocofy_lvis(
+ os.path.join(dataset_dir, "{}.json".format(s)),
+ os.path.join(dataset_dir, "{}_cocofied.json".format(s)),
+ )
diff --git a/detectron2/datasets/prepare_for_tests.sh b/detectron2/datasets/prepare_for_tests.sh
new file mode 100755
index 0000000..67e875a
--- /dev/null
+++ b/detectron2/datasets/prepare_for_tests.sh
@@ -0,0 +1,31 @@
+#!/bin/bash -e
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+# Download the mini dataset (coco val2017_100, with only 100 images)
+# to be used in unittests & integration tests.
+
+cd "${0%/*}"
+
+BASE=https://dl.fbaipublicfiles.com/detectron2
+ROOT=${DETECTRON2_DATASETS:-./}
+ROOT=${ROOT/#\~/$HOME} # expand ~ to HOME
+mkdir -p $ROOT/coco/annotations
+
+for anno in instances_val2017_100 \
+ person_keypoints_val2017_100 ; do
+
+ dest=$ROOT/coco/annotations/$anno.json
+ [[ -s $dest ]] && {
+ echo "$dest exists. Skipping ..."
+ } || {
+ wget $BASE/annotations/coco/$anno.json -O $dest
+ }
+done
+
+dest=$ROOT/coco/val2017_100.tgz
+[[ -d $ROOT/coco/val2017 ]] && {
+ echo "$ROOT/coco/val2017 exists. Skipping ..."
+} || {
+ wget $BASE/annotations/coco/val2017_100.tgz -O $dest
+ tar xzf $dest -C $ROOT/coco/ && rm -f $dest
+}
diff --git a/detectron2/datasets/prepare_panoptic_fpn.py b/detectron2/datasets/prepare_panoptic_fpn.py
new file mode 100755
index 0000000..597d791
--- /dev/null
+++ b/detectron2/datasets/prepare_panoptic_fpn.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import functools
+import json
+import multiprocessing as mp
+import numpy as np
+import os
+import time
+from fvcore.common.download import download
+from panopticapi.utils import rgb2id
+from PIL import Image
+
+from detectron2.data.datasets.builtin_meta import COCO_CATEGORIES
+
+
+def _process_panoptic_to_semantic(input_panoptic, output_semantic, segments, id_map):
+ panoptic = np.asarray(Image.open(input_panoptic), dtype=np.uint32)
+ panoptic = rgb2id(panoptic)
+ output = np.zeros_like(panoptic, dtype=np.uint8) + 255
+ for seg in segments:
+ cat_id = seg["category_id"]
+ new_cat_id = id_map[cat_id]
+ output[panoptic == seg["id"]] = new_cat_id
+ Image.fromarray(output).save(output_semantic)
+
+
+def separate_coco_semantic_from_panoptic(panoptic_json, panoptic_root, sem_seg_root, categories):
+ """
+ Create semantic segmentation annotations from panoptic segmentation
+ annotations, to be used by PanopticFPN.
+
+ It maps all thing categories to class 0, and maps all unlabeled pixels to class 255.
+ It maps all stuff categories to contiguous ids starting from 1.
+
+ Args:
+ panoptic_json (str): path to the panoptic json file, in COCO's format.
+ panoptic_root (str): a directory with panoptic annotation files, in COCO's format.
+ sem_seg_root (str): a directory to output semantic annotation files
+ categories (list[dict]): category metadata. Each dict needs to have:
+ "id": corresponds to the "category_id" in the json annotations
+ "isthing": 0 or 1
+ """
+ os.makedirs(sem_seg_root, exist_ok=True)
+
+ stuff_ids = [k["id"] for k in categories if k["isthing"] == 0]
+ thing_ids = [k["id"] for k in categories if k["isthing"] == 1]
+ id_map = {} # map from category id to id in the output semantic annotation
+ assert len(stuff_ids) <= 254
+ for i, stuff_id in enumerate(stuff_ids):
+ id_map[stuff_id] = i + 1
+ for thing_id in thing_ids:
+ id_map[thing_id] = 0
+ id_map[0] = 255
+
+ with open(panoptic_json) as f:
+ obj = json.load(f)
+
+ pool = mp.Pool(processes=max(mp.cpu_count() // 2, 4))
+
+ def iter_annotations():
+ for anno in obj["annotations"]:
+ file_name = anno["file_name"]
+ segments = anno["segments_info"]
+ input = os.path.join(panoptic_root, file_name)
+ output = os.path.join(sem_seg_root, file_name)
+ yield input, output, segments
+
+ print("Start writing to {} ...".format(sem_seg_root))
+ start = time.time()
+ pool.starmap(
+ functools.partial(_process_panoptic_to_semantic, id_map=id_map),
+ iter_annotations(),
+ chunksize=100,
+ )
+ print("Finished. time: {:.2f}s".format(time.time() - start))
+
+
+if __name__ == "__main__":
+ dataset_dir = os.path.join(os.getenv("DETECTRON2_DATASETS", "datasets"), "coco")
+ for s in ["val2017", "train2017"]:
+ separate_coco_semantic_from_panoptic(
+ os.path.join(dataset_dir, "annotations/panoptic_{}.json".format(s)),
+ os.path.join(dataset_dir, "panoptic_{}".format(s)),
+ os.path.join(dataset_dir, "panoptic_stuff_{}".format(s)),
+ COCO_CATEGORIES,
+ )
+
+ # Prepare val2017_100 for quick testing:
+
+ dest_dir = os.path.join(dataset_dir, "annotations/")
+ URL_PREFIX = "https://dl.fbaipublicfiles.com/detectron2/"
+ download(URL_PREFIX + "annotations/coco/panoptic_val2017_100.json", dest_dir)
+ with open(os.path.join(dest_dir, "panoptic_val2017_100.json")) as f:
+ obj = json.load(f)
+
+ def link_val100(dir_full, dir_100):
+ print("Creating " + dir_100 + " ...")
+ os.makedirs(dir_100, exist_ok=True)
+ for img in obj["images"]:
+ basename = os.path.splitext(img["file_name"])[0]
+ src = os.path.join(dir_full, basename + ".png")
+ dst = os.path.join(dir_100, basename + ".png")
+ src = os.path.relpath(src, start=dir_100)
+ os.symlink(src, dst)
+
+ link_val100(
+ os.path.join(dataset_dir, "panoptic_val2017"),
+ os.path.join(dataset_dir, "panoptic_val2017_100"),
+ )
+
+ link_val100(
+ os.path.join(dataset_dir, "panoptic_stuff_val2017"),
+ os.path.join(dataset_dir, "panoptic_stuff_val2017_100"),
+ )
diff --git a/detectron2/demo/README.md b/detectron2/demo/README.md
new file mode 100755
index 0000000..133d8d3
--- /dev/null
+++ b/detectron2/demo/README.md
@@ -0,0 +1,8 @@
+
+## Detectron2 Demo
+
+We provide a command line tool to run a simple demo of builtin configs.
+The usage is explained in [GETTING_STARTED.md](../GETTING_STARTED.md).
+
+See our [blog post](https://ai.facebook.com/blog/-detectron2-a-pytorch-based-modular-object-detection-library-)
+for a high-quality demo generated with this tool.
diff --git a/detectron2/demo/demo.py b/detectron2/demo/demo.py
new file mode 100755
index 0000000..4baa876
--- /dev/null
+++ b/detectron2/demo/demo.py
@@ -0,0 +1,188 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import argparse
+import glob
+import multiprocessing as mp
+import numpy as np
+import os
+import tempfile
+import time
+import warnings
+import cv2
+import tqdm
+
+from detectron2.config import get_cfg
+from detectron2.data.detection_utils import read_image
+from detectron2.utils.logger import setup_logger
+
+from predictor import VisualizationDemo
+
+# constants
+WINDOW_NAME = "COCO detections"
+
+
+def setup_cfg(args):
+ # load config from file and command-line arguments
+ cfg = get_cfg()
+ # To use demo for Panoptic-DeepLab, please uncomment the following two lines.
+ # from detectron2.projects.panoptic_deeplab import add_panoptic_deeplab_config # noqa
+ # add_panoptic_deeplab_config(cfg)
+ cfg.merge_from_file(args.config_file)
+ cfg.merge_from_list(args.opts)
+ # Set score_threshold for builtin models
+ cfg.MODEL.RETINANET.SCORE_THRESH_TEST = args.confidence_threshold
+ cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = args.confidence_threshold
+ cfg.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = args.confidence_threshold
+ cfg.freeze()
+ return cfg
+
+
+def get_parser():
+ parser = argparse.ArgumentParser(description="Detectron2 demo for builtin configs")
+ parser.add_argument(
+ "--config-file",
+ default="configs/quick_schedules/mask_rcnn_R_50_FPN_inference_acc_test.yaml",
+ metavar="FILE",
+ help="path to config file",
+ )
+ parser.add_argument("--webcam", action="store_true", help="Take inputs from webcam.")
+ parser.add_argument("--video-input", help="Path to video file.")
+ parser.add_argument(
+ "--input",
+ nargs="+",
+ help="A list of space separated input images; "
+ "or a single glob pattern such as 'directory/*.jpg'",
+ )
+ parser.add_argument(
+ "--output",
+ help="A file or directory to save output visualizations. "
+ "If not given, will show output in an OpenCV window.",
+ )
+
+ parser.add_argument(
+ "--confidence-threshold",
+ type=float,
+ default=0.5,
+ help="Minimum score for instance predictions to be shown",
+ )
+ parser.add_argument(
+ "--opts",
+ help="Modify config options using the command-line 'KEY VALUE' pairs",
+ default=[],
+ nargs=argparse.REMAINDER,
+ )
+ return parser
+
+
+def test_opencv_video_format(codec, file_ext):
+ with tempfile.TemporaryDirectory(prefix="video_format_test") as dir:
+ filename = os.path.join(dir, "test_file" + file_ext)
+ writer = cv2.VideoWriter(
+ filename=filename,
+ fourcc=cv2.VideoWriter_fourcc(*codec),
+ fps=float(30),
+ frameSize=(10, 10),
+ isColor=True,
+ )
+ [writer.write(np.zeros((10, 10, 3), np.uint8)) for _ in range(30)]
+ writer.release()
+ if os.path.isfile(filename):
+ return True
+ return False
+
+
+if __name__ == "__main__":
+ mp.set_start_method("spawn", force=True)
+ args = get_parser().parse_args()
+ setup_logger(name="fvcore")
+ logger = setup_logger()
+ logger.info("Arguments: " + str(args))
+
+ cfg = setup_cfg(args)
+
+ demo = VisualizationDemo(cfg)
+
+ if args.input:
+ if len(args.input) == 1:
+ args.input = glob.glob(os.path.expanduser(args.input[0]))
+ assert args.input, "The input path(s) was not found"
+ for path in tqdm.tqdm(args.input, disable=not args.output):
+ # use PIL, to be consistent with evaluation
+ img = read_image(path, format="BGR")
+ start_time = time.time()
+ predictions, visualized_output = demo.run_on_image(img)
+ logger.info(
+ "{}: {} in {:.2f}s".format(
+ path,
+ "detected {} instances".format(len(predictions["instances"]))
+ if "instances" in predictions
+ else "finished",
+ time.time() - start_time,
+ )
+ )
+
+ if args.output:
+ if os.path.isdir(args.output):
+ assert os.path.isdir(args.output), args.output
+ out_filename = os.path.join(args.output, os.path.basename(path))
+ else:
+ assert len(args.input) == 1, "Please specify a directory with args.output"
+ out_filename = args.output
+ visualized_output.save(out_filename)
+ else:
+ cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
+ cv2.imshow(WINDOW_NAME, visualized_output.get_image()[:, :, ::-1])
+ if cv2.waitKey(0) == 27:
+ break # esc to quit
+ elif args.webcam:
+ assert args.input is None, "Cannot have both --input and --webcam!"
+ assert args.output is None, "output not yet supported with --webcam!"
+ cam = cv2.VideoCapture(0)
+ for vis in tqdm.tqdm(demo.run_on_video(cam)):
+ cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
+ cv2.imshow(WINDOW_NAME, vis)
+ if cv2.waitKey(1) == 27:
+ break # esc to quit
+ cam.release()
+ cv2.destroyAllWindows()
+ elif args.video_input:
+ video = cv2.VideoCapture(args.video_input)
+ width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
+ height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
+ frames_per_second = video.get(cv2.CAP_PROP_FPS)
+ num_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
+ basename = os.path.basename(args.video_input)
+ codec, file_ext = (
+ ("x264", ".mkv") if test_opencv_video_format("x264", ".mkv") else ("mp4v", ".mp4")
+ )
+ if codec == ".mp4v":
+ warnings.warn("x264 codec not available, switching to mp4v")
+ if args.output:
+ if os.path.isdir(args.output):
+ output_fname = os.path.join(args.output, basename)
+ output_fname = os.path.splitext(output_fname)[0] + file_ext
+ else:
+ output_fname = args.output
+ assert not os.path.isfile(output_fname), output_fname
+ output_file = cv2.VideoWriter(
+ filename=output_fname,
+ # some installation of opencv may not support x264 (due to its license),
+ # you can try other format (e.g. MPEG)
+ fourcc=cv2.VideoWriter_fourcc(*codec),
+ fps=float(frames_per_second),
+ frameSize=(width, height),
+ isColor=True,
+ )
+ assert os.path.isfile(args.video_input)
+ for vis_frame in tqdm.tqdm(demo.run_on_video(video), total=num_frames):
+ if args.output:
+ output_file.write(vis_frame)
+ else:
+ cv2.namedWindow(basename, cv2.WINDOW_NORMAL)
+ cv2.imshow(basename, vis_frame)
+ if cv2.waitKey(1) == 27:
+ break # esc to quit
+ video.release()
+ if args.output:
+ output_file.release()
+ else:
+ cv2.destroyAllWindows()
diff --git a/detectron2/demo/predictor.py b/detectron2/demo/predictor.py
new file mode 100755
index 0000000..7b7ebd3
--- /dev/null
+++ b/detectron2/demo/predictor.py
@@ -0,0 +1,220 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import atexit
+import bisect
+import multiprocessing as mp
+from collections import deque
+import cv2
+import torch
+
+from detectron2.data import MetadataCatalog
+from detectron2.engine.defaults import DefaultPredictor
+from detectron2.utils.video_visualizer import VideoVisualizer
+from detectron2.utils.visualizer import ColorMode, Visualizer
+
+
+class VisualizationDemo(object):
+ def __init__(self, cfg, instance_mode=ColorMode.IMAGE, parallel=False):
+ """
+ Args:
+ cfg (CfgNode):
+ instance_mode (ColorMode):
+ parallel (bool): whether to run the model in different processes from visualization.
+ Useful since the visualization logic can be slow.
+ """
+ self.metadata = MetadataCatalog.get(
+ cfg.DATASETS.TEST[0] if len(cfg.DATASETS.TEST) else "__unused"
+ )
+ self.cpu_device = torch.device("cpu")
+ self.instance_mode = instance_mode
+
+ self.parallel = parallel
+ if parallel:
+ num_gpu = torch.cuda.device_count()
+ self.predictor = AsyncPredictor(cfg, num_gpus=num_gpu)
+ else:
+ self.predictor = DefaultPredictor(cfg)
+
+ def run_on_image(self, image):
+ """
+ Args:
+ image (np.ndarray): an image of shape (H, W, C) (in BGR order).
+ This is the format used by OpenCV.
+
+ Returns:
+ predictions (dict): the output of the model.
+ vis_output (VisImage): the visualized image output.
+ """
+ vis_output = None
+ predictions = self.predictor(image)
+ # Convert image from OpenCV BGR format to Matplotlib RGB format.
+ image = image[:, :, ::-1]
+ visualizer = Visualizer(image, self.metadata, instance_mode=self.instance_mode)
+ if "panoptic_seg" in predictions:
+ panoptic_seg, segments_info = predictions["panoptic_seg"]
+ vis_output = visualizer.draw_panoptic_seg_predictions(
+ panoptic_seg.to(self.cpu_device), segments_info
+ )
+ else:
+ if "sem_seg" in predictions:
+ vis_output = visualizer.draw_sem_seg(
+ predictions["sem_seg"].argmax(dim=0).to(self.cpu_device)
+ )
+ if "instances" in predictions:
+ instances = predictions["instances"].to(self.cpu_device)
+ vis_output = visualizer.draw_instance_predictions(predictions=instances)
+
+ return predictions, vis_output
+
+ def _frame_from_video(self, video):
+ while video.isOpened():
+ success, frame = video.read()
+ if success:
+ yield frame
+ else:
+ break
+
+ def run_on_video(self, video):
+ """
+ Visualizes predictions on frames of the input video.
+
+ Args:
+ video (cv2.VideoCapture): a :class:`VideoCapture` object, whose source can be
+ either a webcam or a video file.
+
+ Yields:
+ ndarray: BGR visualizations of each video frame.
+ """
+ video_visualizer = VideoVisualizer(self.metadata, self.instance_mode)
+
+ def process_predictions(frame, predictions):
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
+ if "panoptic_seg" in predictions:
+ panoptic_seg, segments_info = predictions["panoptic_seg"]
+ vis_frame = video_visualizer.draw_panoptic_seg_predictions(
+ frame, panoptic_seg.to(self.cpu_device), segments_info
+ )
+ elif "instances" in predictions:
+ predictions = predictions["instances"].to(self.cpu_device)
+ vis_frame = video_visualizer.draw_instance_predictions(frame, predictions)
+ elif "sem_seg" in predictions:
+ vis_frame = video_visualizer.draw_sem_seg(
+ frame, predictions["sem_seg"].argmax(dim=0).to(self.cpu_device)
+ )
+
+ # Converts Matplotlib RGB format to OpenCV BGR format
+ vis_frame = cv2.cvtColor(vis_frame.get_image(), cv2.COLOR_RGB2BGR)
+ return vis_frame
+
+ frame_gen = self._frame_from_video(video)
+ if self.parallel:
+ buffer_size = self.predictor.default_buffer_size
+
+ frame_data = deque()
+
+ for cnt, frame in enumerate(frame_gen):
+ frame_data.append(frame)
+ self.predictor.put(frame)
+
+ if cnt >= buffer_size:
+ frame = frame_data.popleft()
+ predictions = self.predictor.get()
+ yield process_predictions(frame, predictions)
+
+ while len(frame_data):
+ frame = frame_data.popleft()
+ predictions = self.predictor.get()
+ yield process_predictions(frame, predictions)
+ else:
+ for frame in frame_gen:
+ yield process_predictions(frame, self.predictor(frame))
+
+
+class AsyncPredictor:
+ """
+ A predictor that runs the model asynchronously, possibly on >1 GPUs.
+ Because rendering the visualization takes considerably amount of time,
+ this helps improve throughput a little bit when rendering videos.
+ """
+
+ class _StopToken:
+ pass
+
+ class _PredictWorker(mp.Process):
+ def __init__(self, cfg, task_queue, result_queue):
+ self.cfg = cfg
+ self.task_queue = task_queue
+ self.result_queue = result_queue
+ super().__init__()
+
+ def run(self):
+ predictor = DefaultPredictor(self.cfg)
+
+ while True:
+ task = self.task_queue.get()
+ if isinstance(task, AsyncPredictor._StopToken):
+ break
+ idx, data = task
+ result = predictor(data)
+ self.result_queue.put((idx, result))
+
+ def __init__(self, cfg, num_gpus: int = 1):
+ """
+ Args:
+ cfg (CfgNode):
+ num_gpus (int): if 0, will run on CPU
+ """
+ num_workers = max(num_gpus, 1)
+ self.task_queue = mp.Queue(maxsize=num_workers * 3)
+ self.result_queue = mp.Queue(maxsize=num_workers * 3)
+ self.procs = []
+ for gpuid in range(max(num_gpus, 1)):
+ cfg = cfg.clone()
+ cfg.defrost()
+ cfg.MODEL.DEVICE = "cuda:{}".format(gpuid) if num_gpus > 0 else "cpu"
+ self.procs.append(
+ AsyncPredictor._PredictWorker(cfg, self.task_queue, self.result_queue)
+ )
+
+ self.put_idx = 0
+ self.get_idx = 0
+ self.result_rank = []
+ self.result_data = []
+
+ for p in self.procs:
+ p.start()
+ atexit.register(self.shutdown)
+
+ def put(self, image):
+ self.put_idx += 1
+ self.task_queue.put((self.put_idx, image))
+
+ def get(self):
+ self.get_idx += 1 # the index needed for this request
+ if len(self.result_rank) and self.result_rank[0] == self.get_idx:
+ res = self.result_data[0]
+ del self.result_data[0], self.result_rank[0]
+ return res
+
+ while True:
+ # make sure the results are returned in the correct order
+ idx, res = self.result_queue.get()
+ if idx == self.get_idx:
+ return res
+ insert = bisect.bisect(self.result_rank, idx)
+ self.result_rank.insert(insert, idx)
+ self.result_data.insert(insert, res)
+
+ def __len__(self):
+ return self.put_idx - self.get_idx
+
+ def __call__(self, image):
+ self.put(image)
+ return self.get()
+
+ def shutdown(self):
+ for _ in self.procs:
+ self.task_queue.put(AsyncPredictor._StopToken())
+
+ @property
+ def default_buffer_size(self):
+ return len(self.procs) * 5
diff --git a/detectron2/detectron2/__init__.py b/detectron2/detectron2/__init__.py
new file mode 100755
index 0000000..bdd994b
--- /dev/null
+++ b/detectron2/detectron2/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+from .utils.env import setup_environment
+
+setup_environment()
+
+
+# This line will be programatically read/write by setup.py.
+# Leave them at the bottom of this file and don't touch them.
+__version__ = "0.6"
diff --git a/detectron2/detectron2/checkpoint/__init__.py b/detectron2/detectron2/checkpoint/__init__.py
new file mode 100755
index 0000000..99da046
--- /dev/null
+++ b/detectron2/detectron2/checkpoint/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+# File:
+
+
+from . import catalog as _UNUSED # register the handler
+from .detection_checkpoint import DetectionCheckpointer
+from fvcore.common.checkpoint import Checkpointer, PeriodicCheckpointer
+
+__all__ = ["Checkpointer", "PeriodicCheckpointer", "DetectionCheckpointer"]
diff --git a/detectron2/detectron2/checkpoint/c2_model_loading.py b/detectron2/detectron2/checkpoint/c2_model_loading.py
new file mode 100755
index 0000000..c6de2a3
--- /dev/null
+++ b/detectron2/detectron2/checkpoint/c2_model_loading.py
@@ -0,0 +1,412 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import re
+from typing import Dict, List
+import torch
+from tabulate import tabulate
+
+
+def convert_basic_c2_names(original_keys):
+ """
+ Apply some basic name conversion to names in C2 weights.
+ It only deals with typical backbone models.
+
+ Args:
+ original_keys (list[str]):
+ Returns:
+ list[str]: The same number of strings matching those in original_keys.
+ """
+ layer_keys = copy.deepcopy(original_keys)
+ layer_keys = [
+ {"pred_b": "linear_b", "pred_w": "linear_w"}.get(k, k) for k in layer_keys
+ ] # some hard-coded mappings
+
+ layer_keys = [k.replace("_", ".") for k in layer_keys]
+ layer_keys = [re.sub("\\.b$", ".bias", k) for k in layer_keys]
+ layer_keys = [re.sub("\\.w$", ".weight", k) for k in layer_keys]
+ # Uniform both bn and gn names to "norm"
+ layer_keys = [re.sub("bn\\.s$", "norm.weight", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.bias$", "norm.bias", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.rm", "norm.running_mean", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.running.mean$", "norm.running_mean", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.riv$", "norm.running_var", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.running.var$", "norm.running_var", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.gamma$", "norm.weight", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.beta$", "norm.bias", k) for k in layer_keys]
+ layer_keys = [re.sub("gn\\.s$", "norm.weight", k) for k in layer_keys]
+ layer_keys = [re.sub("gn\\.bias$", "norm.bias", k) for k in layer_keys]
+
+ # stem
+ layer_keys = [re.sub("^res\\.conv1\\.norm\\.", "conv1.norm.", k) for k in layer_keys]
+ # to avoid mis-matching with "conv1" in other components (e.g. detection head)
+ layer_keys = [re.sub("^conv1\\.", "stem.conv1.", k) for k in layer_keys]
+
+ # layer1-4 is used by torchvision, however we follow the C2 naming strategy (res2-5)
+ # layer_keys = [re.sub("^res2.", "layer1.", k) for k in layer_keys]
+ # layer_keys = [re.sub("^res3.", "layer2.", k) for k in layer_keys]
+ # layer_keys = [re.sub("^res4.", "layer3.", k) for k in layer_keys]
+ # layer_keys = [re.sub("^res5.", "layer4.", k) for k in layer_keys]
+
+ # blocks
+ layer_keys = [k.replace(".branch1.", ".shortcut.") for k in layer_keys]
+ layer_keys = [k.replace(".branch2a.", ".conv1.") for k in layer_keys]
+ layer_keys = [k.replace(".branch2b.", ".conv2.") for k in layer_keys]
+ layer_keys = [k.replace(".branch2c.", ".conv3.") for k in layer_keys]
+
+ # DensePose substitutions
+ layer_keys = [re.sub("^body.conv.fcn", "body_conv_fcn", k) for k in layer_keys]
+ layer_keys = [k.replace("AnnIndex.lowres", "ann_index_lowres") for k in layer_keys]
+ layer_keys = [k.replace("Index.UV.lowres", "index_uv_lowres") for k in layer_keys]
+ layer_keys = [k.replace("U.lowres", "u_lowres") for k in layer_keys]
+ layer_keys = [k.replace("V.lowres", "v_lowres") for k in layer_keys]
+ return layer_keys
+
+
+def convert_c2_detectron_names(weights):
+ """
+ Map Caffe2 Detectron weight names to Detectron2 names.
+
+ Args:
+ weights (dict): name -> tensor
+
+ Returns:
+ dict: detectron2 names -> tensor
+ dict: detectron2 names -> C2 names
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Renaming Caffe2 weights ......")
+ original_keys = sorted(weights.keys())
+ layer_keys = copy.deepcopy(original_keys)
+
+ layer_keys = convert_basic_c2_names(layer_keys)
+
+ # --------------------------------------------------------------------------
+ # RPN hidden representation conv
+ # --------------------------------------------------------------------------
+ # FPN case
+ # In the C2 model, the RPN hidden layer conv is defined for FPN level 2 and then
+ # shared for all other levels, hence the appearance of "fpn2"
+ layer_keys = [
+ k.replace("conv.rpn.fpn2", "proposal_generator.rpn_head.conv") for k in layer_keys
+ ]
+ # Non-FPN case
+ layer_keys = [k.replace("conv.rpn", "proposal_generator.rpn_head.conv") for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # RPN box transformation conv
+ # --------------------------------------------------------------------------
+ # FPN case (see note above about "fpn2")
+ layer_keys = [
+ k.replace("rpn.bbox.pred.fpn2", "proposal_generator.rpn_head.anchor_deltas")
+ for k in layer_keys
+ ]
+ layer_keys = [
+ k.replace("rpn.cls.logits.fpn2", "proposal_generator.rpn_head.objectness_logits")
+ for k in layer_keys
+ ]
+ # Non-FPN case
+ layer_keys = [
+ k.replace("rpn.bbox.pred", "proposal_generator.rpn_head.anchor_deltas") for k in layer_keys
+ ]
+ layer_keys = [
+ k.replace("rpn.cls.logits", "proposal_generator.rpn_head.objectness_logits")
+ for k in layer_keys
+ ]
+
+ # --------------------------------------------------------------------------
+ # Fast R-CNN box head
+ # --------------------------------------------------------------------------
+ layer_keys = [re.sub("^bbox\\.pred", "bbox_pred", k) for k in layer_keys]
+ layer_keys = [re.sub("^cls\\.score", "cls_score", k) for k in layer_keys]
+ layer_keys = [re.sub("^fc6\\.", "box_head.fc1.", k) for k in layer_keys]
+ layer_keys = [re.sub("^fc7\\.", "box_head.fc2.", k) for k in layer_keys]
+ # 4conv1fc head tensor names: head_conv1_w, head_conv1_gn_s
+ layer_keys = [re.sub("^head\\.conv", "box_head.conv", k) for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # FPN lateral and output convolutions
+ # --------------------------------------------------------------------------
+ def fpn_map(name):
+ """
+ Look for keys with the following patterns:
+ 1) Starts with "fpn.inner."
+ Example: "fpn.inner.res2.2.sum.lateral.weight"
+ Meaning: These are lateral pathway convolutions
+ 2) Starts with "fpn.res"
+ Example: "fpn.res2.2.sum.weight"
+ Meaning: These are FPN output convolutions
+ """
+ splits = name.split(".")
+ norm = ".norm" if "norm" in splits else ""
+ if name.startswith("fpn.inner."):
+ # splits example: ['fpn', 'inner', 'res2', '2', 'sum', 'lateral', 'weight']
+ stage = int(splits[2][len("res") :])
+ return "fpn_lateral{}{}.{}".format(stage, norm, splits[-1])
+ elif name.startswith("fpn.res"):
+ # splits example: ['fpn', 'res2', '2', 'sum', 'weight']
+ stage = int(splits[1][len("res") :])
+ return "fpn_output{}{}.{}".format(stage, norm, splits[-1])
+ return name
+
+ layer_keys = [fpn_map(k) for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # Mask R-CNN mask head
+ # --------------------------------------------------------------------------
+ # roi_heads.StandardROIHeads case
+ layer_keys = [k.replace(".[mask].fcn", "mask_head.mask_fcn") for k in layer_keys]
+ layer_keys = [re.sub("^\\.mask\\.fcn", "mask_head.mask_fcn", k) for k in layer_keys]
+ layer_keys = [k.replace("mask.fcn.logits", "mask_head.predictor") for k in layer_keys]
+ # roi_heads.Res5ROIHeads case
+ layer_keys = [k.replace("conv5.mask", "mask_head.deconv") for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # Keypoint R-CNN head
+ # --------------------------------------------------------------------------
+ # interestingly, the keypoint head convs have blob names that are simply "conv_fcnX"
+ layer_keys = [k.replace("conv.fcn", "roi_heads.keypoint_head.conv_fcn") for k in layer_keys]
+ layer_keys = [
+ k.replace("kps.score.lowres", "roi_heads.keypoint_head.score_lowres") for k in layer_keys
+ ]
+ layer_keys = [k.replace("kps.score.", "roi_heads.keypoint_head.score.") for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # Done with replacements
+ # --------------------------------------------------------------------------
+ assert len(set(layer_keys)) == len(layer_keys)
+ assert len(original_keys) == len(layer_keys)
+
+ new_weights = {}
+ new_keys_to_original_keys = {}
+ for orig, renamed in zip(original_keys, layer_keys):
+ new_keys_to_original_keys[renamed] = orig
+ if renamed.startswith("bbox_pred.") or renamed.startswith("mask_head.predictor."):
+ # remove the meaningless prediction weight for background class
+ new_start_idx = 4 if renamed.startswith("bbox_pred.") else 1
+ new_weights[renamed] = weights[orig][new_start_idx:]
+ logger.info(
+ "Remove prediction weight for background class in {}. The shape changes from "
+ "{} to {}.".format(
+ renamed, tuple(weights[orig].shape), tuple(new_weights[renamed].shape)
+ )
+ )
+ elif renamed.startswith("cls_score."):
+ # move weights of bg class from original index 0 to last index
+ logger.info(
+ "Move classification weights for background class in {} from index 0 to "
+ "index {}.".format(renamed, weights[orig].shape[0] - 1)
+ )
+ new_weights[renamed] = torch.cat([weights[orig][1:], weights[orig][:1]])
+ else:
+ new_weights[renamed] = weights[orig]
+
+ return new_weights, new_keys_to_original_keys
+
+
+# Note the current matching is not symmetric.
+# it assumes model_state_dict will have longer names.
+def align_and_update_state_dicts(model_state_dict, ckpt_state_dict, c2_conversion=True):
+ """
+ Match names between the two state-dict, and returns a new chkpt_state_dict with names
+ converted to match model_state_dict with heuristics. The returned dict can be later
+ loaded with fvcore checkpointer.
+ If `c2_conversion==True`, `ckpt_state_dict` is assumed to be a Caffe2
+ model and will be renamed at first.
+
+ Strategy: suppose that the models that we will create will have prefixes appended
+ to each of its keys, for example due to an extra level of nesting that the original
+ pre-trained weights from ImageNet won't contain. For example, model.state_dict()
+ might return backbone[0].body.res2.conv1.weight, while the pre-trained model contains
+ res2.conv1.weight. We thus want to match both parameters together.
+ For that, we look for each model weight, look among all loaded keys if there is one
+ that is a suffix of the current weight name, and use it if that's the case.
+ If multiple matches exist, take the one with longest size
+ of the corresponding name. For example, for the same model as before, the pretrained
+ weight file can contain both res2.conv1.weight, as well as conv1.weight. In this case,
+ we want to match backbone[0].body.conv1.weight to conv1.weight, and
+ backbone[0].body.res2.conv1.weight to res2.conv1.weight.
+ """
+ model_keys = sorted(model_state_dict.keys())
+ if c2_conversion:
+ ckpt_state_dict, original_keys = convert_c2_detectron_names(ckpt_state_dict)
+ # original_keys: the name in the original dict (before renaming)
+ else:
+ original_keys = {x: x for x in ckpt_state_dict.keys()}
+ ckpt_keys = sorted(ckpt_state_dict.keys())
+
+ def match(a, b):
+ # Matched ckpt_key should be a complete (starts with '.') suffix.
+ # For example, roi_heads.mesh_head.whatever_conv1 does not match conv1,
+ # but matches whatever_conv1 or mesh_head.whatever_conv1.
+ return a == b or a.endswith("." + b)
+
+ # get a matrix of string matches, where each (i, j) entry correspond to the size of the
+ # ckpt_key string, if it matches
+ match_matrix = [len(j) if match(i, j) else 0 for i in model_keys for j in ckpt_keys]
+ match_matrix = torch.as_tensor(match_matrix).view(len(model_keys), len(ckpt_keys))
+ # use the matched one with longest size in case of multiple matches
+ max_match_size, idxs = match_matrix.max(1)
+ # remove indices that correspond to no-match
+ idxs[max_match_size == 0] = -1
+
+ logger = logging.getLogger(__name__)
+ # matched_pairs (matched checkpoint key --> matched model key)
+ matched_keys = {}
+ result_state_dict = {}
+ for idx_model, idx_ckpt in enumerate(idxs.tolist()):
+ if idx_ckpt == -1:
+ continue
+ key_model = model_keys[idx_model]
+ key_ckpt = ckpt_keys[idx_ckpt]
+ value_ckpt = ckpt_state_dict[key_ckpt]
+ shape_in_model = model_state_dict[key_model].shape
+
+ if shape_in_model != value_ckpt.shape:
+ logger.warning(
+ "Shape of {} in checkpoint is {}, while shape of {} in model is {}.".format(
+ key_ckpt, value_ckpt.shape, key_model, shape_in_model
+ )
+ )
+ logger.warning(
+ "{} will not be loaded. Please double check and see if this is desired.".format(
+ key_ckpt
+ )
+ )
+ continue
+
+ assert key_model not in result_state_dict
+ result_state_dict[key_model] = value_ckpt
+ if key_ckpt in matched_keys: # already added to matched_keys
+ logger.error(
+ "Ambiguity found for {} in checkpoint!"
+ "It matches at least two keys in the model ({} and {}).".format(
+ key_ckpt, key_model, matched_keys[key_ckpt]
+ )
+ )
+ raise ValueError("Cannot match one checkpoint key to multiple keys in the model.")
+
+ matched_keys[key_ckpt] = key_model
+
+ # logging:
+ matched_model_keys = sorted(matched_keys.values())
+ if len(matched_model_keys) == 0:
+ logger.warning("No weights in checkpoint matched with model.")
+ return ckpt_state_dict
+ common_prefix = _longest_common_prefix(matched_model_keys)
+ rev_matched_keys = {v: k for k, v in matched_keys.items()}
+ original_keys = {k: original_keys[rev_matched_keys[k]] for k in matched_model_keys}
+
+ model_key_groups = _group_keys_by_module(matched_model_keys, original_keys)
+ table = []
+ memo = set()
+ for key_model in matched_model_keys:
+ if key_model in memo:
+ continue
+ if key_model in model_key_groups:
+ group = model_key_groups[key_model]
+ memo |= set(group)
+ shapes = [tuple(model_state_dict[k].shape) for k in group]
+ table.append(
+ (
+ _longest_common_prefix([k[len(common_prefix) :] for k in group]) + "*",
+ _group_str([original_keys[k] for k in group]),
+ " ".join([str(x).replace(" ", "") for x in shapes]),
+ )
+ )
+ else:
+ key_checkpoint = original_keys[key_model]
+ shape = str(tuple(model_state_dict[key_model].shape))
+ table.append((key_model[len(common_prefix) :], key_checkpoint, shape))
+ table_str = tabulate(
+ table, tablefmt="pipe", headers=["Names in Model", "Names in Checkpoint", "Shapes"]
+ )
+ logger.info(
+ "Following weights matched with "
+ + (f"submodule {common_prefix[:-1]}" if common_prefix else "model")
+ + ":\n"
+ + table_str
+ )
+
+ unmatched_ckpt_keys = [k for k in ckpt_keys if k not in set(matched_keys.keys())]
+ for k in unmatched_ckpt_keys:
+ result_state_dict[k] = ckpt_state_dict[k]
+ return result_state_dict
+
+
+def _group_keys_by_module(keys: List[str], original_names: Dict[str, str]):
+ """
+ Params in the same submodule are grouped together.
+
+ Args:
+ keys: names of all parameters
+ original_names: mapping from parameter name to their name in the checkpoint
+
+ Returns:
+ dict[name -> all other names in the same group]
+ """
+
+ def _submodule_name(key):
+ pos = key.rfind(".")
+ if pos < 0:
+ return None
+ prefix = key[: pos + 1]
+ return prefix
+
+ all_submodules = [_submodule_name(k) for k in keys]
+ all_submodules = [x for x in all_submodules if x]
+ all_submodules = sorted(all_submodules, key=len)
+
+ ret = {}
+ for prefix in all_submodules:
+ group = [k for k in keys if k.startswith(prefix)]
+ if len(group) <= 1:
+ continue
+ original_name_lcp = _longest_common_prefix_str([original_names[k] for k in group])
+ if len(original_name_lcp) == 0:
+ # don't group weights if original names don't share prefix
+ continue
+
+ for k in group:
+ if k in ret:
+ continue
+ ret[k] = group
+ return ret
+
+
+def _longest_common_prefix(names: List[str]) -> str:
+ """
+ ["abc.zfg", "abc.zef"] -> "abc."
+ """
+ names = [n.split(".") for n in names]
+ m1, m2 = min(names), max(names)
+ ret = [a for a, b in zip(m1, m2) if a == b]
+ ret = ".".join(ret) + "." if len(ret) else ""
+ return ret
+
+
+def _longest_common_prefix_str(names: List[str]) -> str:
+ m1, m2 = min(names), max(names)
+ lcp = []
+ for a, b in zip(m1, m2):
+ if a == b:
+ lcp.append(a)
+ else:
+ break
+ lcp = "".join(lcp)
+ return lcp
+
+
+def _group_str(names: List[str]) -> str:
+ """
+ Turn "common1", "common2", "common3" into "common{1,2,3}"
+ """
+ lcp = _longest_common_prefix_str(names)
+ rest = [x[len(lcp) :] for x in names]
+ rest = "{" + ",".join(rest) + "}"
+ ret = lcp + rest
+
+ # add some simplification for BN specifically
+ ret = ret.replace("bn_{beta,running_mean,running_var,gamma}", "bn_*")
+ ret = ret.replace("bn_beta,bn_running_mean,bn_running_var,bn_gamma", "bn_*")
+ return ret
diff --git a/detectron2/detectron2/checkpoint/catalog.py b/detectron2/detectron2/checkpoint/catalog.py
new file mode 100755
index 0000000..9a85736
--- /dev/null
+++ b/detectron2/detectron2/checkpoint/catalog.py
@@ -0,0 +1,115 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+
+from detectron2.utils.file_io import PathHandler, PathManager
+
+
+class ModelCatalog(object):
+ """
+ Store mappings from names to third-party models.
+ """
+
+ S3_C2_DETECTRON_PREFIX = "https://dl.fbaipublicfiles.com/detectron"
+
+ # MSRA models have STRIDE_IN_1X1=True. False otherwise.
+ # NOTE: all BN models here have fused BN into an affine layer.
+ # As a result, you should only load them to a model with "FrozenBN".
+ # Loading them to a model with regular BN or SyncBN is wrong.
+ # Even when loaded to FrozenBN, it is still different from affine by an epsilon,
+ # which should be negligible for training.
+ # NOTE: all models here uses PIXEL_STD=[1,1,1]
+ # NOTE: Most of the BN models here are no longer used. We use the
+ # re-converted pre-trained models under detectron2 model zoo instead.
+ C2_IMAGENET_MODELS = {
+ "MSRA/R-50": "ImageNetPretrained/MSRA/R-50.pkl",
+ "MSRA/R-101": "ImageNetPretrained/MSRA/R-101.pkl",
+ "FAIR/R-50-GN": "ImageNetPretrained/47261647/R-50-GN.pkl",
+ "FAIR/R-101-GN": "ImageNetPretrained/47592356/R-101-GN.pkl",
+ "FAIR/X-101-32x8d": "ImageNetPretrained/20171220/X-101-32x8d.pkl",
+ "FAIR/X-101-64x4d": "ImageNetPretrained/FBResNeXt/X-101-64x4d.pkl",
+ "FAIR/X-152-32x8d-IN5k": "ImageNetPretrained/25093814/X-152-32x8d-IN5k.pkl",
+ }
+
+ C2_DETECTRON_PATH_FORMAT = (
+ "{prefix}/{url}/output/train/{dataset}/{type}/model_final.pkl" # noqa B950
+ )
+
+ C2_DATASET_COCO = "coco_2014_train%3Acoco_2014_valminusminival"
+ C2_DATASET_COCO_KEYPOINTS = "keypoints_coco_2014_train%3Akeypoints_coco_2014_valminusminival"
+
+ # format: {model_name} -> part of the url
+ C2_DETECTRON_MODELS = {
+ "35857197/e2e_faster_rcnn_R-50-C4_1x": "35857197/12_2017_baselines/e2e_faster_rcnn_R-50-C4_1x.yaml.01_33_49.iAX0mXvW", # noqa B950
+ "35857345/e2e_faster_rcnn_R-50-FPN_1x": "35857345/12_2017_baselines/e2e_faster_rcnn_R-50-FPN_1x.yaml.01_36_30.cUF7QR7I", # noqa B950
+ "35857890/e2e_faster_rcnn_R-101-FPN_1x": "35857890/12_2017_baselines/e2e_faster_rcnn_R-101-FPN_1x.yaml.01_38_50.sNxI7sX7", # noqa B950
+ "36761737/e2e_faster_rcnn_X-101-32x8d-FPN_1x": "36761737/12_2017_baselines/e2e_faster_rcnn_X-101-32x8d-FPN_1x.yaml.06_31_39.5MIHi1fZ", # noqa B950
+ "35858791/e2e_mask_rcnn_R-50-C4_1x": "35858791/12_2017_baselines/e2e_mask_rcnn_R-50-C4_1x.yaml.01_45_57.ZgkA7hPB", # noqa B950
+ "35858933/e2e_mask_rcnn_R-50-FPN_1x": "35858933/12_2017_baselines/e2e_mask_rcnn_R-50-FPN_1x.yaml.01_48_14.DzEQe4wC", # noqa B950
+ "35861795/e2e_mask_rcnn_R-101-FPN_1x": "35861795/12_2017_baselines/e2e_mask_rcnn_R-101-FPN_1x.yaml.02_31_37.KqyEK4tT", # noqa B950
+ "36761843/e2e_mask_rcnn_X-101-32x8d-FPN_1x": "36761843/12_2017_baselines/e2e_mask_rcnn_X-101-32x8d-FPN_1x.yaml.06_35_59.RZotkLKI", # noqa B950
+ "48616381/e2e_mask_rcnn_R-50-FPN_2x_gn": "GN/48616381/04_2018_gn_baselines/e2e_mask_rcnn_R-50-FPN_2x_gn_0416.13_23_38.bTlTI97Q", # noqa B950
+ "37697547/e2e_keypoint_rcnn_R-50-FPN_1x": "37697547/12_2017_baselines/e2e_keypoint_rcnn_R-50-FPN_1x.yaml.08_42_54.kdzV35ao", # noqa B950
+ "35998355/rpn_R-50-C4_1x": "35998355/12_2017_baselines/rpn_R-50-C4_1x.yaml.08_00_43.njH5oD9L", # noqa B950
+ "35998814/rpn_R-50-FPN_1x": "35998814/12_2017_baselines/rpn_R-50-FPN_1x.yaml.08_06_03.Axg0r179", # noqa B950
+ "36225147/fast_R-50-FPN_1x": "36225147/12_2017_baselines/fast_rcnn_R-50-FPN_1x.yaml.08_39_09.L3obSdQ2", # noqa B950
+ }
+
+ @staticmethod
+ def get(name):
+ if name.startswith("Caffe2Detectron/COCO"):
+ return ModelCatalog._get_c2_detectron_baseline(name)
+ if name.startswith("ImageNetPretrained/"):
+ return ModelCatalog._get_c2_imagenet_pretrained(name)
+ raise RuntimeError("model not present in the catalog: {}".format(name))
+
+ @staticmethod
+ def _get_c2_imagenet_pretrained(name):
+ prefix = ModelCatalog.S3_C2_DETECTRON_PREFIX
+ name = name[len("ImageNetPretrained/") :]
+ name = ModelCatalog.C2_IMAGENET_MODELS[name]
+ url = "/".join([prefix, name])
+ return url
+
+ @staticmethod
+ def _get_c2_detectron_baseline(name):
+ name = name[len("Caffe2Detectron/COCO/") :]
+ url = ModelCatalog.C2_DETECTRON_MODELS[name]
+ if "keypoint_rcnn" in name:
+ dataset = ModelCatalog.C2_DATASET_COCO_KEYPOINTS
+ else:
+ dataset = ModelCatalog.C2_DATASET_COCO
+
+ if "35998355/rpn_R-50-C4_1x" in name:
+ # this one model is somehow different from others ..
+ type = "rpn"
+ else:
+ type = "generalized_rcnn"
+
+ # Detectron C2 models are stored in the structure defined in `C2_DETECTRON_PATH_FORMAT`.
+ url = ModelCatalog.C2_DETECTRON_PATH_FORMAT.format(
+ prefix=ModelCatalog.S3_C2_DETECTRON_PREFIX, url=url, type=type, dataset=dataset
+ )
+ return url
+
+
+class ModelCatalogHandler(PathHandler):
+ """
+ Resolve URL like catalog://.
+ """
+
+ PREFIX = "catalog://"
+
+ def _get_supported_prefixes(self):
+ return [self.PREFIX]
+
+ def _get_local_path(self, path, **kwargs):
+ logger = logging.getLogger(__name__)
+ catalog_path = ModelCatalog.get(path[len(self.PREFIX) :])
+ logger.info("Catalog entry {} points to {}".format(path, catalog_path))
+ return PathManager.get_local_path(catalog_path, **kwargs)
+
+ def _open(self, path, mode="r", **kwargs):
+ return PathManager.open(self._get_local_path(path), mode, **kwargs)
+
+
+PathManager.register_handler(ModelCatalogHandler())
diff --git a/detectron2/detectron2/checkpoint/detection_checkpoint.py b/detectron2/detectron2/checkpoint/detection_checkpoint.py
new file mode 100755
index 0000000..cecb1fc
--- /dev/null
+++ b/detectron2/detectron2/checkpoint/detection_checkpoint.py
@@ -0,0 +1,143 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import os
+import pickle
+from urllib.parse import parse_qs, urlparse
+import torch
+from fvcore.common.checkpoint import Checkpointer
+from torch.nn.parallel import DistributedDataParallel
+
+import detectron2.utils.comm as comm
+from detectron2.utils.file_io import PathManager
+
+from .c2_model_loading import align_and_update_state_dicts
+
+
+class DetectionCheckpointer(Checkpointer):
+ """
+ Same as :class:`Checkpointer`, but is able to:
+ 1. handle models in detectron & detectron2 model zoo, and apply conversions for legacy models.
+ 2. correctly load checkpoints that are only available on the master worker
+ """
+
+ def __init__(self, model, save_dir="", *, save_to_disk=None, **checkpointables):
+ is_main_process = comm.is_main_process()
+ super().__init__(
+ model,
+ save_dir,
+ save_to_disk=is_main_process if save_to_disk is None else save_to_disk,
+ **checkpointables,
+ )
+ self.path_manager = PathManager
+ self._parsed_url_during_load = None
+
+ def load(self, path, *args, **kwargs):
+ assert self._parsed_url_during_load is None
+ need_sync = False
+ logger = logging.getLogger(__name__)
+ logger.info("[DetectionCheckpointer] Loading from {} ...".format(path))
+
+ if path and isinstance(self.model, DistributedDataParallel):
+ path = self.path_manager.get_local_path(path)
+ has_file = os.path.isfile(path)
+ all_has_file = comm.all_gather(has_file)
+ if not all_has_file[0]:
+ raise OSError(f"File {path} not found on main worker.")
+ if not all(all_has_file):
+ logger.warning(
+ f"Not all workers can read checkpoint {path}. "
+ "Training may fail to fully resume."
+ )
+ # TODO: broadcast the checkpoint file contents from main
+ # worker, and load from it instead.
+ need_sync = True
+ if not has_file:
+ path = None # don't load if not readable
+
+ if path:
+ parsed_url = urlparse(path)
+ self._parsed_url_during_load = parsed_url
+ path = parsed_url._replace(query="").geturl() # remove query from filename
+ path = self.path_manager.get_local_path(path)
+ ret = super().load(path, *args, **kwargs)
+
+ if need_sync:
+ logger.info("Broadcasting model states from main worker ...")
+ self.model._sync_params_and_buffers()
+ self._parsed_url_during_load = None # reset to None
+ return ret
+
+ def _load_file(self, filename):
+ if filename.endswith(".pkl"):
+ with PathManager.open(filename, "rb") as f:
+ data = pickle.load(f, encoding="latin1")
+ if "model" in data and "__author__" in data:
+ # file is in Detectron2 model zoo format
+ self.logger.info("Reading a file from '{}'".format(data["__author__"]))
+ return data
+ else:
+ # assume file is from Caffe2 / Detectron1 model zoo
+ if "blobs" in data:
+ # Detection models have "blobs", but ImageNet models don't
+ data = data["blobs"]
+ data = {k: v for k, v in data.items() if not k.endswith("_momentum")}
+ return {"model": data, "__author__": "Caffe2", "matching_heuristics": True}
+ elif filename.endswith(".pyth"):
+ # assume file is from pycls; no one else seems to use the ".pyth" extension
+ with PathManager.open(filename, "rb") as f:
+ data = torch.load(f)
+ assert (
+ "model_state" in data
+ ), f"Cannot load .pyth file {filename}; pycls checkpoints must contain 'model_state'."
+ model_state = {
+ k: v
+ for k, v in data["model_state"].items()
+ if not k.endswith("num_batches_tracked")
+ }
+ return {"model": model_state, "__author__": "pycls", "matching_heuristics": True}
+
+ loaded = self._torch_load(filename)
+ if "model" not in loaded:
+ loaded = {"model": loaded}
+ assert self._parsed_url_during_load is not None, "`_load_file` must be called inside `load`"
+ parsed_url = self._parsed_url_during_load
+ queries = parse_qs(parsed_url.query)
+ if queries.pop("matching_heuristics", "False") == ["True"]:
+ loaded["matching_heuristics"] = True
+ if len(queries) > 0:
+ raise ValueError(
+ f"Unsupported query remaining: f{queries}, orginal filename: {parsed_url.geturl()}"
+ )
+ return loaded
+
+ def _torch_load(self, f):
+ return super()._load_file(f)
+
+ def _load_model(self, checkpoint):
+ if checkpoint.get("matching_heuristics", False):
+ self._convert_ndarray_to_tensor(checkpoint["model"])
+ # convert weights by name-matching heuristics
+ checkpoint["model"] = align_and_update_state_dicts(
+ self.model.state_dict(),
+ checkpoint["model"],
+ c2_conversion=checkpoint.get("__author__", None) == "Caffe2",
+ )
+ # for non-caffe2 models, use standard ways to load it
+ incompatible = super()._load_model(checkpoint)
+
+ model_buffers = dict(self.model.named_buffers(recurse=False))
+ for k in ["pixel_mean", "pixel_std"]:
+ # Ignore missing key message about pixel_mean/std.
+ # Though they may be missing in old checkpoints, they will be correctly
+ # initialized from config anyway.
+ if k in model_buffers:
+ try:
+ incompatible.missing_keys.remove(k)
+ except ValueError:
+ pass
+ for k in incompatible.unexpected_keys[:]:
+ # Ignore unexpected keys about cell anchors. They exist in old checkpoints
+ # but now they are non-persistent buffers and will not be in new checkpoints.
+ if "anchor_generator.cell_anchors" in k:
+ incompatible.unexpected_keys.remove(k)
+ return incompatible
diff --git a/detectron2/detectron2/config/__init__.py b/detectron2/detectron2/config/__init__.py
new file mode 100755
index 0000000..4e648e6
--- /dev/null
+++ b/detectron2/detectron2/config/__init__.py
@@ -0,0 +1,24 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .compat import downgrade_config, upgrade_config
+from .config import CfgNode, get_cfg, global_cfg, set_global_cfg, configurable
+from .instantiate import instantiate
+from .lazy import LazyCall, LazyConfig
+
+__all__ = [
+ "CfgNode",
+ "get_cfg",
+ "global_cfg",
+ "set_global_cfg",
+ "downgrade_config",
+ "upgrade_config",
+ "configurable",
+ "instantiate",
+ "LazyCall",
+ "LazyConfig",
+]
+
+
+from detectron2.utils.env import fixup_module_metadata
+
+fixup_module_metadata(__name__, globals(), __all__)
+del fixup_module_metadata
diff --git a/detectron2/detectron2/config/compat.py b/detectron2/detectron2/config/compat.py
new file mode 100755
index 0000000..11a08c4
--- /dev/null
+++ b/detectron2/detectron2/config/compat.py
@@ -0,0 +1,229 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+"""
+Backward compatibility of configs.
+
+Instructions to bump version:
++ It's not needed to bump version if new keys are added.
+ It's only needed when backward-incompatible changes happen
+ (i.e., some existing keys disappear, or the meaning of a key changes)
++ To bump version, do the following:
+ 1. Increment _C.VERSION in defaults.py
+ 2. Add a converter in this file.
+
+ Each ConverterVX has a function "upgrade" which in-place upgrades config from X-1 to X,
+ and a function "downgrade" which in-place downgrades config from X to X-1
+
+ In each function, VERSION is left unchanged.
+
+ Each converter assumes that its input has the relevant keys
+ (i.e., the input is not a partial config).
+ 3. Run the tests (test_config.py) to make sure the upgrade & downgrade
+ functions are consistent.
+"""
+
+import logging
+from typing import List, Optional, Tuple
+
+from .config import CfgNode as CN
+from .defaults import _C
+
+__all__ = ["upgrade_config", "downgrade_config"]
+
+
+def upgrade_config(cfg: CN, to_version: Optional[int] = None) -> CN:
+ """
+ Upgrade a config from its current version to a newer version.
+
+ Args:
+ cfg (CfgNode):
+ to_version (int): defaults to the latest version.
+ """
+ cfg = cfg.clone()
+ if to_version is None:
+ to_version = _C.VERSION
+
+ assert cfg.VERSION <= to_version, "Cannot upgrade from v{} to v{}!".format(
+ cfg.VERSION, to_version
+ )
+ for k in range(cfg.VERSION, to_version):
+ converter = globals()["ConverterV" + str(k + 1)]
+ converter.upgrade(cfg)
+ cfg.VERSION = k + 1
+ return cfg
+
+
+def downgrade_config(cfg: CN, to_version: int) -> CN:
+ """
+ Downgrade a config from its current version to an older version.
+
+ Args:
+ cfg (CfgNode):
+ to_version (int):
+
+ Note:
+ A general downgrade of arbitrary configs is not always possible due to the
+ different functionalities in different versions.
+ The purpose of downgrade is only to recover the defaults in old versions,
+ allowing it to load an old partial yaml config.
+ Therefore, the implementation only needs to fill in the default values
+ in the old version when a general downgrade is not possible.
+ """
+ cfg = cfg.clone()
+ assert cfg.VERSION >= to_version, "Cannot downgrade from v{} to v{}!".format(
+ cfg.VERSION, to_version
+ )
+ for k in range(cfg.VERSION, to_version, -1):
+ converter = globals()["ConverterV" + str(k)]
+ converter.downgrade(cfg)
+ cfg.VERSION = k - 1
+ return cfg
+
+
+def guess_version(cfg: CN, filename: str) -> int:
+ """
+ Guess the version of a partial config where the VERSION field is not specified.
+ Returns the version, or the latest if cannot make a guess.
+
+ This makes it easier for users to migrate.
+ """
+ logger = logging.getLogger(__name__)
+
+ def _has(name: str) -> bool:
+ cur = cfg
+ for n in name.split("."):
+ if n not in cur:
+ return False
+ cur = cur[n]
+ return True
+
+ # Most users' partial configs have "MODEL.WEIGHT", so guess on it
+ ret = None
+ if _has("MODEL.WEIGHT") or _has("TEST.AUG_ON"):
+ ret = 1
+
+ if ret is not None:
+ logger.warning("Config '{}' has no VERSION. Assuming it to be v{}.".format(filename, ret))
+ else:
+ ret = _C.VERSION
+ logger.warning(
+ "Config '{}' has no VERSION. Assuming it to be compatible with latest v{}.".format(
+ filename, ret
+ )
+ )
+ return ret
+
+
+def _rename(cfg: CN, old: str, new: str) -> None:
+ old_keys = old.split(".")
+ new_keys = new.split(".")
+
+ def _set(key_seq: List[str], val: str) -> None:
+ cur = cfg
+ for k in key_seq[:-1]:
+ if k not in cur:
+ cur[k] = CN()
+ cur = cur[k]
+ cur[key_seq[-1]] = val
+
+ def _get(key_seq: List[str]) -> CN:
+ cur = cfg
+ for k in key_seq:
+ cur = cur[k]
+ return cur
+
+ def _del(key_seq: List[str]) -> None:
+ cur = cfg
+ for k in key_seq[:-1]:
+ cur = cur[k]
+ del cur[key_seq[-1]]
+ if len(cur) == 0 and len(key_seq) > 1:
+ _del(key_seq[:-1])
+
+ _set(new_keys, _get(old_keys))
+ _del(old_keys)
+
+
+class _RenameConverter:
+ """
+ A converter that handles simple rename.
+ """
+
+ RENAME: List[Tuple[str, str]] = [] # list of tuples of (old name, new name)
+
+ @classmethod
+ def upgrade(cls, cfg: CN) -> None:
+ for old, new in cls.RENAME:
+ _rename(cfg, old, new)
+
+ @classmethod
+ def downgrade(cls, cfg: CN) -> None:
+ for old, new in cls.RENAME[::-1]:
+ _rename(cfg, new, old)
+
+
+class ConverterV1(_RenameConverter):
+ RENAME = [("MODEL.RPN_HEAD.NAME", "MODEL.RPN.HEAD_NAME")]
+
+
+class ConverterV2(_RenameConverter):
+ """
+ A large bulk of rename, before public release.
+ """
+
+ RENAME = [
+ ("MODEL.WEIGHT", "MODEL.WEIGHTS"),
+ ("MODEL.PANOPTIC_FPN.SEMANTIC_LOSS_SCALE", "MODEL.SEM_SEG_HEAD.LOSS_WEIGHT"),
+ ("MODEL.PANOPTIC_FPN.RPN_LOSS_SCALE", "MODEL.RPN.LOSS_WEIGHT"),
+ ("MODEL.PANOPTIC_FPN.INSTANCE_LOSS_SCALE", "MODEL.PANOPTIC_FPN.INSTANCE_LOSS_WEIGHT"),
+ ("MODEL.PANOPTIC_FPN.COMBINE_ON", "MODEL.PANOPTIC_FPN.COMBINE.ENABLED"),
+ (
+ "MODEL.PANOPTIC_FPN.COMBINE_OVERLAP_THRESHOLD",
+ "MODEL.PANOPTIC_FPN.COMBINE.OVERLAP_THRESH",
+ ),
+ (
+ "MODEL.PANOPTIC_FPN.COMBINE_STUFF_AREA_LIMIT",
+ "MODEL.PANOPTIC_FPN.COMBINE.STUFF_AREA_LIMIT",
+ ),
+ (
+ "MODEL.PANOPTIC_FPN.COMBINE_INSTANCES_CONFIDENCE_THRESHOLD",
+ "MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH",
+ ),
+ ("MODEL.ROI_HEADS.SCORE_THRESH", "MODEL.ROI_HEADS.SCORE_THRESH_TEST"),
+ ("MODEL.ROI_HEADS.NMS", "MODEL.ROI_HEADS.NMS_THRESH_TEST"),
+ ("MODEL.RETINANET.INFERENCE_SCORE_THRESHOLD", "MODEL.RETINANET.SCORE_THRESH_TEST"),
+ ("MODEL.RETINANET.INFERENCE_TOPK_CANDIDATES", "MODEL.RETINANET.TOPK_CANDIDATES_TEST"),
+ ("MODEL.RETINANET.INFERENCE_NMS_THRESHOLD", "MODEL.RETINANET.NMS_THRESH_TEST"),
+ ("TEST.DETECTIONS_PER_IMG", "TEST.DETECTIONS_PER_IMAGE"),
+ ("TEST.AUG_ON", "TEST.AUG.ENABLED"),
+ ("TEST.AUG_MIN_SIZES", "TEST.AUG.MIN_SIZES"),
+ ("TEST.AUG_MAX_SIZE", "TEST.AUG.MAX_SIZE"),
+ ("TEST.AUG_FLIP", "TEST.AUG.FLIP"),
+ ]
+
+ @classmethod
+ def upgrade(cls, cfg: CN) -> None:
+ super().upgrade(cfg)
+
+ if cfg.MODEL.META_ARCHITECTURE == "RetinaNet":
+ _rename(
+ cfg, "MODEL.RETINANET.ANCHOR_ASPECT_RATIOS", "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS"
+ )
+ _rename(cfg, "MODEL.RETINANET.ANCHOR_SIZES", "MODEL.ANCHOR_GENERATOR.SIZES")
+ del cfg["MODEL"]["RPN"]["ANCHOR_SIZES"]
+ del cfg["MODEL"]["RPN"]["ANCHOR_ASPECT_RATIOS"]
+ else:
+ _rename(cfg, "MODEL.RPN.ANCHOR_ASPECT_RATIOS", "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS")
+ _rename(cfg, "MODEL.RPN.ANCHOR_SIZES", "MODEL.ANCHOR_GENERATOR.SIZES")
+ del cfg["MODEL"]["RETINANET"]["ANCHOR_SIZES"]
+ del cfg["MODEL"]["RETINANET"]["ANCHOR_ASPECT_RATIOS"]
+ del cfg["MODEL"]["RETINANET"]["ANCHOR_STRIDES"]
+
+ @classmethod
+ def downgrade(cls, cfg: CN) -> None:
+ super().downgrade(cfg)
+
+ _rename(cfg, "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS", "MODEL.RPN.ANCHOR_ASPECT_RATIOS")
+ _rename(cfg, "MODEL.ANCHOR_GENERATOR.SIZES", "MODEL.RPN.ANCHOR_SIZES")
+ cfg.MODEL.RETINANET.ANCHOR_ASPECT_RATIOS = cfg.MODEL.RPN.ANCHOR_ASPECT_RATIOS
+ cfg.MODEL.RETINANET.ANCHOR_SIZES = cfg.MODEL.RPN.ANCHOR_SIZES
+ cfg.MODEL.RETINANET.ANCHOR_STRIDES = [] # this is not used anywhere in any version
diff --git a/detectron2/detectron2/config/config.py b/detectron2/detectron2/config/config.py
new file mode 100755
index 0000000..49a55b1
--- /dev/null
+++ b/detectron2/detectron2/config/config.py
@@ -0,0 +1,265 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import functools
+import inspect
+import logging
+from fvcore.common.config import CfgNode as _CfgNode
+
+from detectron2.utils.file_io import PathManager
+
+
+class CfgNode(_CfgNode):
+ """
+ The same as `fvcore.common.config.CfgNode`, but different in:
+
+ 1. Use unsafe yaml loading by default.
+ Note that this may lead to arbitrary code execution: you must not
+ load a config file from untrusted sources before manually inspecting
+ the content of the file.
+ 2. Support config versioning.
+ When attempting to merge an old config, it will convert the old config automatically.
+
+ .. automethod:: clone
+ .. automethod:: freeze
+ .. automethod:: defrost
+ .. automethod:: is_frozen
+ .. automethod:: load_yaml_with_base
+ .. automethod:: merge_from_list
+ .. automethod:: merge_from_other_cfg
+ """
+
+ @classmethod
+ def _open_cfg(cls, filename):
+ return PathManager.open(filename, "r")
+
+ # Note that the default value of allow_unsafe is changed to True
+ def merge_from_file(self, cfg_filename: str, allow_unsafe: bool = True) -> None:
+ """
+ Load content from the given config file and merge it into self.
+
+ Args:
+ cfg_filename: config filename
+ allow_unsafe: allow unsafe yaml syntax
+ """
+ assert PathManager.isfile(cfg_filename), f"Config file '{cfg_filename}' does not exist!"
+ loaded_cfg = self.load_yaml_with_base(cfg_filename, allow_unsafe=allow_unsafe)
+ loaded_cfg = type(self)(loaded_cfg)
+
+ # defaults.py needs to import CfgNode
+ from .defaults import _C
+
+ latest_ver = _C.VERSION
+ assert (
+ latest_ver == self.VERSION
+ ), "CfgNode.merge_from_file is only allowed on a config object of latest version!"
+
+ logger = logging.getLogger(__name__)
+
+ loaded_ver = loaded_cfg.get("VERSION", None)
+ if loaded_ver is None:
+ from .compat import guess_version
+
+ loaded_ver = guess_version(loaded_cfg, cfg_filename)
+ assert loaded_ver <= self.VERSION, "Cannot merge a v{} config into a v{} config.".format(
+ loaded_ver, self.VERSION
+ )
+
+ if loaded_ver == self.VERSION:
+ self.merge_from_other_cfg(loaded_cfg)
+ else:
+ # compat.py needs to import CfgNode
+ from .compat import upgrade_config, downgrade_config
+
+ logger.warning(
+ "Loading an old v{} config file '{}' by automatically upgrading to v{}. "
+ "See docs/CHANGELOG.md for instructions to update your files.".format(
+ loaded_ver, cfg_filename, self.VERSION
+ )
+ )
+ # To convert, first obtain a full config at an old version
+ old_self = downgrade_config(self, to_version=loaded_ver)
+ old_self.merge_from_other_cfg(loaded_cfg)
+ new_config = upgrade_config(old_self)
+ self.clear()
+ self.update(new_config)
+
+ def dump(self, *args, **kwargs):
+ """
+ Returns:
+ str: a yaml string representation of the config
+ """
+ # to make it show up in docs
+ return super().dump(*args, **kwargs)
+
+
+global_cfg = CfgNode()
+
+
+def get_cfg() -> CfgNode:
+ """
+ Get a copy of the default config.
+
+ Returns:
+ a detectron2 CfgNode instance.
+ """
+ from .defaults import _C
+
+ return _C.clone()
+
+
+def set_global_cfg(cfg: CfgNode) -> None:
+ """
+ Let the global config point to the given cfg.
+
+ Assume that the given "cfg" has the key "KEY", after calling
+ `set_global_cfg(cfg)`, the key can be accessed by:
+ ::
+ from detectron2.config import global_cfg
+ print(global_cfg.KEY)
+
+ By using a hacky global config, you can access these configs anywhere,
+ without having to pass the config object or the values deep into the code.
+ This is a hacky feature introduced for quick prototyping / research exploration.
+ """
+ global global_cfg
+ global_cfg.clear()
+ global_cfg.update(cfg)
+
+
+def configurable(init_func=None, *, from_config=None):
+ """
+ Decorate a function or a class's __init__ method so that it can be called
+ with a :class:`CfgNode` object using a :func:`from_config` function that translates
+ :class:`CfgNode` to arguments.
+
+ Examples:
+ ::
+ # Usage 1: Decorator on __init__:
+ class A:
+ @configurable
+ def __init__(self, a, b=2, c=3):
+ pass
+
+ @classmethod
+ def from_config(cls, cfg): # 'cfg' must be the first argument
+ # Returns kwargs to be passed to __init__
+ return {"a": cfg.A, "b": cfg.B}
+
+ a1 = A(a=1, b=2) # regular construction
+ a2 = A(cfg) # construct with a cfg
+ a3 = A(cfg, b=3, c=4) # construct with extra overwrite
+
+ # Usage 2: Decorator on any function. Needs an extra from_config argument:
+ @configurable(from_config=lambda cfg: {"a: cfg.A, "b": cfg.B})
+ def a_func(a, b=2, c=3):
+ pass
+
+ a1 = a_func(a=1, b=2) # regular call
+ a2 = a_func(cfg) # call with a cfg
+ a3 = a_func(cfg, b=3, c=4) # call with extra overwrite
+
+ Args:
+ init_func (callable): a class's ``__init__`` method in usage 1. The
+ class must have a ``from_config`` classmethod which takes `cfg` as
+ the first argument.
+ from_config (callable): the from_config function in usage 2. It must take `cfg`
+ as its first argument.
+ """
+
+ if init_func is not None:
+ assert (
+ inspect.isfunction(init_func)
+ and from_config is None
+ and init_func.__name__ == "__init__"
+ ), "Incorrect use of @configurable. Check API documentation for examples."
+
+ @functools.wraps(init_func)
+ def wrapped(self, *args, **kwargs):
+ try:
+ from_config_func = type(self).from_config
+ except AttributeError as e:
+ raise AttributeError(
+ "Class with @configurable must have a 'from_config' classmethod."
+ ) from e
+ if not inspect.ismethod(from_config_func):
+ raise TypeError("Class with @configurable must have a 'from_config' classmethod.")
+
+ if _called_with_cfg(*args, **kwargs):
+ explicit_args = _get_args_from_config(from_config_func, *args, **kwargs)
+ init_func(self, **explicit_args)
+ else:
+ init_func(self, *args, **kwargs)
+
+ return wrapped
+
+ else:
+ if from_config is None:
+ return configurable # @configurable() is made equivalent to @configurable
+ assert inspect.isfunction(
+ from_config
+ ), "from_config argument of configurable must be a function!"
+
+ def wrapper(orig_func):
+ @functools.wraps(orig_func)
+ def wrapped(*args, **kwargs):
+ if _called_with_cfg(*args, **kwargs):
+ explicit_args = _get_args_from_config(from_config, *args, **kwargs)
+ return orig_func(**explicit_args)
+ else:
+ return orig_func(*args, **kwargs)
+
+ wrapped.from_config = from_config
+ return wrapped
+
+ return wrapper
+
+
+def _get_args_from_config(from_config_func, *args, **kwargs):
+ """
+ Use `from_config` to obtain explicit arguments.
+
+ Returns:
+ dict: arguments to be used for cls.__init__
+ """
+ signature = inspect.signature(from_config_func)
+ if list(signature.parameters.keys())[0] != "cfg":
+ if inspect.isfunction(from_config_func):
+ name = from_config_func.__name__
+ else:
+ name = f"{from_config_func.__self__}.from_config"
+ raise TypeError(f"{name} must take 'cfg' as the first argument!")
+ support_var_arg = any(
+ param.kind in [param.VAR_POSITIONAL, param.VAR_KEYWORD]
+ for param in signature.parameters.values()
+ )
+ if support_var_arg: # forward all arguments to from_config, if from_config accepts them
+ ret = from_config_func(*args, **kwargs)
+ else:
+ # forward supported arguments to from_config
+ supported_arg_names = set(signature.parameters.keys())
+ extra_kwargs = {}
+ for name in list(kwargs.keys()):
+ if name not in supported_arg_names:
+ extra_kwargs[name] = kwargs.pop(name)
+ ret = from_config_func(*args, **kwargs)
+ # forward the other arguments to __init__
+ ret.update(extra_kwargs)
+ return ret
+
+
+def _called_with_cfg(*args, **kwargs):
+ """
+ Returns:
+ bool: whether the arguments contain CfgNode and should be considered
+ forwarded to from_config.
+ """
+ from omegaconf import DictConfig
+
+ if len(args) and isinstance(args[0], (_CfgNode, DictConfig)):
+ return True
+ if isinstance(kwargs.pop("cfg", None), (_CfgNode, DictConfig)):
+ return True
+ # `from_config`'s first argument is forced to be "cfg".
+ # So the above check covers all cases.
+ return False
diff --git a/detectron2/detectron2/config/defaults.py b/detectron2/detectron2/config/defaults.py
new file mode 100755
index 0000000..bd2a5f6
--- /dev/null
+++ b/detectron2/detectron2/config/defaults.py
@@ -0,0 +1,650 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .config import CfgNode as CN
+
+# NOTE: given the new config system
+# (https://detectron2.readthedocs.io/en/latest/tutorials/lazyconfigs.html),
+# we will stop adding new functionalities to default CfgNode.
+
+# -----------------------------------------------------------------------------
+# Convention about Training / Test specific parameters
+# -----------------------------------------------------------------------------
+# Whenever an argument can be either used for training or for testing, the
+# corresponding name will be post-fixed by a _TRAIN for a training parameter,
+# or _TEST for a test-specific parameter.
+# For example, the number of images during training will be
+# IMAGES_PER_BATCH_TRAIN, while the number of images for testing will be
+# IMAGES_PER_BATCH_TEST
+
+# -----------------------------------------------------------------------------
+# Config definition
+# -----------------------------------------------------------------------------
+
+_C = CN()
+
+# The version number, to upgrade from old configs to new ones if any
+# changes happen. It's recommended to keep a VERSION in your config file.
+_C.VERSION = 2
+
+_C.MODEL = CN()
+_C.MODEL.LOAD_PROPOSALS = False
+_C.MODEL.MASK_ON = False
+_C.MODEL.KEYPOINT_ON = False
+_C.MODEL.DEVICE = "cuda"
+_C.MODEL.META_ARCHITECTURE = "GeneralizedRCNN"
+
+# Path (a file path, or URL like detectron2://.., https://..) to a checkpoint file
+# to be loaded to the model. You can find available models in the model zoo.
+_C.MODEL.WEIGHTS = ""
+
+# Values to be used for image normalization (BGR order, since INPUT.FORMAT defaults to BGR).
+# To train on images of different number of channels, just set different mean & std.
+# Default values are the mean pixel value from ImageNet: [103.53, 116.28, 123.675]
+_C.MODEL.PIXEL_MEAN = [103.530, 116.280, 123.675]
+# When using pre-trained models in Detectron1 or any MSRA models,
+# std has been absorbed into its conv1 weights, so the std needs to be set 1.
+# Otherwise, you can use [57.375, 57.120, 58.395] (ImageNet std)
+_C.MODEL.PIXEL_STD = [1.0, 1.0, 1.0]
+
+
+# -----------------------------------------------------------------------------
+# INPUT
+# -----------------------------------------------------------------------------
+_C.INPUT = CN()
+# By default, {MIN,MAX}_SIZE options are used in transforms.ResizeShortestEdge.
+# Please refer to ResizeShortestEdge for detailed definition.
+# Size of the smallest side of the image during training
+_C.INPUT.MIN_SIZE_TRAIN = (800,)
+# Sample size of smallest side by choice or random selection from range give by
+# INPUT.MIN_SIZE_TRAIN
+_C.INPUT.MIN_SIZE_TRAIN_SAMPLING = "choice"
+# Maximum size of the side of the image during training
+_C.INPUT.MAX_SIZE_TRAIN = 1333
+# Size of the smallest side of the image during testing. Set to zero to disable resize in testing.
+_C.INPUT.MIN_SIZE_TEST = 800
+# Maximum size of the side of the image during testing
+_C.INPUT.MAX_SIZE_TEST = 1333
+# Mode for flipping images used in data augmentation during training
+# choose one of ["horizontal, "vertical", "none"]
+_C.INPUT.RANDOM_FLIP = "horizontal"
+
+# `True` if cropping is used for data augmentation during training
+_C.INPUT.CROP = CN({"ENABLED": False})
+# Cropping type. See documentation of `detectron2.data.transforms.RandomCrop` for explanation.
+_C.INPUT.CROP.TYPE = "relative_range"
+# Size of crop in range (0, 1] if CROP.TYPE is "relative" or "relative_range" and in number of
+# pixels if CROP.TYPE is "absolute"
+_C.INPUT.CROP.SIZE = [0.9, 0.9]
+
+
+# Whether the model needs RGB, YUV, HSV etc.
+# Should be one of the modes defined here, as we use PIL to read the image:
+# https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes
+# with BGR being the one exception. One can set image format to BGR, we will
+# internally use RGB for conversion and flip the channels over
+_C.INPUT.FORMAT = "BGR"
+# The ground truth mask format that the model will use.
+# Mask R-CNN supports either "polygon" or "bitmask" as ground truth.
+_C.INPUT.MASK_FORMAT = "polygon" # alternative: "bitmask"
+
+
+# -----------------------------------------------------------------------------
+# Dataset
+# -----------------------------------------------------------------------------
+_C.DATASETS = CN()
+# List of the dataset names for training. Must be registered in DatasetCatalog
+# Samples from these datasets will be merged and used as one dataset.
+_C.DATASETS.TRAIN = ()
+# List of the pre-computed proposal files for training, which must be consistent
+# with datasets listed in DATASETS.TRAIN.
+_C.DATASETS.PROPOSAL_FILES_TRAIN = ()
+# Number of top scoring precomputed proposals to keep for training
+_C.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TRAIN = 2000
+# List of the dataset names for testing. Must be registered in DatasetCatalog
+_C.DATASETS.TEST = ()
+# List of the pre-computed proposal files for test, which must be consistent
+# with datasets listed in DATASETS.TEST.
+_C.DATASETS.PROPOSAL_FILES_TEST = ()
+# Number of top scoring precomputed proposals to keep for test
+_C.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TEST = 1000
+
+# -----------------------------------------------------------------------------
+# DataLoader
+# -----------------------------------------------------------------------------
+_C.DATALOADER = CN()
+# Number of data loading threads
+_C.DATALOADER.NUM_WORKERS = 4
+# If True, each batch should contain only images for which the aspect ratio
+# is compatible. This groups portrait images together, and landscape images
+# are not batched with portrait images.
+_C.DATALOADER.ASPECT_RATIO_GROUPING = True
+# Options: TrainingSampler, RepeatFactorTrainingSampler
+_C.DATALOADER.SAMPLER_TRAIN = "TrainingSampler"
+# Repeat threshold for RepeatFactorTrainingSampler
+_C.DATALOADER.REPEAT_THRESHOLD = 0.0
+# Tf True, when working on datasets that have instance annotations, the
+# training dataloader will filter out images without associated annotations
+_C.DATALOADER.FILTER_EMPTY_ANNOTATIONS = True
+
+# ---------------------------------------------------------------------------- #
+# Backbone options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.BACKBONE = CN()
+
+_C.MODEL.BACKBONE.NAME = "build_resnet_backbone"
+# Freeze the first several stages so they are not trained.
+# There are 5 stages in ResNet. The first is a convolution, and the following
+# stages are each group of residual blocks.
+_C.MODEL.BACKBONE.FREEZE_AT = 2
+
+
+# ---------------------------------------------------------------------------- #
+# FPN options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.FPN = CN()
+# Names of the input feature maps to be used by FPN
+# They must have contiguous power of 2 strides
+# e.g., ["res2", "res3", "res4", "res5"]
+_C.MODEL.FPN.IN_FEATURES = []
+_C.MODEL.FPN.OUT_CHANNELS = 256
+
+# Options: "" (no norm), "GN"
+_C.MODEL.FPN.NORM = ""
+
+# Types for fusing the FPN top-down and lateral features. Can be either "sum" or "avg"
+_C.MODEL.FPN.FUSE_TYPE = "sum"
+
+
+# ---------------------------------------------------------------------------- #
+# Proposal generator options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.PROPOSAL_GENERATOR = CN()
+# Current proposal generators include "RPN", "RRPN" and "PrecomputedProposals"
+_C.MODEL.PROPOSAL_GENERATOR.NAME = "RPN"
+# Proposal height and width both need to be greater than MIN_SIZE
+# (a the scale used during training or inference)
+_C.MODEL.PROPOSAL_GENERATOR.MIN_SIZE = 0
+
+
+# ---------------------------------------------------------------------------- #
+# Anchor generator options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ANCHOR_GENERATOR = CN()
+# The generator can be any name in the ANCHOR_GENERATOR registry
+_C.MODEL.ANCHOR_GENERATOR.NAME = "DefaultAnchorGenerator"
+# Anchor sizes (i.e. sqrt of area) in absolute pixels w.r.t. the network input.
+# Format: list[list[float]]. SIZES[i] specifies the list of sizes to use for
+# IN_FEATURES[i]; len(SIZES) must be equal to len(IN_FEATURES) or 1.
+# When len(SIZES) == 1, SIZES[0] is used for all IN_FEATURES.
+_C.MODEL.ANCHOR_GENERATOR.SIZES = [[32, 64, 128, 256, 512]]
+# Anchor aspect ratios. For each area given in `SIZES`, anchors with different aspect
+# ratios are generated by an anchor generator.
+# Format: list[list[float]]. ASPECT_RATIOS[i] specifies the list of aspect ratios (H/W)
+# to use for IN_FEATURES[i]; len(ASPECT_RATIOS) == len(IN_FEATURES) must be true,
+# or len(ASPECT_RATIOS) == 1 is true and aspect ratio list ASPECT_RATIOS[0] is used
+# for all IN_FEATURES.
+_C.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.5, 1.0, 2.0]]
+# Anchor angles.
+# list[list[float]], the angle in degrees, for each input feature map.
+# ANGLES[i] specifies the list of angles for IN_FEATURES[i].
+_C.MODEL.ANCHOR_GENERATOR.ANGLES = [[-90, 0, 90]]
+# Relative offset between the center of the first anchor and the top-left corner of the image
+# Value has to be in [0, 1). Recommend to use 0.5, which means half stride.
+# The value is not expected to affect model accuracy.
+_C.MODEL.ANCHOR_GENERATOR.OFFSET = 0.0
+
+# ---------------------------------------------------------------------------- #
+# RPN options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.RPN = CN()
+_C.MODEL.RPN.HEAD_NAME = "StandardRPNHead" # used by RPN_HEAD_REGISTRY
+
+# Names of the input feature maps to be used by RPN
+# e.g., ["p2", "p3", "p4", "p5", "p6"] for FPN
+_C.MODEL.RPN.IN_FEATURES = ["res4"]
+# Remove RPN anchors that go outside the image by BOUNDARY_THRESH pixels
+# Set to -1 or a large value, e.g. 100000, to disable pruning anchors
+_C.MODEL.RPN.BOUNDARY_THRESH = -1
+# IOU overlap ratios [BG_IOU_THRESHOLD, FG_IOU_THRESHOLD]
+# Minimum overlap required between an anchor and ground-truth box for the
+# (anchor, gt box) pair to be a positive example (IoU >= FG_IOU_THRESHOLD
+# ==> positive RPN example: 1)
+# Maximum overlap allowed between an anchor and ground-truth box for the
+# (anchor, gt box) pair to be a negative examples (IoU < BG_IOU_THRESHOLD
+# ==> negative RPN example: 0)
+# Anchors with overlap in between (BG_IOU_THRESHOLD <= IoU < FG_IOU_THRESHOLD)
+# are ignored (-1)
+_C.MODEL.RPN.IOU_THRESHOLDS = [0.3, 0.7]
+_C.MODEL.RPN.IOU_LABELS = [0, -1, 1]
+# Number of regions per image used to train RPN
+_C.MODEL.RPN.BATCH_SIZE_PER_IMAGE = 256
+# Target fraction of foreground (positive) examples per RPN minibatch
+_C.MODEL.RPN.POSITIVE_FRACTION = 0.5
+# Options are: "smooth_l1", "giou", "diou", "ciou"
+_C.MODEL.RPN.BBOX_REG_LOSS_TYPE = "smooth_l1"
+_C.MODEL.RPN.BBOX_REG_LOSS_WEIGHT = 1.0
+# Weights on (dx, dy, dw, dh) for normalizing RPN anchor regression targets
+_C.MODEL.RPN.BBOX_REG_WEIGHTS = (1.0, 1.0, 1.0, 1.0)
+# The transition point from L1 to L2 loss. Set to 0.0 to make the loss simply L1.
+_C.MODEL.RPN.SMOOTH_L1_BETA = 0.0
+_C.MODEL.RPN.LOSS_WEIGHT = 1.0
+# Number of top scoring RPN proposals to keep before applying NMS
+# When FPN is used, this is *per FPN level* (not total)
+_C.MODEL.RPN.PRE_NMS_TOPK_TRAIN = 12000
+_C.MODEL.RPN.PRE_NMS_TOPK_TEST = 6000
+# Number of top scoring RPN proposals to keep after applying NMS
+# When FPN is used, this limit is applied per level and then again to the union
+# of proposals from all levels
+# NOTE: When FPN is used, the meaning of this config is different from Detectron1.
+# It means per-batch topk in Detectron1, but per-image topk here.
+# See the "find_top_rpn_proposals" function for details.
+_C.MODEL.RPN.POST_NMS_TOPK_TRAIN = 2000
+_C.MODEL.RPN.POST_NMS_TOPK_TEST = 1000
+# NMS threshold used on RPN proposals
+_C.MODEL.RPN.NMS_THRESH = 0.7
+# Set this to -1 to use the same number of output channels as input channels.
+_C.MODEL.RPN.CONV_DIMS = [-1]
+
+# ---------------------------------------------------------------------------- #
+# ROI HEADS options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_HEADS = CN()
+_C.MODEL.ROI_HEADS.NAME = "Res5ROIHeads"
+# Number of foreground classes
+_C.MODEL.ROI_HEADS.NUM_CLASSES = 80
+# Names of the input feature maps to be used by ROI heads
+# Currently all heads (box, mask, ...) use the same input feature map list
+# e.g., ["p2", "p3", "p4", "p5"] is commonly used for FPN
+_C.MODEL.ROI_HEADS.IN_FEATURES = ["res4"]
+# IOU overlap ratios [IOU_THRESHOLD]
+# Overlap threshold for an RoI to be considered background (if < IOU_THRESHOLD)
+# Overlap threshold for an RoI to be considered foreground (if >= IOU_THRESHOLD)
+_C.MODEL.ROI_HEADS.IOU_THRESHOLDS = [0.5]
+_C.MODEL.ROI_HEADS.IOU_LABELS = [0, 1]
+# RoI minibatch size *per image* (number of regions of interest [ROIs]) during training
+# Total number of RoIs per training minibatch =
+# ROI_HEADS.BATCH_SIZE_PER_IMAGE * SOLVER.IMS_PER_BATCH
+# E.g., a common configuration is: 512 * 16 = 8192
+_C.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 512
+# Target fraction of RoI minibatch that is labeled foreground (i.e. class > 0)
+_C.MODEL.ROI_HEADS.POSITIVE_FRACTION = 0.25
+
+# Only used on test mode
+
+# Minimum score threshold (assuming scores in a [0, 1] range); a value chosen to
+# balance obtaining high recall with not having too many low precision
+# detections that will slow down inference post processing steps (like NMS)
+# A default threshold of 0.0 increases AP by ~0.2-0.3 but significantly slows down
+# inference.
+_C.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.05
+# Overlap threshold used for non-maximum suppression (suppress boxes with
+# IoU >= this threshold)
+_C.MODEL.ROI_HEADS.NMS_THRESH_TEST = 0.5
+# If True, augment proposals with ground-truth boxes before sampling proposals to
+# train ROI heads.
+_C.MODEL.ROI_HEADS.PROPOSAL_APPEND_GT = True
+
+# ---------------------------------------------------------------------------- #
+# Box Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_BOX_HEAD = CN()
+# C4 don't use head name option
+# Options for non-C4 models: FastRCNNConvFCHead,
+_C.MODEL.ROI_BOX_HEAD.NAME = ""
+# Options are: "smooth_l1", "giou", "diou", "ciou"
+_C.MODEL.ROI_BOX_HEAD.BBOX_REG_LOSS_TYPE = "smooth_l1"
+# The final scaling coefficient on the box regression loss, used to balance the magnitude of its
+# gradients with other losses in the model. See also `MODEL.ROI_KEYPOINT_HEAD.LOSS_WEIGHT`.
+_C.MODEL.ROI_BOX_HEAD.BBOX_REG_LOSS_WEIGHT = 1.0
+# Default weights on (dx, dy, dw, dh) for normalizing bbox regression targets
+# These are empirically chosen to approximately lead to unit variance targets
+_C.MODEL.ROI_BOX_HEAD.BBOX_REG_WEIGHTS = (10.0, 10.0, 5.0, 5.0)
+# The transition point from L1 to L2 loss. Set to 0.0 to make the loss simply L1.
+_C.MODEL.ROI_BOX_HEAD.SMOOTH_L1_BETA = 0.0
+_C.MODEL.ROI_BOX_HEAD.POOLER_RESOLUTION = 14
+_C.MODEL.ROI_BOX_HEAD.POOLER_SAMPLING_RATIO = 0
+# Type of pooling operation applied to the incoming feature map for each RoI
+_C.MODEL.ROI_BOX_HEAD.POOLER_TYPE = "ROIAlignV2"
+
+_C.MODEL.ROI_BOX_HEAD.NUM_FC = 0
+# Hidden layer dimension for FC layers in the RoI box head
+_C.MODEL.ROI_BOX_HEAD.FC_DIM = 1024
+_C.MODEL.ROI_BOX_HEAD.NUM_CONV = 0
+# Channel dimension for Conv layers in the RoI box head
+_C.MODEL.ROI_BOX_HEAD.CONV_DIM = 256
+# Normalization method for the convolution layers.
+# Options: "" (no norm), "GN", "SyncBN".
+_C.MODEL.ROI_BOX_HEAD.NORM = ""
+# Whether to use class agnostic for bbox regression
+_C.MODEL.ROI_BOX_HEAD.CLS_AGNOSTIC_BBOX_REG = False
+# If true, RoI heads use bounding boxes predicted by the box head rather than proposal boxes.
+_C.MODEL.ROI_BOX_HEAD.TRAIN_ON_PRED_BOXES = False
+
+# Federated loss can be used to improve the training of LVIS
+_C.MODEL.ROI_BOX_HEAD.USE_FED_LOSS = False
+# Sigmoid cross entrophy is used with federated loss
+_C.MODEL.ROI_BOX_HEAD.USE_SIGMOID_CE = False
+# The power value applied to image_count when calcualting frequency weight
+_C.MODEL.ROI_BOX_HEAD.FED_LOSS_FREQ_WEIGHT_POWER = 0.5
+# Number of classes to keep in total
+_C.MODEL.ROI_BOX_HEAD.FED_LOSS_NUM_CLASSES = 50
+
+# ---------------------------------------------------------------------------- #
+# Cascaded Box Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_BOX_CASCADE_HEAD = CN()
+# The number of cascade stages is implicitly defined by the length of the following two configs.
+_C.MODEL.ROI_BOX_CASCADE_HEAD.BBOX_REG_WEIGHTS = (
+ (10.0, 10.0, 5.0, 5.0),
+ (20.0, 20.0, 10.0, 10.0),
+ (30.0, 30.0, 15.0, 15.0),
+)
+_C.MODEL.ROI_BOX_CASCADE_HEAD.IOUS = (0.5, 0.6, 0.7)
+
+
+# ---------------------------------------------------------------------------- #
+# Mask Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_MASK_HEAD = CN()
+_C.MODEL.ROI_MASK_HEAD.NAME = "MaskRCNNConvUpsampleHead"
+_C.MODEL.ROI_MASK_HEAD.POOLER_RESOLUTION = 14
+_C.MODEL.ROI_MASK_HEAD.POOLER_SAMPLING_RATIO = 0
+_C.MODEL.ROI_MASK_HEAD.NUM_CONV = 0 # The number of convs in the mask head
+_C.MODEL.ROI_MASK_HEAD.CONV_DIM = 256
+# Normalization method for the convolution layers.
+# Options: "" (no norm), "GN", "SyncBN".
+_C.MODEL.ROI_MASK_HEAD.NORM = ""
+# Whether to use class agnostic for mask prediction
+_C.MODEL.ROI_MASK_HEAD.CLS_AGNOSTIC_MASK = False
+# Type of pooling operation applied to the incoming feature map for each RoI
+_C.MODEL.ROI_MASK_HEAD.POOLER_TYPE = "ROIAlignV2"
+
+
+# ---------------------------------------------------------------------------- #
+# Keypoint Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_KEYPOINT_HEAD = CN()
+_C.MODEL.ROI_KEYPOINT_HEAD.NAME = "KRCNNConvDeconvUpsampleHead"
+_C.MODEL.ROI_KEYPOINT_HEAD.POOLER_RESOLUTION = 14
+_C.MODEL.ROI_KEYPOINT_HEAD.POOLER_SAMPLING_RATIO = 0
+_C.MODEL.ROI_KEYPOINT_HEAD.CONV_DIMS = tuple(512 for _ in range(8))
+_C.MODEL.ROI_KEYPOINT_HEAD.NUM_KEYPOINTS = 17 # 17 is the number of keypoints in COCO.
+
+# Images with too few (or no) keypoints are excluded from training.
+_C.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE = 1
+# Normalize by the total number of visible keypoints in the minibatch if True.
+# Otherwise, normalize by the total number of keypoints that could ever exist
+# in the minibatch.
+# The keypoint softmax loss is only calculated on visible keypoints.
+# Since the number of visible keypoints can vary significantly between
+# minibatches, this has the effect of up-weighting the importance of
+# minibatches with few visible keypoints. (Imagine the extreme case of
+# only one visible keypoint versus N: in the case of N, each one
+# contributes 1/N to the gradient compared to the single keypoint
+# determining the gradient direction). Instead, we can normalize the
+# loss by the total number of keypoints, if it were the case that all
+# keypoints were visible in a full minibatch. (Returning to the example,
+# this means that the one visible keypoint contributes as much as each
+# of the N keypoints.)
+_C.MODEL.ROI_KEYPOINT_HEAD.NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS = True
+# Multi-task loss weight to use for keypoints
+# Recommended values:
+# - use 1.0 if NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS is True
+# - use 4.0 if NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS is False
+_C.MODEL.ROI_KEYPOINT_HEAD.LOSS_WEIGHT = 1.0
+# Type of pooling operation applied to the incoming feature map for each RoI
+_C.MODEL.ROI_KEYPOINT_HEAD.POOLER_TYPE = "ROIAlignV2"
+
+# ---------------------------------------------------------------------------- #
+# Semantic Segmentation Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.SEM_SEG_HEAD = CN()
+_C.MODEL.SEM_SEG_HEAD.NAME = "SemSegFPNHead"
+_C.MODEL.SEM_SEG_HEAD.IN_FEATURES = ["p2", "p3", "p4", "p5"]
+# Label in the semantic segmentation ground truth that is ignored, i.e., no loss is calculated for
+# the correposnding pixel.
+_C.MODEL.SEM_SEG_HEAD.IGNORE_VALUE = 255
+# Number of classes in the semantic segmentation head
+_C.MODEL.SEM_SEG_HEAD.NUM_CLASSES = 54
+# Number of channels in the 3x3 convs inside semantic-FPN heads.
+_C.MODEL.SEM_SEG_HEAD.CONVS_DIM = 128
+# Outputs from semantic-FPN heads are up-scaled to the COMMON_STRIDE stride.
+_C.MODEL.SEM_SEG_HEAD.COMMON_STRIDE = 4
+# Normalization method for the convolution layers. Options: "" (no norm), "GN".
+_C.MODEL.SEM_SEG_HEAD.NORM = "GN"
+_C.MODEL.SEM_SEG_HEAD.LOSS_WEIGHT = 1.0
+
+_C.MODEL.PANOPTIC_FPN = CN()
+# Scaling of all losses from instance detection / segmentation head.
+_C.MODEL.PANOPTIC_FPN.INSTANCE_LOSS_WEIGHT = 1.0
+
+# options when combining instance & semantic segmentation outputs
+_C.MODEL.PANOPTIC_FPN.COMBINE = CN({"ENABLED": True}) # "COMBINE.ENABLED" is deprecated & not used
+_C.MODEL.PANOPTIC_FPN.COMBINE.OVERLAP_THRESH = 0.5
+_C.MODEL.PANOPTIC_FPN.COMBINE.STUFF_AREA_LIMIT = 4096
+_C.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = 0.5
+
+
+# ---------------------------------------------------------------------------- #
+# RetinaNet Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.RETINANET = CN()
+
+# This is the number of foreground classes.
+_C.MODEL.RETINANET.NUM_CLASSES = 80
+
+_C.MODEL.RETINANET.IN_FEATURES = ["p3", "p4", "p5", "p6", "p7"]
+
+# Convolutions to use in the cls and bbox tower
+# NOTE: this doesn't include the last conv for logits
+_C.MODEL.RETINANET.NUM_CONVS = 4
+
+# IoU overlap ratio [bg, fg] for labeling anchors.
+# Anchors with < bg are labeled negative (0)
+# Anchors with >= bg and < fg are ignored (-1)
+# Anchors with >= fg are labeled positive (1)
+_C.MODEL.RETINANET.IOU_THRESHOLDS = [0.4, 0.5]
+_C.MODEL.RETINANET.IOU_LABELS = [0, -1, 1]
+
+# Prior prob for rare case (i.e. foreground) at the beginning of training.
+# This is used to set the bias for the logits layer of the classifier subnet.
+# This improves training stability in the case of heavy class imbalance.
+_C.MODEL.RETINANET.PRIOR_PROB = 0.01
+
+# Inference cls score threshold, only anchors with score > INFERENCE_TH are
+# considered for inference (to improve speed)
+_C.MODEL.RETINANET.SCORE_THRESH_TEST = 0.05
+# Select topk candidates before NMS
+_C.MODEL.RETINANET.TOPK_CANDIDATES_TEST = 1000
+_C.MODEL.RETINANET.NMS_THRESH_TEST = 0.5
+
+# Weights on (dx, dy, dw, dh) for normalizing Retinanet anchor regression targets
+_C.MODEL.RETINANET.BBOX_REG_WEIGHTS = (1.0, 1.0, 1.0, 1.0)
+
+# Loss parameters
+_C.MODEL.RETINANET.FOCAL_LOSS_GAMMA = 2.0
+_C.MODEL.RETINANET.FOCAL_LOSS_ALPHA = 0.25
+_C.MODEL.RETINANET.SMOOTH_L1_LOSS_BETA = 0.1
+# Options are: "smooth_l1", "giou", "diou", "ciou"
+_C.MODEL.RETINANET.BBOX_REG_LOSS_TYPE = "smooth_l1"
+
+# One of BN, SyncBN, FrozenBN, GN
+# Only supports GN until unshared norm is implemented
+_C.MODEL.RETINANET.NORM = ""
+
+
+# ---------------------------------------------------------------------------- #
+# ResNe[X]t options (ResNets = {ResNet, ResNeXt}
+# Note that parts of a resnet may be used for both the backbone and the head
+# These options apply to both
+# ---------------------------------------------------------------------------- #
+_C.MODEL.RESNETS = CN()
+
+_C.MODEL.RESNETS.DEPTH = 50
+_C.MODEL.RESNETS.OUT_FEATURES = ["res4"] # res4 for C4 backbone, res2..5 for FPN backbone
+
+# Number of groups to use; 1 ==> ResNet; > 1 ==> ResNeXt
+_C.MODEL.RESNETS.NUM_GROUPS = 1
+
+# Options: FrozenBN, GN, "SyncBN", "BN"
+_C.MODEL.RESNETS.NORM = "FrozenBN"
+
+# Baseline width of each group.
+# Scaling this parameters will scale the width of all bottleneck layers.
+_C.MODEL.RESNETS.WIDTH_PER_GROUP = 64
+
+# Place the stride 2 conv on the 1x1 filter
+# Use True only for the original MSRA ResNet; use False for C2 and Torch models
+_C.MODEL.RESNETS.STRIDE_IN_1X1 = True
+
+# Apply dilation in stage "res5"
+_C.MODEL.RESNETS.RES5_DILATION = 1
+
+# Output width of res2. Scaling this parameters will scale the width of all 1x1 convs in ResNet
+# For R18 and R34, this needs to be set to 64
+_C.MODEL.RESNETS.RES2_OUT_CHANNELS = 256
+_C.MODEL.RESNETS.STEM_OUT_CHANNELS = 64
+
+# Apply Deformable Convolution in stages
+# Specify if apply deform_conv on Res2, Res3, Res4, Res5
+_C.MODEL.RESNETS.DEFORM_ON_PER_STAGE = [False, False, False, False]
+# Use True to use modulated deform_conv (DeformableV2, https://arxiv.org/abs/1811.11168);
+# Use False for DeformableV1.
+_C.MODEL.RESNETS.DEFORM_MODULATED = False
+# Number of groups in deformable conv.
+_C.MODEL.RESNETS.DEFORM_NUM_GROUPS = 1
+
+
+# ---------------------------------------------------------------------------- #
+# Solver
+# ---------------------------------------------------------------------------- #
+_C.SOLVER = CN()
+
+# Options: WarmupMultiStepLR, WarmupCosineLR.
+# See detectron2/solver/build.py for definition.
+_C.SOLVER.LR_SCHEDULER_NAME = "WarmupMultiStepLR"
+
+_C.SOLVER.MAX_ITER = 40000
+
+_C.SOLVER.BASE_LR = 0.001
+# The end lr, only used by WarmupCosineLR
+_C.SOLVER.BASE_LR_END = 0.0
+
+_C.SOLVER.MOMENTUM = 0.9
+
+_C.SOLVER.NESTEROV = False
+
+_C.SOLVER.WEIGHT_DECAY = 0.0001
+# The weight decay that's applied to parameters of normalization layers
+# (typically the affine transformation)
+_C.SOLVER.WEIGHT_DECAY_NORM = 0.0
+
+_C.SOLVER.GAMMA = 0.1
+# The iteration number to decrease learning rate by GAMMA.
+_C.SOLVER.STEPS = (30000,)
+# Number of decays in WarmupStepWithFixedGammaLR schedule
+_C.SOLVER.NUM_DECAYS = 3
+
+_C.SOLVER.WARMUP_FACTOR = 1.0 / 1000
+_C.SOLVER.WARMUP_ITERS = 1000
+_C.SOLVER.WARMUP_METHOD = "linear"
+# Whether to rescale the interval for the learning schedule after warmup
+_C.SOLVER.RESCALE_INTERVAL = False
+
+# Save a checkpoint after every this number of iterations
+_C.SOLVER.CHECKPOINT_PERIOD = 5000
+
+# Number of images per batch across all machines. This is also the number
+# of training images per step (i.e. per iteration). If we use 16 GPUs
+# and IMS_PER_BATCH = 32, each GPU will see 2 images per batch.
+# May be adjusted automatically if REFERENCE_WORLD_SIZE is set.
+_C.SOLVER.IMS_PER_BATCH = 16
+
+# The reference number of workers (GPUs) this config is meant to train with.
+# It takes no effect when set to 0.
+# With a non-zero value, it will be used by DefaultTrainer to compute a desired
+# per-worker batch size, and then scale the other related configs (total batch size,
+# learning rate, etc) to match the per-worker batch size.
+# See documentation of `DefaultTrainer.auto_scale_workers` for details:
+_C.SOLVER.REFERENCE_WORLD_SIZE = 0
+
+# Detectron v1 (and previous detection code) used a 2x higher LR and 0 WD for
+# biases. This is not useful (at least for recent models). You should avoid
+# changing these and they exist only to reproduce Detectron v1 training if
+# desired.
+_C.SOLVER.BIAS_LR_FACTOR = 1.0
+_C.SOLVER.WEIGHT_DECAY_BIAS = None # None means following WEIGHT_DECAY
+
+# Gradient clipping
+_C.SOLVER.CLIP_GRADIENTS = CN({"ENABLED": False})
+# Type of gradient clipping, currently 2 values are supported:
+# - "value": the absolute values of elements of each gradients are clipped
+# - "norm": the norm of the gradient for each parameter is clipped thus
+# affecting all elements in the parameter
+_C.SOLVER.CLIP_GRADIENTS.CLIP_TYPE = "value"
+# Maximum absolute value used for clipping gradients
+_C.SOLVER.CLIP_GRADIENTS.CLIP_VALUE = 1.0
+# Floating point number p for L-p norm to be used with the "norm"
+# gradient clipping type; for L-inf, please specify .inf
+_C.SOLVER.CLIP_GRADIENTS.NORM_TYPE = 2.0
+
+# Enable automatic mixed precision for training
+# Note that this does not change model's inference behavior.
+# To use AMP in inference, run inference under autocast()
+_C.SOLVER.AMP = CN({"ENABLED": False})
+
+# ---------------------------------------------------------------------------- #
+# Specific test options
+# ---------------------------------------------------------------------------- #
+_C.TEST = CN()
+# For end-to-end tests to verify the expected accuracy.
+# Each item is [task, metric, value, tolerance]
+# e.g.: [['bbox', 'AP', 38.5, 0.2]]
+_C.TEST.EXPECTED_RESULTS = []
+# The period (in terms of steps) to evaluate the model during training.
+# Set to 0 to disable.
+_C.TEST.EVAL_PERIOD = 0
+# The sigmas used to calculate keypoint OKS. See http://cocodataset.org/#keypoints-eval
+# When empty, it will use the defaults in COCO.
+# Otherwise it should be a list[float] with the same length as ROI_KEYPOINT_HEAD.NUM_KEYPOINTS.
+_C.TEST.KEYPOINT_OKS_SIGMAS = []
+# Maximum number of detections to return per image during inference (100 is
+# based on the limit established for the COCO dataset).
+_C.TEST.DETECTIONS_PER_IMAGE = 100
+
+_C.TEST.AUG = CN({"ENABLED": False})
+_C.TEST.AUG.MIN_SIZES = (400, 500, 600, 700, 800, 900, 1000, 1100, 1200)
+_C.TEST.AUG.MAX_SIZE = 4000
+_C.TEST.AUG.FLIP = True
+
+_C.TEST.PRECISE_BN = CN({"ENABLED": False})
+_C.TEST.PRECISE_BN.NUM_ITER = 200
+
+# ---------------------------------------------------------------------------- #
+# Misc options
+# ---------------------------------------------------------------------------- #
+# Directory where output files are written
+_C.OUTPUT_DIR = "./output"
+# Set seed to negative to fully randomize everything.
+# Set seed to positive to use a fixed seed. Note that a fixed seed increases
+# reproducibility but does not guarantee fully deterministic behavior.
+# Disabling all parallelism further increases reproducibility.
+_C.SEED = -1
+# Benchmark different cudnn algorithms.
+# If input images have very different sizes, this option will have large overhead
+# for about 10k iterations. It usually hurts total time, but can benefit for certain models.
+# If input images have the same or similar sizes, benchmark is often helpful.
+_C.CUDNN_BENCHMARK = False
+# The period (in terms of steps) for minibatch visualization at train time.
+# Set to 0 to disable.
+_C.VIS_PERIOD = 0
+
+# global config is for quick hack purposes.
+# You can set them in command line or config files,
+# and access it with:
+#
+# from detectron2.config import global_cfg
+# print(global_cfg.HACK)
+#
+# Do not commit any configs into it.
+_C.GLOBAL = CN()
+_C.GLOBAL.HACK = 1.0
diff --git a/detectron2/detectron2/config/instantiate.py b/detectron2/detectron2/config/instantiate.py
new file mode 100755
index 0000000..05ee2c7
--- /dev/null
+++ b/detectron2/detectron2/config/instantiate.py
@@ -0,0 +1,88 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import collections.abc as abc
+import dataclasses
+import logging
+from typing import Any
+
+from detectron2.utils.registry import _convert_target_to_string, locate
+
+__all__ = ["dump_dataclass", "instantiate"]
+
+
+def dump_dataclass(obj: Any):
+ """
+ Dump a dataclass recursively into a dict that can be later instantiated.
+
+ Args:
+ obj: a dataclass object
+
+ Returns:
+ dict
+ """
+ assert dataclasses.is_dataclass(obj) and not isinstance(
+ obj, type
+ ), "dump_dataclass() requires an instance of a dataclass."
+ ret = {"_target_": _convert_target_to_string(type(obj))}
+ for f in dataclasses.fields(obj):
+ v = getattr(obj, f.name)
+ if dataclasses.is_dataclass(v):
+ v = dump_dataclass(v)
+ if isinstance(v, (list, tuple)):
+ v = [dump_dataclass(x) if dataclasses.is_dataclass(x) else x for x in v]
+ ret[f.name] = v
+ return ret
+
+
+def instantiate(cfg):
+ """
+ Recursively instantiate objects defined in dictionaries by
+ "_target_" and arguments.
+
+ Args:
+ cfg: a dict-like object with "_target_" that defines the caller, and
+ other keys that define the arguments
+
+ Returns:
+ object instantiated by cfg
+ """
+ from omegaconf import ListConfig, DictConfig, OmegaConf
+
+ if isinstance(cfg, ListConfig):
+ lst = [instantiate(x) for x in cfg]
+ return ListConfig(lst, flags={"allow_objects": True})
+ if isinstance(cfg, list):
+ # Specialize for list, because many classes take
+ # list[objects] as arguments, such as ResNet, DatasetMapper
+ return [instantiate(x) for x in cfg]
+
+ # If input is a DictConfig backed by dataclasses (i.e. omegaconf's structured config),
+ # instantiate it to the actual dataclass.
+ if isinstance(cfg, DictConfig) and dataclasses.is_dataclass(cfg._metadata.object_type):
+ return OmegaConf.to_object(cfg)
+
+ if isinstance(cfg, abc.Mapping) and "_target_" in cfg:
+ # conceptually equivalent to hydra.utils.instantiate(cfg) with _convert_=all,
+ # but faster: https://github.com/facebookresearch/hydra/issues/1200
+ cfg = {k: instantiate(v) for k, v in cfg.items()}
+ cls = cfg.pop("_target_")
+ cls = instantiate(cls)
+
+ if isinstance(cls, str):
+ cls_name = cls
+ cls = locate(cls_name)
+ assert cls is not None, cls_name
+ else:
+ try:
+ cls_name = cls.__module__ + "." + cls.__qualname__
+ except Exception:
+ # target could be anything, so the above could fail
+ cls_name = str(cls)
+ assert callable(cls), f"_target_ {cls} does not define a callable object"
+ try:
+ return cls(**cfg)
+ except TypeError:
+ logger = logging.getLogger(__name__)
+ logger.error(f"Error when instantiating {cls_name}!")
+ raise
+ return cfg # return as-is if don't know what to do
diff --git a/detectron2/detectron2/config/lazy.py b/detectron2/detectron2/config/lazy.py
new file mode 100755
index 0000000..ea93e86
--- /dev/null
+++ b/detectron2/detectron2/config/lazy.py
@@ -0,0 +1,436 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import ast
+import builtins
+import collections.abc as abc
+import importlib
+import inspect
+import logging
+import os
+import uuid
+from contextlib import contextmanager
+from copy import deepcopy
+from dataclasses import is_dataclass
+from typing import List, Tuple, Union
+import cloudpickle
+import yaml
+from omegaconf import DictConfig, ListConfig, OmegaConf, SCMode
+
+from detectron2.utils.file_io import PathManager
+from detectron2.utils.registry import _convert_target_to_string
+
+__all__ = ["LazyCall", "LazyConfig"]
+
+
+class LazyCall:
+ """
+ Wrap a callable so that when it's called, the call will not be executed,
+ but returns a dict that describes the call.
+
+ LazyCall object has to be called with only keyword arguments. Positional
+ arguments are not yet supported.
+
+ Examples:
+ ::
+ from detectron2.config import instantiate, LazyCall
+
+ layer_cfg = LazyCall(nn.Conv2d)(in_channels=32, out_channels=32)
+ layer_cfg.out_channels = 64 # can edit it afterwards
+ layer = instantiate(layer_cfg)
+ """
+
+ def __init__(self, target):
+ if not (callable(target) or isinstance(target, (str, abc.Mapping))):
+ raise TypeError(
+ f"target of LazyCall must be a callable or defines a callable! Got {target}"
+ )
+ self._target = target
+
+ def __call__(self, **kwargs):
+ if is_dataclass(self._target):
+ # omegaconf object cannot hold dataclass type
+ # https://github.com/omry/omegaconf/issues/784
+ target = _convert_target_to_string(self._target)
+ else:
+ target = self._target
+ kwargs["_target_"] = target
+
+ return DictConfig(content=kwargs, flags={"allow_objects": True})
+
+
+def _visit_dict_config(cfg, func):
+ """
+ Apply func recursively to all DictConfig in cfg.
+ """
+ if isinstance(cfg, DictConfig):
+ func(cfg)
+ for v in cfg.values():
+ _visit_dict_config(v, func)
+ elif isinstance(cfg, ListConfig):
+ for v in cfg:
+ _visit_dict_config(v, func)
+
+
+def _validate_py_syntax(filename):
+ # see also https://github.com/open-mmlab/mmcv/blob/master/mmcv/utils/config.py
+ with PathManager.open(filename, "r") as f:
+ content = f.read()
+ try:
+ ast.parse(content)
+ except SyntaxError as e:
+ raise SyntaxError(f"Config file {filename} has syntax error!") from e
+
+
+def _cast_to_config(obj):
+ # if given a dict, return DictConfig instead
+ if isinstance(obj, dict):
+ return DictConfig(obj, flags={"allow_objects": True})
+ return obj
+
+
+_CFG_PACKAGE_NAME = "detectron2._cfg_loader"
+"""
+A namespace to put all imported config into.
+"""
+
+
+def _random_package_name(filename):
+ # generate a random package name when loading config files
+ return _CFG_PACKAGE_NAME + str(uuid.uuid4())[:4] + "." + os.path.basename(filename)
+
+
+@contextmanager
+def _patch_import():
+ """
+ Enhance relative import statements in config files, so that they:
+ 1. locate files purely based on relative location, regardless of packages.
+ e.g. you can import file without having __init__
+ 2. do not cache modules globally; modifications of module states has no side effect
+ 3. support other storage system through PathManager, so config files can be in the cloud
+ 4. imported dict are turned into omegaconf.DictConfig automatically
+ """
+ old_import = builtins.__import__
+
+ def find_relative_file(original_file, relative_import_path, level):
+ # NOTE: "from . import x" is not handled. Because then it's unclear
+ # if such import should produce `x` as a python module or DictConfig.
+ # This can be discussed further if needed.
+ relative_import_err = """
+Relative import of directories is not allowed within config files.
+Within a config file, relative import can only import other config files.
+""".replace(
+ "\n", " "
+ )
+ if not len(relative_import_path):
+ raise ImportError(relative_import_err)
+
+ cur_file = os.path.dirname(original_file)
+ for _ in range(level - 1):
+ cur_file = os.path.dirname(cur_file)
+ cur_name = relative_import_path.lstrip(".")
+ for part in cur_name.split("."):
+ cur_file = os.path.join(cur_file, part)
+ if not cur_file.endswith(".py"):
+ cur_file += ".py"
+ if not PathManager.isfile(cur_file):
+ cur_file_no_suffix = cur_file[: -len(".py")]
+ if PathManager.isdir(cur_file_no_suffix):
+ raise ImportError(f"Cannot import from {cur_file_no_suffix}." + relative_import_err)
+ else:
+ raise ImportError(
+ f"Cannot import name {relative_import_path} from "
+ f"{original_file}: {cur_file} does not exist."
+ )
+ return cur_file
+
+ def new_import(name, globals=None, locals=None, fromlist=(), level=0):
+ if (
+ # Only deal with relative imports inside config files
+ level != 0
+ and globals is not None
+ and (globals.get("__package__", "") or "").startswith(_CFG_PACKAGE_NAME)
+ ):
+ cur_file = find_relative_file(globals["__file__"], name, level)
+ _validate_py_syntax(cur_file)
+ spec = importlib.machinery.ModuleSpec(
+ _random_package_name(cur_file), None, origin=cur_file
+ )
+ module = importlib.util.module_from_spec(spec)
+ module.__file__ = cur_file
+ with PathManager.open(cur_file) as f:
+ content = f.read()
+ exec(compile(content, cur_file, "exec"), module.__dict__)
+ for name in fromlist: # turn imported dict into DictConfig automatically
+ val = _cast_to_config(module.__dict__[name])
+ module.__dict__[name] = val
+ return module
+ return old_import(name, globals, locals, fromlist=fromlist, level=level)
+
+ builtins.__import__ = new_import
+ yield new_import
+ builtins.__import__ = old_import
+
+
+class LazyConfig:
+ """
+ Provide methods to save, load, and overrides an omegaconf config object
+ which may contain definition of lazily-constructed objects.
+ """
+
+ @staticmethod
+ def load_rel(filename: str, keys: Union[None, str, Tuple[str, ...]] = None):
+ """
+ Similar to :meth:`load()`, but load path relative to the caller's
+ source file.
+
+ This has the same functionality as a relative import, except that this method
+ accepts filename as a string, so more characters are allowed in the filename.
+ """
+ caller_frame = inspect.stack()[1]
+ caller_fname = caller_frame[0].f_code.co_filename
+ assert caller_fname != "", "load_rel Unable to find caller"
+ caller_dir = os.path.dirname(caller_fname)
+ filename = os.path.join(caller_dir, filename)
+ return LazyConfig.load(filename, keys)
+
+ @staticmethod
+ def load(filename: str, keys: Union[None, str, Tuple[str, ...]] = None):
+ """
+ Load a config file.
+
+ Args:
+ filename: absolute path or relative path w.r.t. the current working directory
+ keys: keys to load and return. If not given, return all keys
+ (whose values are config objects) in a dict.
+ """
+ has_keys = keys is not None
+ filename = filename.replace("/./", "/") # redundant
+ if os.path.splitext(filename)[1] not in [".py", ".yaml", ".yml"]:
+ raise ValueError(f"Config file {filename} has to be a python or yaml file.")
+ if filename.endswith(".py"):
+ _validate_py_syntax(filename)
+
+ with _patch_import():
+ # Record the filename
+ module_namespace = {
+ "__file__": filename,
+ "__package__": _random_package_name(filename),
+ }
+ with PathManager.open(filename) as f:
+ content = f.read()
+ # Compile first with filename to:
+ # 1. make filename appears in stacktrace
+ # 2. make load_rel able to find its parent's (possibly remote) location
+ exec(compile(content, filename, "exec"), module_namespace)
+
+ ret = module_namespace
+ else:
+ with PathManager.open(filename) as f:
+ obj = yaml.unsafe_load(f)
+ ret = OmegaConf.create(obj, flags={"allow_objects": True})
+
+ if has_keys:
+ if isinstance(keys, str):
+ return _cast_to_config(ret[keys])
+ else:
+ return tuple(_cast_to_config(ret[a]) for a in keys)
+ else:
+ if filename.endswith(".py"):
+ # when not specified, only load those that are config objects
+ ret = DictConfig(
+ {
+ name: _cast_to_config(value)
+ for name, value in ret.items()
+ if isinstance(value, (DictConfig, ListConfig, dict))
+ and not name.startswith("_")
+ },
+ flags={"allow_objects": True},
+ )
+ return ret
+
+ @staticmethod
+ def save(cfg, filename: str):
+ """
+ Save a config object to a yaml file.
+ Note that when the config dictionary contains complex objects (e.g. lambda),
+ it can't be saved to yaml. In that case we will print an error and
+ attempt to save to a pkl file instead.
+
+ Args:
+ cfg: an omegaconf config object
+ filename: yaml file name to save the config file
+ """
+ logger = logging.getLogger(__name__)
+ try:
+ cfg = deepcopy(cfg)
+ except Exception:
+ pass
+ else:
+ # if it's deep-copyable, then...
+ def _replace_type_by_name(x):
+ if "_target_" in x and callable(x._target_):
+ try:
+ x._target_ = _convert_target_to_string(x._target_)
+ except AttributeError:
+ pass
+
+ # not necessary, but makes yaml looks nicer
+ _visit_dict_config(cfg, _replace_type_by_name)
+
+ save_pkl = False
+ try:
+ dict = OmegaConf.to_container(
+ cfg,
+ # Do not resolve interpolation when saving, i.e. do not turn ${a} into
+ # actual values when saving.
+ resolve=False,
+ # Save structures (dataclasses) in a format that can be instantiated later.
+ # Without this option, the type information of the dataclass will be erased.
+ structured_config_mode=SCMode.INSTANTIATE,
+ )
+ dumped = yaml.dump(dict, default_flow_style=None, allow_unicode=True, width=9999)
+ with PathManager.open(filename, "w") as f:
+ f.write(dumped)
+
+ try:
+ _ = yaml.unsafe_load(dumped) # test that it is loadable
+ except Exception:
+ logger.warning(
+ "The config contains objects that cannot serialize to a valid yaml. "
+ f"{filename} is human-readable but cannot be loaded."
+ )
+ save_pkl = True
+ except Exception:
+ logger.exception("Unable to serialize the config to yaml. Error:")
+ save_pkl = True
+
+ if save_pkl:
+ new_filename = filename + ".pkl"
+ try:
+ # retry by pickle
+ with PathManager.open(new_filename, "wb") as f:
+ cloudpickle.dump(cfg, f)
+ logger.warning(f"Config is saved using cloudpickle at {new_filename}.")
+ except Exception:
+ pass
+
+ @staticmethod
+ def apply_overrides(cfg, overrides: List[str]):
+ """
+ In-place override contents of cfg.
+
+ Args:
+ cfg: an omegaconf config object
+ overrides: list of strings in the format of "a=b" to override configs.
+ See https://hydra.cc/docs/next/advanced/override_grammar/basic/
+ for syntax.
+
+ Returns:
+ the cfg object
+ """
+
+ def safe_update(cfg, key, value):
+ parts = key.split(".")
+ for idx in range(1, len(parts)):
+ prefix = ".".join(parts[:idx])
+ v = OmegaConf.select(cfg, prefix, default=None)
+ if v is None:
+ break
+ if not OmegaConf.is_config(v):
+ raise KeyError(
+ f"Trying to update key {key}, but {prefix} "
+ f"is not a config, but has type {type(v)}."
+ )
+ OmegaConf.update(cfg, key, value, merge=True)
+
+ try:
+ from hydra.core.override_parser.overrides_parser import OverridesParser
+
+ has_hydra = True
+ except ImportError:
+ has_hydra = False
+
+ if has_hydra:
+ parser = OverridesParser.create()
+ overrides = parser.parse_overrides(overrides)
+ for o in overrides:
+ key = o.key_or_group
+ value = o.value()
+ if o.is_delete():
+ # TODO support this
+ raise NotImplementedError("deletion is not yet a supported override")
+ safe_update(cfg, key, value)
+ else:
+ # Fallback. Does not support all the features and error checking like hydra.
+ for o in overrides:
+ key, value = o.split("=")
+ try:
+ value = eval(value, {})
+ except NameError:
+ pass
+ safe_update(cfg, key, value)
+ return cfg
+
+ @staticmethod
+ def to_py(cfg, prefix: str = "cfg."):
+ """
+ Try to convert a config object into Python-like psuedo code.
+
+ Note that perfect conversion is not always possible. So the returned
+ results are mainly meant to be human-readable, and not meant to be executed.
+
+ Args:
+ cfg: an omegaconf config object
+ prefix: root name for the resulting code (default: "cfg.")
+
+
+ Returns:
+ str of formatted Python code
+ """
+ import black
+
+ cfg = OmegaConf.to_container(cfg, resolve=True)
+
+ def _to_str(obj, prefix=None, inside_call=False):
+ if prefix is None:
+ prefix = []
+ if isinstance(obj, abc.Mapping) and "_target_" in obj:
+ # Dict representing a function call
+ target = _convert_target_to_string(obj.pop("_target_"))
+ args = []
+ for k, v in sorted(obj.items()):
+ args.append(f"{k}={_to_str(v, inside_call=True)}")
+ args = ", ".join(args)
+ call = f"{target}({args})"
+ return "".join(prefix) + call
+ elif isinstance(obj, abc.Mapping) and not inside_call:
+ # Dict that is not inside a call is a list of top-level config objects that we
+ # render as one object per line with dot separated prefixes
+ key_list = []
+ for k, v in sorted(obj.items()):
+ if isinstance(v, abc.Mapping) and "_target_" not in v:
+ key_list.append(_to_str(v, prefix=prefix + [k + "."]))
+ else:
+ key = "".join(prefix) + k
+ key_list.append(f"{key}={_to_str(v)}")
+ return "\n".join(key_list)
+ elif isinstance(obj, abc.Mapping):
+ # Dict that is inside a call is rendered as a regular dict
+ return (
+ "{"
+ + ",".join(
+ f"{repr(k)}: {_to_str(v, inside_call=inside_call)}"
+ for k, v in sorted(obj.items())
+ )
+ + "}"
+ )
+ elif isinstance(obj, list):
+ return "[" + ",".join(_to_str(x, inside_call=inside_call) for x in obj) + "]"
+ else:
+ return repr(obj)
+
+ py_str = _to_str(cfg, prefix=[prefix])
+ try:
+ return black.format_str(py_str, mode=black.Mode())
+ except black.InvalidInput:
+ return py_str
diff --git a/detectron2/detectron2/data/__init__.py b/detectron2/detectron2/data/__init__.py
new file mode 100755
index 0000000..259f669
--- /dev/null
+++ b/detectron2/detectron2/data/__init__.py
@@ -0,0 +1,19 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from . import transforms # isort:skip
+
+from .build import (
+ build_batch_data_loader,
+ build_detection_test_loader,
+ build_detection_train_loader,
+ get_detection_dataset_dicts,
+ load_proposals_into_dataset,
+ print_instances_class_histogram,
+)
+from .catalog import DatasetCatalog, MetadataCatalog, Metadata
+from .common import DatasetFromList, MapDataset, ToIterableDataset
+from .dataset_mapper import DatasetMapper
+
+# ensure the builtin datasets are registered
+from . import datasets, samplers # isort:skip
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/detectron2/detectron2/data/benchmark.py b/detectron2/detectron2/data/benchmark.py
new file mode 100755
index 0000000..ac2f372
--- /dev/null
+++ b/detectron2/detectron2/data/benchmark.py
@@ -0,0 +1,225 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import numpy as np
+from itertools import count
+from typing import List, Tuple
+import torch
+import tqdm
+from fvcore.common.timer import Timer
+
+from detectron2.utils import comm
+
+from .build import build_batch_data_loader
+from .common import DatasetFromList, MapDataset
+from .samplers import TrainingSampler
+
+logger = logging.getLogger(__name__)
+
+
+class _EmptyMapDataset(torch.utils.data.Dataset):
+ """
+ Map anything to emptiness.
+ """
+
+ def __init__(self, dataset):
+ self.ds = dataset
+
+ def __len__(self):
+ return len(self.ds)
+
+ def __getitem__(self, idx):
+ _ = self.ds[idx]
+ return [0]
+
+
+def iter_benchmark(
+ iterator, num_iter: int, warmup: int = 5, max_time_seconds: float = 60
+) -> Tuple[float, List[float]]:
+ """
+ Benchmark an iterator/iterable for `num_iter` iterations with an extra
+ `warmup` iterations of warmup.
+ End early if `max_time_seconds` time is spent on iterations.
+
+ Returns:
+ float: average time (seconds) per iteration
+ list[float]: time spent on each iteration. Sometimes useful for further analysis.
+ """
+ num_iter, warmup = int(num_iter), int(warmup)
+
+ iterator = iter(iterator)
+ for _ in range(warmup):
+ next(iterator)
+ timer = Timer()
+ all_times = []
+ for curr_iter in tqdm.trange(num_iter):
+ start = timer.seconds()
+ if start > max_time_seconds:
+ num_iter = curr_iter
+ break
+ next(iterator)
+ all_times.append(timer.seconds() - start)
+ avg = timer.seconds() / num_iter
+ return avg, all_times
+
+
+class DataLoaderBenchmark:
+ """
+ Some common benchmarks that help understand perf bottleneck of a standard dataloader
+ made of dataset, mapper and sampler.
+ """
+
+ def __init__(
+ self,
+ dataset,
+ *,
+ mapper,
+ sampler=None,
+ total_batch_size,
+ num_workers=0,
+ max_time_seconds: int = 90,
+ ):
+ """
+ Args:
+ max_time_seconds (int): maximum time to spent for each benchmark
+ other args: same as in `build.py:build_detection_train_loader`
+ """
+ if isinstance(dataset, list):
+ dataset = DatasetFromList(dataset, copy=False, serialize=True)
+ if sampler is None:
+ sampler = TrainingSampler(len(dataset))
+
+ self.dataset = dataset
+ self.mapper = mapper
+ self.sampler = sampler
+ self.total_batch_size = total_batch_size
+ self.num_workers = num_workers
+ self.per_gpu_batch_size = self.total_batch_size // comm.get_world_size()
+
+ self.max_time_seconds = max_time_seconds
+
+ def _benchmark(self, iterator, num_iter, warmup, msg=None):
+ avg, all_times = iter_benchmark(iterator, num_iter, warmup, self.max_time_seconds)
+ if msg is not None:
+ self._log_time(msg, avg, all_times)
+ return avg, all_times
+
+ def _log_time(self, msg, avg, all_times, distributed=False):
+ percentiles = [np.percentile(all_times, k, interpolation="nearest") for k in [1, 5, 95, 99]]
+ if not distributed:
+ logger.info(
+ f"{msg}: avg={1.0/avg:.1f} it/s, "
+ f"p1={percentiles[0]:.2g}s, p5={percentiles[1]:.2g}s, "
+ f"p95={percentiles[2]:.2g}s, p99={percentiles[3]:.2g}s."
+ )
+ return
+ avg_per_gpu = comm.all_gather(avg)
+ percentiles_per_gpu = comm.all_gather(percentiles)
+ if comm.get_rank() > 0:
+ return
+ for idx, avg, percentiles in zip(count(), avg_per_gpu, percentiles_per_gpu):
+ logger.info(
+ f"GPU{idx} {msg}: avg={1.0/avg:.1f} it/s, "
+ f"p1={percentiles[0]:.2g}s, p5={percentiles[1]:.2g}s, "
+ f"p95={percentiles[2]:.2g}s, p99={percentiles[3]:.2g}s."
+ )
+
+ def benchmark_dataset(self, num_iter, warmup=5):
+ """
+ Benchmark the speed of taking raw samples from the dataset.
+ """
+
+ def loader():
+ while True:
+ for k in self.sampler:
+ yield self.dataset[k]
+
+ self._benchmark(loader(), num_iter, warmup, "Dataset Alone")
+
+ def benchmark_mapper(self, num_iter, warmup=5):
+ """
+ Benchmark the speed of taking raw samples from the dataset and map
+ them in a single process.
+ """
+
+ def loader():
+ while True:
+ for k in self.sampler:
+ yield self.mapper(self.dataset[k])
+
+ self._benchmark(loader(), num_iter, warmup, "Single Process Mapper (sec/sample)")
+
+ def benchmark_workers(self, num_iter, warmup=10):
+ """
+ Benchmark the dataloader by tuning num_workers to [0, 1, self.num_workers].
+ """
+ candidates = [0, 1]
+ if self.num_workers not in candidates:
+ candidates.append(self.num_workers)
+
+ dataset = MapDataset(self.dataset, self.mapper)
+ for n in candidates:
+ loader = build_batch_data_loader(
+ dataset,
+ self.sampler,
+ self.total_batch_size,
+ num_workers=n,
+ )
+ self._benchmark(
+ iter(loader),
+ num_iter * max(n, 1),
+ warmup * max(n, 1),
+ f"DataLoader ({n} workers, bs={self.per_gpu_batch_size})",
+ )
+ del loader
+
+ def benchmark_IPC(self, num_iter, warmup=10):
+ """
+ Benchmark the dataloader where each worker outputs nothing. This
+ eliminates the IPC overhead compared to the regular dataloader.
+
+ PyTorch multiprocessing's IPC only optimizes for torch tensors.
+ Large numpy arrays or other data structure may incur large IPC overhead.
+ """
+ n = self.num_workers
+ dataset = _EmptyMapDataset(MapDataset(self.dataset, self.mapper))
+ loader = build_batch_data_loader(
+ dataset, self.sampler, self.total_batch_size, num_workers=n
+ )
+ self._benchmark(
+ iter(loader),
+ num_iter * max(n, 1),
+ warmup * max(n, 1),
+ f"DataLoader ({n} workers, bs={self.per_gpu_batch_size}) w/o comm",
+ )
+
+ def benchmark_distributed(self, num_iter, warmup=10):
+ """
+ Benchmark the dataloader in each distributed worker, and log results of
+ all workers. This helps understand the final performance as well as
+ the variances among workers.
+
+ It also prints startup time (first iter) of the dataloader.
+ """
+ gpu = comm.get_world_size()
+ dataset = MapDataset(self.dataset, self.mapper)
+ n = self.num_workers
+ loader = build_batch_data_loader(
+ dataset, self.sampler, self.total_batch_size, num_workers=n
+ )
+
+ timer = Timer()
+ loader = iter(loader)
+ next(loader)
+ startup_time = timer.seconds()
+ logger.info("Dataloader startup time: {:.2f} seconds".format(startup_time))
+
+ comm.synchronize()
+
+ avg, all_times = self._benchmark(loader, num_iter * max(n, 1), warmup * max(n, 1))
+ del loader
+ self._log_time(
+ f"DataLoader ({gpu} GPUs x {n} workers, total bs={self.total_batch_size})",
+ avg,
+ all_times,
+ True,
+ )
diff --git a/detectron2/detectron2/data/build.py b/detectron2/detectron2/data/build.py
new file mode 100755
index 0000000..d0b951c
--- /dev/null
+++ b/detectron2/detectron2/data/build.py
@@ -0,0 +1,556 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import logging
+import numpy as np
+import operator
+import pickle
+from typing import Any, Callable, Dict, List, Optional, Union
+import torch
+import torch.utils.data as torchdata
+from tabulate import tabulate
+from termcolor import colored
+
+from detectron2.config import configurable
+from detectron2.structures import BoxMode
+from detectron2.utils.comm import get_world_size
+from detectron2.utils.env import seed_all_rng
+from detectron2.utils.file_io import PathManager
+from detectron2.utils.logger import _log_api_usage, log_first_n
+
+from .catalog import DatasetCatalog, MetadataCatalog
+from .common import AspectRatioGroupedDataset, DatasetFromList, MapDataset, ToIterableDataset
+from .dataset_mapper import DatasetMapper
+from .detection_utils import check_metadata_consistency
+from .samplers import (
+ InferenceSampler,
+ RandomSubsetTrainingSampler,
+ RepeatFactorTrainingSampler,
+ TrainingSampler,
+)
+
+"""
+This file contains the default logic to build a dataloader for training or testing.
+"""
+
+__all__ = [
+ "build_batch_data_loader",
+ "build_detection_train_loader",
+ "build_detection_test_loader",
+ "get_detection_dataset_dicts",
+ "load_proposals_into_dataset",
+ "print_instances_class_histogram",
+]
+
+
+def filter_images_with_only_crowd_annotations(dataset_dicts):
+ """
+ Filter out images with none annotations or only crowd annotations
+ (i.e., images without non-crowd annotations).
+ A common training-time preprocessing on COCO dataset.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 Dataset format.
+
+ Returns:
+ list[dict]: the same format, but filtered.
+ """
+ num_before = len(dataset_dicts)
+
+ def valid(anns):
+ for ann in anns:
+ if ann.get("iscrowd", 0) == 0:
+ return True
+ return False
+
+ dataset_dicts = [x for x in dataset_dicts if valid(x["annotations"])]
+ num_after = len(dataset_dicts)
+ logger = logging.getLogger(__name__)
+ logger.info(
+ "Removed {} images with no usable annotations. {} images left.".format(
+ num_before - num_after, num_after
+ )
+ )
+ return dataset_dicts
+
+
+def filter_images_with_few_keypoints(dataset_dicts, min_keypoints_per_image):
+ """
+ Filter out images with too few number of keypoints.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 Dataset format.
+
+ Returns:
+ list[dict]: the same format as dataset_dicts, but filtered.
+ """
+ num_before = len(dataset_dicts)
+
+ def visible_keypoints_in_image(dic):
+ # Each keypoints field has the format [x1, y1, v1, ...], where v is visibility
+ annotations = dic["annotations"]
+ return sum(
+ (np.array(ann["keypoints"][2::3]) > 0).sum()
+ for ann in annotations
+ if "keypoints" in ann
+ )
+
+ dataset_dicts = [
+ x for x in dataset_dicts if visible_keypoints_in_image(x) >= min_keypoints_per_image
+ ]
+ num_after = len(dataset_dicts)
+ logger = logging.getLogger(__name__)
+ logger.info(
+ "Removed {} images with fewer than {} keypoints.".format(
+ num_before - num_after, min_keypoints_per_image
+ )
+ )
+ return dataset_dicts
+
+
+def load_proposals_into_dataset(dataset_dicts, proposal_file):
+ """
+ Load precomputed object proposals into the dataset.
+
+ The proposal file should be a pickled dict with the following keys:
+
+ - "ids": list[int] or list[str], the image ids
+ - "boxes": list[np.ndarray], each is an Nx4 array of boxes corresponding to the image id
+ - "objectness_logits": list[np.ndarray], each is an N sized array of objectness scores
+ corresponding to the boxes.
+ - "bbox_mode": the BoxMode of the boxes array. Defaults to ``BoxMode.XYXY_ABS``.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 Dataset format.
+ proposal_file (str): file path of pre-computed proposals, in pkl format.
+
+ Returns:
+ list[dict]: the same format as dataset_dicts, but added proposal field.
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Loading proposals from: {}".format(proposal_file))
+
+ with PathManager.open(proposal_file, "rb") as f:
+ proposals = pickle.load(f, encoding="latin1")
+
+ # Rename the key names in D1 proposal files
+ rename_keys = {"indexes": "ids", "scores": "objectness_logits"}
+ for key in rename_keys:
+ if key in proposals:
+ proposals[rename_keys[key]] = proposals.pop(key)
+
+ # Fetch the indexes of all proposals that are in the dataset
+ # Convert image_id to str since they could be int.
+ img_ids = set({str(record["image_id"]) for record in dataset_dicts})
+ id_to_index = {str(id): i for i, id in enumerate(proposals["ids"]) if str(id) in img_ids}
+
+ # Assuming default bbox_mode of precomputed proposals are 'XYXY_ABS'
+ bbox_mode = BoxMode(proposals["bbox_mode"]) if "bbox_mode" in proposals else BoxMode.XYXY_ABS
+
+ for record in dataset_dicts:
+ # Get the index of the proposal
+ i = id_to_index[str(record["image_id"])]
+
+ boxes = proposals["boxes"][i]
+ objectness_logits = proposals["objectness_logits"][i]
+ # Sort the proposals in descending order of the scores
+ inds = objectness_logits.argsort()[::-1]
+ record["proposal_boxes"] = boxes[inds]
+ record["proposal_objectness_logits"] = objectness_logits[inds]
+ record["proposal_bbox_mode"] = bbox_mode
+
+ return dataset_dicts
+
+
+def print_instances_class_histogram(dataset_dicts, class_names):
+ """
+ Args:
+ dataset_dicts (list[dict]): list of dataset dicts.
+ class_names (list[str]): list of class names (zero-indexed).
+ """
+ num_classes = len(class_names)
+ hist_bins = np.arange(num_classes + 1)
+ histogram = np.zeros((num_classes,), dtype=np.int)
+ for entry in dataset_dicts:
+ annos = entry["annotations"]
+ classes = np.asarray(
+ [x["category_id"] for x in annos if not x.get("iscrowd", 0)], dtype=np.int
+ )
+ if len(classes):
+ assert classes.min() >= 0, f"Got an invalid category_id={classes.min()}"
+ assert (
+ classes.max() < num_classes
+ ), f"Got an invalid category_id={classes.max()} for a dataset of {num_classes} classes"
+ histogram += np.histogram(classes, bins=hist_bins)[0]
+
+ N_COLS = min(6, len(class_names) * 2)
+
+ def short_name(x):
+ # make long class names shorter. useful for lvis
+ if len(x) > 13:
+ return x[:11] + ".."
+ return x
+
+ data = list(
+ itertools.chain(*[[short_name(class_names[i]), int(v)] for i, v in enumerate(histogram)])
+ )
+ total_num_instances = sum(data[1::2])
+ data.extend([None] * (N_COLS - (len(data) % N_COLS)))
+ if num_classes > 1:
+ data.extend(["total", total_num_instances])
+ data = itertools.zip_longest(*[data[i::N_COLS] for i in range(N_COLS)])
+ table = tabulate(
+ data,
+ headers=["category", "#instances"] * (N_COLS // 2),
+ tablefmt="pipe",
+ numalign="left",
+ stralign="center",
+ )
+ log_first_n(
+ logging.INFO,
+ "Distribution of instances among all {} categories:\n".format(num_classes)
+ + colored(table, "cyan"),
+ key="message",
+ )
+
+
+def get_detection_dataset_dicts(
+ names,
+ filter_empty=True,
+ min_keypoints=0,
+ proposal_files=None,
+ check_consistency=True,
+):
+ """
+ Load and prepare dataset dicts for instance detection/segmentation and semantic segmentation.
+
+ Args:
+ names (str or list[str]): a dataset name or a list of dataset names
+ filter_empty (bool): whether to filter out images without instance annotations
+ min_keypoints (int): filter out images with fewer keypoints than
+ `min_keypoints`. Set to 0 to do nothing.
+ proposal_files (list[str]): if given, a list of object proposal files
+ that match each dataset in `names`.
+ check_consistency (bool): whether to check if datasets have consistent metadata.
+
+ Returns:
+ list[dict]: a list of dicts following the standard dataset dict format.
+ """
+ if isinstance(names, str):
+ names = [names]
+ assert len(names), names
+ dataset_dicts = [DatasetCatalog.get(dataset_name) for dataset_name in names]
+
+ if isinstance(dataset_dicts[0], torchdata.Dataset):
+ if len(dataset_dicts) > 1:
+ # ConcatDataset does not work for iterable style dataset.
+ # We could support concat for iterable as well, but it's often
+ # not a good idea to concat iterables anyway.
+ return torchdata.ConcatDataset(dataset_dicts)
+ return dataset_dicts[0]
+
+ for dataset_name, dicts in zip(names, dataset_dicts):
+ assert len(dicts), "Dataset '{}' is empty!".format(dataset_name)
+
+ if proposal_files is not None:
+ assert len(names) == len(proposal_files)
+ # load precomputed proposals from proposal files
+ dataset_dicts = [
+ load_proposals_into_dataset(dataset_i_dicts, proposal_file)
+ for dataset_i_dicts, proposal_file in zip(dataset_dicts, proposal_files)
+ ]
+
+ dataset_dicts = list(itertools.chain.from_iterable(dataset_dicts))
+
+ has_instances = "annotations" in dataset_dicts[0]
+ if filter_empty and has_instances:
+ dataset_dicts = filter_images_with_only_crowd_annotations(dataset_dicts)
+ if min_keypoints > 0 and has_instances:
+ dataset_dicts = filter_images_with_few_keypoints(dataset_dicts, min_keypoints)
+
+ if check_consistency and has_instances:
+ try:
+ class_names = MetadataCatalog.get(names[0]).thing_classes
+ check_metadata_consistency("thing_classes", names)
+ print_instances_class_histogram(dataset_dicts, class_names)
+ except AttributeError: # class names are not available for this dataset
+ pass
+
+ assert len(dataset_dicts), "No valid data found in {}.".format(",".join(names))
+ return dataset_dicts
+
+
+def build_batch_data_loader(
+ dataset,
+ sampler,
+ total_batch_size,
+ *,
+ aspect_ratio_grouping=False,
+ num_workers=0,
+ collate_fn=None,
+):
+ """
+ Build a batched dataloader. The main differences from `torch.utils.data.DataLoader` are:
+ 1. support aspect ratio grouping options
+ 2. use no "batch collation", because this is common for detection training
+
+ Args:
+ dataset (torch.utils.data.Dataset): a pytorch map-style or iterable dataset.
+ sampler (torch.utils.data.sampler.Sampler or None): a sampler that produces indices.
+ Must be provided iff. ``dataset`` is a map-style dataset.
+ total_batch_size, aspect_ratio_grouping, num_workers, collate_fn: see
+ :func:`build_detection_train_loader`.
+
+ Returns:
+ iterable[list]. Length of each list is the batch size of the current
+ GPU. Each element in the list comes from the dataset.
+ """
+ world_size = get_world_size()
+ assert (
+ total_batch_size > 0 and total_batch_size % world_size == 0
+ ), "Total batch size ({}) must be divisible by the number of gpus ({}).".format(
+ total_batch_size, world_size
+ )
+ batch_size = total_batch_size // world_size
+
+ if isinstance(dataset, torchdata.IterableDataset):
+ assert sampler is None, "sampler must be None if dataset is IterableDataset"
+ else:
+ dataset = ToIterableDataset(dataset, sampler)
+
+ if aspect_ratio_grouping:
+ data_loader = torchdata.DataLoader(
+ dataset,
+ num_workers=num_workers,
+ collate_fn=operator.itemgetter(0), # don't batch, but yield individual elements
+ worker_init_fn=worker_init_reset_seed,
+ ) # yield individual mapped dict
+ data_loader = AspectRatioGroupedDataset(data_loader, batch_size)
+ if collate_fn is None:
+ return data_loader
+ return MapDataset(data_loader, collate_fn)
+ else:
+ return torchdata.DataLoader(
+ dataset,
+ batch_size=batch_size,
+ drop_last=True,
+ num_workers=num_workers,
+ collate_fn=trivial_batch_collator if collate_fn is None else collate_fn,
+ worker_init_fn=worker_init_reset_seed,
+ )
+
+
+def _train_loader_from_config(cfg, mapper=None, *, dataset=None, sampler=None):
+ if dataset is None:
+ dataset = get_detection_dataset_dicts(
+ cfg.DATASETS.TRAIN,
+ filter_empty=cfg.DATALOADER.FILTER_EMPTY_ANNOTATIONS,
+ min_keypoints=cfg.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE
+ if cfg.MODEL.KEYPOINT_ON
+ else 0,
+ proposal_files=cfg.DATASETS.PROPOSAL_FILES_TRAIN if cfg.MODEL.LOAD_PROPOSALS else None,
+ )
+ _log_api_usage("dataset." + cfg.DATASETS.TRAIN[0])
+
+ if mapper is None:
+ mapper = DatasetMapper(cfg, True)
+
+ if sampler is None:
+ sampler_name = cfg.DATALOADER.SAMPLER_TRAIN
+ logger = logging.getLogger(__name__)
+ if isinstance(dataset, torchdata.IterableDataset):
+ logger.info("Not using any sampler since the dataset is IterableDataset.")
+ sampler = None
+ else:
+ logger.info("Using training sampler {}".format(sampler_name))
+ if sampler_name == "TrainingSampler":
+ sampler = TrainingSampler(len(dataset))
+ elif sampler_name == "RepeatFactorTrainingSampler":
+ repeat_factors = RepeatFactorTrainingSampler.repeat_factors_from_category_frequency(
+ dataset, cfg.DATALOADER.REPEAT_THRESHOLD
+ )
+ sampler = RepeatFactorTrainingSampler(repeat_factors)
+ elif sampler_name == "RandomSubsetTrainingSampler":
+ sampler = RandomSubsetTrainingSampler(
+ len(dataset), cfg.DATALOADER.RANDOM_SUBSET_RATIO
+ )
+ else:
+ raise ValueError("Unknown training sampler: {}".format(sampler_name))
+
+ return {
+ "dataset": dataset,
+ "sampler": sampler,
+ "mapper": mapper,
+ "total_batch_size": cfg.SOLVER.IMS_PER_BATCH,
+ "aspect_ratio_grouping": cfg.DATALOADER.ASPECT_RATIO_GROUPING,
+ "num_workers": cfg.DATALOADER.NUM_WORKERS,
+ }
+
+
+@configurable(from_config=_train_loader_from_config)
+def build_detection_train_loader(
+ dataset,
+ *,
+ mapper,
+ sampler=None,
+ total_batch_size,
+ aspect_ratio_grouping=True,
+ num_workers=0,
+ collate_fn=None,
+):
+ """
+ Build a dataloader for object detection with some default features.
+
+ Args:
+ dataset (list or torch.utils.data.Dataset): a list of dataset dicts,
+ or a pytorch dataset (either map-style or iterable). It can be obtained
+ by using :func:`DatasetCatalog.get` or :func:`get_detection_dataset_dicts`.
+ mapper (callable): a callable which takes a sample (dict) from dataset and
+ returns the format to be consumed by the model.
+ When using cfg, the default choice is ``DatasetMapper(cfg, is_train=True)``.
+ sampler (torch.utils.data.sampler.Sampler or None): a sampler that produces
+ indices to be applied on ``dataset``.
+ If ``dataset`` is map-style, the default sampler is a :class:`TrainingSampler`,
+ which coordinates an infinite random shuffle sequence across all workers.
+ Sampler must be None if ``dataset`` is iterable.
+ total_batch_size (int): total batch size across all workers.
+ aspect_ratio_grouping (bool): whether to group images with similar
+ aspect ratio for efficiency. When enabled, it requires each
+ element in dataset be a dict with keys "width" and "height".
+ num_workers (int): number of parallel data loading workers
+ collate_fn: a function that determines how to do batching, same as the argument of
+ `torch.utils.data.DataLoader`. Defaults to do no collation and return a list of
+ data. No collation is OK for small batch size and simple data structures.
+ If your batch size is large and each sample contains too many small tensors,
+ it's more efficient to collate them in data loader.
+
+ Returns:
+ torch.utils.data.DataLoader:
+ a dataloader. Each output from it is a ``list[mapped_element]`` of length
+ ``total_batch_size / num_workers``, where ``mapped_element`` is produced
+ by the ``mapper``.
+ """
+ if isinstance(dataset, list):
+ dataset = DatasetFromList(dataset, copy=False)
+ if mapper is not None:
+ dataset = MapDataset(dataset, mapper)
+
+ if isinstance(dataset, torchdata.IterableDataset):
+ assert sampler is None, "sampler must be None if dataset is IterableDataset"
+ else:
+ if sampler is None:
+ sampler = TrainingSampler(len(dataset))
+ assert isinstance(sampler, torchdata.Sampler), f"Expect a Sampler but got {type(sampler)}"
+ return build_batch_data_loader(
+ dataset,
+ sampler,
+ total_batch_size,
+ aspect_ratio_grouping=aspect_ratio_grouping,
+ num_workers=num_workers,
+ collate_fn=collate_fn,
+ )
+
+
+def _test_loader_from_config(cfg, dataset_name, mapper=None):
+ """
+ Uses the given `dataset_name` argument (instead of the names in cfg), because the
+ standard practice is to evaluate each test set individually (not combining them).
+ """
+ if isinstance(dataset_name, str):
+ dataset_name = [dataset_name]
+
+ dataset = get_detection_dataset_dicts(
+ dataset_name,
+ filter_empty=cfg.DATALOADER.FILTER_EMPTY_ANNOTATIONS,
+ proposal_files=[
+ cfg.DATASETS.PROPOSAL_FILES_TEST[list(cfg.DATASETS.TEST).index(x)] for x in dataset_name
+ ]
+ if cfg.MODEL.LOAD_PROPOSALS
+ else None,
+ )
+ if mapper is None:
+ mapper = DatasetMapper(cfg, False)
+ return {
+ "dataset": dataset,
+ "mapper": mapper,
+ "num_workers": cfg.DATALOADER.NUM_WORKERS,
+ "sampler": InferenceSampler(len(dataset))
+ if not isinstance(dataset, torchdata.IterableDataset)
+ else None,
+ }
+
+
+@configurable(from_config=_test_loader_from_config)
+def build_detection_test_loader(
+ dataset: Union[List[Any], torchdata.Dataset],
+ *,
+ mapper: Callable[[Dict[str, Any]], Any],
+ sampler: Optional[torchdata.Sampler] = None,
+ batch_size: int = 1,
+ num_workers: int = 0,
+ collate_fn: Optional[Callable[[List[Any]], Any]] = None,
+) -> torchdata.DataLoader:
+ """
+ Similar to `build_detection_train_loader`, with default batch size = 1,
+ and sampler = :class:`InferenceSampler`. This sampler coordinates all workers
+ to produce the exact set of all samples.
+
+ Args:
+ dataset: a list of dataset dicts,
+ or a pytorch dataset (either map-style or iterable). They can be obtained
+ by using :func:`DatasetCatalog.get` or :func:`get_detection_dataset_dicts`.
+ mapper: a callable which takes a sample (dict) from dataset
+ and returns the format to be consumed by the model.
+ When using cfg, the default choice is ``DatasetMapper(cfg, is_train=False)``.
+ sampler: a sampler that produces
+ indices to be applied on ``dataset``. Default to :class:`InferenceSampler`,
+ which splits the dataset across all workers. Sampler must be None
+ if `dataset` is iterable.
+ batch_size: the batch size of the data loader to be created.
+ Default to 1 image per worker since this is the standard when reporting
+ inference time in papers.
+ num_workers: number of parallel data loading workers
+ collate_fn: same as the argument of `torch.utils.data.DataLoader`.
+ Defaults to do no collation and return a list of data.
+
+ Returns:
+ DataLoader: a torch DataLoader, that loads the given detection
+ dataset, with test-time transformation and batching.
+
+ Examples:
+ ::
+ data_loader = build_detection_test_loader(
+ DatasetRegistry.get("my_test"),
+ mapper=DatasetMapper(...))
+
+ # or, instantiate with a CfgNode:
+ data_loader = build_detection_test_loader(cfg, "my_test")
+ """
+ if isinstance(dataset, list):
+ dataset = DatasetFromList(dataset, copy=False)
+ if mapper is not None:
+ dataset = MapDataset(dataset, mapper)
+ if isinstance(dataset, torchdata.IterableDataset):
+ assert sampler is None, "sampler must be None if dataset is IterableDataset"
+ else:
+ if sampler is None:
+ sampler = InferenceSampler(len(dataset))
+ return torchdata.DataLoader(
+ dataset,
+ batch_size=batch_size,
+ sampler=sampler,
+ drop_last=False,
+ num_workers=num_workers,
+ collate_fn=trivial_batch_collator if collate_fn is None else collate_fn,
+ )
+
+
+def trivial_batch_collator(batch):
+ """
+ A batch collator that does nothing.
+ """
+ return batch
+
+
+def worker_init_reset_seed(worker_id):
+ initial_seed = torch.initial_seed() % 2**31
+ seed_all_rng(initial_seed + worker_id)
diff --git a/detectron2/detectron2/data/catalog.py b/detectron2/detectron2/data/catalog.py
new file mode 100755
index 0000000..45c110c
--- /dev/null
+++ b/detectron2/detectron2/data/catalog.py
@@ -0,0 +1,236 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import types
+from collections import UserDict
+from typing import List
+
+from detectron2.utils.logger import log_first_n
+
+__all__ = ["DatasetCatalog", "MetadataCatalog", "Metadata"]
+
+
+class _DatasetCatalog(UserDict):
+ """
+ A global dictionary that stores information about the datasets and how to obtain them.
+
+ It contains a mapping from strings
+ (which are names that identify a dataset, e.g. "coco_2014_train")
+ to a function which parses the dataset and returns the samples in the
+ format of `list[dict]`.
+
+ The returned dicts should be in Detectron2 Dataset format (See DATASETS.md for details)
+ if used with the data loader functionalities in `data/build.py,data/detection_transform.py`.
+
+ The purpose of having this catalog is to make it easy to choose
+ different datasets, by just using the strings in the config.
+ """
+
+ def register(self, name, func):
+ """
+ Args:
+ name (str): the name that identifies a dataset, e.g. "coco_2014_train".
+ func (callable): a callable which takes no arguments and returns a list of dicts.
+ It must return the same results if called multiple times.
+ """
+ assert callable(func), "You must register a function with `DatasetCatalog.register`!"
+ assert name not in self, "Dataset '{}' is already registered!".format(name)
+ self[name] = func
+
+ def get(self, name):
+ """
+ Call the registered function and return its results.
+
+ Args:
+ name (str): the name that identifies a dataset, e.g. "coco_2014_train".
+
+ Returns:
+ list[dict]: dataset annotations.
+ """
+ try:
+ f = self[name]
+ except KeyError as e:
+ raise KeyError(
+ "Dataset '{}' is not registered! Available datasets are: {}".format(
+ name, ", ".join(list(self.keys()))
+ )
+ ) from e
+ return f()
+
+ def list(self) -> List[str]:
+ """
+ List all registered datasets.
+
+ Returns:
+ list[str]
+ """
+ return list(self.keys())
+
+ def remove(self, name):
+ """
+ Alias of ``pop``.
+ """
+ self.pop(name)
+
+ def __str__(self):
+ return "DatasetCatalog(registered datasets: {})".format(", ".join(self.keys()))
+
+ __repr__ = __str__
+
+
+DatasetCatalog = _DatasetCatalog()
+DatasetCatalog.__doc__ = (
+ _DatasetCatalog.__doc__
+ + """
+ .. automethod:: detectron2.data.catalog.DatasetCatalog.register
+ .. automethod:: detectron2.data.catalog.DatasetCatalog.get
+"""
+)
+
+
+class Metadata(types.SimpleNamespace):
+ """
+ A class that supports simple attribute setter/getter.
+ It is intended for storing metadata of a dataset and make it accessible globally.
+
+ Examples:
+ ::
+ # somewhere when you load the data:
+ MetadataCatalog.get("mydataset").thing_classes = ["person", "dog"]
+
+ # somewhere when you print statistics or visualize:
+ classes = MetadataCatalog.get("mydataset").thing_classes
+ """
+
+ # the name of the dataset
+ # set default to N/A so that `self.name` in the errors will not trigger getattr again
+ name: str = "N/A"
+
+ _RENAMED = {
+ "class_names": "thing_classes",
+ "dataset_id_to_contiguous_id": "thing_dataset_id_to_contiguous_id",
+ "stuff_class_names": "stuff_classes",
+ }
+
+ def __getattr__(self, key):
+ if key in self._RENAMED:
+ log_first_n(
+ logging.WARNING,
+ "Metadata '{}' was renamed to '{}'!".format(key, self._RENAMED[key]),
+ n=10,
+ )
+ return getattr(self, self._RENAMED[key])
+
+ # "name" exists in every metadata
+ if len(self.__dict__) > 1:
+ raise AttributeError(
+ "Attribute '{}' does not exist in the metadata of dataset '{}'. Available "
+ "keys are {}.".format(key, self.name, str(self.__dict__.keys()))
+ )
+ else:
+ raise AttributeError(
+ f"Attribute '{key}' does not exist in the metadata of dataset '{self.name}': "
+ "metadata is empty."
+ )
+
+ def __setattr__(self, key, val):
+ if key in self._RENAMED:
+ log_first_n(
+ logging.WARNING,
+ "Metadata '{}' was renamed to '{}'!".format(key, self._RENAMED[key]),
+ n=10,
+ )
+ setattr(self, self._RENAMED[key], val)
+
+ # Ensure that metadata of the same name stays consistent
+ try:
+ oldval = getattr(self, key)
+ assert oldval == val, (
+ "Attribute '{}' in the metadata of '{}' cannot be set "
+ "to a different value!\n{} != {}".format(key, self.name, oldval, val)
+ )
+ except AttributeError:
+ super().__setattr__(key, val)
+
+ def as_dict(self):
+ """
+ Returns all the metadata as a dict.
+ Note that modifications to the returned dict will not reflect on the Metadata object.
+ """
+ return copy.copy(self.__dict__)
+
+ def set(self, **kwargs):
+ """
+ Set multiple metadata with kwargs.
+ """
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+ return self
+
+ def get(self, key, default=None):
+ """
+ Access an attribute and return its value if exists.
+ Otherwise return default.
+ """
+ try:
+ return getattr(self, key)
+ except AttributeError:
+ return default
+
+
+class _MetadataCatalog(UserDict):
+ """
+ MetadataCatalog is a global dictionary that provides access to
+ :class:`Metadata` of a given dataset.
+
+ The metadata associated with a certain name is a singleton: once created, the
+ metadata will stay alive and will be returned by future calls to ``get(name)``.
+
+ It's like global variables, so don't abuse it.
+ It's meant for storing knowledge that's constant and shared across the execution
+ of the program, e.g.: the class names in COCO.
+ """
+
+ def get(self, name):
+ """
+ Args:
+ name (str): name of a dataset (e.g. coco_2014_train).
+
+ Returns:
+ Metadata: The :class:`Metadata` instance associated with this name,
+ or create an empty one if none is available.
+ """
+ assert len(name)
+ r = super().get(name, None)
+ if r is None:
+ r = self[name] = Metadata(name=name)
+ return r
+
+ def list(self):
+ """
+ List all registered metadata.
+
+ Returns:
+ list[str]: keys (names of datasets) of all registered metadata
+ """
+ return list(self.keys())
+
+ def remove(self, name):
+ """
+ Alias of ``pop``.
+ """
+ self.pop(name)
+
+ def __str__(self):
+ return "MetadataCatalog(registered metadata: {})".format(", ".join(self.keys()))
+
+ __repr__ = __str__
+
+
+MetadataCatalog = _MetadataCatalog()
+MetadataCatalog.__doc__ = (
+ _MetadataCatalog.__doc__
+ + """
+ .. automethod:: detectron2.data.catalog.MetadataCatalog.get
+"""
+)
diff --git a/detectron2/detectron2/data/common.py b/detectron2/detectron2/data/common.py
new file mode 100755
index 0000000..bf24b1d
--- /dev/null
+++ b/detectron2/detectron2/data/common.py
@@ -0,0 +1,301 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import copy
+import itertools
+import logging
+import numpy as np
+import pickle
+import random
+from typing import Callable, Union
+import torch
+import torch.utils.data as data
+from torch.utils.data.sampler import Sampler
+
+from detectron2.utils.serialize import PicklableWrapper
+
+__all__ = ["MapDataset", "DatasetFromList", "AspectRatioGroupedDataset", "ToIterableDataset"]
+
+logger = logging.getLogger(__name__)
+
+
+def _shard_iterator_dataloader_worker(iterable):
+ # Shard the iterable if we're currently inside pytorch dataloader worker.
+ worker_info = data.get_worker_info()
+ if worker_info is None or worker_info.num_workers == 1:
+ # do nothing
+ yield from iterable
+ else:
+ yield from itertools.islice(iterable, worker_info.id, None, worker_info.num_workers)
+
+
+class _MapIterableDataset(data.IterableDataset):
+ """
+ Map a function over elements in an IterableDataset.
+
+ Similar to pytorch's MapIterDataPipe, but support filtering when map_func
+ returns None.
+
+ This class is not public-facing. Will be called by `MapDataset`.
+ """
+
+ def __init__(self, dataset, map_func):
+ self._dataset = dataset
+ self._map_func = PicklableWrapper(map_func) # wrap so that a lambda will work
+
+ def __len__(self):
+ return len(self._dataset)
+
+ def __iter__(self):
+ for x in map(self._map_func, self._dataset):
+ if x is not None:
+ yield x
+
+
+class MapDataset(data.Dataset):
+ """
+ Map a function over the elements in a dataset.
+ """
+
+ def __init__(self, dataset, map_func):
+ """
+ Args:
+ dataset: a dataset where map function is applied. Can be either
+ map-style or iterable dataset. When given an iterable dataset,
+ the returned object will also be an iterable dataset.
+ map_func: a callable which maps the element in dataset. map_func can
+ return None to skip the data (e.g. in case of errors).
+ How None is handled depends on the style of `dataset`.
+ If `dataset` is map-style, it randomly tries other elements.
+ If `dataset` is iterable, it skips the data and tries the next.
+ """
+ self._dataset = dataset
+ self._map_func = PicklableWrapper(map_func) # wrap so that a lambda will work
+
+ self._rng = random.Random(42)
+ self._fallback_candidates = set(range(len(dataset)))
+
+ def __new__(cls, dataset, map_func):
+ is_iterable = isinstance(dataset, data.IterableDataset)
+ if is_iterable:
+ return _MapIterableDataset(dataset, map_func)
+ else:
+ return super().__new__(cls)
+
+ def __getnewargs__(self):
+ return self._dataset, self._map_func
+
+ def __len__(self):
+ return len(self._dataset)
+
+ def __getitem__(self, idx):
+ retry_count = 0
+ cur_idx = int(idx)
+
+ while True:
+ data = self._map_func(self._dataset[cur_idx])
+ if data is not None:
+ self._fallback_candidates.add(cur_idx)
+ return data
+
+ # _map_func fails for this idx, use a random new index from the pool
+ retry_count += 1
+ self._fallback_candidates.discard(cur_idx)
+ cur_idx = self._rng.sample(self._fallback_candidates, k=1)[0]
+
+ if retry_count >= 3:
+ logger = logging.getLogger(__name__)
+ logger.warning(
+ "Failed to apply `_map_func` for idx: {}, retry count: {}".format(
+ idx, retry_count
+ )
+ )
+
+
+class _TorchSerializedList(object):
+ """
+ A list-like object whose items are serialized and stored in a torch tensor. When
+ launching a process that uses TorchSerializedList with "fork" start method,
+ the subprocess can read the same buffer without triggering copy-on-access. When
+ launching a process that uses TorchSerializedList with "spawn/forkserver" start
+ method, the list will be pickled by a special ForkingPickler registered by PyTorch
+ that moves data to shared memory. In both cases, this allows parent and child
+ processes to share RAM for the list data, hence avoids the issue in
+ https://github.com/pytorch/pytorch/issues/13246.
+
+ See also https://ppwwyyxx.com/blog/2022/Demystify-RAM-Usage-in-Multiprocess-DataLoader/
+ on how it works.
+ """
+
+ def __init__(self, lst: list):
+ self._lst = lst
+
+ def _serialize(data):
+ buffer = pickle.dumps(data, protocol=-1)
+ return np.frombuffer(buffer, dtype=np.uint8)
+
+ logger.info(
+ "Serializing {} elements to byte tensors and concatenating them all ...".format(
+ len(self._lst)
+ )
+ )
+ self._lst = [_serialize(x) for x in self._lst]
+ self._addr = np.asarray([len(x) for x in self._lst], dtype=np.int64)
+ self._addr = torch.from_numpy(np.cumsum(self._addr))
+ self._lst = torch.from_numpy(np.concatenate(self._lst))
+ logger.info("Serialized dataset takes {:.2f} MiB".format(len(self._lst) / 1024**2))
+
+ def __len__(self):
+ return len(self._addr)
+
+ def __getitem__(self, idx):
+ start_addr = 0 if idx == 0 else self._addr[idx - 1].item()
+ end_addr = self._addr[idx].item()
+ bytes = memoryview(self._lst[start_addr:end_addr].numpy())
+
+ # @lint-ignore PYTHONPICKLEISBAD
+ return pickle.loads(bytes)
+
+
+_DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD = _TorchSerializedList
+
+
+@contextlib.contextmanager
+def set_default_dataset_from_list_serialize_method(new):
+ """
+ Context manager for using custom serialize function when creating DatasetFromList
+ """
+
+ global _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD
+ orig = _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD
+ _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD = new
+ yield
+ _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD = orig
+
+
+class DatasetFromList(data.Dataset):
+ """
+ Wrap a list to a torch Dataset. It produces elements of the list as data.
+ """
+
+ def __init__(
+ self,
+ lst: list,
+ copy: bool = True,
+ serialize: Union[bool, Callable] = True,
+ ):
+ """
+ Args:
+ lst (list): a list which contains elements to produce.
+ copy (bool): whether to deepcopy the element when producing it,
+ so that the result can be modified in place without affecting the
+ source in the list.
+ serialize (bool or callable): whether to serialize the stroage to other
+ backend. If `True`, the default serialize method will be used, if given
+ a callable, the callable will be used as serialize method.
+ """
+ self._lst = lst
+ self._copy = copy
+ if not isinstance(serialize, (bool, Callable)):
+ raise TypeError(f"Unsupported type for argument `serailzie`: {serialize}")
+ self._serialize = serialize is not False
+
+ if self._serialize:
+ serialize_method = (
+ serialize
+ if isinstance(serialize, Callable)
+ else _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD
+ )
+ logger.info(f"Serializing the dataset using: {serialize_method}")
+ self._lst = serialize_method(self._lst)
+
+ def __len__(self):
+ return len(self._lst)
+
+ def __getitem__(self, idx):
+ if self._copy and not self._serialize:
+ return copy.deepcopy(self._lst[idx])
+ else:
+ return self._lst[idx]
+
+
+class ToIterableDataset(data.IterableDataset):
+ """
+ Convert an old indices-based (also called map-style) dataset
+ to an iterable-style dataset.
+ """
+
+ def __init__(self, dataset: data.Dataset, sampler: Sampler, shard_sampler: bool = True):
+ """
+ Args:
+ dataset: an old-style dataset with ``__getitem__``
+ sampler: a cheap iterable that produces indices to be applied on ``dataset``.
+ shard_sampler: whether to shard the sampler based on the current pytorch data loader
+ worker id. When an IterableDataset is forked by pytorch's DataLoader into multiple
+ workers, it is responsible for sharding its data based on worker id so that workers
+ don't produce identical data.
+
+ Most samplers (like our TrainingSampler) do not shard based on dataloader worker id
+ and this argument should be set to True. But certain samplers may be already
+ sharded, in that case this argument should be set to False.
+ """
+ assert not isinstance(dataset, data.IterableDataset), dataset
+ assert isinstance(sampler, Sampler), sampler
+ self.dataset = dataset
+ self.sampler = sampler
+ self.shard_sampler = shard_sampler
+
+ def __iter__(self):
+ if not self.shard_sampler:
+ sampler = self.sampler
+ else:
+ # With map-style dataset, `DataLoader(dataset, sampler)` runs the
+ # sampler in main process only. But `DataLoader(ToIterableDataset(dataset, sampler))`
+ # will run sampler in every of the N worker. So we should only keep 1/N of the ids on
+ # each worker. The assumption is that sampler is cheap to iterate so it's fine to
+ # discard ids in workers.
+ sampler = _shard_iterator_dataloader_worker(self.sampler)
+ for idx in sampler:
+ yield self.dataset[idx]
+
+ def __len__(self):
+ return len(self.sampler)
+
+
+class AspectRatioGroupedDataset(data.IterableDataset):
+ """
+ Batch data that have similar aspect ratio together.
+ In this implementation, images whose aspect ratio < (or >) 1 will
+ be batched together.
+ This improves training speed because the images then need less padding
+ to form a batch.
+
+ It assumes the underlying dataset produces dicts with "width" and "height" keys.
+ It will then produce a list of original dicts with length = batch_size,
+ all with similar aspect ratios.
+ """
+
+ def __init__(self, dataset, batch_size):
+ """
+ Args:
+ dataset: an iterable. Each element must be a dict with keys
+ "width" and "height", which will be used to batch data.
+ batch_size (int):
+ """
+ self.dataset = dataset
+ self.batch_size = batch_size
+ self._buckets = [[] for _ in range(2)]
+ # Hard-coded two aspect ratio groups: w > h and w < h.
+ # Can add support for more aspect ratio groups, but doesn't seem useful
+
+ def __iter__(self):
+ for d in self.dataset:
+ w, h = d["width"], d["height"]
+ bucket_id = 0 if w > h else 1
+ bucket = self._buckets[bucket_id]
+ bucket.append(d)
+ if len(bucket) == self.batch_size:
+ data = bucket[:]
+ # Clear bucket first, because code after yield is not
+ # guaranteed to execute
+ del bucket[:]
+ yield data
diff --git a/detectron2/detectron2/data/dataset_mapper.py b/detectron2/detectron2/data/dataset_mapper.py
new file mode 100755
index 0000000..a38c8df
--- /dev/null
+++ b/detectron2/detectron2/data/dataset_mapper.py
@@ -0,0 +1,191 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import numpy as np
+from typing import List, Optional, Union
+import torch
+
+from detectron2.config import configurable
+
+from . import detection_utils as utils
+from . import transforms as T
+
+"""
+This file contains the default mapping that's applied to "dataset dicts".
+"""
+
+__all__ = ["DatasetMapper"]
+
+
+class DatasetMapper:
+ """
+ A callable which takes a dataset dict in Detectron2 Dataset format,
+ and map it into a format used by the model.
+
+ This is the default callable to be used to map your dataset dict into training data.
+ You may need to follow it to implement your own one for customized logic,
+ such as a different way to read or transform images.
+ See :doc:`/tutorials/data_loading` for details.
+
+ The callable currently does the following:
+
+ 1. Read the image from "file_name"
+ 2. Applies cropping/geometric transforms to the image and annotations
+ 3. Prepare data and annotations to Tensor and :class:`Instances`
+ """
+
+ @configurable
+ def __init__(
+ self,
+ is_train: bool,
+ *,
+ augmentations: List[Union[T.Augmentation, T.Transform]],
+ image_format: str,
+ use_instance_mask: bool = False,
+ use_keypoint: bool = False,
+ instance_mask_format: str = "polygon",
+ keypoint_hflip_indices: Optional[np.ndarray] = None,
+ precomputed_proposal_topk: Optional[int] = None,
+ recompute_boxes: bool = False,
+ ):
+ """
+ NOTE: this interface is experimental.
+
+ Args:
+ is_train: whether it's used in training or inference
+ augmentations: a list of augmentations or deterministic transforms to apply
+ image_format: an image format supported by :func:`detection_utils.read_image`.
+ use_instance_mask: whether to process instance segmentation annotations, if available
+ use_keypoint: whether to process keypoint annotations if available
+ instance_mask_format: one of "polygon" or "bitmask". Process instance segmentation
+ masks into this format.
+ keypoint_hflip_indices: see :func:`detection_utils.create_keypoint_hflip_indices`
+ precomputed_proposal_topk: if given, will load pre-computed
+ proposals from dataset_dict and keep the top k proposals for each image.
+ recompute_boxes: whether to overwrite bounding box annotations
+ by computing tight bounding boxes from instance mask annotations.
+ """
+ if recompute_boxes:
+ assert use_instance_mask, "recompute_boxes requires instance masks"
+ # fmt: off
+ self.is_train = is_train
+ self.augmentations = T.AugmentationList(augmentations)
+ self.image_format = image_format
+ self.use_instance_mask = use_instance_mask
+ self.instance_mask_format = instance_mask_format
+ self.use_keypoint = use_keypoint
+ self.keypoint_hflip_indices = keypoint_hflip_indices
+ self.proposal_topk = precomputed_proposal_topk
+ self.recompute_boxes = recompute_boxes
+ # fmt: on
+ logger = logging.getLogger(__name__)
+ mode = "training" if is_train else "inference"
+ logger.info(f"[DatasetMapper] Augmentations used in {mode}: {augmentations}")
+
+ @classmethod
+ def from_config(cls, cfg, is_train: bool = True):
+ augs = utils.build_augmentation(cfg, is_train)
+ if cfg.INPUT.CROP.ENABLED and is_train:
+ augs.insert(0, T.RandomCrop(cfg.INPUT.CROP.TYPE, cfg.INPUT.CROP.SIZE))
+ recompute_boxes = cfg.MODEL.MASK_ON
+ else:
+ recompute_boxes = False
+
+ ret = {
+ "is_train": is_train,
+ "augmentations": augs,
+ "image_format": cfg.INPUT.FORMAT,
+ "use_instance_mask": cfg.MODEL.MASK_ON,
+ "instance_mask_format": cfg.INPUT.MASK_FORMAT,
+ "use_keypoint": cfg.MODEL.KEYPOINT_ON,
+ "recompute_boxes": recompute_boxes,
+ }
+
+ if cfg.MODEL.KEYPOINT_ON:
+ ret["keypoint_hflip_indices"] = utils.create_keypoint_hflip_indices(cfg.DATASETS.TRAIN)
+
+ if cfg.MODEL.LOAD_PROPOSALS:
+ ret["precomputed_proposal_topk"] = (
+ cfg.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TRAIN
+ if is_train
+ else cfg.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TEST
+ )
+ return ret
+
+ def _transform_annotations(self, dataset_dict, transforms, image_shape):
+ # USER: Modify this if you want to keep them for some reason.
+ for anno in dataset_dict["annotations"]:
+ if not self.use_instance_mask:
+ anno.pop("segmentation", None)
+ if not self.use_keypoint:
+ anno.pop("keypoints", None)
+
+ # USER: Implement additional transformations if you have other types of data
+ annos = [
+ utils.transform_instance_annotations(
+ obj, transforms, image_shape, keypoint_hflip_indices=self.keypoint_hflip_indices
+ )
+ for obj in dataset_dict.pop("annotations")
+ if obj.get("iscrowd", 0) == 0
+ ]
+ instances = utils.annotations_to_instances(
+ annos, image_shape, mask_format=self.instance_mask_format
+ )
+
+ # After transforms such as cropping are applied, the bounding box may no longer
+ # tightly bound the object. As an example, imagine a triangle object
+ # [(0,0), (2,0), (0,2)] cropped by a box [(1,0),(2,2)] (XYXY format). The tight
+ # bounding box of the cropped triangle should be [(1,0),(2,1)], which is not equal to
+ # the intersection of original bounding box and the cropping box.
+ if self.recompute_boxes:
+ instances.gt_boxes = instances.gt_masks.get_bounding_boxes()
+ dataset_dict["instances"] = utils.filter_empty_instances(instances)
+
+ def __call__(self, dataset_dict):
+ """
+ Args:
+ dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
+
+ Returns:
+ dict: a format that builtin models in detectron2 accept
+ """
+ dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
+ # USER: Write your own image loading if it's not from a file
+ image = utils.read_image(dataset_dict["file_name"], format=self.image_format)
+ utils.check_image_size(dataset_dict, image)
+
+ # USER: Remove if you don't do semantic/panoptic segmentation.
+ if "sem_seg_file_name" in dataset_dict:
+ sem_seg_gt = utils.read_image(dataset_dict.pop("sem_seg_file_name"), "L").squeeze(2)
+ else:
+ sem_seg_gt = None
+
+ aug_input = T.AugInput(image, sem_seg=sem_seg_gt)
+ transforms = self.augmentations(aug_input)
+ image, sem_seg_gt = aug_input.image, aug_input.sem_seg
+
+ image_shape = image.shape[:2] # h, w
+ # Pytorch's dataloader is efficient on torch.Tensor due to shared-memory,
+ # but not efficient on large generic data structures due to the use of pickle & mp.Queue.
+ # Therefore it's important to use torch.Tensor.
+ dataset_dict["image"] = torch.as_tensor(np.ascontiguousarray(image.transpose(2, 0, 1)))
+ if sem_seg_gt is not None:
+ dataset_dict["sem_seg"] = torch.as_tensor(sem_seg_gt.astype("long"))
+
+ # USER: Remove if you don't use pre-computed proposals.
+ # Most users would not need this feature.
+ if self.proposal_topk is not None:
+ utils.transform_proposals(
+ dataset_dict, image_shape, transforms, proposal_topk=self.proposal_topk
+ )
+
+ if not self.is_train:
+ # USER: Modify this if you want to keep them for some reason.
+ # dataset_dict.pop("annotations", None)
+ dataset_dict.pop("sem_seg_file_name", None)
+ # return dataset_dict
+
+ if "annotations" in dataset_dict:
+ self._transform_annotations(dataset_dict, transforms, image_shape)
+
+ return dataset_dict
diff --git a/detectron2/detectron2/data/datasets/README.md b/detectron2/detectron2/data/datasets/README.md
new file mode 100755
index 0000000..9fb3e4f
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/README.md
@@ -0,0 +1,9 @@
+
+
+### Common Datasets
+
+The dataset implemented here do not need to load the data into the final format.
+It should provide the minimal data structure needed to use the dataset, so it can be very efficient.
+
+For example, for an image dataset, just provide the file names and labels, but don't read the images.
+Let the downstream decide how to read.
diff --git a/detectron2/detectron2/data/datasets/__init__.py b/detectron2/detectron2/data/datasets/__init__.py
new file mode 100755
index 0000000..a44bedc
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .coco import load_coco_json, load_sem_seg, register_coco_instances, convert_to_coco_json
+from .coco_panoptic import register_coco_panoptic, register_coco_panoptic_separated
+from .lvis import load_lvis_json, register_lvis_instances, get_lvis_instances_meta
+from .pascal_voc import load_voc_instances, register_pascal_voc
+from . import builtin as _builtin # ensure the builtin datasets are registered
+
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/detectron2/detectron2/data/datasets/builtin.py b/detectron2/detectron2/data/datasets/builtin.py
new file mode 100755
index 0000000..c943f52
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/builtin.py
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+
+"""
+This file registers pre-defined datasets at hard-coded paths, and their metadata.
+
+We hard-code metadata for common datasets. This will enable:
+1. Consistency check when loading the datasets
+2. Use models on these standard datasets directly and run demos,
+ without having to download the dataset annotations
+
+We hard-code some paths to the dataset that's assumed to
+exist in "./datasets/".
+
+Users SHOULD NOT use this file to create new dataset / metadata for new dataset.
+To add new dataset, refer to the tutorial "docs/DATASETS.md".
+"""
+
+import os
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+
+from .builtin_meta import ADE20K_SEM_SEG_CATEGORIES, _get_builtin_metadata
+from .cityscapes import load_cityscapes_instances, load_cityscapes_semantic
+from .cityscapes_panoptic import register_all_cityscapes_panoptic
+from .coco import load_sem_seg, register_coco_instances
+from .coco_panoptic import register_coco_panoptic, register_coco_panoptic_separated
+from .lvis import get_lvis_instances_meta, register_lvis_instances
+from .pascal_voc import register_pascal_voc
+
+# ==== Predefined datasets and splits for COCO ==========
+
+_PREDEFINED_SPLITS_COCO = {}
+_PREDEFINED_SPLITS_COCO["coco"] = {
+ "coco_2014_train": ("coco/train2014", "coco/annotations/instances_train2014.json"),
+ "coco_2014_val": ("coco/val2014", "coco/annotations/instances_val2014.json"),
+ "coco_2014_minival": ("coco/val2014", "coco/annotations/instances_minival2014.json"),
+ "coco_2014_valminusminival": (
+ "coco/val2014",
+ "coco/annotations/instances_valminusminival2014.json",
+ ),
+ "coco_2017_train": ("coco/train2017", "coco/annotations/instances_train2017.json"),
+ "coco_2017_val": ("coco/val2017", "coco/annotations/instances_val2017.json"),
+ "coco_2017_val_100": ("coco/val2017", "coco/annotations/instances_val2017_100.json"),
+ "coco_2017_test": ("coco/test2017", "coco/annotations/image_info_test2017.json"),
+ "coco_2017_test-dev": ("coco/test2017", "coco/annotations/image_info_test-dev2017.json"),
+
+ # COCO subsets for PointWSSIS, Weakly Semi-Supervised Instance Segmentation with Point Labels.
+ "coco_2017_train_1p_s": ("coco/train2017", "coco/annotations/instances_train2017_1p_s.json"),
+ "coco_2017_train_2p_s": ("coco/train2017", "coco/annotations/instances_train2017_2p_s.json"),
+ "coco_2017_train_5p_s": ("coco/train2017", "coco/annotations/instances_train2017_5p_s.json"),
+ "coco_2017_train_10p_s": ("coco/train2017", "coco/annotations/instances_train2017_10p_s.json"),
+ "coco_2017_train_20p_s": ("coco/train2017", "coco/annotations/instances_train2017_20p_s.json"),
+ "coco_2017_train_30p_s": ("coco/train2017", "coco/annotations/instances_train2017_30p_s.json"),
+ "coco_2017_train_40p_s": ("coco/train2017", "coco/annotations/instances_train2017_40p_s.json"),
+ "coco_2017_train_50p_s": ("coco/train2017", "coco/annotations/instances_train2017_50p_s.json"),
+ "coco_2017_train_60p_s": ("coco/train2017", "coco/annotations/instances_train2017_60p_s.json"),
+ "coco_2017_train_70p_s": ("coco/train2017", "coco/annotations/instances_train2017_70p_s.json"),
+ "coco_2017_train_80p_s": ("coco/train2017", "coco/annotations/instances_train2017_80p_s.json"),
+ "coco_2017_train_90p_s": ("coco/train2017", "coco/annotations/instances_train2017_90p_s.json"),
+
+ "coco_2017_train_1p_w": ("coco/train2017", "coco/annotations/instances_train2017_1p_w.json"),
+ "coco_2017_train_2p_w": ("coco/train2017", "coco/annotations/instances_train2017_2p_w.json"),
+ "coco_2017_train_5p_w": ("coco/train2017", "coco/annotations/instances_train2017_5p_w.json"),
+ "coco_2017_train_10p_w": ("coco/train2017", "coco/annotations/instances_train2017_10p_w.json"),
+ "coco_2017_train_20p_w": ("coco/train2017", "coco/annotations/instances_train2017_20p_w.json"),
+ "coco_2017_train_30p_w": ("coco/train2017", "coco/annotations/instances_train2017_30p_w.json"),
+ "coco_2017_train_40p_w": ("coco/train2017", "coco/annotations/instances_train2017_40p_w.json"),
+ "coco_2017_train_50p_w": ("coco/train2017", "coco/annotations/instances_train2017_50p_w.json"),
+ "coco_2017_train_60p_w": ("coco/train2017", "coco/annotations/instances_train2017_60p_w.json"),
+ "coco_2017_train_70p_w": ("coco/train2017", "coco/annotations/instances_train2017_70p_w.json"),
+ "coco_2017_train_80p_w": ("coco/train2017", "coco/annotations/instances_train2017_80p_w.json"),
+ "coco_2017_train_90p_w": ("coco/train2017", "coco/annotations/instances_train2017_90p_w.json"),
+
+ "coco_2017_train_1p_sw": ("coco/train2017", "coco/annotations/instances_train2017_1p_sw.json"),
+ "coco_2017_train_2p_sw": ("coco/train2017", "coco/annotations/instances_train2017_2p_sw.json"),
+ "coco_2017_train_5p_sw": ("coco/train2017", "coco/annotations/instances_train2017_5p_sw.json"),
+ "coco_2017_train_10p_sw": ("coco/train2017", "coco/annotations/instances_train2017_10p_sw.json"),
+ "coco_2017_train_20p_sw": ("coco/train2017", "coco/annotations/instances_train2017_20p_sw.json"),
+ "coco_2017_train_30p_sw": ("coco/train2017", "coco/annotations/instances_train2017_30p_sw.json"),
+ "coco_2017_train_40p_sw": ("coco/train2017", "coco/annotations/instances_train2017_40p_sw.json"),
+ "coco_2017_train_50p_sw": ("coco/train2017", "coco/annotations/instances_train2017_50p_sw.json"),
+ "coco_2017_train_60p_sw": ("coco/train2017", "coco/annotations/instances_train2017_60p_sw.json"),
+ "coco_2017_train_70p_sw": ("coco/train2017", "coco/annotations/instances_train2017_70p_sw.json"),
+ "coco_2017_train_80p_sw": ("coco/train2017", "coco/annotations/instances_train2017_80p_sw.json"),
+ "coco_2017_train_90p_sw": ("coco/train2017", "coco/annotations/instances_train2017_90p_sw.json"),
+
+ "coco_2017_train_1p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_1p_sw_refined.json"),
+ "coco_2017_train_2p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_2p_sw_refined.json"),
+ "coco_2017_train_5p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_5p_sw_refined.json"),
+ "coco_2017_train_10p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_10p_sw_refined.json"),
+ "coco_2017_train_20p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_20p_sw_refined.json"),
+ "coco_2017_train_30p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_30p_sw_refined.json"),
+ "coco_2017_train_40p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_40p_sw_refined.json"),
+ "coco_2017_train_50p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_50p_sw_refined.json"),
+ "coco_2017_train_60p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_60p_sw_refined.json"),
+ "coco_2017_train_70p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_70p_sw_refined.json"),
+ "coco_2017_train_80p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_80p_sw_refined.json"),
+ "coco_2017_train_90p_sw_refined": ("coco/train2017", "coco/annotations/instances_train2017_90p_sw_refined.json"),
+}
+
+
+_PREDEFINED_SPLITS_COCO["coco_person"] = {
+ "keypoints_coco_2014_train": (
+ "coco/train2014",
+ "coco/annotations/person_keypoints_train2014.json",
+ ),
+ "keypoints_coco_2014_val": ("coco/val2014", "coco/annotations/person_keypoints_val2014.json"),
+ "keypoints_coco_2014_minival": (
+ "coco/val2014",
+ "coco/annotations/person_keypoints_minival2014.json",
+ ),
+ "keypoints_coco_2014_valminusminival": (
+ "coco/val2014",
+ "coco/annotations/person_keypoints_valminusminival2014.json",
+ ),
+ "keypoints_coco_2017_train": (
+ "coco/train2017",
+ "coco/annotations/person_keypoints_train2017.json",
+ ),
+ "keypoints_coco_2017_val": ("coco/val2017", "coco/annotations/person_keypoints_val2017.json"),
+ "keypoints_coco_2017_val_100": (
+ "coco/val2017",
+ "coco/annotations/person_keypoints_val2017_100.json",
+ ),
+}
+
+
+_PREDEFINED_SPLITS_COCO_PANOPTIC = {
+ "coco_2017_train_panoptic": (
+ # This is the original panoptic annotation directory
+ "coco/panoptic_train2017",
+ "coco/annotations/panoptic_train2017.json",
+ # This directory contains semantic annotations that are
+ # converted from panoptic annotations.
+ # It is used by PanopticFPN.
+ # You can use the script at detectron2/datasets/prepare_panoptic_fpn.py
+ # to create these directories.
+ "coco/panoptic_stuff_train2017",
+ ),
+ "coco_2017_val_panoptic": (
+ "coco/panoptic_val2017",
+ "coco/annotations/panoptic_val2017.json",
+ "coco/panoptic_stuff_val2017",
+ ),
+ "coco_2017_val_100_panoptic": (
+ "coco/panoptic_val2017_100",
+ "coco/annotations/panoptic_val2017_100.json",
+ "coco/panoptic_stuff_val2017_100",
+ ),
+}
+
+
+def register_all_coco(root):
+ for dataset_name, splits_per_dataset in _PREDEFINED_SPLITS_COCO.items():
+ for key, (image_root, json_file) in splits_per_dataset.items():
+ # Assume pre-defined datasets live in `./datasets`.
+ register_coco_instances(
+ key,
+ _get_builtin_metadata(dataset_name),
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+
+ for (
+ prefix,
+ (panoptic_root, panoptic_json, semantic_root),
+ ) in _PREDEFINED_SPLITS_COCO_PANOPTIC.items():
+ prefix_instances = prefix[: -len("_panoptic")]
+ instances_meta = MetadataCatalog.get(prefix_instances)
+ image_root, instances_json = instances_meta.image_root, instances_meta.json_file
+ # The "separated" version of COCO panoptic segmentation dataset,
+ # e.g. used by Panoptic FPN
+ register_coco_panoptic_separated(
+ prefix,
+ _get_builtin_metadata("coco_panoptic_separated"),
+ image_root,
+ os.path.join(root, panoptic_root),
+ os.path.join(root, panoptic_json),
+ os.path.join(root, semantic_root),
+ instances_json,
+ )
+ # The "standard" version of COCO panoptic segmentation dataset,
+ # e.g. used by Panoptic-DeepLab
+ register_coco_panoptic(
+ prefix,
+ _get_builtin_metadata("coco_panoptic_standard"),
+ image_root,
+ os.path.join(root, panoptic_root),
+ os.path.join(root, panoptic_json),
+ instances_json,
+ )
+
+
+# ==== Predefined datasets and splits for BDD100K ==========
+_PREDEFINED_SPLITS_BDD100K = {}
+_PREDEFINED_SPLITS_BDD100K["bdd100k"] = {
+
+ "bdd100k_det_train": ("BDD100K/train", "BDD100K/annotations/bdd100k_det_labels_train_reid.json"),
+ "bdd100k_det_val": ("BDD100K/val", "BDD100K/annotations/bdd100k_det_labels_val.json"),
+ "bdd100k_inst_det_train": ("BDD100K/train", "BDD100K/annotations/bdd100k_inst_det_labels_train_reid.json"),
+ "bdd100k_inst_det_val": ("BDD100K/val", "BDD100K/annotations/bdd100k_inst_det_labels_val.json"),
+
+ "bdd100k_train": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train.json"),
+ "bdd100k_val": ("BDD100K/val", "BDD100K/annotations/bdd100k_ins_labels_val.json"),
+
+ "bdd100k_train_5p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_5p_s.json"),
+ "bdd100k_train_10p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_10p_s.json"),
+ "bdd100k_train_20p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_20p_s.json"),
+ "bdd100k_train_30p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_30p_s.json"),
+ "bdd100k_train_40p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_40p_s.json"),
+ "bdd100k_train_50p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_50p_s.json"),
+ "bdd100k_train_60p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_60p_s.json"),
+ "bdd100k_train_70p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_70p_s.json"),
+ "bdd100k_train_80p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_80p_s.json"),
+ "bdd100k_train_90p_s": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_90p_s.json"),
+
+ "bdd100k_train_5p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_5p_w.json"),
+ "bdd100k_train_10p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_10p_w.json"),
+ "bdd100k_train_20p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_20p_w.json"),
+ "bdd100k_train_30p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_30p_w.json"),
+ "bdd100k_train_40p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_40p_w.json"),
+ "bdd100k_train_50p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_50p_w.json"),
+ "bdd100k_train_60p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_60p_w.json"),
+ "bdd100k_train_70p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_70p_w.json"),
+ "bdd100k_train_80p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_80p_w.json"),
+ "bdd100k_train_90p_w": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_90p_w.json"),
+
+ "bdd100k_train_5p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_5p_sw.json"),
+ "bdd100k_train_10p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_10p_sw.json"),
+ "bdd100k_train_20p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_20p_sw.json"),
+ "bdd100k_train_30p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_30p_sw.json"),
+ "bdd100k_train_40p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_40p_sw.json"),
+ "bdd100k_train_50p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_50p_sw.json"),
+ "bdd100k_train_60p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_60p_sw.json"),
+ "bdd100k_train_70p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_70p_sw.json"),
+ "bdd100k_train_80p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_80p_sw.json"),
+ "bdd100k_train_90p_sw": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_90p_sw.json"),
+
+ "bdd100k_train_5p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_5p_sw_refined.json"),
+ "bdd100k_train_10p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_10p_sw_refined.json"),
+ "bdd100k_train_20p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_20p_sw_refined.json"),
+ "bdd100k_train_30p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_30p_sw_refined.json"),
+ "bdd100k_train_40p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_40p_sw_refined.json"),
+ "bdd100k_train_50p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_50p_sw_refined.json"),
+ "bdd100k_train_60p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_60p_sw_refined.json"),
+ "bdd100k_train_70p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_70p_sw_refined.json"),
+ "bdd100k_train_80p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_80p_sw_refined.json"),
+ "bdd100k_train_90p_sw_refined": ("BDD100K/train", "BDD100K/annotations/bdd100k_ins_labels_train_90p_sw_refined.json"),
+ }
+
+
+def register_all_bdd100k(root):
+ for dataset_name, splits_per_dataset in _PREDEFINED_SPLITS_BDD100K.items():
+ for key, (image_root, json_file) in splits_per_dataset.items():
+ # Assume pre-defined datasets live in `./datasets`.
+ register_coco_instances(
+ key,
+ _get_builtin_metadata(dataset_name),
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+
+
+# ==== Predefined datasets and splits for LVIS ==========
+
+
+_PREDEFINED_SPLITS_LVIS = {
+ "lvis_v1": {
+ "lvis_v1_train": ("coco/", "lvis/lvis_v1_train.json"),
+ "lvis_v1_val": ("coco/", "lvis/lvis_v1_val.json"),
+ "lvis_v1_test_dev": ("coco/", "lvis/lvis_v1_image_info_test_dev.json"),
+ "lvis_v1_test_challenge": ("coco/", "lvis/lvis_v1_image_info_test_challenge.json"),
+ },
+ "lvis_v0.5": {
+ "lvis_v0.5_train": ("coco/", "lvis/lvis_v0.5_train.json"),
+ "lvis_v0.5_val": ("coco/", "lvis/lvis_v0.5_val.json"),
+ "lvis_v0.5_val_rand_100": ("coco/", "lvis/lvis_v0.5_val_rand_100.json"),
+ "lvis_v0.5_test": ("coco/", "lvis/lvis_v0.5_image_info_test.json"),
+ },
+ "lvis_v0.5_cocofied": {
+ "lvis_v0.5_train_cocofied": ("coco/", "lvis/lvis_v0.5_train_cocofied.json"),
+ "lvis_v0.5_val_cocofied": ("coco/", "lvis/lvis_v0.5_val_cocofied.json"),
+ },
+}
+
+
+def register_all_lvis(root):
+ for dataset_name, splits_per_dataset in _PREDEFINED_SPLITS_LVIS.items():
+ for key, (image_root, json_file) in splits_per_dataset.items():
+ register_lvis_instances(
+ key,
+ get_lvis_instances_meta(dataset_name),
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+
+
+# ==== Predefined splits for raw cityscapes images ===========
+_RAW_CITYSCAPES_SPLITS = {
+ "cityscapes_fine_{task}_train": ("cityscapes/leftImg8bit/train/", "cityscapes/gtFine/train/"),
+ "cityscapes_fine_{task}_val": ("cityscapes/leftImg8bit/val/", "cityscapes/gtFine/val/"),
+ "cityscapes_fine_{task}_test": ("cityscapes/leftImg8bit/test/", "cityscapes/gtFine/test/"),
+}
+
+
+def register_all_cityscapes(root):
+ for key, (image_dir, gt_dir) in _RAW_CITYSCAPES_SPLITS.items():
+ meta = _get_builtin_metadata("cityscapes")
+ image_dir = os.path.join(root, image_dir)
+ gt_dir = os.path.join(root, gt_dir)
+
+ inst_key = key.format(task="instance_seg")
+ DatasetCatalog.register(
+ inst_key,
+ lambda x=image_dir, y=gt_dir: load_cityscapes_instances(
+ x, y, from_json=True, to_polygons=True
+ ),
+ )
+ MetadataCatalog.get(inst_key).set(
+ image_dir=image_dir, gt_dir=gt_dir, evaluator_type="cityscapes_instance", **meta
+ )
+
+ sem_key = key.format(task="sem_seg")
+ DatasetCatalog.register(
+ sem_key, lambda x=image_dir, y=gt_dir: load_cityscapes_semantic(x, y)
+ )
+ MetadataCatalog.get(sem_key).set(
+ image_dir=image_dir,
+ gt_dir=gt_dir,
+ evaluator_type="cityscapes_sem_seg",
+ ignore_label=255,
+ **meta,
+ )
+
+
+# ==== Predefined splits for PASCAL VOC ===========
+def register_all_pascal_voc(root):
+ SPLITS = [
+ ("voc_2007_trainval", "VOC2007", "trainval"),
+ ("voc_2007_train", "VOC2007", "train"),
+ ("voc_2007_val", "VOC2007", "val"),
+ ("voc_2007_test", "VOC2007", "test"),
+ ("voc_2012_trainval", "VOC2012", "trainval"),
+ ("voc_2012_train", "VOC2012", "train"),
+ ("voc_2012_val", "VOC2012", "val"),
+ ]
+ for name, dirname, split in SPLITS:
+ year = 2007 if "2007" in name else 2012
+ register_pascal_voc(name, os.path.join(root, dirname), split, year)
+ MetadataCatalog.get(name).evaluator_type = "pascal_voc"
+
+
+def register_all_ade20k(root):
+ root = os.path.join(root, "ADEChallengeData2016")
+ for name, dirname in [("train", "training"), ("val", "validation")]:
+ image_dir = os.path.join(root, "images", dirname)
+ gt_dir = os.path.join(root, "annotations_detectron2", dirname)
+ name = f"ade20k_sem_seg_{name}"
+ DatasetCatalog.register(
+ name, lambda x=image_dir, y=gt_dir: load_sem_seg(y, x, gt_ext="png", image_ext="jpg")
+ )
+ MetadataCatalog.get(name).set(
+ stuff_classes=ADE20K_SEM_SEG_CATEGORIES[:],
+ image_root=image_dir,
+ sem_seg_root=gt_dir,
+ evaluator_type="sem_seg",
+ ignore_label=255,
+ )
+
+
+# True for open source;
+# Internally at fb, we register them elsewhere
+if __name__.endswith(".builtin"):
+ # Assume pre-defined datasets live in `./datasets`.
+ _root = os.path.expanduser(os.getenv("DETECTRON2_DATASETS", "datasets"))
+ register_all_coco(_root)
+ register_all_lvis(_root)
+ register_all_cityscapes(_root)
+ register_all_cityscapes_panoptic(_root)
+ register_all_pascal_voc(_root)
+ register_all_ade20k(_root)
+ register_all_bdd100k(_root)
diff --git a/detectron2/detectron2/data/datasets/builtin_meta.py b/detectron2/detectron2/data/datasets/builtin_meta.py
new file mode 100755
index 0000000..b7f50fc
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/builtin_meta.py
@@ -0,0 +1,376 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+Note:
+For your custom dataset, there is no need to hard-code metadata anywhere in the code.
+For example, for COCO-format dataset, metadata will be obtained automatically
+when calling `load_coco_json`. For other dataset, metadata may also be obtained in other ways
+during loading.
+
+However, we hard-coded metadata for a few common dataset here.
+The only goal is to allow users who don't have these dataset to use pre-trained models.
+Users don't have to download a COCO json (which contains metadata), in order to visualize a
+COCO model (with correct class names and colors).
+"""
+
+
+# All coco categories, together with their nice-looking visualization colors
+# It's from https://github.com/cocodataset/panopticapi/blob/master/panoptic_coco_categories.json
+COCO_CATEGORIES = [
+ {"color": [220, 20, 60], "isthing": 1, "id": 1, "name": "person"},
+ {"color": [119, 11, 32], "isthing": 1, "id": 2, "name": "bicycle"},
+ {"color": [0, 0, 142], "isthing": 1, "id": 3, "name": "car"},
+ {"color": [0, 0, 230], "isthing": 1, "id": 4, "name": "motorcycle"},
+ {"color": [106, 0, 228], "isthing": 1, "id": 5, "name": "airplane"},
+ {"color": [0, 60, 100], "isthing": 1, "id": 6, "name": "bus"},
+ {"color": [0, 80, 100], "isthing": 1, "id": 7, "name": "train"},
+ {"color": [0, 0, 70], "isthing": 1, "id": 8, "name": "truck"},
+ {"color": [0, 0, 192], "isthing": 1, "id": 9, "name": "boat"},
+ {"color": [250, 170, 30], "isthing": 1, "id": 10, "name": "traffic light"},
+ {"color": [100, 170, 30], "isthing": 1, "id": 11, "name": "fire hydrant"},
+ {"color": [220, 220, 0], "isthing": 1, "id": 13, "name": "stop sign"},
+ {"color": [175, 116, 175], "isthing": 1, "id": 14, "name": "parking meter"},
+ {"color": [250, 0, 30], "isthing": 1, "id": 15, "name": "bench"},
+ {"color": [165, 42, 42], "isthing": 1, "id": 16, "name": "bird"},
+ {"color": [255, 77, 255], "isthing": 1, "id": 17, "name": "cat"},
+ {"color": [0, 226, 252], "isthing": 1, "id": 18, "name": "dog"},
+ {"color": [182, 182, 255], "isthing": 1, "id": 19, "name": "horse"},
+ {"color": [0, 82, 0], "isthing": 1, "id": 20, "name": "sheep"},
+ {"color": [120, 166, 157], "isthing": 1, "id": 21, "name": "cow"},
+ {"color": [110, 76, 0], "isthing": 1, "id": 22, "name": "elephant"},
+ {"color": [174, 57, 255], "isthing": 1, "id": 23, "name": "bear"},
+ {"color": [199, 100, 0], "isthing": 1, "id": 24, "name": "zebra"},
+ {"color": [72, 0, 118], "isthing": 1, "id": 25, "name": "giraffe"},
+ {"color": [255, 179, 240], "isthing": 1, "id": 27, "name": "backpack"},
+ {"color": [0, 125, 92], "isthing": 1, "id": 28, "name": "umbrella"},
+ {"color": [209, 0, 151], "isthing": 1, "id": 31, "name": "handbag"},
+ {"color": [188, 208, 182], "isthing": 1, "id": 32, "name": "tie"},
+ {"color": [0, 220, 176], "isthing": 1, "id": 33, "name": "suitcase"},
+ {"color": [255, 99, 164], "isthing": 1, "id": 34, "name": "frisbee"},
+ {"color": [92, 0, 73], "isthing": 1, "id": 35, "name": "skis"},
+ {"color": [133, 129, 255], "isthing": 1, "id": 36, "name": "snowboard"},
+ {"color": [78, 180, 255], "isthing": 1, "id": 37, "name": "sports ball"},
+ {"color": [0, 228, 0], "isthing": 1, "id": 38, "name": "kite"},
+ {"color": [174, 255, 243], "isthing": 1, "id": 39, "name": "baseball bat"},
+ {"color": [45, 89, 255], "isthing": 1, "id": 40, "name": "baseball glove"},
+ {"color": [134, 134, 103], "isthing": 1, "id": 41, "name": "skateboard"},
+ {"color": [145, 148, 174], "isthing": 1, "id": 42, "name": "surfboard"},
+ {"color": [255, 208, 186], "isthing": 1, "id": 43, "name": "tennis racket"},
+ {"color": [197, 226, 255], "isthing": 1, "id": 44, "name": "bottle"},
+ {"color": [171, 134, 1], "isthing": 1, "id": 46, "name": "wine glass"},
+ {"color": [109, 63, 54], "isthing": 1, "id": 47, "name": "cup"},
+ {"color": [207, 138, 255], "isthing": 1, "id": 48, "name": "fork"},
+ {"color": [151, 0, 95], "isthing": 1, "id": 49, "name": "knife"},
+ {"color": [9, 80, 61], "isthing": 1, "id": 50, "name": "spoon"},
+ {"color": [84, 105, 51], "isthing": 1, "id": 51, "name": "bowl"},
+ {"color": [74, 65, 105], "isthing": 1, "id": 52, "name": "banana"},
+ {"color": [166, 196, 102], "isthing": 1, "id": 53, "name": "apple"},
+ {"color": [208, 195, 210], "isthing": 1, "id": 54, "name": "sandwich"},
+ {"color": [255, 109, 65], "isthing": 1, "id": 55, "name": "orange"},
+ {"color": [0, 143, 149], "isthing": 1, "id": 56, "name": "broccoli"},
+ {"color": [179, 0, 194], "isthing": 1, "id": 57, "name": "carrot"},
+ {"color": [209, 99, 106], "isthing": 1, "id": 58, "name": "hot dog"},
+ {"color": [5, 121, 0], "isthing": 1, "id": 59, "name": "pizza"},
+ {"color": [227, 255, 205], "isthing": 1, "id": 60, "name": "donut"},
+ {"color": [147, 186, 208], "isthing": 1, "id": 61, "name": "cake"},
+ {"color": [153, 69, 1], "isthing": 1, "id": 62, "name": "chair"},
+ {"color": [3, 95, 161], "isthing": 1, "id": 63, "name": "couch"},
+ {"color": [163, 255, 0], "isthing": 1, "id": 64, "name": "potted plant"},
+ {"color": [119, 0, 170], "isthing": 1, "id": 65, "name": "bed"},
+ {"color": [0, 182, 199], "isthing": 1, "id": 67, "name": "dining table"},
+ {"color": [0, 165, 120], "isthing": 1, "id": 70, "name": "toilet"},
+ {"color": [183, 130, 88], "isthing": 1, "id": 72, "name": "tv"},
+ {"color": [95, 32, 0], "isthing": 1, "id": 73, "name": "laptop"},
+ {"color": [130, 114, 135], "isthing": 1, "id": 74, "name": "mouse"},
+ {"color": [110, 129, 133], "isthing": 1, "id": 75, "name": "remote"},
+ {"color": [166, 74, 118], "isthing": 1, "id": 76, "name": "keyboard"},
+ {"color": [219, 142, 185], "isthing": 1, "id": 77, "name": "cell phone"},
+ {"color": [79, 210, 114], "isthing": 1, "id": 78, "name": "microwave"},
+ {"color": [178, 90, 62], "isthing": 1, "id": 79, "name": "oven"},
+ {"color": [65, 70, 15], "isthing": 1, "id": 80, "name": "toaster"},
+ {"color": [127, 167, 115], "isthing": 1, "id": 81, "name": "sink"},
+ {"color": [59, 105, 106], "isthing": 1, "id": 82, "name": "refrigerator"},
+ {"color": [142, 108, 45], "isthing": 1, "id": 84, "name": "book"},
+ {"color": [196, 172, 0], "isthing": 1, "id": 85, "name": "clock"},
+ {"color": [95, 54, 80], "isthing": 1, "id": 86, "name": "vase"},
+ {"color": [128, 76, 255], "isthing": 1, "id": 87, "name": "scissors"},
+ {"color": [201, 57, 1], "isthing": 1, "id": 88, "name": "teddy bear"},
+ {"color": [246, 0, 122], "isthing": 1, "id": 89, "name": "hair drier"},
+ {"color": [191, 162, 208], "isthing": 1, "id": 90, "name": "toothbrush"},
+ {"color": [255, 255, 128], "isthing": 0, "id": 92, "name": "banner"},
+ {"color": [147, 211, 203], "isthing": 0, "id": 93, "name": "blanket"},
+ {"color": [150, 100, 100], "isthing": 0, "id": 95, "name": "bridge"},
+ {"color": [168, 171, 172], "isthing": 0, "id": 100, "name": "cardboard"},
+ {"color": [146, 112, 198], "isthing": 0, "id": 107, "name": "counter"},
+ {"color": [210, 170, 100], "isthing": 0, "id": 109, "name": "curtain"},
+ {"color": [92, 136, 89], "isthing": 0, "id": 112, "name": "door-stuff"},
+ {"color": [218, 88, 184], "isthing": 0, "id": 118, "name": "floor-wood"},
+ {"color": [241, 129, 0], "isthing": 0, "id": 119, "name": "flower"},
+ {"color": [217, 17, 255], "isthing": 0, "id": 122, "name": "fruit"},
+ {"color": [124, 74, 181], "isthing": 0, "id": 125, "name": "gravel"},
+ {"color": [70, 70, 70], "isthing": 0, "id": 128, "name": "house"},
+ {"color": [255, 228, 255], "isthing": 0, "id": 130, "name": "light"},
+ {"color": [154, 208, 0], "isthing": 0, "id": 133, "name": "mirror-stuff"},
+ {"color": [193, 0, 92], "isthing": 0, "id": 138, "name": "net"},
+ {"color": [76, 91, 113], "isthing": 0, "id": 141, "name": "pillow"},
+ {"color": [255, 180, 195], "isthing": 0, "id": 144, "name": "platform"},
+ {"color": [106, 154, 176], "isthing": 0, "id": 145, "name": "playingfield"},
+ {"color": [230, 150, 140], "isthing": 0, "id": 147, "name": "railroad"},
+ {"color": [60, 143, 255], "isthing": 0, "id": 148, "name": "river"},
+ {"color": [128, 64, 128], "isthing": 0, "id": 149, "name": "road"},
+ {"color": [92, 82, 55], "isthing": 0, "id": 151, "name": "roof"},
+ {"color": [254, 212, 124], "isthing": 0, "id": 154, "name": "sand"},
+ {"color": [73, 77, 174], "isthing": 0, "id": 155, "name": "sea"},
+ {"color": [255, 160, 98], "isthing": 0, "id": 156, "name": "shelf"},
+ {"color": [255, 255, 255], "isthing": 0, "id": 159, "name": "snow"},
+ {"color": [104, 84, 109], "isthing": 0, "id": 161, "name": "stairs"},
+ {"color": [169, 164, 131], "isthing": 0, "id": 166, "name": "tent"},
+ {"color": [225, 199, 255], "isthing": 0, "id": 168, "name": "towel"},
+ {"color": [137, 54, 74], "isthing": 0, "id": 171, "name": "wall-brick"},
+ {"color": [135, 158, 223], "isthing": 0, "id": 175, "name": "wall-stone"},
+ {"color": [7, 246, 231], "isthing": 0, "id": 176, "name": "wall-tile"},
+ {"color": [107, 255, 200], "isthing": 0, "id": 177, "name": "wall-wood"},
+ {"color": [58, 41, 149], "isthing": 0, "id": 178, "name": "water-other"},
+ {"color": [183, 121, 142], "isthing": 0, "id": 180, "name": "window-blind"},
+ {"color": [255, 73, 97], "isthing": 0, "id": 181, "name": "window-other"},
+ {"color": [107, 142, 35], "isthing": 0, "id": 184, "name": "tree-merged"},
+ {"color": [190, 153, 153], "isthing": 0, "id": 185, "name": "fence-merged"},
+ {"color": [146, 139, 141], "isthing": 0, "id": 186, "name": "ceiling-merged"},
+ {"color": [70, 130, 180], "isthing": 0, "id": 187, "name": "sky-other-merged"},
+ {"color": [134, 199, 156], "isthing": 0, "id": 188, "name": "cabinet-merged"},
+ {"color": [209, 226, 140], "isthing": 0, "id": 189, "name": "table-merged"},
+ {"color": [96, 36, 108], "isthing": 0, "id": 190, "name": "floor-other-merged"},
+ {"color": [96, 96, 96], "isthing": 0, "id": 191, "name": "pavement-merged"},
+ {"color": [64, 170, 64], "isthing": 0, "id": 192, "name": "mountain-merged"},
+ {"color": [152, 251, 152], "isthing": 0, "id": 193, "name": "grass-merged"},
+ {"color": [208, 229, 228], "isthing": 0, "id": 194, "name": "dirt-merged"},
+ {"color": [206, 186, 171], "isthing": 0, "id": 195, "name": "paper-merged"},
+ {"color": [152, 161, 64], "isthing": 0, "id": 196, "name": "food-other-merged"},
+ {"color": [116, 112, 0], "isthing": 0, "id": 197, "name": "building-other-merged"},
+ {"color": [0, 114, 143], "isthing": 0, "id": 198, "name": "rock-merged"},
+ {"color": [102, 102, 156], "isthing": 0, "id": 199, "name": "wall-other-merged"},
+ {"color": [250, 141, 255], "isthing": 0, "id": 200, "name": "rug-merged"},
+]
+
+# fmt: off
+COCO_PERSON_KEYPOINT_NAMES = (
+ "nose",
+ "left_eye", "right_eye",
+ "left_ear", "right_ear",
+ "left_shoulder", "right_shoulder",
+ "left_elbow", "right_elbow",
+ "left_wrist", "right_wrist",
+ "left_hip", "right_hip",
+ "left_knee", "right_knee",
+ "left_ankle", "right_ankle",
+)
+# fmt: on
+
+# Pairs of keypoints that should be exchanged under horizontal flipping
+COCO_PERSON_KEYPOINT_FLIP_MAP = (
+ ("left_eye", "right_eye"),
+ ("left_ear", "right_ear"),
+ ("left_shoulder", "right_shoulder"),
+ ("left_elbow", "right_elbow"),
+ ("left_wrist", "right_wrist"),
+ ("left_hip", "right_hip"),
+ ("left_knee", "right_knee"),
+ ("left_ankle", "right_ankle"),
+)
+
+# rules for pairs of keypoints to draw a line between, and the line color to use.
+KEYPOINT_CONNECTION_RULES = [
+ # face
+ ("left_ear", "left_eye", (102, 204, 255)),
+ ("right_ear", "right_eye", (51, 153, 255)),
+ ("left_eye", "nose", (102, 0, 204)),
+ ("nose", "right_eye", (51, 102, 255)),
+ # upper-body
+ ("left_shoulder", "right_shoulder", (255, 128, 0)),
+ ("left_shoulder", "left_elbow", (153, 255, 204)),
+ ("right_shoulder", "right_elbow", (128, 229, 255)),
+ ("left_elbow", "left_wrist", (153, 255, 153)),
+ ("right_elbow", "right_wrist", (102, 255, 224)),
+ # lower-body
+ ("left_hip", "right_hip", (255, 102, 0)),
+ ("left_hip", "left_knee", (255, 255, 77)),
+ ("right_hip", "right_knee", (153, 255, 204)),
+ ("left_knee", "left_ankle", (191, 255, 128)),
+ ("right_knee", "right_ankle", (255, 195, 77)),
+]
+
+# All Cityscapes categories, together with their nice-looking visualization colors
+# It's from https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/helpers/labels.py # noqa
+CITYSCAPES_CATEGORIES = [
+ {"color": (128, 64, 128), "isthing": 0, "id": 7, "trainId": 0, "name": "road"},
+ {"color": (244, 35, 232), "isthing": 0, "id": 8, "trainId": 1, "name": "sidewalk"},
+ {"color": (70, 70, 70), "isthing": 0, "id": 11, "trainId": 2, "name": "building"},
+ {"color": (102, 102, 156), "isthing": 0, "id": 12, "trainId": 3, "name": "wall"},
+ {"color": (190, 153, 153), "isthing": 0, "id": 13, "trainId": 4, "name": "fence"},
+ {"color": (153, 153, 153), "isthing": 0, "id": 17, "trainId": 5, "name": "pole"},
+ {"color": (250, 170, 30), "isthing": 0, "id": 19, "trainId": 6, "name": "traffic light"},
+ {"color": (220, 220, 0), "isthing": 0, "id": 20, "trainId": 7, "name": "traffic sign"},
+ {"color": (107, 142, 35), "isthing": 0, "id": 21, "trainId": 8, "name": "vegetation"},
+ {"color": (152, 251, 152), "isthing": 0, "id": 22, "trainId": 9, "name": "terrain"},
+ {"color": (70, 130, 180), "isthing": 0, "id": 23, "trainId": 10, "name": "sky"},
+ {"color": (220, 20, 60), "isthing": 1, "id": 24, "trainId": 11, "name": "person"},
+ {"color": (255, 0, 0), "isthing": 1, "id": 25, "trainId": 12, "name": "rider"},
+ {"color": (0, 0, 142), "isthing": 1, "id": 26, "trainId": 13, "name": "car"},
+ {"color": (0, 0, 70), "isthing": 1, "id": 27, "trainId": 14, "name": "truck"},
+ {"color": (0, 60, 100), "isthing": 1, "id": 28, "trainId": 15, "name": "bus"},
+ {"color": (0, 80, 100), "isthing": 1, "id": 31, "trainId": 16, "name": "train"},
+ {"color": (0, 0, 230), "isthing": 1, "id": 32, "trainId": 17, "name": "motorcycle"},
+ {"color": (119, 11, 32), "isthing": 1, "id": 33, "trainId": 18, "name": "bicycle"},
+]
+
+# fmt: off
+ADE20K_SEM_SEG_CATEGORIES = [
+ "wall", "building", "sky", "floor", "tree", "ceiling", "road, route", "bed", "window ", "grass", "cabinet", "sidewalk, pavement", "person", "earth, ground", "door", "table", "mountain, mount", "plant", "curtain", "chair", "car", "water", "painting, picture", "sofa", "shelf", "house", "sea", "mirror", "rug", "field", "armchair", "seat", "fence", "desk", "rock, stone", "wardrobe, closet, press", "lamp", "tub", "rail", "cushion", "base, pedestal, stand", "box", "column, pillar", "signboard, sign", "chest of drawers, chest, bureau, dresser", "counter", "sand", "sink", "skyscraper", "fireplace", "refrigerator, icebox", "grandstand, covered stand", "path", "stairs", "runway", "case, display case, showcase, vitrine", "pool table, billiard table, snooker table", "pillow", "screen door, screen", "stairway, staircase", "river", "bridge, span", "bookcase", "blind, screen", "coffee table", "toilet, can, commode, crapper, pot, potty, stool, throne", "flower", "book", "hill", "bench", "countertop", "stove", "palm, palm tree", "kitchen island", "computer", "swivel chair", "boat", "bar", "arcade machine", "hovel, hut, hutch, shack, shanty", "bus", "towel", "light", "truck", "tower", "chandelier", "awning, sunshade, sunblind", "street lamp", "booth", "tv", "plane", "dirt track", "clothes", "pole", "land, ground, soil", "bannister, banister, balustrade, balusters, handrail", "escalator, moving staircase, moving stairway", "ottoman, pouf, pouffe, puff, hassock", "bottle", "buffet, counter, sideboard", "poster, posting, placard, notice, bill, card", "stage", "van", "ship", "fountain", "conveyer belt, conveyor belt, conveyer, conveyor, transporter", "canopy", "washer, automatic washer, washing machine", "plaything, toy", "pool", "stool", "barrel, cask", "basket, handbasket", "falls", "tent", "bag", "minibike, motorbike", "cradle", "oven", "ball", "food, solid food", "step, stair", "tank, storage tank", "trade name", "microwave", "pot", "animal", "bicycle", "lake", "dishwasher", "screen", "blanket, cover", "sculpture", "hood, exhaust hood", "sconce", "vase", "traffic light", "tray", "trash can", "fan", "pier", "crt screen", "plate", "monitor", "bulletin board", "shower", "radiator", "glass, drinking glass", "clock", "flag", # noqa
+]
+# After processed by `prepare_ade20k_sem_seg.py`, id 255 means ignore
+# fmt: on
+
+BDD100K_CATEGORIES = [
+ {"color": [220, 20, 60], "isthing": 1, "id": 1, "name": "pedestrian"},
+ {"color": [119, 11, 32], "isthing": 1, "id": 2, "name": "rider"},
+ {"color": [0, 0, 142], "isthing": 1, "id": 3, "name": "car"},
+ {"color": [0, 0, 230], "isthing": 1, "id": 4, "name": "truck"},
+ {"color": [106, 0, 228], "isthing": 1, "id": 5, "name": "bus"},
+ {"color": [0, 60, 100], "isthing": 1, "id": 6, "name": "train"},
+ {"color": [0, 80, 100], "isthing": 1, "id": 7, "name": "motorcycle"},
+ {"color": [0, 0, 70], "isthing": 1, "id": 8, "name": "bicycle"},
+]
+
+def _get_coco_instances_meta():
+ thing_ids = [k["id"] for k in COCO_CATEGORIES if k["isthing"] == 1]
+ thing_colors = [k["color"] for k in COCO_CATEGORIES if k["isthing"] == 1]
+ assert len(thing_ids) == 80, len(thing_ids)
+ # Mapping from the incontiguous COCO category id to an id in [0, 79]
+ thing_dataset_id_to_contiguous_id = {k: i for i, k in enumerate(thing_ids)}
+ thing_classes = [k["name"] for k in COCO_CATEGORIES if k["isthing"] == 1]
+ ret = {
+ "thing_dataset_id_to_contiguous_id": thing_dataset_id_to_contiguous_id,
+ "thing_classes": thing_classes,
+ "thing_colors": thing_colors,
+ }
+ return ret
+
+
+def _get_coco_panoptic_separated_meta():
+ """
+ Returns metadata for "separated" version of the panoptic segmentation dataset.
+ """
+ stuff_ids = [k["id"] for k in COCO_CATEGORIES if k["isthing"] == 0]
+ assert len(stuff_ids) == 53, len(stuff_ids)
+
+ # For semantic segmentation, this mapping maps from contiguous stuff id
+ # (in [0, 53], used in models) to ids in the dataset (used for processing results)
+ # The id 0 is mapped to an extra category "thing".
+ stuff_dataset_id_to_contiguous_id = {k: i + 1 for i, k in enumerate(stuff_ids)}
+ # When converting COCO panoptic annotations to semantic annotations
+ # We label the "thing" category to 0
+ stuff_dataset_id_to_contiguous_id[0] = 0
+
+ # 54 names for COCO stuff categories (including "things")
+ stuff_classes = ["things"] + [
+ k["name"].replace("-other", "").replace("-merged", "")
+ for k in COCO_CATEGORIES
+ if k["isthing"] == 0
+ ]
+
+ # NOTE: I randomly picked a color for things
+ stuff_colors = [[82, 18, 128]] + [k["color"] for k in COCO_CATEGORIES if k["isthing"] == 0]
+ ret = {
+ "stuff_dataset_id_to_contiguous_id": stuff_dataset_id_to_contiguous_id,
+ "stuff_classes": stuff_classes,
+ "stuff_colors": stuff_colors,
+ }
+ ret.update(_get_coco_instances_meta())
+ return ret
+
+def _get_bdd100k_instances_meta():
+ thing_ids = [k["id"] for k in BDD100K_CATEGORIES if k["isthing"] == 1]
+ thing_colors = [k["color"] for k in BDD100K_CATEGORIES if k["isthing"] == 1]
+ assert len(thing_ids) == 8, len(thing_ids)
+ # Mapping from the incontiguous COCO category id to an id in [0, 79]
+ thing_dataset_id_to_contiguous_id = {k: i for i, k in enumerate(thing_ids)}
+ thing_classes = [k["name"] for k in BDD100K_CATEGORIES if k["isthing"] == 1]
+ ret = {
+ "thing_dataset_id_to_contiguous_id": thing_dataset_id_to_contiguous_id,
+ "thing_classes": thing_classes,
+ "thing_colors": thing_colors,
+ }
+ return ret
+
+
+def _get_builtin_metadata(dataset_name):
+ if dataset_name == "coco":
+ return _get_coco_instances_meta()
+ if dataset_name == "coco_panoptic_separated":
+ return _get_coco_panoptic_separated_meta()
+ elif dataset_name == "coco_panoptic_standard":
+ meta = {}
+ # The following metadata maps contiguous id from [0, #thing categories +
+ # #stuff categories) to their names and colors. We have to replica of the
+ # same name and color under "thing_*" and "stuff_*" because the current
+ # visualization function in D2 handles thing and class classes differently
+ # due to some heuristic used in Panoptic FPN. We keep the same naming to
+ # enable reusing existing visualization functions.
+ thing_classes = [k["name"] for k in COCO_CATEGORIES]
+ thing_colors = [k["color"] for k in COCO_CATEGORIES]
+ stuff_classes = [k["name"] for k in COCO_CATEGORIES]
+ stuff_colors = [k["color"] for k in COCO_CATEGORIES]
+
+ meta["thing_classes"] = thing_classes
+ meta["thing_colors"] = thing_colors
+ meta["stuff_classes"] = stuff_classes
+ meta["stuff_colors"] = stuff_colors
+
+ # Convert category id for training:
+ # category id: like semantic segmentation, it is the class id for each
+ # pixel. Since there are some classes not used in evaluation, the category
+ # id is not always contiguous and thus we have two set of category ids:
+ # - original category id: category id in the original dataset, mainly
+ # used for evaluation.
+ # - contiguous category id: [0, #classes), in order to train the linear
+ # softmax classifier.
+ thing_dataset_id_to_contiguous_id = {}
+ stuff_dataset_id_to_contiguous_id = {}
+
+ for i, cat in enumerate(COCO_CATEGORIES):
+ if cat["isthing"]:
+ thing_dataset_id_to_contiguous_id[cat["id"]] = i
+ else:
+ stuff_dataset_id_to_contiguous_id[cat["id"]] = i
+
+ meta["thing_dataset_id_to_contiguous_id"] = thing_dataset_id_to_contiguous_id
+ meta["stuff_dataset_id_to_contiguous_id"] = stuff_dataset_id_to_contiguous_id
+
+ return meta
+ elif dataset_name == "coco_person":
+ return {
+ "thing_classes": ["person"],
+ "keypoint_names": COCO_PERSON_KEYPOINT_NAMES,
+ "keypoint_flip_map": COCO_PERSON_KEYPOINT_FLIP_MAP,
+ "keypoint_connection_rules": KEYPOINT_CONNECTION_RULES,
+ }
+ elif dataset_name == "bdd100k":
+ return _get_bdd100k_instances_meta()
+ elif dataset_name == "cityscapes":
+ # fmt: off
+ CITYSCAPES_THING_CLASSES = [
+ "person", "rider", "car", "truck",
+ "bus", "train", "motorcycle", "bicycle",
+ ]
+ CITYSCAPES_STUFF_CLASSES = [
+ "road", "sidewalk", "building", "wall", "fence", "pole", "traffic light",
+ "traffic sign", "vegetation", "terrain", "sky", "person", "rider", "car",
+ "truck", "bus", "train", "motorcycle", "bicycle",
+ ]
+ # fmt: on
+ return {
+ "thing_classes": CITYSCAPES_THING_CLASSES,
+ "stuff_classes": CITYSCAPES_STUFF_CLASSES,
+ }
+ raise KeyError("No built-in metadata for dataset {}".format(dataset_name))
diff --git a/detectron2/detectron2/data/datasets/cityscapes.py b/detectron2/detectron2/data/datasets/cityscapes.py
new file mode 100755
index 0000000..1e84a5b
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/cityscapes.py
@@ -0,0 +1,329 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import functools
+import json
+import logging
+import multiprocessing as mp
+import numpy as np
+import os
+from itertools import chain
+import pycocotools.mask as mask_util
+from PIL import Image
+
+from detectron2.structures import BoxMode
+from detectron2.utils.comm import get_world_size
+from detectron2.utils.file_io import PathManager
+from detectron2.utils.logger import setup_logger
+
+try:
+ import cv2 # noqa
+except ImportError:
+ # OpenCV is an optional dependency at the moment
+ pass
+
+
+logger = logging.getLogger(__name__)
+
+
+def _get_cityscapes_files(image_dir, gt_dir):
+ files = []
+ # scan through the directory
+ cities = PathManager.ls(image_dir)
+ logger.info(f"{len(cities)} cities found in '{image_dir}'.")
+ for city in cities:
+ city_img_dir = os.path.join(image_dir, city)
+ city_gt_dir = os.path.join(gt_dir, city)
+ for basename in PathManager.ls(city_img_dir):
+ image_file = os.path.join(city_img_dir, basename)
+
+ suffix = "leftImg8bit.png"
+ assert basename.endswith(suffix), basename
+ basename = basename[: -len(suffix)]
+
+ instance_file = os.path.join(city_gt_dir, basename + "gtFine_instanceIds.png")
+ label_file = os.path.join(city_gt_dir, basename + "gtFine_labelIds.png")
+ json_file = os.path.join(city_gt_dir, basename + "gtFine_polygons.json")
+
+ files.append((image_file, instance_file, label_file, json_file))
+ assert len(files), "No images found in {}".format(image_dir)
+ for f in files[0]:
+ assert PathManager.isfile(f), f
+ return files
+
+
+def load_cityscapes_instances(image_dir, gt_dir, from_json=True, to_polygons=True):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train".
+ gt_dir (str): path to the raw annotations. e.g., "~/cityscapes/gtFine/train".
+ from_json (bool): whether to read annotations from the raw json file or the png files.
+ to_polygons (bool): whether to represent the segmentation as polygons
+ (COCO's format) instead of masks (cityscapes's format).
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+ """
+ if from_json:
+ assert to_polygons, (
+ "Cityscapes's json annotations are in polygon format. "
+ "Converting to mask format is not supported now."
+ )
+ files = _get_cityscapes_files(image_dir, gt_dir)
+
+ logger.info("Preprocessing cityscapes annotations ...")
+ # This is still not fast: all workers will execute duplicate works and will
+ # take up to 10m on a 8GPU server.
+ pool = mp.Pool(processes=max(mp.cpu_count() // get_world_size() // 2, 4))
+
+ ret = pool.map(
+ functools.partial(_cityscapes_files_to_dict, from_json=from_json, to_polygons=to_polygons),
+ files,
+ )
+ logger.info("Loaded {} images from {}".format(len(ret), image_dir))
+
+ # Map cityscape ids to contiguous ids
+ from cityscapesscripts.helpers.labels import labels
+
+ labels = [l for l in labels if l.hasInstances and not l.ignoreInEval]
+ dataset_id_to_contiguous_id = {l.id: idx for idx, l in enumerate(labels)}
+ for dict_per_image in ret:
+ for anno in dict_per_image["annotations"]:
+ anno["category_id"] = dataset_id_to_contiguous_id[anno["category_id"]]
+ return ret
+
+
+def load_cityscapes_semantic(image_dir, gt_dir):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train".
+ gt_dir (str): path to the raw annotations. e.g., "~/cityscapes/gtFine/train".
+
+ Returns:
+ list[dict]: a list of dict, each has "file_name" and
+ "sem_seg_file_name".
+ """
+ ret = []
+ # gt_dir is small and contain many small files. make sense to fetch to local first
+ gt_dir = PathManager.get_local_path(gt_dir)
+ for image_file, _, label_file, json_file in _get_cityscapes_files(image_dir, gt_dir):
+ label_file = label_file.replace("labelIds", "labelTrainIds")
+
+ with PathManager.open(json_file, "r") as f:
+ jsonobj = json.load(f)
+ ret.append(
+ {
+ "file_name": image_file,
+ "sem_seg_file_name": label_file,
+ "height": jsonobj["imgHeight"],
+ "width": jsonobj["imgWidth"],
+ }
+ )
+ assert len(ret), f"No images found in {image_dir}!"
+ assert PathManager.isfile(
+ ret[0]["sem_seg_file_name"]
+ ), "Please generate labelTrainIds.png with cityscapesscripts/preparation/createTrainIdLabelImgs.py" # noqa
+ return ret
+
+
+def _cityscapes_files_to_dict(files, from_json, to_polygons):
+ """
+ Parse cityscapes annotation files to a instance segmentation dataset dict.
+
+ Args:
+ files (tuple): consists of (image_file, instance_id_file, label_id_file, json_file)
+ from_json (bool): whether to read annotations from the raw json file or the png files.
+ to_polygons (bool): whether to represent the segmentation as polygons
+ (COCO's format) instead of masks (cityscapes's format).
+
+ Returns:
+ A dict in Detectron2 Dataset format.
+ """
+ from cityscapesscripts.helpers.labels import id2label, name2label
+
+ image_file, instance_id_file, _, json_file = files
+
+ annos = []
+
+ if from_json:
+ from shapely.geometry import MultiPolygon, Polygon
+
+ with PathManager.open(json_file, "r") as f:
+ jsonobj = json.load(f)
+ ret = {
+ "file_name": image_file,
+ "image_id": os.path.basename(image_file),
+ "height": jsonobj["imgHeight"],
+ "width": jsonobj["imgWidth"],
+ }
+
+ # `polygons_union` contains the union of all valid polygons.
+ polygons_union = Polygon()
+
+ # CityscapesScripts draw the polygons in sequential order
+ # and each polygon *overwrites* existing ones. See
+ # (https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/preparation/json2instanceImg.py) # noqa
+ # We use reverse order, and each polygon *avoids* early ones.
+ # This will resolve the ploygon overlaps in the same way as CityscapesScripts.
+ for obj in jsonobj["objects"][::-1]:
+ if "deleted" in obj: # cityscapes data format specific
+ continue
+ label_name = obj["label"]
+
+ try:
+ label = name2label[label_name]
+ except KeyError:
+ if label_name.endswith("group"): # crowd area
+ label = name2label[label_name[: -len("group")]]
+ else:
+ raise
+ if label.id < 0: # cityscapes data format
+ continue
+
+ # Cityscapes's raw annotations uses integer coordinates
+ # Therefore +0.5 here
+ poly_coord = np.asarray(obj["polygon"], dtype="f4") + 0.5
+ # CityscapesScript uses PIL.ImageDraw.polygon to rasterize
+ # polygons for evaluation. This function operates in integer space
+ # and draws each pixel whose center falls into the polygon.
+ # Therefore it draws a polygon which is 0.5 "fatter" in expectation.
+ # We therefore dilate the input polygon by 0.5 as our input.
+ poly = Polygon(poly_coord).buffer(0.5, resolution=4)
+
+ if not label.hasInstances or label.ignoreInEval:
+ # even if we won't store the polygon it still contributes to overlaps resolution
+ polygons_union = polygons_union.union(poly)
+ continue
+
+ # Take non-overlapping part of the polygon
+ poly_wo_overlaps = poly.difference(polygons_union)
+ if poly_wo_overlaps.is_empty:
+ continue
+ polygons_union = polygons_union.union(poly)
+
+ anno = {}
+ anno["iscrowd"] = label_name.endswith("group")
+ anno["category_id"] = label.id
+
+ if isinstance(poly_wo_overlaps, Polygon):
+ poly_list = [poly_wo_overlaps]
+ elif isinstance(poly_wo_overlaps, MultiPolygon):
+ poly_list = poly_wo_overlaps.geoms
+ else:
+ raise NotImplementedError("Unknown geometric structure {}".format(poly_wo_overlaps))
+
+ poly_coord = []
+ for poly_el in poly_list:
+ # COCO API can work only with exterior boundaries now, hence we store only them.
+ # TODO: store both exterior and interior boundaries once other parts of the
+ # codebase support holes in polygons.
+ poly_coord.append(list(chain(*poly_el.exterior.coords)))
+ anno["segmentation"] = poly_coord
+ (xmin, ymin, xmax, ymax) = poly_wo_overlaps.bounds
+
+ anno["bbox"] = (xmin, ymin, xmax, ymax)
+ anno["bbox_mode"] = BoxMode.XYXY_ABS
+
+ annos.append(anno)
+ else:
+ # See also the official annotation parsing scripts at
+ # https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/instances2dict.py # noqa
+ with PathManager.open(instance_id_file, "rb") as f:
+ inst_image = np.asarray(Image.open(f), order="F")
+ # ids < 24 are stuff labels (filtering them first is about 5% faster)
+ flattened_ids = np.unique(inst_image[inst_image >= 24])
+
+ ret = {
+ "file_name": image_file,
+ "image_id": os.path.basename(image_file),
+ "height": inst_image.shape[0],
+ "width": inst_image.shape[1],
+ }
+
+ for instance_id in flattened_ids:
+ # For non-crowd annotations, instance_id // 1000 is the label_id
+ # Crowd annotations have <1000 instance ids
+ label_id = instance_id // 1000 if instance_id >= 1000 else instance_id
+ label = id2label[label_id]
+ if not label.hasInstances or label.ignoreInEval:
+ continue
+
+ anno = {}
+ anno["iscrowd"] = instance_id < 1000
+ anno["category_id"] = label.id
+
+ mask = np.asarray(inst_image == instance_id, dtype=np.uint8, order="F")
+
+ inds = np.nonzero(mask)
+ ymin, ymax = inds[0].min(), inds[0].max()
+ xmin, xmax = inds[1].min(), inds[1].max()
+ anno["bbox"] = (xmin, ymin, xmax, ymax)
+ if xmax <= xmin or ymax <= ymin:
+ continue
+ anno["bbox_mode"] = BoxMode.XYXY_ABS
+ if to_polygons:
+ # This conversion comes from D4809743 and D5171122,
+ # when Mask-RCNN was first developed.
+ contours = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[
+ -2
+ ]
+ polygons = [c.reshape(-1).tolist() for c in contours if len(c) >= 3]
+ # opencv's can produce invalid polygons
+ if len(polygons) == 0:
+ continue
+ anno["segmentation"] = polygons
+ else:
+ anno["segmentation"] = mask_util.encode(mask[:, :, None])[0]
+ annos.append(anno)
+ ret["annotations"] = annos
+ return ret
+
+
+if __name__ == "__main__":
+ """
+ Test the cityscapes dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.cityscapes \
+ cityscapes/leftImg8bit/train cityscapes/gtFine/train
+ """
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("image_dir")
+ parser.add_argument("gt_dir")
+ parser.add_argument("--type", choices=["instance", "semantic"], default="instance")
+ args = parser.parse_args()
+ from detectron2.data.catalog import Metadata
+ from detectron2.utils.visualizer import Visualizer
+ from cityscapesscripts.helpers.labels import labels
+
+ logger = setup_logger(name=__name__)
+
+ dirname = "cityscapes-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+
+ if args.type == "instance":
+ dicts = load_cityscapes_instances(
+ args.image_dir, args.gt_dir, from_json=True, to_polygons=True
+ )
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ thing_classes = [k.name for k in labels if k.hasInstances and not k.ignoreInEval]
+ meta = Metadata().set(thing_classes=thing_classes)
+
+ else:
+ dicts = load_cityscapes_semantic(args.image_dir, args.gt_dir)
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ stuff_classes = [k.name for k in labels if k.trainId != 255]
+ stuff_colors = [k.color for k in labels if k.trainId != 255]
+ meta = Metadata().set(stuff_classes=stuff_classes, stuff_colors=stuff_colors)
+
+ for d in dicts:
+ img = np.array(Image.open(PathManager.open(d["file_name"], "rb")))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ # cv2.imshow("a", vis.get_image()[:, :, ::-1])
+ # cv2.waitKey()
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
diff --git a/detectron2/detectron2/data/datasets/cityscapes_panoptic.py b/detectron2/detectron2/data/datasets/cityscapes_panoptic.py
new file mode 100755
index 0000000..48c136f
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/cityscapes_panoptic.py
@@ -0,0 +1,187 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import json
+import logging
+import os
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+from detectron2.data.datasets.builtin_meta import CITYSCAPES_CATEGORIES
+from detectron2.utils.file_io import PathManager
+
+"""
+This file contains functions to register the Cityscapes panoptic dataset to the DatasetCatalog.
+"""
+
+
+logger = logging.getLogger(__name__)
+
+
+def get_cityscapes_panoptic_files(image_dir, gt_dir, json_info):
+ files = []
+ # scan through the directory
+ cities = PathManager.ls(image_dir)
+ logger.info(f"{len(cities)} cities found in '{image_dir}'.")
+ image_dict = {}
+ for city in cities:
+ city_img_dir = os.path.join(image_dir, city)
+ for basename in PathManager.ls(city_img_dir):
+ image_file = os.path.join(city_img_dir, basename)
+
+ suffix = "_leftImg8bit.png"
+ assert basename.endswith(suffix), basename
+ basename = os.path.basename(basename)[: -len(suffix)]
+
+ image_dict[basename] = image_file
+
+ for ann in json_info["annotations"]:
+ image_file = image_dict.get(ann["image_id"], None)
+ assert image_file is not None, "No image {} found for annotation {}".format(
+ ann["image_id"], ann["file_name"]
+ )
+ label_file = os.path.join(gt_dir, ann["file_name"])
+ segments_info = ann["segments_info"]
+
+ files.append((image_file, label_file, segments_info))
+
+ assert len(files), "No images found in {}".format(image_dir)
+ assert PathManager.isfile(files[0][0]), files[0][0]
+ assert PathManager.isfile(files[0][1]), files[0][1]
+ return files
+
+
+def load_cityscapes_panoptic(image_dir, gt_dir, gt_json, meta):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train".
+ gt_dir (str): path to the raw annotations. e.g.,
+ "~/cityscapes/gtFine/cityscapes_panoptic_train".
+ gt_json (str): path to the json file. e.g.,
+ "~/cityscapes/gtFine/cityscapes_panoptic_train.json".
+ meta (dict): dictionary containing "thing_dataset_id_to_contiguous_id"
+ and "stuff_dataset_id_to_contiguous_id" to map category ids to
+ contiguous ids for training.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+ """
+
+ def _convert_category_id(segment_info, meta):
+ if segment_info["category_id"] in meta["thing_dataset_id_to_contiguous_id"]:
+ segment_info["category_id"] = meta["thing_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ else:
+ segment_info["category_id"] = meta["stuff_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ return segment_info
+
+ assert os.path.exists(
+ gt_json
+ ), "Please run `python cityscapesscripts/preparation/createPanopticImgs.py` to generate label files." # noqa
+ with open(gt_json) as f:
+ json_info = json.load(f)
+ files = get_cityscapes_panoptic_files(image_dir, gt_dir, json_info)
+ ret = []
+ for image_file, label_file, segments_info in files:
+ sem_label_file = (
+ image_file.replace("leftImg8bit", "gtFine").split(".")[0] + "_labelTrainIds.png"
+ )
+ segments_info = [_convert_category_id(x, meta) for x in segments_info]
+ ret.append(
+ {
+ "file_name": image_file,
+ "image_id": "_".join(
+ os.path.splitext(os.path.basename(image_file))[0].split("_")[:3]
+ ),
+ "sem_seg_file_name": sem_label_file,
+ "pan_seg_file_name": label_file,
+ "segments_info": segments_info,
+ }
+ )
+ assert len(ret), f"No images found in {image_dir}!"
+ assert PathManager.isfile(
+ ret[0]["sem_seg_file_name"]
+ ), "Please generate labelTrainIds.png with cityscapesscripts/preparation/createTrainIdLabelImgs.py" # noqa
+ assert PathManager.isfile(
+ ret[0]["pan_seg_file_name"]
+ ), "Please generate panoptic annotation with python cityscapesscripts/preparation/createPanopticImgs.py" # noqa
+ return ret
+
+
+_RAW_CITYSCAPES_PANOPTIC_SPLITS = {
+ "cityscapes_fine_panoptic_train": (
+ "cityscapes/leftImg8bit/train",
+ "cityscapes/gtFine/cityscapes_panoptic_train",
+ "cityscapes/gtFine/cityscapes_panoptic_train.json",
+ ),
+ "cityscapes_fine_panoptic_val": (
+ "cityscapes/leftImg8bit/val",
+ "cityscapes/gtFine/cityscapes_panoptic_val",
+ "cityscapes/gtFine/cityscapes_panoptic_val.json",
+ ),
+ # "cityscapes_fine_panoptic_test": not supported yet
+}
+
+
+def register_all_cityscapes_panoptic(root):
+ meta = {}
+ # The following metadata maps contiguous id from [0, #thing categories +
+ # #stuff categories) to their names and colors. We have to replica of the
+ # same name and color under "thing_*" and "stuff_*" because the current
+ # visualization function in D2 handles thing and class classes differently
+ # due to some heuristic used in Panoptic FPN. We keep the same naming to
+ # enable reusing existing visualization functions.
+ thing_classes = [k["name"] for k in CITYSCAPES_CATEGORIES]
+ thing_colors = [k["color"] for k in CITYSCAPES_CATEGORIES]
+ stuff_classes = [k["name"] for k in CITYSCAPES_CATEGORIES]
+ stuff_colors = [k["color"] for k in CITYSCAPES_CATEGORIES]
+
+ meta["thing_classes"] = thing_classes
+ meta["thing_colors"] = thing_colors
+ meta["stuff_classes"] = stuff_classes
+ meta["stuff_colors"] = stuff_colors
+
+ # There are three types of ids in cityscapes panoptic segmentation:
+ # (1) category id: like semantic segmentation, it is the class id for each
+ # pixel. Since there are some classes not used in evaluation, the category
+ # id is not always contiguous and thus we have two set of category ids:
+ # - original category id: category id in the original dataset, mainly
+ # used for evaluation.
+ # - contiguous category id: [0, #classes), in order to train the classifier
+ # (2) instance id: this id is used to differentiate different instances from
+ # the same category. For "stuff" classes, the instance id is always 0; for
+ # "thing" classes, the instance id starts from 1 and 0 is reserved for
+ # ignored instances (e.g. crowd annotation).
+ # (3) panoptic id: this is the compact id that encode both category and
+ # instance id by: category_id * 1000 + instance_id.
+ thing_dataset_id_to_contiguous_id = {}
+ stuff_dataset_id_to_contiguous_id = {}
+
+ for k in CITYSCAPES_CATEGORIES:
+ if k["isthing"] == 1:
+ thing_dataset_id_to_contiguous_id[k["id"]] = k["trainId"]
+ else:
+ stuff_dataset_id_to_contiguous_id[k["id"]] = k["trainId"]
+
+ meta["thing_dataset_id_to_contiguous_id"] = thing_dataset_id_to_contiguous_id
+ meta["stuff_dataset_id_to_contiguous_id"] = stuff_dataset_id_to_contiguous_id
+
+ for key, (image_dir, gt_dir, gt_json) in _RAW_CITYSCAPES_PANOPTIC_SPLITS.items():
+ image_dir = os.path.join(root, image_dir)
+ gt_dir = os.path.join(root, gt_dir)
+ gt_json = os.path.join(root, gt_json)
+
+ DatasetCatalog.register(
+ key, lambda x=image_dir, y=gt_dir, z=gt_json: load_cityscapes_panoptic(x, y, z, meta)
+ )
+ MetadataCatalog.get(key).set(
+ panoptic_root=gt_dir,
+ image_root=image_dir,
+ panoptic_json=gt_json,
+ gt_dir=gt_dir.replace("cityscapes_panoptic_", ""),
+ evaluator_type="cityscapes_panoptic_seg",
+ ignore_label=255,
+ label_divisor=1000,
+ **meta,
+ )
diff --git a/detectron2/detectron2/data/datasets/coco.py b/detectron2/detectron2/data/datasets/coco.py
new file mode 100755
index 0000000..ed4f7cc
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/coco.py
@@ -0,0 +1,539 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import datetime
+import io
+import json
+import logging
+import numpy as np
+import os
+import shutil
+import pycocotools.mask as mask_util
+from fvcore.common.timer import Timer
+from iopath.common.file_io import file_lock
+from PIL import Image
+
+from detectron2.structures import Boxes, BoxMode, PolygonMasks, RotatedBoxes
+from detectron2.utils.file_io import PathManager
+
+from .. import DatasetCatalog, MetadataCatalog
+
+"""
+This file contains functions to parse COCO-format annotations into dicts in "Detectron2 format".
+"""
+
+
+logger = logging.getLogger(__name__)
+
+__all__ = ["load_coco_json", "load_sem_seg", "convert_to_coco_json", "register_coco_instances"]
+
+
+def load_coco_json(json_file, image_root, dataset_name=None, extra_annotation_keys=None):
+ """
+ Load a json file with COCO's instances annotation format.
+ Currently supports instance detection, instance segmentation,
+ and person keypoints annotations.
+
+ Args:
+ json_file (str): full path to the json file in COCO instances annotation format.
+ image_root (str or path-like): the directory where the images in this json file exists.
+ dataset_name (str or None): the name of the dataset (e.g., coco_2017_train).
+ When provided, this function will also do the following:
+
+ * Put "thing_classes" into the metadata associated with this dataset.
+ * Map the category ids into a contiguous range (needed by standard dataset format),
+ and add "thing_dataset_id_to_contiguous_id" to the metadata associated
+ with this dataset.
+
+ This option should usually be provided, unless users need to load
+ the original json content and apply more processing manually.
+ extra_annotation_keys (list[str]): list of per-annotation keys that should also be
+ loaded into the dataset dict (besides "iscrowd", "bbox", "keypoints",
+ "category_id", "segmentation"). The values for these keys will be returned as-is.
+ For example, the densepose annotations are loaded in this way.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard dataset dicts format (See
+ `Using Custom Datasets `_ ) when `dataset_name` is not None.
+ If `dataset_name` is None, the returned `category_ids` may be
+ incontiguous and may not conform to the Detectron2 standard format.
+
+ Notes:
+ 1. This function does not read the image files.
+ The results do not have the "image" field.
+ """
+ from pycocotools.coco import COCO
+
+ timer = Timer()
+ json_file = PathManager.get_local_path(json_file)
+ with contextlib.redirect_stdout(io.StringIO()):
+ coco_api = COCO(json_file)
+ if timer.seconds() > 1:
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
+
+ id_map = None
+ if dataset_name is not None:
+ meta = MetadataCatalog.get(dataset_name)
+ cat_ids = sorted(coco_api.getCatIds())
+ cats = coco_api.loadCats(cat_ids)
+ # The categories in a custom json file may not be sorted.
+ thing_classes = [c["name"] for c in sorted(cats, key=lambda x: x["id"])]
+ meta.thing_classes = thing_classes
+
+ # In COCO, certain category ids are artificially removed,
+ # and by convention they are always ignored.
+ # We deal with COCO's id issue and translate
+ # the category ids to contiguous ids in [0, 80).
+
+ # It works by looking at the "categories" field in the json, therefore
+ # if users' own json also have incontiguous ids, we'll
+ # apply this mapping as well but print a warning.
+ if not (min(cat_ids) == 1 and max(cat_ids) == len(cat_ids)):
+ if "coco" not in dataset_name:
+ logger.warning(
+ """
+Category ids in annotations are not in [1, #categories]! We'll apply a mapping for you.
+"""
+ )
+ id_map = {v: i for i, v in enumerate(cat_ids)}
+ meta.thing_dataset_id_to_contiguous_id = id_map
+
+ # sort indices for reproducible results
+ img_ids = sorted(coco_api.imgs.keys())
+ # imgs is a list of dicts, each looks something like:
+ # {'license': 4,
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
+ # 'height': 427,
+ # 'width': 640,
+ # 'date_captured': '2013-11-17 05:57:24',
+ # 'id': 1268}
+ imgs = coco_api.loadImgs(img_ids)
+ # anns is a list[list[dict]], where each dict is an annotation
+ # record for an object. The inner list enumerates the objects in an image
+ # and the outer list enumerates over images. Example of anns[0]:
+ # [{'segmentation': [[192.81,
+ # 247.09,
+ # ...
+ # 219.03,
+ # 249.06]],
+ # 'area': 1035.749,
+ # 'iscrowd': 0,
+ # 'image_id': 1268,
+ # 'bbox': [192.81, 224.8, 74.73, 33.43],
+ # 'category_id': 16,
+ # 'id': 42986},
+ # ...]
+ anns = [coco_api.imgToAnns[img_id] for img_id in img_ids]
+ total_num_valid_anns = sum([len(x) for x in anns])
+ total_num_anns = len(coco_api.anns)
+ if total_num_valid_anns < total_num_anns:
+ logger.warning(
+ f"{json_file} contains {total_num_anns} annotations, but only "
+ f"{total_num_valid_anns} of them match to images in the file."
+ )
+
+ if "minival" not in json_file:
+ # The popular valminusminival & minival annotations for COCO2014 contain this bug.
+ # However the ratio of buggy annotations there is tiny and does not affect accuracy.
+ # Therefore we explicitly white-list them.
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
+ json_file
+ )
+
+ imgs_anns = list(zip(imgs, anns))
+ logger.info("Loaded {} images in COCO format from {}".format(len(imgs_anns), json_file))
+
+ dataset_dicts = []
+
+ ann_keys = ["iscrowd", "bbox", "keypoints", "category_id"] + (extra_annotation_keys or [])
+
+ num_instances_without_valid_segmentation = 0
+
+ for (img_dict, anno_dict_list) in imgs_anns:
+ record = {}
+ record["file_name"] = os.path.join(image_root, img_dict["file_name"])
+ record["height"] = img_dict["height"]
+ record["width"] = img_dict["width"]
+ image_id = record["image_id"] = img_dict["id"]
+
+ objs = []
+ for anno in anno_dict_list:
+ # Check that the image_id in this annotation is the same as
+ # the image_id we're looking at.
+ # This fails only when the data parsing logic or the annotation file is buggy.
+
+ # The original COCO valminusminival2014 & minival2014 annotation files
+ # actually contains bugs that, together with certain ways of using COCO API,
+ # can trigger this assertion.
+ assert anno["image_id"] == image_id
+
+ assert anno.get("ignore", 0) == 0, '"ignore" in COCO json file is not supported.'
+
+ obj = {key: anno[key] for key in ann_keys if key in anno}
+ if "bbox" in obj and len(obj["bbox"]) == 0:
+ raise ValueError(
+ f"One annotation of image {image_id} contains empty 'bbox' value! "
+ "This json does not have valid COCO format."
+ )
+
+ segm = anno.get("segmentation", None)
+ if segm: # either list[list[float]] or dict(RLE)
+ if isinstance(segm, dict):
+ if isinstance(segm["counts"], list):
+ # convert to compressed RLE
+ segm = mask_util.frPyObjects(segm, *segm["size"])
+ else:
+ # filter out invalid polygons (< 3 points)
+ segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
+ if len(segm) == 0:
+ num_instances_without_valid_segmentation += 1
+ continue # ignore this instance
+ obj["segmentation"] = segm
+
+ keypts = anno.get("keypoints", None)
+ if keypts: # list[int]
+ for idx, v in enumerate(keypts):
+ if idx % 3 != 2:
+ # COCO's segmentation coordinates are floating points in [0, H or W],
+ # but keypoint coordinates are integers in [0, H-1 or W-1]
+ # Therefore we assume the coordinates are "pixel indices" and
+ # add 0.5 to convert to floating point coordinates.
+ keypts[idx] = v + 0.5
+ obj["keypoints"] = keypts
+
+ obj["bbox_mode"] = BoxMode.XYWH_ABS
+ if id_map:
+ annotation_category_id = obj["category_id"]
+ try:
+ obj["category_id"] = id_map[annotation_category_id]
+ except KeyError as e:
+ raise KeyError(
+ f"Encountered category_id={annotation_category_id} "
+ "but this id does not exist in 'categories' of the json file."
+ ) from e
+ objs.append(obj)
+ record["annotations"] = objs
+ dataset_dicts.append(record)
+
+ if num_instances_without_valid_segmentation > 0:
+ logger.warning(
+ "Filtered out {} instances without valid segmentation. ".format(
+ num_instances_without_valid_segmentation
+ )
+ + "There might be issues in your dataset generation process. Please "
+ "check https://detectron2.readthedocs.io/en/latest/tutorials/datasets.html carefully"
+ )
+ return dataset_dicts
+
+
+def load_sem_seg(gt_root, image_root, gt_ext="png", image_ext="jpg"):
+ """
+ Load semantic segmentation datasets. All files under "gt_root" with "gt_ext" extension are
+ treated as ground truth annotations and all files under "image_root" with "image_ext" extension
+ as input images. Ground truth and input images are matched using file paths relative to
+ "gt_root" and "image_root" respectively without taking into account file extensions.
+ This works for COCO as well as some other datasets.
+
+ Args:
+ gt_root (str): full path to ground truth semantic segmentation files. Semantic segmentation
+ annotations are stored as images with integer values in pixels that represent
+ corresponding semantic labels.
+ image_root (str): the directory where the input images are.
+ gt_ext (str): file extension for ground truth annotations.
+ image_ext (str): file extension for input images.
+
+ Returns:
+ list[dict]:
+ a list of dicts in detectron2 standard format without instance-level
+ annotation.
+
+ Notes:
+ 1. This function does not read the image and ground truth files.
+ The results do not have the "image" and "sem_seg" fields.
+ """
+
+ # We match input images with ground truth based on their relative filepaths (without file
+ # extensions) starting from 'image_root' and 'gt_root' respectively.
+ def file2id(folder_path, file_path):
+ # extract relative path starting from `folder_path`
+ image_id = os.path.normpath(os.path.relpath(file_path, start=folder_path))
+ # remove file extension
+ image_id = os.path.splitext(image_id)[0]
+ return image_id
+
+ input_files = sorted(
+ (os.path.join(image_root, f) for f in PathManager.ls(image_root) if f.endswith(image_ext)),
+ key=lambda file_path: file2id(image_root, file_path),
+ )
+ gt_files = sorted(
+ (os.path.join(gt_root, f) for f in PathManager.ls(gt_root) if f.endswith(gt_ext)),
+ key=lambda file_path: file2id(gt_root, file_path),
+ )
+
+ assert len(gt_files) > 0, "No annotations found in {}.".format(gt_root)
+
+ # Use the intersection, so that val2017_100 annotations can run smoothly with val2017 images
+ if len(input_files) != len(gt_files):
+ logger.warn(
+ "Directory {} and {} has {} and {} files, respectively.".format(
+ image_root, gt_root, len(input_files), len(gt_files)
+ )
+ )
+ input_basenames = [os.path.basename(f)[: -len(image_ext)] for f in input_files]
+ gt_basenames = [os.path.basename(f)[: -len(gt_ext)] for f in gt_files]
+ intersect = list(set(input_basenames) & set(gt_basenames))
+ # sort, otherwise each worker may obtain a list[dict] in different order
+ intersect = sorted(intersect)
+ logger.warn("Will use their intersection of {} files.".format(len(intersect)))
+ input_files = [os.path.join(image_root, f + image_ext) for f in intersect]
+ gt_files = [os.path.join(gt_root, f + gt_ext) for f in intersect]
+
+ logger.info(
+ "Loaded {} images with semantic segmentation from {}".format(len(input_files), image_root)
+ )
+
+ dataset_dicts = []
+ for (img_path, gt_path) in zip(input_files, gt_files):
+ record = {}
+ record["file_name"] = img_path
+ record["sem_seg_file_name"] = gt_path
+ dataset_dicts.append(record)
+
+ return dataset_dicts
+
+
+def convert_to_coco_dict(dataset_name):
+ """
+ Convert an instance detection/segmentation or keypoint detection dataset
+ in detectron2's standard format into COCO json format.
+
+ Generic dataset description can be found here:
+ https://detectron2.readthedocs.io/tutorials/datasets.html#register-a-dataset
+
+ COCO data format description can be found here:
+ http://cocodataset.org/#format-data
+
+ Args:
+ dataset_name (str):
+ name of the source dataset
+ Must be registered in DatastCatalog and in detectron2's standard format.
+ Must have corresponding metadata "thing_classes"
+ Returns:
+ coco_dict: serializable dict in COCO json format
+ """
+
+ dataset_dicts = DatasetCatalog.get(dataset_name)
+ metadata = MetadataCatalog.get(dataset_name)
+
+ # unmap the category mapping ids for COCO
+ if hasattr(metadata, "thing_dataset_id_to_contiguous_id"):
+ reverse_id_mapping = {v: k for k, v in metadata.thing_dataset_id_to_contiguous_id.items()}
+ reverse_id_mapper = lambda contiguous_id: reverse_id_mapping[contiguous_id] # noqa
+ else:
+ reverse_id_mapper = lambda contiguous_id: contiguous_id # noqa
+
+ categories = [
+ {"id": reverse_id_mapper(id), "name": name}
+ for id, name in enumerate(metadata.thing_classes)
+ ]
+
+ logger.info("Converting dataset dicts into COCO format")
+ coco_images = []
+ coco_annotations = []
+
+ for image_id, image_dict in enumerate(dataset_dicts):
+ coco_image = {
+ "id": image_dict.get("image_id", image_id),
+ "width": int(image_dict["width"]),
+ "height": int(image_dict["height"]),
+ "file_name": str(image_dict["file_name"]),
+ }
+ coco_images.append(coco_image)
+
+ anns_per_image = image_dict.get("annotations", [])
+ for annotation in anns_per_image:
+ # create a new dict with only COCO fields
+ coco_annotation = {}
+
+ # COCO requirement: XYWH box format for axis-align and XYWHA for rotated
+ bbox = annotation["bbox"]
+ if isinstance(bbox, np.ndarray):
+ if bbox.ndim != 1:
+ raise ValueError(f"bbox has to be 1-dimensional. Got shape={bbox.shape}.")
+ bbox = bbox.tolist()
+ if len(bbox) not in [4, 5]:
+ raise ValueError(f"bbox has to has length 4 or 5. Got {bbox}.")
+ from_bbox_mode = annotation["bbox_mode"]
+ to_bbox_mode = BoxMode.XYWH_ABS if len(bbox) == 4 else BoxMode.XYWHA_ABS
+ bbox = BoxMode.convert(bbox, from_bbox_mode, to_bbox_mode)
+
+ # COCO requirement: instance area
+ if "segmentation" in annotation:
+ # Computing areas for instances by counting the pixels
+ segmentation = annotation["segmentation"]
+ # TODO: check segmentation type: RLE, BinaryMask or Polygon
+ if isinstance(segmentation, list):
+ polygons = PolygonMasks([segmentation])
+ area = polygons.area()[0].item()
+ elif isinstance(segmentation, dict): # RLE
+ area = mask_util.area(segmentation).item()
+ else:
+ raise TypeError(f"Unknown segmentation type {type(segmentation)}!")
+ else:
+ # Computing areas using bounding boxes
+ if to_bbox_mode == BoxMode.XYWH_ABS:
+ bbox_xy = BoxMode.convert(bbox, to_bbox_mode, BoxMode.XYXY_ABS)
+ area = Boxes([bbox_xy]).area()[0].item()
+ else:
+ area = RotatedBoxes([bbox]).area()[0].item()
+
+ if "keypoints" in annotation:
+ keypoints = annotation["keypoints"] # list[int]
+ for idx, v in enumerate(keypoints):
+ if idx % 3 != 2:
+ # COCO's segmentation coordinates are floating points in [0, H or W],
+ # but keypoint coordinates are integers in [0, H-1 or W-1]
+ # For COCO format consistency we substract 0.5
+ # https://github.com/facebookresearch/detectron2/pull/175#issuecomment-551202163
+ keypoints[idx] = v - 0.5
+ if "num_keypoints" in annotation:
+ num_keypoints = annotation["num_keypoints"]
+ else:
+ num_keypoints = sum(kp > 0 for kp in keypoints[2::3])
+
+ # COCO requirement:
+ # linking annotations to images
+ # "id" field must start with 1
+ coco_annotation["id"] = len(coco_annotations) + 1
+ coco_annotation["image_id"] = coco_image["id"]
+ coco_annotation["bbox"] = [round(float(x), 3) for x in bbox]
+ coco_annotation["area"] = float(area)
+ coco_annotation["iscrowd"] = int(annotation.get("iscrowd", 0))
+ coco_annotation["category_id"] = int(reverse_id_mapper(annotation["category_id"]))
+
+ # Add optional fields
+ if "keypoints" in annotation:
+ coco_annotation["keypoints"] = keypoints
+ coco_annotation["num_keypoints"] = num_keypoints
+
+ if "segmentation" in annotation:
+ seg = coco_annotation["segmentation"] = annotation["segmentation"]
+ if isinstance(seg, dict): # RLE
+ counts = seg["counts"]
+ if not isinstance(counts, str):
+ # make it json-serializable
+ seg["counts"] = counts.decode("ascii")
+
+ coco_annotations.append(coco_annotation)
+
+ logger.info(
+ "Conversion finished, "
+ f"#images: {len(coco_images)}, #annotations: {len(coco_annotations)}"
+ )
+
+ info = {
+ "date_created": str(datetime.datetime.now()),
+ "description": "Automatically generated COCO json file for Detectron2.",
+ }
+ coco_dict = {"info": info, "images": coco_images, "categories": categories, "licenses": None}
+ if len(coco_annotations) > 0:
+ coco_dict["annotations"] = coco_annotations
+ return coco_dict
+
+
+def convert_to_coco_json(dataset_name, output_file, allow_cached=True):
+ """
+ Converts dataset into COCO format and saves it to a json file.
+ dataset_name must be registered in DatasetCatalog and in detectron2's standard format.
+
+ Args:
+ dataset_name:
+ reference from the config file to the catalogs
+ must be registered in DatasetCatalog and in detectron2's standard format
+ output_file: path of json file that will be saved to
+ allow_cached: if json file is already present then skip conversion
+ """
+
+ # TODO: The dataset or the conversion script *may* change,
+ # a checksum would be useful for validating the cached data
+
+ PathManager.mkdirs(os.path.dirname(output_file))
+ with file_lock(output_file):
+ if PathManager.exists(output_file) and allow_cached:
+ logger.warning(
+ f"Using previously cached COCO format annotations at '{output_file}'. "
+ "You need to clear the cache file if your dataset has been modified."
+ )
+ else:
+ logger.info(f"Converting annotations of dataset '{dataset_name}' to COCO format ...)")
+ coco_dict = convert_to_coco_dict(dataset_name)
+
+ logger.info(f"Caching COCO format annotations at '{output_file}' ...")
+ tmp_file = output_file + ".tmp"
+ with PathManager.open(tmp_file, "w") as f:
+ json.dump(coco_dict, f)
+ shutil.move(tmp_file, output_file)
+
+
+def register_coco_instances(name, metadata, json_file, image_root):
+ """
+ Register a dataset in COCO's json annotation format for
+ instance detection, instance segmentation and keypoint detection.
+ (i.e., Type 1 and 2 in http://cocodataset.org/#format-data.
+ `instances*.json` and `person_keypoints*.json` in the dataset).
+
+ This is an example of how to register a new dataset.
+ You can do something similar to this function, to register new datasets.
+
+ Args:
+ name (str): the name that identifies a dataset, e.g. "coco_2014_train".
+ metadata (dict): extra metadata associated with this dataset. You can
+ leave it as an empty dict.
+ json_file (str): path to the json instance annotation file.
+ image_root (str or path-like): directory which contains all the images.
+ """
+ assert isinstance(name, str), name
+ assert isinstance(json_file, (str, os.PathLike)), json_file
+ assert isinstance(image_root, (str, os.PathLike)), image_root
+ # 1. register a function which returns dicts
+ DatasetCatalog.register(name, lambda: load_coco_json(json_file, image_root, name))
+
+ # 2. Optionally, add metadata about this dataset,
+ # since they might be useful in evaluation, visualization or logging
+ MetadataCatalog.get(name).set(
+ json_file=json_file, image_root=image_root, evaluator_type="coco", **metadata
+ )
+
+
+if __name__ == "__main__":
+ """
+ Test the COCO json dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.coco \
+ path/to/json path/to/image_root dataset_name
+
+ "dataset_name" can be "coco_2014_minival_100", or other
+ pre-registered ones
+ """
+ from detectron2.utils.logger import setup_logger
+ from detectron2.utils.visualizer import Visualizer
+ import detectron2.data.datasets # noqa # add pre-defined metadata
+ import sys
+
+ logger = setup_logger(name=__name__)
+ assert sys.argv[3] in DatasetCatalog.list()
+ meta = MetadataCatalog.get(sys.argv[3])
+
+ dicts = load_coco_json(sys.argv[1], sys.argv[2], sys.argv[3])
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ dirname = "coco-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+ for d in dicts:
+ img = np.array(Image.open(d["file_name"]))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
diff --git a/detectron2/detectron2/data/datasets/coco_panoptic.py b/detectron2/detectron2/data/datasets/coco_panoptic.py
new file mode 100755
index 0000000..b8dae44
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/coco_panoptic.py
@@ -0,0 +1,228 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import json
+import os
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+from detectron2.utils.file_io import PathManager
+
+from .coco import load_coco_json, load_sem_seg
+
+__all__ = ["register_coco_panoptic", "register_coco_panoptic_separated"]
+
+
+def load_coco_panoptic_json(json_file, image_dir, gt_dir, meta):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/coco/train2017".
+ gt_dir (str): path to the raw annotations. e.g., "~/coco/panoptic_train2017".
+ json_file (str): path to the json file. e.g., "~/coco/annotations/panoptic_train2017.json".
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+ """
+
+ def _convert_category_id(segment_info, meta):
+ if segment_info["category_id"] in meta["thing_dataset_id_to_contiguous_id"]:
+ segment_info["category_id"] = meta["thing_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ segment_info["isthing"] = True
+ else:
+ segment_info["category_id"] = meta["stuff_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ segment_info["isthing"] = False
+ return segment_info
+
+ with PathManager.open(json_file) as f:
+ json_info = json.load(f)
+
+ ret = []
+ for ann in json_info["annotations"]:
+ image_id = int(ann["image_id"])
+ # TODO: currently we assume image and label has the same filename but
+ # different extension, and images have extension ".jpg" for COCO. Need
+ # to make image extension a user-provided argument if we extend this
+ # function to support other COCO-like datasets.
+ image_file = os.path.join(image_dir, os.path.splitext(ann["file_name"])[0] + ".jpg")
+ label_file = os.path.join(gt_dir, ann["file_name"])
+ segments_info = [_convert_category_id(x, meta) for x in ann["segments_info"]]
+ ret.append(
+ {
+ "file_name": image_file,
+ "image_id": image_id,
+ "pan_seg_file_name": label_file,
+ "segments_info": segments_info,
+ }
+ )
+ assert len(ret), f"No images found in {image_dir}!"
+ assert PathManager.isfile(ret[0]["file_name"]), ret[0]["file_name"]
+ assert PathManager.isfile(ret[0]["pan_seg_file_name"]), ret[0]["pan_seg_file_name"]
+ return ret
+
+
+def register_coco_panoptic(
+ name, metadata, image_root, panoptic_root, panoptic_json, instances_json=None
+):
+ """
+ Register a "standard" version of COCO panoptic segmentation dataset named `name`.
+ The dictionaries in this registered dataset follows detectron2's standard format.
+ Hence it's called "standard".
+
+ Args:
+ name (str): the name that identifies a dataset,
+ e.g. "coco_2017_train_panoptic"
+ metadata (dict): extra metadata associated with this dataset.
+ image_root (str): directory which contains all the images
+ panoptic_root (str): directory which contains panoptic annotation images in COCO format
+ panoptic_json (str): path to the json panoptic annotation file in COCO format
+ sem_seg_root (none): not used, to be consistent with
+ `register_coco_panoptic_separated`.
+ instances_json (str): path to the json instance annotation file
+ """
+ panoptic_name = name
+ DatasetCatalog.register(
+ panoptic_name,
+ lambda: load_coco_panoptic_json(panoptic_json, image_root, panoptic_root, metadata),
+ )
+ MetadataCatalog.get(panoptic_name).set(
+ panoptic_root=panoptic_root,
+ image_root=image_root,
+ panoptic_json=panoptic_json,
+ json_file=instances_json,
+ evaluator_type="coco_panoptic_seg",
+ ignore_label=255,
+ label_divisor=1000,
+ **metadata,
+ )
+
+
+def register_coco_panoptic_separated(
+ name, metadata, image_root, panoptic_root, panoptic_json, sem_seg_root, instances_json
+):
+ """
+ Register a "separated" version of COCO panoptic segmentation dataset named `name`.
+ The annotations in this registered dataset will contain both instance annotations and
+ semantic annotations, each with its own contiguous ids. Hence it's called "separated".
+
+ It follows the setting used by the PanopticFPN paper:
+
+ 1. The instance annotations directly come from polygons in the COCO
+ instances annotation task, rather than from the masks in the COCO panoptic annotations.
+
+ The two format have small differences:
+ Polygons in the instance annotations may have overlaps.
+ The mask annotations are produced by labeling the overlapped polygons
+ with depth ordering.
+
+ 2. The semantic annotations are converted from panoptic annotations, where
+ all "things" are assigned a semantic id of 0.
+ All semantic categories will therefore have ids in contiguous
+ range [1, #stuff_categories].
+
+ This function will also register a pure semantic segmentation dataset
+ named ``name + '_stuffonly'``.
+
+ Args:
+ name (str): the name that identifies a dataset,
+ e.g. "coco_2017_train_panoptic"
+ metadata (dict): extra metadata associated with this dataset.
+ image_root (str): directory which contains all the images
+ panoptic_root (str): directory which contains panoptic annotation images
+ panoptic_json (str): path to the json panoptic annotation file
+ sem_seg_root (str): directory which contains all the ground truth segmentation annotations.
+ instances_json (str): path to the json instance annotation file
+ """
+ panoptic_name = name + "_separated"
+ DatasetCatalog.register(
+ panoptic_name,
+ lambda: merge_to_panoptic(
+ load_coco_json(instances_json, image_root, panoptic_name),
+ load_sem_seg(sem_seg_root, image_root),
+ ),
+ )
+ MetadataCatalog.get(panoptic_name).set(
+ panoptic_root=panoptic_root,
+ image_root=image_root,
+ panoptic_json=panoptic_json,
+ sem_seg_root=sem_seg_root,
+ json_file=instances_json, # TODO rename
+ evaluator_type="coco_panoptic_seg",
+ ignore_label=255,
+ **metadata,
+ )
+
+ semantic_name = name + "_stuffonly"
+ DatasetCatalog.register(semantic_name, lambda: load_sem_seg(sem_seg_root, image_root))
+ MetadataCatalog.get(semantic_name).set(
+ sem_seg_root=sem_seg_root,
+ image_root=image_root,
+ evaluator_type="sem_seg",
+ ignore_label=255,
+ **metadata,
+ )
+
+
+def merge_to_panoptic(detection_dicts, sem_seg_dicts):
+ """
+ Create dataset dicts for panoptic segmentation, by
+ merging two dicts using "file_name" field to match their entries.
+
+ Args:
+ detection_dicts (list[dict]): lists of dicts for object detection or instance segmentation.
+ sem_seg_dicts (list[dict]): lists of dicts for semantic segmentation.
+
+ Returns:
+ list[dict] (one per input image): Each dict contains all (key, value) pairs from dicts in
+ both detection_dicts and sem_seg_dicts that correspond to the same image.
+ The function assumes that the same key in different dicts has the same value.
+ """
+ results = []
+ sem_seg_file_to_entry = {x["file_name"]: x for x in sem_seg_dicts}
+ assert len(sem_seg_file_to_entry) > 0
+
+ for det_dict in detection_dicts:
+ dic = copy.copy(det_dict)
+ dic.update(sem_seg_file_to_entry[dic["file_name"]])
+ results.append(dic)
+ return results
+
+
+if __name__ == "__main__":
+ """
+ Test the COCO panoptic dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.coco_panoptic \
+ path/to/image_root path/to/panoptic_root path/to/panoptic_json dataset_name 10
+
+ "dataset_name" can be "coco_2017_train_panoptic", or other
+ pre-registered ones
+ """
+ from detectron2.utils.logger import setup_logger
+ from detectron2.utils.visualizer import Visualizer
+ import detectron2.data.datasets # noqa # add pre-defined metadata
+ import sys
+ from PIL import Image
+ import numpy as np
+
+ logger = setup_logger(name=__name__)
+ assert sys.argv[4] in DatasetCatalog.list()
+ meta = MetadataCatalog.get(sys.argv[4])
+
+ dicts = load_coco_panoptic_json(sys.argv[3], sys.argv[1], sys.argv[2], meta.as_dict())
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ dirname = "coco-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+ num_imgs_to_vis = int(sys.argv[5])
+ for i, d in enumerate(dicts):
+ img = np.array(Image.open(d["file_name"]))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
+ if i + 1 >= num_imgs_to_vis:
+ break
diff --git a/detectron2/detectron2/data/datasets/lvis.py b/detectron2/detectron2/data/datasets/lvis.py
new file mode 100755
index 0000000..576d962
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/lvis.py
@@ -0,0 +1,241 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import os
+from fvcore.common.timer import Timer
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+from detectron2.structures import BoxMode
+from detectron2.utils.file_io import PathManager
+
+from .builtin_meta import _get_coco_instances_meta
+from .lvis_v0_5_categories import LVIS_CATEGORIES as LVIS_V0_5_CATEGORIES
+from .lvis_v1_categories import LVIS_CATEGORIES as LVIS_V1_CATEGORIES
+from .lvis_v1_category_image_count import LVIS_CATEGORY_IMAGE_COUNT as LVIS_V1_CATEGORY_IMAGE_COUNT
+
+"""
+This file contains functions to parse LVIS-format annotations into dicts in the
+"Detectron2 format".
+"""
+
+logger = logging.getLogger(__name__)
+
+__all__ = ["load_lvis_json", "register_lvis_instances", "get_lvis_instances_meta"]
+
+
+def register_lvis_instances(name, metadata, json_file, image_root):
+ """
+ Register a dataset in LVIS's json annotation format for instance detection and segmentation.
+
+ Args:
+ name (str): a name that identifies the dataset, e.g. "lvis_v0.5_train".
+ metadata (dict): extra metadata associated with this dataset. It can be an empty dict.
+ json_file (str): path to the json instance annotation file.
+ image_root (str or path-like): directory which contains all the images.
+ """
+ DatasetCatalog.register(name, lambda: load_lvis_json(json_file, image_root, name))
+ MetadataCatalog.get(name).set(
+ json_file=json_file, image_root=image_root, evaluator_type="lvis", **metadata
+ )
+
+
+def load_lvis_json(json_file, image_root, dataset_name=None, extra_annotation_keys=None):
+ """
+ Load a json file in LVIS's annotation format.
+
+ Args:
+ json_file (str): full path to the LVIS json annotation file.
+ image_root (str): the directory where the images in this json file exists.
+ dataset_name (str): the name of the dataset (e.g., "lvis_v0.5_train").
+ If provided, this function will put "thing_classes" into the metadata
+ associated with this dataset.
+ extra_annotation_keys (list[str]): list of per-annotation keys that should also be
+ loaded into the dataset dict (besides "bbox", "bbox_mode", "category_id",
+ "segmentation"). The values for these keys will be returned as-is.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+
+ Notes:
+ 1. This function does not read the image files.
+ The results do not have the "image" field.
+ """
+ from lvis import LVIS
+
+ json_file = PathManager.get_local_path(json_file)
+
+ timer = Timer()
+ lvis_api = LVIS(json_file)
+ if timer.seconds() > 1:
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
+
+ if dataset_name is not None:
+ meta = get_lvis_instances_meta(dataset_name)
+ MetadataCatalog.get(dataset_name).set(**meta)
+
+ # sort indices for reproducible results
+ img_ids = sorted(lvis_api.imgs.keys())
+ # imgs is a list of dicts, each looks something like:
+ # {'license': 4,
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
+ # 'height': 427,
+ # 'width': 640,
+ # 'date_captured': '2013-11-17 05:57:24',
+ # 'id': 1268}
+ imgs = lvis_api.load_imgs(img_ids)
+ # anns is a list[list[dict]], where each dict is an annotation
+ # record for an object. The inner list enumerates the objects in an image
+ # and the outer list enumerates over images. Example of anns[0]:
+ # [{'segmentation': [[192.81,
+ # 247.09,
+ # ...
+ # 219.03,
+ # 249.06]],
+ # 'area': 1035.749,
+ # 'image_id': 1268,
+ # 'bbox': [192.81, 224.8, 74.73, 33.43],
+ # 'category_id': 16,
+ # 'id': 42986},
+ # ...]
+ anns = [lvis_api.img_ann_map[img_id] for img_id in img_ids]
+
+ # Sanity check that each annotation has a unique id
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique".format(
+ json_file
+ )
+
+ imgs_anns = list(zip(imgs, anns))
+
+ logger.info("Loaded {} images in the LVIS format from {}".format(len(imgs_anns), json_file))
+
+ if extra_annotation_keys:
+ logger.info(
+ "The following extra annotation keys will be loaded: {} ".format(extra_annotation_keys)
+ )
+ else:
+ extra_annotation_keys = []
+
+ def get_file_name(img_root, img_dict):
+ # Determine the path including the split folder ("train2017", "val2017", "test2017") from
+ # the coco_url field. Example:
+ # 'coco_url': 'http://images.cocodataset.org/train2017/000000155379.jpg'
+ split_folder, file_name = img_dict["coco_url"].split("/")[-2:]
+ return os.path.join(img_root + split_folder, file_name)
+
+ dataset_dicts = []
+
+ for (img_dict, anno_dict_list) in imgs_anns:
+ record = {}
+ record["file_name"] = get_file_name(image_root, img_dict)
+ record["height"] = img_dict["height"]
+ record["width"] = img_dict["width"]
+ record["not_exhaustive_category_ids"] = img_dict.get("not_exhaustive_category_ids", [])
+ record["neg_category_ids"] = img_dict.get("neg_category_ids", [])
+ image_id = record["image_id"] = img_dict["id"]
+
+ objs = []
+ for anno in anno_dict_list:
+ # Check that the image_id in this annotation is the same as
+ # the image_id we're looking at.
+ # This fails only when the data parsing logic or the annotation file is buggy.
+ assert anno["image_id"] == image_id
+ obj = {"bbox": anno["bbox"], "bbox_mode": BoxMode.XYWH_ABS}
+ # LVIS data loader can be used to load COCO dataset categories. In this case `meta`
+ # variable will have a field with COCO-specific category mapping.
+ if dataset_name is not None and "thing_dataset_id_to_contiguous_id" in meta:
+ obj["category_id"] = meta["thing_dataset_id_to_contiguous_id"][anno["category_id"]]
+ else:
+ obj["category_id"] = anno["category_id"] - 1 # Convert 1-indexed to 0-indexed
+ segm = anno["segmentation"] # list[list[float]]
+ # filter out invalid polygons (< 3 points)
+ valid_segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
+ assert len(segm) == len(
+ valid_segm
+ ), "Annotation contains an invalid polygon with < 3 points"
+ assert len(segm) > 0
+ obj["segmentation"] = segm
+ for extra_ann_key in extra_annotation_keys:
+ obj[extra_ann_key] = anno[extra_ann_key]
+ objs.append(obj)
+ record["annotations"] = objs
+ dataset_dicts.append(record)
+
+ return dataset_dicts
+
+
+def get_lvis_instances_meta(dataset_name):
+ """
+ Load LVIS metadata.
+
+ Args:
+ dataset_name (str): LVIS dataset name without the split name (e.g., "lvis_v0.5").
+
+ Returns:
+ dict: LVIS metadata with keys: thing_classes
+ """
+ if "cocofied" in dataset_name:
+ return _get_coco_instances_meta()
+ if "v0.5" in dataset_name:
+ return _get_lvis_instances_meta_v0_5()
+ elif "v1" in dataset_name:
+ return _get_lvis_instances_meta_v1()
+ raise ValueError("No built-in metadata for dataset {}".format(dataset_name))
+
+
+def _get_lvis_instances_meta_v0_5():
+ assert len(LVIS_V0_5_CATEGORIES) == 1230
+ cat_ids = [k["id"] for k in LVIS_V0_5_CATEGORIES]
+ assert min(cat_ids) == 1 and max(cat_ids) == len(
+ cat_ids
+ ), "Category ids are not in [1, #categories], as expected"
+ # Ensure that the category list is sorted by id
+ lvis_categories = sorted(LVIS_V0_5_CATEGORIES, key=lambda x: x["id"])
+ thing_classes = [k["synonyms"][0] for k in lvis_categories]
+ meta = {"thing_classes": thing_classes}
+ return meta
+
+
+def _get_lvis_instances_meta_v1():
+ assert len(LVIS_V1_CATEGORIES) == 1203
+ cat_ids = [k["id"] for k in LVIS_V1_CATEGORIES]
+ assert min(cat_ids) == 1 and max(cat_ids) == len(
+ cat_ids
+ ), "Category ids are not in [1, #categories], as expected"
+ # Ensure that the category list is sorted by id
+ lvis_categories = sorted(LVIS_V1_CATEGORIES, key=lambda x: x["id"])
+ thing_classes = [k["synonyms"][0] for k in lvis_categories]
+ meta = {"thing_classes": thing_classes, "class_image_count": LVIS_V1_CATEGORY_IMAGE_COUNT}
+ return meta
+
+
+if __name__ == "__main__":
+ """
+ Test the LVIS json dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.lvis \
+ path/to/json path/to/image_root dataset_name vis_limit
+ """
+ import sys
+ import numpy as np
+ from detectron2.utils.logger import setup_logger
+ from PIL import Image
+ import detectron2.data.datasets # noqa # add pre-defined metadata
+ from detectron2.utils.visualizer import Visualizer
+
+ logger = setup_logger(name=__name__)
+ meta = MetadataCatalog.get(sys.argv[3])
+
+ dicts = load_lvis_json(sys.argv[1], sys.argv[2], sys.argv[3])
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ dirname = "lvis-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+ for d in dicts[: int(sys.argv[4])]:
+ img = np.array(Image.open(d["file_name"]))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
diff --git a/detectron2/detectron2/data/datasets/lvis_v0_5_categories.py b/detectron2/detectron2/data/datasets/lvis_v0_5_categories.py
new file mode 100755
index 0000000..d3dab61
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/lvis_v0_5_categories.py
@@ -0,0 +1,13 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Autogen with
+# with open("lvis_v0.5_val.json", "r") as f:
+# a = json.load(f)
+# c = a["categories"]
+# for x in c:
+# del x["image_count"]
+# del x["instance_count"]
+# LVIS_CATEGORIES = repr(c) + " # noqa"
+
+# fmt: off
+LVIS_CATEGORIES = [{'frequency': 'r', 'id': 1, 'synset': 'acorn.n.01', 'synonyms': ['acorn'], 'def': 'nut from an oak tree', 'name': 'acorn'}, {'frequency': 'c', 'id': 2, 'synset': 'aerosol.n.02', 'synonyms': ['aerosol_can', 'spray_can'], 'def': 'a dispenser that holds a substance under pressure', 'name': 'aerosol_can'}, {'frequency': 'f', 'id': 3, 'synset': 'air_conditioner.n.01', 'synonyms': ['air_conditioner'], 'def': 'a machine that keeps air cool and dry', 'name': 'air_conditioner'}, {'frequency': 'f', 'id': 4, 'synset': 'airplane.n.01', 'synonyms': ['airplane', 'aeroplane'], 'def': 'an aircraft that has a fixed wing and is powered by propellers or jets', 'name': 'airplane'}, {'frequency': 'c', 'id': 5, 'synset': 'alarm_clock.n.01', 'synonyms': ['alarm_clock'], 'def': 'a clock that wakes a sleeper at some preset time', 'name': 'alarm_clock'}, {'frequency': 'c', 'id': 6, 'synset': 'alcohol.n.01', 'synonyms': ['alcohol', 'alcoholic_beverage'], 'def': 'a liquor or brew containing alcohol as the active agent', 'name': 'alcohol'}, {'frequency': 'r', 'id': 7, 'synset': 'alligator.n.02', 'synonyms': ['alligator', 'gator'], 'def': 'amphibious reptiles related to crocodiles but with shorter broader snouts', 'name': 'alligator'}, {'frequency': 'c', 'id': 8, 'synset': 'almond.n.02', 'synonyms': ['almond'], 'def': 'oval-shaped edible seed of the almond tree', 'name': 'almond'}, {'frequency': 'c', 'id': 9, 'synset': 'ambulance.n.01', 'synonyms': ['ambulance'], 'def': 'a vehicle that takes people to and from hospitals', 'name': 'ambulance'}, {'frequency': 'r', 'id': 10, 'synset': 'amplifier.n.01', 'synonyms': ['amplifier'], 'def': 'electronic equipment that increases strength of signals', 'name': 'amplifier'}, {'frequency': 'c', 'id': 11, 'synset': 'anklet.n.03', 'synonyms': ['anklet', 'ankle_bracelet'], 'def': 'an ornament worn around the ankle', 'name': 'anklet'}, {'frequency': 'f', 'id': 12, 'synset': 'antenna.n.01', 'synonyms': ['antenna', 'aerial', 'transmitting_aerial'], 'def': 'an electrical device that sends or receives radio or television signals', 'name': 'antenna'}, {'frequency': 'f', 'id': 13, 'synset': 'apple.n.01', 'synonyms': ['apple'], 'def': 'fruit with red or yellow or green skin and sweet to tart crisp whitish flesh', 'name': 'apple'}, {'frequency': 'r', 'id': 14, 'synset': 'apple_juice.n.01', 'synonyms': ['apple_juice'], 'def': 'the juice of apples', 'name': 'apple_juice'}, {'frequency': 'r', 'id': 15, 'synset': 'applesauce.n.01', 'synonyms': ['applesauce'], 'def': 'puree of stewed apples usually sweetened and spiced', 'name': 'applesauce'}, {'frequency': 'r', 'id': 16, 'synset': 'apricot.n.02', 'synonyms': ['apricot'], 'def': 'downy yellow to rosy-colored fruit resembling a small peach', 'name': 'apricot'}, {'frequency': 'f', 'id': 17, 'synset': 'apron.n.01', 'synonyms': ['apron'], 'def': 'a garment of cloth that is tied about the waist and worn to protect clothing', 'name': 'apron'}, {'frequency': 'c', 'id': 18, 'synset': 'aquarium.n.01', 'synonyms': ['aquarium', 'fish_tank'], 'def': 'a tank/pool/bowl filled with water for keeping live fish and underwater animals', 'name': 'aquarium'}, {'frequency': 'c', 'id': 19, 'synset': 'armband.n.02', 'synonyms': ['armband'], 'def': 'a band worn around the upper arm', 'name': 'armband'}, {'frequency': 'f', 'id': 20, 'synset': 'armchair.n.01', 'synonyms': ['armchair'], 'def': 'chair with a support on each side for arms', 'name': 'armchair'}, {'frequency': 'r', 'id': 21, 'synset': 'armoire.n.01', 'synonyms': ['armoire'], 'def': 'a large wardrobe or cabinet', 'name': 'armoire'}, {'frequency': 'r', 'id': 22, 'synset': 'armor.n.01', 'synonyms': ['armor', 'armour'], 'def': 'protective covering made of metal and used in combat', 'name': 'armor'}, {'frequency': 'c', 'id': 23, 'synset': 'artichoke.n.02', 'synonyms': ['artichoke'], 'def': 'a thistlelike flower head with edible fleshy leaves and heart', 'name': 'artichoke'}, {'frequency': 'f', 'id': 24, 'synset': 'ashcan.n.01', 'synonyms': ['trash_can', 'garbage_can', 'wastebin', 'dustbin', 'trash_barrel', 'trash_bin'], 'def': 'a bin that holds rubbish until it is collected', 'name': 'trash_can'}, {'frequency': 'c', 'id': 25, 'synset': 'ashtray.n.01', 'synonyms': ['ashtray'], 'def': "a receptacle for the ash from smokers' cigars or cigarettes", 'name': 'ashtray'}, {'frequency': 'c', 'id': 26, 'synset': 'asparagus.n.02', 'synonyms': ['asparagus'], 'def': 'edible young shoots of the asparagus plant', 'name': 'asparagus'}, {'frequency': 'c', 'id': 27, 'synset': 'atomizer.n.01', 'synonyms': ['atomizer', 'atomiser', 'spray', 'sprayer', 'nebulizer', 'nebuliser'], 'def': 'a dispenser that turns a liquid (such as perfume) into a fine mist', 'name': 'atomizer'}, {'frequency': 'c', 'id': 28, 'synset': 'avocado.n.01', 'synonyms': ['avocado'], 'def': 'a pear-shaped fruit with green or blackish skin and rich yellowish pulp enclosing a single large seed', 'name': 'avocado'}, {'frequency': 'c', 'id': 29, 'synset': 'award.n.02', 'synonyms': ['award', 'accolade'], 'def': 'a tangible symbol signifying approval or distinction', 'name': 'award'}, {'frequency': 'f', 'id': 30, 'synset': 'awning.n.01', 'synonyms': ['awning'], 'def': 'a canopy made of canvas to shelter people or things from rain or sun', 'name': 'awning'}, {'frequency': 'r', 'id': 31, 'synset': 'ax.n.01', 'synonyms': ['ax', 'axe'], 'def': 'an edge tool with a heavy bladed head mounted across a handle', 'name': 'ax'}, {'frequency': 'f', 'id': 32, 'synset': 'baby_buggy.n.01', 'synonyms': ['baby_buggy', 'baby_carriage', 'perambulator', 'pram', 'stroller'], 'def': 'a small vehicle with four wheels in which a baby or child is pushed around', 'name': 'baby_buggy'}, {'frequency': 'c', 'id': 33, 'synset': 'backboard.n.01', 'synonyms': ['basketball_backboard'], 'def': 'a raised vertical board with basket attached; used to play basketball', 'name': 'basketball_backboard'}, {'frequency': 'f', 'id': 34, 'synset': 'backpack.n.01', 'synonyms': ['backpack', 'knapsack', 'packsack', 'rucksack', 'haversack'], 'def': 'a bag carried by a strap on your back or shoulder', 'name': 'backpack'}, {'frequency': 'f', 'id': 35, 'synset': 'bag.n.04', 'synonyms': ['handbag', 'purse', 'pocketbook'], 'def': 'a container used for carrying money and small personal items or accessories', 'name': 'handbag'}, {'frequency': 'f', 'id': 36, 'synset': 'bag.n.06', 'synonyms': ['suitcase', 'baggage', 'luggage'], 'def': 'cases used to carry belongings when traveling', 'name': 'suitcase'}, {'frequency': 'c', 'id': 37, 'synset': 'bagel.n.01', 'synonyms': ['bagel', 'beigel'], 'def': 'glazed yeast-raised doughnut-shaped roll with hard crust', 'name': 'bagel'}, {'frequency': 'r', 'id': 38, 'synset': 'bagpipe.n.01', 'synonyms': ['bagpipe'], 'def': 'a tubular wind instrument; the player blows air into a bag and squeezes it out', 'name': 'bagpipe'}, {'frequency': 'r', 'id': 39, 'synset': 'baguet.n.01', 'synonyms': ['baguet', 'baguette'], 'def': 'narrow French stick loaf', 'name': 'baguet'}, {'frequency': 'r', 'id': 40, 'synset': 'bait.n.02', 'synonyms': ['bait', 'lure'], 'def': 'something used to lure fish or other animals into danger so they can be trapped or killed', 'name': 'bait'}, {'frequency': 'f', 'id': 41, 'synset': 'ball.n.06', 'synonyms': ['ball'], 'def': 'a spherical object used as a plaything', 'name': 'ball'}, {'frequency': 'r', 'id': 42, 'synset': 'ballet_skirt.n.01', 'synonyms': ['ballet_skirt', 'tutu'], 'def': 'very short skirt worn by ballerinas', 'name': 'ballet_skirt'}, {'frequency': 'f', 'id': 43, 'synset': 'balloon.n.01', 'synonyms': ['balloon'], 'def': 'large tough nonrigid bag filled with gas or heated air', 'name': 'balloon'}, {'frequency': 'c', 'id': 44, 'synset': 'bamboo.n.02', 'synonyms': ['bamboo'], 'def': 'woody tropical grass having hollow woody stems', 'name': 'bamboo'}, {'frequency': 'f', 'id': 45, 'synset': 'banana.n.02', 'synonyms': ['banana'], 'def': 'elongated crescent-shaped yellow fruit with soft sweet flesh', 'name': 'banana'}, {'frequency': 'r', 'id': 46, 'synset': 'band_aid.n.01', 'synonyms': ['Band_Aid'], 'def': 'trade name for an adhesive bandage to cover small cuts or blisters', 'name': 'Band_Aid'}, {'frequency': 'c', 'id': 47, 'synset': 'bandage.n.01', 'synonyms': ['bandage'], 'def': 'a piece of soft material that covers and protects an injured part of the body', 'name': 'bandage'}, {'frequency': 'c', 'id': 48, 'synset': 'bandanna.n.01', 'synonyms': ['bandanna', 'bandana'], 'def': 'large and brightly colored handkerchief; often used as a neckerchief', 'name': 'bandanna'}, {'frequency': 'r', 'id': 49, 'synset': 'banjo.n.01', 'synonyms': ['banjo'], 'def': 'a stringed instrument of the guitar family with a long neck and circular body', 'name': 'banjo'}, {'frequency': 'f', 'id': 50, 'synset': 'banner.n.01', 'synonyms': ['banner', 'streamer'], 'def': 'long strip of cloth or paper used for decoration or advertising', 'name': 'banner'}, {'frequency': 'r', 'id': 51, 'synset': 'barbell.n.01', 'synonyms': ['barbell'], 'def': 'a bar to which heavy discs are attached at each end; used in weightlifting', 'name': 'barbell'}, {'frequency': 'r', 'id': 52, 'synset': 'barge.n.01', 'synonyms': ['barge'], 'def': 'a flatbottom boat for carrying heavy loads (especially on canals)', 'name': 'barge'}, {'frequency': 'f', 'id': 53, 'synset': 'barrel.n.02', 'synonyms': ['barrel', 'cask'], 'def': 'a cylindrical container that holds liquids', 'name': 'barrel'}, {'frequency': 'c', 'id': 54, 'synset': 'barrette.n.01', 'synonyms': ['barrette'], 'def': "a pin for holding women's hair in place", 'name': 'barrette'}, {'frequency': 'c', 'id': 55, 'synset': 'barrow.n.03', 'synonyms': ['barrow', 'garden_cart', 'lawn_cart', 'wheelbarrow'], 'def': 'a cart for carrying small loads; has handles and one or more wheels', 'name': 'barrow'}, {'frequency': 'f', 'id': 56, 'synset': 'base.n.03', 'synonyms': ['baseball_base'], 'def': 'a place that the runner must touch before scoring', 'name': 'baseball_base'}, {'frequency': 'f', 'id': 57, 'synset': 'baseball.n.02', 'synonyms': ['baseball'], 'def': 'a ball used in playing baseball', 'name': 'baseball'}, {'frequency': 'f', 'id': 58, 'synset': 'baseball_bat.n.01', 'synonyms': ['baseball_bat'], 'def': 'an implement used in baseball by the batter', 'name': 'baseball_bat'}, {'frequency': 'f', 'id': 59, 'synset': 'baseball_cap.n.01', 'synonyms': ['baseball_cap', 'jockey_cap', 'golf_cap'], 'def': 'a cap with a bill', 'name': 'baseball_cap'}, {'frequency': 'f', 'id': 60, 'synset': 'baseball_glove.n.01', 'synonyms': ['baseball_glove', 'baseball_mitt'], 'def': 'the handwear used by fielders in playing baseball', 'name': 'baseball_glove'}, {'frequency': 'f', 'id': 61, 'synset': 'basket.n.01', 'synonyms': ['basket', 'handbasket'], 'def': 'a container that is usually woven and has handles', 'name': 'basket'}, {'frequency': 'c', 'id': 62, 'synset': 'basket.n.03', 'synonyms': ['basketball_hoop'], 'def': 'metal hoop supporting a net through which players try to throw the basketball', 'name': 'basketball_hoop'}, {'frequency': 'c', 'id': 63, 'synset': 'basketball.n.02', 'synonyms': ['basketball'], 'def': 'an inflated ball used in playing basketball', 'name': 'basketball'}, {'frequency': 'r', 'id': 64, 'synset': 'bass_horn.n.01', 'synonyms': ['bass_horn', 'sousaphone', 'tuba'], 'def': 'the lowest brass wind instrument', 'name': 'bass_horn'}, {'frequency': 'r', 'id': 65, 'synset': 'bat.n.01', 'synonyms': ['bat_(animal)'], 'def': 'nocturnal mouselike mammal with forelimbs modified to form membranous wings', 'name': 'bat_(animal)'}, {'frequency': 'f', 'id': 66, 'synset': 'bath_mat.n.01', 'synonyms': ['bath_mat'], 'def': 'a heavy towel or mat to stand on while drying yourself after a bath', 'name': 'bath_mat'}, {'frequency': 'f', 'id': 67, 'synset': 'bath_towel.n.01', 'synonyms': ['bath_towel'], 'def': 'a large towel; to dry yourself after a bath', 'name': 'bath_towel'}, {'frequency': 'c', 'id': 68, 'synset': 'bathrobe.n.01', 'synonyms': ['bathrobe'], 'def': 'a loose-fitting robe of towelling; worn after a bath or swim', 'name': 'bathrobe'}, {'frequency': 'f', 'id': 69, 'synset': 'bathtub.n.01', 'synonyms': ['bathtub', 'bathing_tub'], 'def': 'a large open container that you fill with water and use to wash the body', 'name': 'bathtub'}, {'frequency': 'r', 'id': 70, 'synset': 'batter.n.02', 'synonyms': ['batter_(food)'], 'def': 'a liquid or semiliquid mixture, as of flour, eggs, and milk, used in cooking', 'name': 'batter_(food)'}, {'frequency': 'c', 'id': 71, 'synset': 'battery.n.02', 'synonyms': ['battery'], 'def': 'a portable device that produces electricity', 'name': 'battery'}, {'frequency': 'r', 'id': 72, 'synset': 'beach_ball.n.01', 'synonyms': ['beachball'], 'def': 'large and light ball; for play at the seaside', 'name': 'beachball'}, {'frequency': 'c', 'id': 73, 'synset': 'bead.n.01', 'synonyms': ['bead'], 'def': 'a small ball with a hole through the middle used for ornamentation, jewellery, etc.', 'name': 'bead'}, {'frequency': 'r', 'id': 74, 'synset': 'beaker.n.01', 'synonyms': ['beaker'], 'def': 'a flatbottomed jar made of glass or plastic; used for chemistry', 'name': 'beaker'}, {'frequency': 'c', 'id': 75, 'synset': 'bean_curd.n.01', 'synonyms': ['bean_curd', 'tofu'], 'def': 'cheeselike food made of curdled soybean milk', 'name': 'bean_curd'}, {'frequency': 'c', 'id': 76, 'synset': 'beanbag.n.01', 'synonyms': ['beanbag'], 'def': 'a bag filled with dried beans or similar items; used in games or to sit on', 'name': 'beanbag'}, {'frequency': 'f', 'id': 77, 'synset': 'beanie.n.01', 'synonyms': ['beanie', 'beany'], 'def': 'a small skullcap; formerly worn by schoolboys and college freshmen', 'name': 'beanie'}, {'frequency': 'f', 'id': 78, 'synset': 'bear.n.01', 'synonyms': ['bear'], 'def': 'large carnivorous or omnivorous mammals with shaggy coats and claws', 'name': 'bear'}, {'frequency': 'f', 'id': 79, 'synset': 'bed.n.01', 'synonyms': ['bed'], 'def': 'a piece of furniture that provides a place to sleep', 'name': 'bed'}, {'frequency': 'c', 'id': 80, 'synset': 'bedspread.n.01', 'synonyms': ['bedspread', 'bedcover', 'bed_covering', 'counterpane', 'spread'], 'def': 'decorative cover for a bed', 'name': 'bedspread'}, {'frequency': 'f', 'id': 81, 'synset': 'beef.n.01', 'synonyms': ['cow'], 'def': 'cattle that are reared for their meat', 'name': 'cow'}, {'frequency': 'c', 'id': 82, 'synset': 'beef.n.02', 'synonyms': ['beef_(food)', 'boeuf_(food)'], 'def': 'meat from an adult domestic bovine', 'name': 'beef_(food)'}, {'frequency': 'r', 'id': 83, 'synset': 'beeper.n.01', 'synonyms': ['beeper', 'pager'], 'def': 'an device that beeps when the person carrying it is being paged', 'name': 'beeper'}, {'frequency': 'f', 'id': 84, 'synset': 'beer_bottle.n.01', 'synonyms': ['beer_bottle'], 'def': 'a bottle that holds beer', 'name': 'beer_bottle'}, {'frequency': 'c', 'id': 85, 'synset': 'beer_can.n.01', 'synonyms': ['beer_can'], 'def': 'a can that holds beer', 'name': 'beer_can'}, {'frequency': 'r', 'id': 86, 'synset': 'beetle.n.01', 'synonyms': ['beetle'], 'def': 'insect with hard wing covers', 'name': 'beetle'}, {'frequency': 'f', 'id': 87, 'synset': 'bell.n.01', 'synonyms': ['bell'], 'def': 'a hollow device made of metal that makes a ringing sound when struck', 'name': 'bell'}, {'frequency': 'f', 'id': 88, 'synset': 'bell_pepper.n.02', 'synonyms': ['bell_pepper', 'capsicum'], 'def': 'large bell-shaped sweet pepper in green or red or yellow or orange or black varieties', 'name': 'bell_pepper'}, {'frequency': 'f', 'id': 89, 'synset': 'belt.n.02', 'synonyms': ['belt'], 'def': 'a band to tie or buckle around the body (usually at the waist)', 'name': 'belt'}, {'frequency': 'f', 'id': 90, 'synset': 'belt_buckle.n.01', 'synonyms': ['belt_buckle'], 'def': 'the buckle used to fasten a belt', 'name': 'belt_buckle'}, {'frequency': 'f', 'id': 91, 'synset': 'bench.n.01', 'synonyms': ['bench'], 'def': 'a long seat for more than one person', 'name': 'bench'}, {'frequency': 'c', 'id': 92, 'synset': 'beret.n.01', 'synonyms': ['beret'], 'def': 'a cap with no brim or bill; made of soft cloth', 'name': 'beret'}, {'frequency': 'c', 'id': 93, 'synset': 'bib.n.02', 'synonyms': ['bib'], 'def': 'a napkin tied under the chin of a child while eating', 'name': 'bib'}, {'frequency': 'r', 'id': 94, 'synset': 'bible.n.01', 'synonyms': ['Bible'], 'def': 'the sacred writings of the Christian religions', 'name': 'Bible'}, {'frequency': 'f', 'id': 95, 'synset': 'bicycle.n.01', 'synonyms': ['bicycle', 'bike_(bicycle)'], 'def': 'a wheeled vehicle that has two wheels and is moved by foot pedals', 'name': 'bicycle'}, {'frequency': 'f', 'id': 96, 'synset': 'bill.n.09', 'synonyms': ['visor', 'vizor'], 'def': 'a brim that projects to the front to shade the eyes', 'name': 'visor'}, {'frequency': 'c', 'id': 97, 'synset': 'binder.n.03', 'synonyms': ['binder', 'ring-binder'], 'def': 'holds loose papers or magazines', 'name': 'binder'}, {'frequency': 'c', 'id': 98, 'synset': 'binoculars.n.01', 'synonyms': ['binoculars', 'field_glasses', 'opera_glasses'], 'def': 'an optical instrument designed for simultaneous use by both eyes', 'name': 'binoculars'}, {'frequency': 'f', 'id': 99, 'synset': 'bird.n.01', 'synonyms': ['bird'], 'def': 'animal characterized by feathers and wings', 'name': 'bird'}, {'frequency': 'r', 'id': 100, 'synset': 'bird_feeder.n.01', 'synonyms': ['birdfeeder'], 'def': 'an outdoor device that supplies food for wild birds', 'name': 'birdfeeder'}, {'frequency': 'r', 'id': 101, 'synset': 'birdbath.n.01', 'synonyms': ['birdbath'], 'def': 'an ornamental basin (usually in a garden) for birds to bathe in', 'name': 'birdbath'}, {'frequency': 'c', 'id': 102, 'synset': 'birdcage.n.01', 'synonyms': ['birdcage'], 'def': 'a cage in which a bird can be kept', 'name': 'birdcage'}, {'frequency': 'c', 'id': 103, 'synset': 'birdhouse.n.01', 'synonyms': ['birdhouse'], 'def': 'a shelter for birds', 'name': 'birdhouse'}, {'frequency': 'f', 'id': 104, 'synset': 'birthday_cake.n.01', 'synonyms': ['birthday_cake'], 'def': 'decorated cake served at a birthday party', 'name': 'birthday_cake'}, {'frequency': 'r', 'id': 105, 'synset': 'birthday_card.n.01', 'synonyms': ['birthday_card'], 'def': 'a card expressing a birthday greeting', 'name': 'birthday_card'}, {'frequency': 'r', 'id': 106, 'synset': 'biscuit.n.01', 'synonyms': ['biscuit_(bread)'], 'def': 'small round bread leavened with baking-powder or soda', 'name': 'biscuit_(bread)'}, {'frequency': 'r', 'id': 107, 'synset': 'black_flag.n.01', 'synonyms': ['pirate_flag'], 'def': 'a flag usually bearing a white skull and crossbones on a black background', 'name': 'pirate_flag'}, {'frequency': 'c', 'id': 108, 'synset': 'black_sheep.n.02', 'synonyms': ['black_sheep'], 'def': 'sheep with a black coat', 'name': 'black_sheep'}, {'frequency': 'c', 'id': 109, 'synset': 'blackboard.n.01', 'synonyms': ['blackboard', 'chalkboard'], 'def': 'sheet of slate; for writing with chalk', 'name': 'blackboard'}, {'frequency': 'f', 'id': 110, 'synset': 'blanket.n.01', 'synonyms': ['blanket'], 'def': 'bedding that keeps a person warm in bed', 'name': 'blanket'}, {'frequency': 'c', 'id': 111, 'synset': 'blazer.n.01', 'synonyms': ['blazer', 'sport_jacket', 'sport_coat', 'sports_jacket', 'sports_coat'], 'def': 'lightweight jacket; often striped in the colors of a club or school', 'name': 'blazer'}, {'frequency': 'f', 'id': 112, 'synset': 'blender.n.01', 'synonyms': ['blender', 'liquidizer', 'liquidiser'], 'def': 'an electrically powered mixer that mix or chop or liquefy foods', 'name': 'blender'}, {'frequency': 'r', 'id': 113, 'synset': 'blimp.n.02', 'synonyms': ['blimp'], 'def': 'a small nonrigid airship used for observation or as a barrage balloon', 'name': 'blimp'}, {'frequency': 'c', 'id': 114, 'synset': 'blinker.n.01', 'synonyms': ['blinker', 'flasher'], 'def': 'a light that flashes on and off; used as a signal or to send messages', 'name': 'blinker'}, {'frequency': 'c', 'id': 115, 'synset': 'blueberry.n.02', 'synonyms': ['blueberry'], 'def': 'sweet edible dark-blue berries of blueberry plants', 'name': 'blueberry'}, {'frequency': 'r', 'id': 116, 'synset': 'boar.n.02', 'synonyms': ['boar'], 'def': 'an uncastrated male hog', 'name': 'boar'}, {'frequency': 'r', 'id': 117, 'synset': 'board.n.09', 'synonyms': ['gameboard'], 'def': 'a flat portable surface (usually rectangular) designed for board games', 'name': 'gameboard'}, {'frequency': 'f', 'id': 118, 'synset': 'boat.n.01', 'synonyms': ['boat', 'ship_(boat)'], 'def': 'a vessel for travel on water', 'name': 'boat'}, {'frequency': 'c', 'id': 119, 'synset': 'bobbin.n.01', 'synonyms': ['bobbin', 'spool', 'reel'], 'def': 'a thing around which thread/tape/film or other flexible materials can be wound', 'name': 'bobbin'}, {'frequency': 'r', 'id': 120, 'synset': 'bobby_pin.n.01', 'synonyms': ['bobby_pin', 'hairgrip'], 'def': 'a flat wire hairpin used to hold bobbed hair in place', 'name': 'bobby_pin'}, {'frequency': 'c', 'id': 121, 'synset': 'boiled_egg.n.01', 'synonyms': ['boiled_egg', 'coddled_egg'], 'def': 'egg cooked briefly in the shell in gently boiling water', 'name': 'boiled_egg'}, {'frequency': 'r', 'id': 122, 'synset': 'bolo_tie.n.01', 'synonyms': ['bolo_tie', 'bolo', 'bola_tie', 'bola'], 'def': 'a cord fastened around the neck with an ornamental clasp and worn as a necktie', 'name': 'bolo_tie'}, {'frequency': 'c', 'id': 123, 'synset': 'bolt.n.03', 'synonyms': ['deadbolt'], 'def': 'the part of a lock that is engaged or withdrawn with a key', 'name': 'deadbolt'}, {'frequency': 'f', 'id': 124, 'synset': 'bolt.n.06', 'synonyms': ['bolt'], 'def': 'a screw that screws into a nut to form a fastener', 'name': 'bolt'}, {'frequency': 'r', 'id': 125, 'synset': 'bonnet.n.01', 'synonyms': ['bonnet'], 'def': 'a hat tied under the chin', 'name': 'bonnet'}, {'frequency': 'f', 'id': 126, 'synset': 'book.n.01', 'synonyms': ['book'], 'def': 'a written work or composition that has been published', 'name': 'book'}, {'frequency': 'r', 'id': 127, 'synset': 'book_bag.n.01', 'synonyms': ['book_bag'], 'def': 'a bag in which students carry their books', 'name': 'book_bag'}, {'frequency': 'c', 'id': 128, 'synset': 'bookcase.n.01', 'synonyms': ['bookcase'], 'def': 'a piece of furniture with shelves for storing books', 'name': 'bookcase'}, {'frequency': 'c', 'id': 129, 'synset': 'booklet.n.01', 'synonyms': ['booklet', 'brochure', 'leaflet', 'pamphlet'], 'def': 'a small book usually having a paper cover', 'name': 'booklet'}, {'frequency': 'r', 'id': 130, 'synset': 'bookmark.n.01', 'synonyms': ['bookmark', 'bookmarker'], 'def': 'a marker (a piece of paper or ribbon) placed between the pages of a book', 'name': 'bookmark'}, {'frequency': 'r', 'id': 131, 'synset': 'boom.n.04', 'synonyms': ['boom_microphone', 'microphone_boom'], 'def': 'a pole carrying an overhead microphone projected over a film or tv set', 'name': 'boom_microphone'}, {'frequency': 'f', 'id': 132, 'synset': 'boot.n.01', 'synonyms': ['boot'], 'def': 'footwear that covers the whole foot and lower leg', 'name': 'boot'}, {'frequency': 'f', 'id': 133, 'synset': 'bottle.n.01', 'synonyms': ['bottle'], 'def': 'a glass or plastic vessel used for storing drinks or other liquids', 'name': 'bottle'}, {'frequency': 'c', 'id': 134, 'synset': 'bottle_opener.n.01', 'synonyms': ['bottle_opener'], 'def': 'an opener for removing caps or corks from bottles', 'name': 'bottle_opener'}, {'frequency': 'c', 'id': 135, 'synset': 'bouquet.n.01', 'synonyms': ['bouquet'], 'def': 'an arrangement of flowers that is usually given as a present', 'name': 'bouquet'}, {'frequency': 'r', 'id': 136, 'synset': 'bow.n.04', 'synonyms': ['bow_(weapon)'], 'def': 'a weapon for shooting arrows', 'name': 'bow_(weapon)'}, {'frequency': 'f', 'id': 137, 'synset': 'bow.n.08', 'synonyms': ['bow_(decorative_ribbons)'], 'def': 'a decorative interlacing of ribbons', 'name': 'bow_(decorative_ribbons)'}, {'frequency': 'f', 'id': 138, 'synset': 'bow_tie.n.01', 'synonyms': ['bow-tie', 'bowtie'], 'def': "a man's tie that ties in a bow", 'name': 'bow-tie'}, {'frequency': 'f', 'id': 139, 'synset': 'bowl.n.03', 'synonyms': ['bowl'], 'def': 'a dish that is round and open at the top for serving foods', 'name': 'bowl'}, {'frequency': 'r', 'id': 140, 'synset': 'bowl.n.08', 'synonyms': ['pipe_bowl'], 'def': 'a small round container that is open at the top for holding tobacco', 'name': 'pipe_bowl'}, {'frequency': 'c', 'id': 141, 'synset': 'bowler_hat.n.01', 'synonyms': ['bowler_hat', 'bowler', 'derby_hat', 'derby', 'plug_hat'], 'def': 'a felt hat that is round and hard with a narrow brim', 'name': 'bowler_hat'}, {'frequency': 'r', 'id': 142, 'synset': 'bowling_ball.n.01', 'synonyms': ['bowling_ball'], 'def': 'a large ball with finger holes used in the sport of bowling', 'name': 'bowling_ball'}, {'frequency': 'r', 'id': 143, 'synset': 'bowling_pin.n.01', 'synonyms': ['bowling_pin'], 'def': 'a club-shaped wooden object used in bowling', 'name': 'bowling_pin'}, {'frequency': 'r', 'id': 144, 'synset': 'boxing_glove.n.01', 'synonyms': ['boxing_glove'], 'def': 'large glove coverings the fists of a fighter worn for the sport of boxing', 'name': 'boxing_glove'}, {'frequency': 'c', 'id': 145, 'synset': 'brace.n.06', 'synonyms': ['suspenders'], 'def': 'elastic straps that hold trousers up (usually used in the plural)', 'name': 'suspenders'}, {'frequency': 'f', 'id': 146, 'synset': 'bracelet.n.02', 'synonyms': ['bracelet', 'bangle'], 'def': 'jewelry worn around the wrist for decoration', 'name': 'bracelet'}, {'frequency': 'r', 'id': 147, 'synset': 'brass.n.07', 'synonyms': ['brass_plaque'], 'def': 'a memorial made of brass', 'name': 'brass_plaque'}, {'frequency': 'c', 'id': 148, 'synset': 'brassiere.n.01', 'synonyms': ['brassiere', 'bra', 'bandeau'], 'def': 'an undergarment worn by women to support their breasts', 'name': 'brassiere'}, {'frequency': 'c', 'id': 149, 'synset': 'bread-bin.n.01', 'synonyms': ['bread-bin', 'breadbox'], 'def': 'a container used to keep bread or cake in', 'name': 'bread-bin'}, {'frequency': 'r', 'id': 150, 'synset': 'breechcloth.n.01', 'synonyms': ['breechcloth', 'breechclout', 'loincloth'], 'def': 'a garment that provides covering for the loins', 'name': 'breechcloth'}, {'frequency': 'c', 'id': 151, 'synset': 'bridal_gown.n.01', 'synonyms': ['bridal_gown', 'wedding_gown', 'wedding_dress'], 'def': 'a gown worn by the bride at a wedding', 'name': 'bridal_gown'}, {'frequency': 'c', 'id': 152, 'synset': 'briefcase.n.01', 'synonyms': ['briefcase'], 'def': 'a case with a handle; for carrying papers or files or books', 'name': 'briefcase'}, {'frequency': 'c', 'id': 153, 'synset': 'bristle_brush.n.01', 'synonyms': ['bristle_brush'], 'def': 'a brush that is made with the short stiff hairs of an animal or plant', 'name': 'bristle_brush'}, {'frequency': 'f', 'id': 154, 'synset': 'broccoli.n.01', 'synonyms': ['broccoli'], 'def': 'plant with dense clusters of tight green flower buds', 'name': 'broccoli'}, {'frequency': 'r', 'id': 155, 'synset': 'brooch.n.01', 'synonyms': ['broach'], 'def': 'a decorative pin worn by women', 'name': 'broach'}, {'frequency': 'c', 'id': 156, 'synset': 'broom.n.01', 'synonyms': ['broom'], 'def': 'bundle of straws or twigs attached to a long handle; used for cleaning', 'name': 'broom'}, {'frequency': 'c', 'id': 157, 'synset': 'brownie.n.03', 'synonyms': ['brownie'], 'def': 'square or bar of very rich chocolate cake usually with nuts', 'name': 'brownie'}, {'frequency': 'c', 'id': 158, 'synset': 'brussels_sprouts.n.01', 'synonyms': ['brussels_sprouts'], 'def': 'the small edible cabbage-like buds growing along a stalk', 'name': 'brussels_sprouts'}, {'frequency': 'r', 'id': 159, 'synset': 'bubble_gum.n.01', 'synonyms': ['bubble_gum'], 'def': 'a kind of chewing gum that can be blown into bubbles', 'name': 'bubble_gum'}, {'frequency': 'f', 'id': 160, 'synset': 'bucket.n.01', 'synonyms': ['bucket', 'pail'], 'def': 'a roughly cylindrical vessel that is open at the top', 'name': 'bucket'}, {'frequency': 'r', 'id': 161, 'synset': 'buggy.n.01', 'synonyms': ['horse_buggy'], 'def': 'a small lightweight carriage; drawn by a single horse', 'name': 'horse_buggy'}, {'frequency': 'c', 'id': 162, 'synset': 'bull.n.11', 'synonyms': ['bull'], 'def': 'mature male cow', 'name': 'bull'}, {'frequency': 'r', 'id': 163, 'synset': 'bulldog.n.01', 'synonyms': ['bulldog'], 'def': 'a thickset short-haired dog with a large head and strong undershot lower jaw', 'name': 'bulldog'}, {'frequency': 'r', 'id': 164, 'synset': 'bulldozer.n.01', 'synonyms': ['bulldozer', 'dozer'], 'def': 'large powerful tractor; a large blade in front flattens areas of ground', 'name': 'bulldozer'}, {'frequency': 'c', 'id': 165, 'synset': 'bullet_train.n.01', 'synonyms': ['bullet_train'], 'def': 'a high-speed passenger train', 'name': 'bullet_train'}, {'frequency': 'c', 'id': 166, 'synset': 'bulletin_board.n.02', 'synonyms': ['bulletin_board', 'notice_board'], 'def': 'a board that hangs on a wall; displays announcements', 'name': 'bulletin_board'}, {'frequency': 'r', 'id': 167, 'synset': 'bulletproof_vest.n.01', 'synonyms': ['bulletproof_vest'], 'def': 'a vest capable of resisting the impact of a bullet', 'name': 'bulletproof_vest'}, {'frequency': 'c', 'id': 168, 'synset': 'bullhorn.n.01', 'synonyms': ['bullhorn', 'megaphone'], 'def': 'a portable loudspeaker with built-in microphone and amplifier', 'name': 'bullhorn'}, {'frequency': 'r', 'id': 169, 'synset': 'bully_beef.n.01', 'synonyms': ['corned_beef', 'corn_beef'], 'def': 'beef cured or pickled in brine', 'name': 'corned_beef'}, {'frequency': 'f', 'id': 170, 'synset': 'bun.n.01', 'synonyms': ['bun', 'roll'], 'def': 'small rounded bread either plain or sweet', 'name': 'bun'}, {'frequency': 'c', 'id': 171, 'synset': 'bunk_bed.n.01', 'synonyms': ['bunk_bed'], 'def': 'beds built one above the other', 'name': 'bunk_bed'}, {'frequency': 'f', 'id': 172, 'synset': 'buoy.n.01', 'synonyms': ['buoy'], 'def': 'a float attached by rope to the seabed to mark channels in a harbor or underwater hazards', 'name': 'buoy'}, {'frequency': 'r', 'id': 173, 'synset': 'burrito.n.01', 'synonyms': ['burrito'], 'def': 'a flour tortilla folded around a filling', 'name': 'burrito'}, {'frequency': 'f', 'id': 174, 'synset': 'bus.n.01', 'synonyms': ['bus_(vehicle)', 'autobus', 'charabanc', 'double-decker', 'motorbus', 'motorcoach'], 'def': 'a vehicle carrying many passengers; used for public transport', 'name': 'bus_(vehicle)'}, {'frequency': 'c', 'id': 175, 'synset': 'business_card.n.01', 'synonyms': ['business_card'], 'def': "a card on which are printed the person's name and business affiliation", 'name': 'business_card'}, {'frequency': 'c', 'id': 176, 'synset': 'butcher_knife.n.01', 'synonyms': ['butcher_knife'], 'def': 'a large sharp knife for cutting or trimming meat', 'name': 'butcher_knife'}, {'frequency': 'c', 'id': 177, 'synset': 'butter.n.01', 'synonyms': ['butter'], 'def': 'an edible emulsion of fat globules made by churning milk or cream; for cooking and table use', 'name': 'butter'}, {'frequency': 'c', 'id': 178, 'synset': 'butterfly.n.01', 'synonyms': ['butterfly'], 'def': 'insect typically having a slender body with knobbed antennae and broad colorful wings', 'name': 'butterfly'}, {'frequency': 'f', 'id': 179, 'synset': 'button.n.01', 'synonyms': ['button'], 'def': 'a round fastener sewn to shirts and coats etc to fit through buttonholes', 'name': 'button'}, {'frequency': 'f', 'id': 180, 'synset': 'cab.n.03', 'synonyms': ['cab_(taxi)', 'taxi', 'taxicab'], 'def': 'a car that takes passengers where they want to go in exchange for money', 'name': 'cab_(taxi)'}, {'frequency': 'r', 'id': 181, 'synset': 'cabana.n.01', 'synonyms': ['cabana'], 'def': 'a small tent used as a dressing room beside the sea or a swimming pool', 'name': 'cabana'}, {'frequency': 'r', 'id': 182, 'synset': 'cabin_car.n.01', 'synonyms': ['cabin_car', 'caboose'], 'def': 'a car on a freight train for use of the train crew; usually the last car on the train', 'name': 'cabin_car'}, {'frequency': 'f', 'id': 183, 'synset': 'cabinet.n.01', 'synonyms': ['cabinet'], 'def': 'a piece of furniture resembling a cupboard with doors and shelves and drawers', 'name': 'cabinet'}, {'frequency': 'r', 'id': 184, 'synset': 'cabinet.n.03', 'synonyms': ['locker', 'storage_locker'], 'def': 'a storage compartment for clothes and valuables; usually it has a lock', 'name': 'locker'}, {'frequency': 'f', 'id': 185, 'synset': 'cake.n.03', 'synonyms': ['cake'], 'def': 'baked goods made from or based on a mixture of flour, sugar, eggs, and fat', 'name': 'cake'}, {'frequency': 'c', 'id': 186, 'synset': 'calculator.n.02', 'synonyms': ['calculator'], 'def': 'a small machine that is used for mathematical calculations', 'name': 'calculator'}, {'frequency': 'f', 'id': 187, 'synset': 'calendar.n.02', 'synonyms': ['calendar'], 'def': 'a list or register of events (appointments/social events/court cases, etc)', 'name': 'calendar'}, {'frequency': 'c', 'id': 188, 'synset': 'calf.n.01', 'synonyms': ['calf'], 'def': 'young of domestic cattle', 'name': 'calf'}, {'frequency': 'c', 'id': 189, 'synset': 'camcorder.n.01', 'synonyms': ['camcorder'], 'def': 'a portable television camera and videocassette recorder', 'name': 'camcorder'}, {'frequency': 'c', 'id': 190, 'synset': 'camel.n.01', 'synonyms': ['camel'], 'def': 'cud-chewing mammal used as a draft or saddle animal in desert regions', 'name': 'camel'}, {'frequency': 'f', 'id': 191, 'synset': 'camera.n.01', 'synonyms': ['camera'], 'def': 'equipment for taking photographs', 'name': 'camera'}, {'frequency': 'c', 'id': 192, 'synset': 'camera_lens.n.01', 'synonyms': ['camera_lens'], 'def': 'a lens that focuses the image in a camera', 'name': 'camera_lens'}, {'frequency': 'c', 'id': 193, 'synset': 'camper.n.02', 'synonyms': ['camper_(vehicle)', 'camping_bus', 'motor_home'], 'def': 'a recreational vehicle equipped for camping out while traveling', 'name': 'camper_(vehicle)'}, {'frequency': 'f', 'id': 194, 'synset': 'can.n.01', 'synonyms': ['can', 'tin_can'], 'def': 'airtight sealed metal container for food or drink or paint etc.', 'name': 'can'}, {'frequency': 'c', 'id': 195, 'synset': 'can_opener.n.01', 'synonyms': ['can_opener', 'tin_opener'], 'def': 'a device for cutting cans open', 'name': 'can_opener'}, {'frequency': 'r', 'id': 196, 'synset': 'candelabrum.n.01', 'synonyms': ['candelabrum', 'candelabra'], 'def': 'branched candlestick; ornamental; has several lights', 'name': 'candelabrum'}, {'frequency': 'f', 'id': 197, 'synset': 'candle.n.01', 'synonyms': ['candle', 'candlestick'], 'def': 'stick of wax with a wick in the middle', 'name': 'candle'}, {'frequency': 'f', 'id': 198, 'synset': 'candlestick.n.01', 'synonyms': ['candle_holder'], 'def': 'a holder with sockets for candles', 'name': 'candle_holder'}, {'frequency': 'r', 'id': 199, 'synset': 'candy_bar.n.01', 'synonyms': ['candy_bar'], 'def': 'a candy shaped as a bar', 'name': 'candy_bar'}, {'frequency': 'c', 'id': 200, 'synset': 'candy_cane.n.01', 'synonyms': ['candy_cane'], 'def': 'a hard candy in the shape of a rod (usually with stripes)', 'name': 'candy_cane'}, {'frequency': 'c', 'id': 201, 'synset': 'cane.n.01', 'synonyms': ['walking_cane'], 'def': 'a stick that people can lean on to help them walk', 'name': 'walking_cane'}, {'frequency': 'c', 'id': 202, 'synset': 'canister.n.02', 'synonyms': ['canister', 'cannister'], 'def': 'metal container for storing dry foods such as tea or flour', 'name': 'canister'}, {'frequency': 'r', 'id': 203, 'synset': 'cannon.n.02', 'synonyms': ['cannon'], 'def': 'heavy gun fired from a tank', 'name': 'cannon'}, {'frequency': 'c', 'id': 204, 'synset': 'canoe.n.01', 'synonyms': ['canoe'], 'def': 'small and light boat; pointed at both ends; propelled with a paddle', 'name': 'canoe'}, {'frequency': 'r', 'id': 205, 'synset': 'cantaloup.n.02', 'synonyms': ['cantaloup', 'cantaloupe'], 'def': 'the fruit of a cantaloup vine; small to medium-sized melon with yellowish flesh', 'name': 'cantaloup'}, {'frequency': 'r', 'id': 206, 'synset': 'canteen.n.01', 'synonyms': ['canteen'], 'def': 'a flask for carrying water; used by soldiers or travelers', 'name': 'canteen'}, {'frequency': 'c', 'id': 207, 'synset': 'cap.n.01', 'synonyms': ['cap_(headwear)'], 'def': 'a tight-fitting headwear', 'name': 'cap_(headwear)'}, {'frequency': 'f', 'id': 208, 'synset': 'cap.n.02', 'synonyms': ['bottle_cap', 'cap_(container_lid)'], 'def': 'a top (as for a bottle)', 'name': 'bottle_cap'}, {'frequency': 'r', 'id': 209, 'synset': 'cape.n.02', 'synonyms': ['cape'], 'def': 'a sleeveless garment like a cloak but shorter', 'name': 'cape'}, {'frequency': 'c', 'id': 210, 'synset': 'cappuccino.n.01', 'synonyms': ['cappuccino', 'coffee_cappuccino'], 'def': 'equal parts of espresso and steamed milk', 'name': 'cappuccino'}, {'frequency': 'f', 'id': 211, 'synset': 'car.n.01', 'synonyms': ['car_(automobile)', 'auto_(automobile)', 'automobile'], 'def': 'a motor vehicle with four wheels', 'name': 'car_(automobile)'}, {'frequency': 'f', 'id': 212, 'synset': 'car.n.02', 'synonyms': ['railcar_(part_of_a_train)', 'railway_car_(part_of_a_train)', 'railroad_car_(part_of_a_train)'], 'def': 'a wheeled vehicle adapted to the rails of railroad', 'name': 'railcar_(part_of_a_train)'}, {'frequency': 'r', 'id': 213, 'synset': 'car.n.04', 'synonyms': ['elevator_car'], 'def': 'where passengers ride up and down', 'name': 'elevator_car'}, {'frequency': 'r', 'id': 214, 'synset': 'car_battery.n.01', 'synonyms': ['car_battery', 'automobile_battery'], 'def': 'a battery in a motor vehicle', 'name': 'car_battery'}, {'frequency': 'c', 'id': 215, 'synset': 'card.n.02', 'synonyms': ['identity_card'], 'def': 'a card certifying the identity of the bearer', 'name': 'identity_card'}, {'frequency': 'c', 'id': 216, 'synset': 'card.n.03', 'synonyms': ['card'], 'def': 'a rectangular piece of paper used to send messages (e.g. greetings or pictures)', 'name': 'card'}, {'frequency': 'r', 'id': 217, 'synset': 'cardigan.n.01', 'synonyms': ['cardigan'], 'def': 'knitted jacket that is fastened up the front with buttons or a zipper', 'name': 'cardigan'}, {'frequency': 'r', 'id': 218, 'synset': 'cargo_ship.n.01', 'synonyms': ['cargo_ship', 'cargo_vessel'], 'def': 'a ship designed to carry cargo', 'name': 'cargo_ship'}, {'frequency': 'r', 'id': 219, 'synset': 'carnation.n.01', 'synonyms': ['carnation'], 'def': 'plant with pink to purple-red spice-scented usually double flowers', 'name': 'carnation'}, {'frequency': 'c', 'id': 220, 'synset': 'carriage.n.02', 'synonyms': ['horse_carriage'], 'def': 'a vehicle with wheels drawn by one or more horses', 'name': 'horse_carriage'}, {'frequency': 'f', 'id': 221, 'synset': 'carrot.n.01', 'synonyms': ['carrot'], 'def': 'deep orange edible root of the cultivated carrot plant', 'name': 'carrot'}, {'frequency': 'c', 'id': 222, 'synset': 'carryall.n.01', 'synonyms': ['tote_bag'], 'def': 'a capacious bag or basket', 'name': 'tote_bag'}, {'frequency': 'c', 'id': 223, 'synset': 'cart.n.01', 'synonyms': ['cart'], 'def': 'a heavy open wagon usually having two wheels and drawn by an animal', 'name': 'cart'}, {'frequency': 'c', 'id': 224, 'synset': 'carton.n.02', 'synonyms': ['carton'], 'def': 'a box made of cardboard; opens by flaps on top', 'name': 'carton'}, {'frequency': 'c', 'id': 225, 'synset': 'cash_register.n.01', 'synonyms': ['cash_register', 'register_(for_cash_transactions)'], 'def': 'a cashbox with an adding machine to register transactions', 'name': 'cash_register'}, {'frequency': 'r', 'id': 226, 'synset': 'casserole.n.01', 'synonyms': ['casserole'], 'def': 'food cooked and served in a casserole', 'name': 'casserole'}, {'frequency': 'r', 'id': 227, 'synset': 'cassette.n.01', 'synonyms': ['cassette'], 'def': 'a container that holds a magnetic tape used for recording or playing sound or video', 'name': 'cassette'}, {'frequency': 'c', 'id': 228, 'synset': 'cast.n.05', 'synonyms': ['cast', 'plaster_cast', 'plaster_bandage'], 'def': 'bandage consisting of a firm covering that immobilizes broken bones while they heal', 'name': 'cast'}, {'frequency': 'f', 'id': 229, 'synset': 'cat.n.01', 'synonyms': ['cat'], 'def': 'a domestic house cat', 'name': 'cat'}, {'frequency': 'c', 'id': 230, 'synset': 'cauliflower.n.02', 'synonyms': ['cauliflower'], 'def': 'edible compact head of white undeveloped flowers', 'name': 'cauliflower'}, {'frequency': 'r', 'id': 231, 'synset': 'caviar.n.01', 'synonyms': ['caviar', 'caviare'], 'def': "salted roe of sturgeon or other large fish; usually served as an hors d'oeuvre", 'name': 'caviar'}, {'frequency': 'c', 'id': 232, 'synset': 'cayenne.n.02', 'synonyms': ['cayenne_(spice)', 'cayenne_pepper_(spice)', 'red_pepper_(spice)'], 'def': 'ground pods and seeds of pungent red peppers of the genus Capsicum', 'name': 'cayenne_(spice)'}, {'frequency': 'c', 'id': 233, 'synset': 'cd_player.n.01', 'synonyms': ['CD_player'], 'def': 'electronic equipment for playing compact discs (CDs)', 'name': 'CD_player'}, {'frequency': 'c', 'id': 234, 'synset': 'celery.n.01', 'synonyms': ['celery'], 'def': 'widely cultivated herb with aromatic leaf stalks that are eaten raw or cooked', 'name': 'celery'}, {'frequency': 'f', 'id': 235, 'synset': 'cellular_telephone.n.01', 'synonyms': ['cellular_telephone', 'cellular_phone', 'cellphone', 'mobile_phone', 'smart_phone'], 'def': 'a hand-held mobile telephone', 'name': 'cellular_telephone'}, {'frequency': 'r', 'id': 236, 'synset': 'chain_mail.n.01', 'synonyms': ['chain_mail', 'ring_mail', 'chain_armor', 'chain_armour', 'ring_armor', 'ring_armour'], 'def': '(Middle Ages) flexible armor made of interlinked metal rings', 'name': 'chain_mail'}, {'frequency': 'f', 'id': 237, 'synset': 'chair.n.01', 'synonyms': ['chair'], 'def': 'a seat for one person, with a support for the back', 'name': 'chair'}, {'frequency': 'r', 'id': 238, 'synset': 'chaise_longue.n.01', 'synonyms': ['chaise_longue', 'chaise', 'daybed'], 'def': 'a long chair; for reclining', 'name': 'chaise_longue'}, {'frequency': 'r', 'id': 239, 'synset': 'champagne.n.01', 'synonyms': ['champagne'], 'def': 'a white sparkling wine produced in Champagne or resembling that produced there', 'name': 'champagne'}, {'frequency': 'f', 'id': 240, 'synset': 'chandelier.n.01', 'synonyms': ['chandelier'], 'def': 'branched lighting fixture; often ornate; hangs from the ceiling', 'name': 'chandelier'}, {'frequency': 'r', 'id': 241, 'synset': 'chap.n.04', 'synonyms': ['chap'], 'def': 'leather leggings without a seat; worn over trousers by cowboys to protect their legs', 'name': 'chap'}, {'frequency': 'r', 'id': 242, 'synset': 'checkbook.n.01', 'synonyms': ['checkbook', 'chequebook'], 'def': 'a book issued to holders of checking accounts', 'name': 'checkbook'}, {'frequency': 'r', 'id': 243, 'synset': 'checkerboard.n.01', 'synonyms': ['checkerboard'], 'def': 'a board having 64 squares of two alternating colors', 'name': 'checkerboard'}, {'frequency': 'c', 'id': 244, 'synset': 'cherry.n.03', 'synonyms': ['cherry'], 'def': 'a red fruit with a single hard stone', 'name': 'cherry'}, {'frequency': 'r', 'id': 245, 'synset': 'chessboard.n.01', 'synonyms': ['chessboard'], 'def': 'a checkerboard used to play chess', 'name': 'chessboard'}, {'frequency': 'r', 'id': 246, 'synset': 'chest_of_drawers.n.01', 'synonyms': ['chest_of_drawers_(furniture)', 'bureau_(furniture)', 'chest_(furniture)'], 'def': 'furniture with drawers for keeping clothes', 'name': 'chest_of_drawers_(furniture)'}, {'frequency': 'c', 'id': 247, 'synset': 'chicken.n.02', 'synonyms': ['chicken_(animal)'], 'def': 'a domestic fowl bred for flesh or eggs', 'name': 'chicken_(animal)'}, {'frequency': 'c', 'id': 248, 'synset': 'chicken_wire.n.01', 'synonyms': ['chicken_wire'], 'def': 'a galvanized wire network with a hexagonal mesh; used to build fences', 'name': 'chicken_wire'}, {'frequency': 'r', 'id': 249, 'synset': 'chickpea.n.01', 'synonyms': ['chickpea', 'garbanzo'], 'def': 'the seed of the chickpea plant; usually dried', 'name': 'chickpea'}, {'frequency': 'r', 'id': 250, 'synset': 'chihuahua.n.03', 'synonyms': ['Chihuahua'], 'def': 'an old breed of tiny short-haired dog with protruding eyes from Mexico', 'name': 'Chihuahua'}, {'frequency': 'r', 'id': 251, 'synset': 'chili.n.02', 'synonyms': ['chili_(vegetable)', 'chili_pepper_(vegetable)', 'chilli_(vegetable)', 'chilly_(vegetable)', 'chile_(vegetable)'], 'def': 'very hot and finely tapering pepper of special pungency', 'name': 'chili_(vegetable)'}, {'frequency': 'r', 'id': 252, 'synset': 'chime.n.01', 'synonyms': ['chime', 'gong'], 'def': 'an instrument consisting of a set of bells that are struck with a hammer', 'name': 'chime'}, {'frequency': 'r', 'id': 253, 'synset': 'chinaware.n.01', 'synonyms': ['chinaware'], 'def': 'dishware made of high quality porcelain', 'name': 'chinaware'}, {'frequency': 'c', 'id': 254, 'synset': 'chip.n.04', 'synonyms': ['crisp_(potato_chip)', 'potato_chip'], 'def': 'a thin crisp slice of potato fried in deep fat', 'name': 'crisp_(potato_chip)'}, {'frequency': 'r', 'id': 255, 'synset': 'chip.n.06', 'synonyms': ['poker_chip'], 'def': 'a small disk-shaped counter used to represent money when gambling', 'name': 'poker_chip'}, {'frequency': 'c', 'id': 256, 'synset': 'chocolate_bar.n.01', 'synonyms': ['chocolate_bar'], 'def': 'a bar of chocolate candy', 'name': 'chocolate_bar'}, {'frequency': 'c', 'id': 257, 'synset': 'chocolate_cake.n.01', 'synonyms': ['chocolate_cake'], 'def': 'cake containing chocolate', 'name': 'chocolate_cake'}, {'frequency': 'r', 'id': 258, 'synset': 'chocolate_milk.n.01', 'synonyms': ['chocolate_milk'], 'def': 'milk flavored with chocolate syrup', 'name': 'chocolate_milk'}, {'frequency': 'r', 'id': 259, 'synset': 'chocolate_mousse.n.01', 'synonyms': ['chocolate_mousse'], 'def': 'dessert mousse made with chocolate', 'name': 'chocolate_mousse'}, {'frequency': 'f', 'id': 260, 'synset': 'choker.n.03', 'synonyms': ['choker', 'collar', 'neckband'], 'def': 'necklace that fits tightly around the neck', 'name': 'choker'}, {'frequency': 'f', 'id': 261, 'synset': 'chopping_board.n.01', 'synonyms': ['chopping_board', 'cutting_board', 'chopping_block'], 'def': 'a wooden board where meats or vegetables can be cut', 'name': 'chopping_board'}, {'frequency': 'c', 'id': 262, 'synset': 'chopstick.n.01', 'synonyms': ['chopstick'], 'def': 'one of a pair of slender sticks used as oriental tableware to eat food with', 'name': 'chopstick'}, {'frequency': 'f', 'id': 263, 'synset': 'christmas_tree.n.05', 'synonyms': ['Christmas_tree'], 'def': 'an ornamented evergreen used as a Christmas decoration', 'name': 'Christmas_tree'}, {'frequency': 'c', 'id': 264, 'synset': 'chute.n.02', 'synonyms': ['slide'], 'def': 'sloping channel through which things can descend', 'name': 'slide'}, {'frequency': 'r', 'id': 265, 'synset': 'cider.n.01', 'synonyms': ['cider', 'cyder'], 'def': 'a beverage made from juice pressed from apples', 'name': 'cider'}, {'frequency': 'r', 'id': 266, 'synset': 'cigar_box.n.01', 'synonyms': ['cigar_box'], 'def': 'a box for holding cigars', 'name': 'cigar_box'}, {'frequency': 'c', 'id': 267, 'synset': 'cigarette.n.01', 'synonyms': ['cigarette'], 'def': 'finely ground tobacco wrapped in paper; for smoking', 'name': 'cigarette'}, {'frequency': 'c', 'id': 268, 'synset': 'cigarette_case.n.01', 'synonyms': ['cigarette_case', 'cigarette_pack'], 'def': 'a small flat case for holding cigarettes', 'name': 'cigarette_case'}, {'frequency': 'f', 'id': 269, 'synset': 'cistern.n.02', 'synonyms': ['cistern', 'water_tank'], 'def': 'a tank that holds the water used to flush a toilet', 'name': 'cistern'}, {'frequency': 'r', 'id': 270, 'synset': 'clarinet.n.01', 'synonyms': ['clarinet'], 'def': 'a single-reed instrument with a straight tube', 'name': 'clarinet'}, {'frequency': 'r', 'id': 271, 'synset': 'clasp.n.01', 'synonyms': ['clasp'], 'def': 'a fastener (as a buckle or hook) that is used to hold two things together', 'name': 'clasp'}, {'frequency': 'c', 'id': 272, 'synset': 'cleansing_agent.n.01', 'synonyms': ['cleansing_agent', 'cleanser', 'cleaner'], 'def': 'a preparation used in cleaning something', 'name': 'cleansing_agent'}, {'frequency': 'r', 'id': 273, 'synset': 'clementine.n.01', 'synonyms': ['clementine'], 'def': 'a variety of mandarin orange', 'name': 'clementine'}, {'frequency': 'c', 'id': 274, 'synset': 'clip.n.03', 'synonyms': ['clip'], 'def': 'any of various small fasteners used to hold loose articles together', 'name': 'clip'}, {'frequency': 'c', 'id': 275, 'synset': 'clipboard.n.01', 'synonyms': ['clipboard'], 'def': 'a small writing board with a clip at the top for holding papers', 'name': 'clipboard'}, {'frequency': 'f', 'id': 276, 'synset': 'clock.n.01', 'synonyms': ['clock', 'timepiece', 'timekeeper'], 'def': 'a timepiece that shows the time of day', 'name': 'clock'}, {'frequency': 'f', 'id': 277, 'synset': 'clock_tower.n.01', 'synonyms': ['clock_tower'], 'def': 'a tower with a large clock visible high up on an outside face', 'name': 'clock_tower'}, {'frequency': 'c', 'id': 278, 'synset': 'clothes_hamper.n.01', 'synonyms': ['clothes_hamper', 'laundry_basket', 'clothes_basket'], 'def': 'a hamper that holds dirty clothes to be washed or wet clothes to be dried', 'name': 'clothes_hamper'}, {'frequency': 'c', 'id': 279, 'synset': 'clothespin.n.01', 'synonyms': ['clothespin', 'clothes_peg'], 'def': 'wood or plastic fastener; for holding clothes on a clothesline', 'name': 'clothespin'}, {'frequency': 'r', 'id': 280, 'synset': 'clutch_bag.n.01', 'synonyms': ['clutch_bag'], 'def': "a woman's strapless purse that is carried in the hand", 'name': 'clutch_bag'}, {'frequency': 'f', 'id': 281, 'synset': 'coaster.n.03', 'synonyms': ['coaster'], 'def': 'a covering (plate or mat) that protects the surface of a table', 'name': 'coaster'}, {'frequency': 'f', 'id': 282, 'synset': 'coat.n.01', 'synonyms': ['coat'], 'def': 'an outer garment that has sleeves and covers the body from shoulder down', 'name': 'coat'}, {'frequency': 'c', 'id': 283, 'synset': 'coat_hanger.n.01', 'synonyms': ['coat_hanger', 'clothes_hanger', 'dress_hanger'], 'def': "a hanger that is shaped like a person's shoulders", 'name': 'coat_hanger'}, {'frequency': 'r', 'id': 284, 'synset': 'coatrack.n.01', 'synonyms': ['coatrack', 'hatrack'], 'def': 'a rack with hooks for temporarily holding coats and hats', 'name': 'coatrack'}, {'frequency': 'c', 'id': 285, 'synset': 'cock.n.04', 'synonyms': ['cock', 'rooster'], 'def': 'adult male chicken', 'name': 'cock'}, {'frequency': 'c', 'id': 286, 'synset': 'coconut.n.02', 'synonyms': ['coconut', 'cocoanut'], 'def': 'large hard-shelled brown oval nut with a fibrous husk', 'name': 'coconut'}, {'frequency': 'r', 'id': 287, 'synset': 'coffee_filter.n.01', 'synonyms': ['coffee_filter'], 'def': 'filter (usually of paper) that passes the coffee and retains the coffee grounds', 'name': 'coffee_filter'}, {'frequency': 'f', 'id': 288, 'synset': 'coffee_maker.n.01', 'synonyms': ['coffee_maker', 'coffee_machine'], 'def': 'a kitchen appliance for brewing coffee automatically', 'name': 'coffee_maker'}, {'frequency': 'f', 'id': 289, 'synset': 'coffee_table.n.01', 'synonyms': ['coffee_table', 'cocktail_table'], 'def': 'low table where magazines can be placed and coffee or cocktails are served', 'name': 'coffee_table'}, {'frequency': 'c', 'id': 290, 'synset': 'coffeepot.n.01', 'synonyms': ['coffeepot'], 'def': 'tall pot in which coffee is brewed', 'name': 'coffeepot'}, {'frequency': 'r', 'id': 291, 'synset': 'coil.n.05', 'synonyms': ['coil'], 'def': 'tubing that is wound in a spiral', 'name': 'coil'}, {'frequency': 'c', 'id': 292, 'synset': 'coin.n.01', 'synonyms': ['coin'], 'def': 'a flat metal piece (usually a disc) used as money', 'name': 'coin'}, {'frequency': 'r', 'id': 293, 'synset': 'colander.n.01', 'synonyms': ['colander', 'cullender'], 'def': 'bowl-shaped strainer; used to wash or drain foods', 'name': 'colander'}, {'frequency': 'c', 'id': 294, 'synset': 'coleslaw.n.01', 'synonyms': ['coleslaw', 'slaw'], 'def': 'basically shredded cabbage', 'name': 'coleslaw'}, {'frequency': 'r', 'id': 295, 'synset': 'coloring_material.n.01', 'synonyms': ['coloring_material', 'colouring_material'], 'def': 'any material used for its color', 'name': 'coloring_material'}, {'frequency': 'r', 'id': 296, 'synset': 'combination_lock.n.01', 'synonyms': ['combination_lock'], 'def': 'lock that can be opened only by turning dials in a special sequence', 'name': 'combination_lock'}, {'frequency': 'c', 'id': 297, 'synset': 'comforter.n.04', 'synonyms': ['pacifier', 'teething_ring'], 'def': 'device used for an infant to suck or bite on', 'name': 'pacifier'}, {'frequency': 'r', 'id': 298, 'synset': 'comic_book.n.01', 'synonyms': ['comic_book'], 'def': 'a magazine devoted to comic strips', 'name': 'comic_book'}, {'frequency': 'f', 'id': 299, 'synset': 'computer_keyboard.n.01', 'synonyms': ['computer_keyboard', 'keyboard_(computer)'], 'def': 'a keyboard that is a data input device for computers', 'name': 'computer_keyboard'}, {'frequency': 'r', 'id': 300, 'synset': 'concrete_mixer.n.01', 'synonyms': ['concrete_mixer', 'cement_mixer'], 'def': 'a machine with a large revolving drum in which cement/concrete is mixed', 'name': 'concrete_mixer'}, {'frequency': 'f', 'id': 301, 'synset': 'cone.n.01', 'synonyms': ['cone', 'traffic_cone'], 'def': 'a cone-shaped object used to direct traffic', 'name': 'cone'}, {'frequency': 'f', 'id': 302, 'synset': 'control.n.09', 'synonyms': ['control', 'controller'], 'def': 'a mechanism that controls the operation of a machine', 'name': 'control'}, {'frequency': 'r', 'id': 303, 'synset': 'convertible.n.01', 'synonyms': ['convertible_(automobile)'], 'def': 'a car that has top that can be folded or removed', 'name': 'convertible_(automobile)'}, {'frequency': 'r', 'id': 304, 'synset': 'convertible.n.03', 'synonyms': ['sofa_bed'], 'def': 'a sofa that can be converted into a bed', 'name': 'sofa_bed'}, {'frequency': 'c', 'id': 305, 'synset': 'cookie.n.01', 'synonyms': ['cookie', 'cooky', 'biscuit_(cookie)'], 'def': "any of various small flat sweet cakes (`biscuit' is the British term)", 'name': 'cookie'}, {'frequency': 'r', 'id': 306, 'synset': 'cookie_jar.n.01', 'synonyms': ['cookie_jar', 'cooky_jar'], 'def': 'a jar in which cookies are kept (and sometimes money is hidden)', 'name': 'cookie_jar'}, {'frequency': 'r', 'id': 307, 'synset': 'cooking_utensil.n.01', 'synonyms': ['cooking_utensil'], 'def': 'a kitchen utensil made of material that does not melt easily; used for cooking', 'name': 'cooking_utensil'}, {'frequency': 'f', 'id': 308, 'synset': 'cooler.n.01', 'synonyms': ['cooler_(for_food)', 'ice_chest'], 'def': 'an insulated box for storing food often with ice', 'name': 'cooler_(for_food)'}, {'frequency': 'c', 'id': 309, 'synset': 'cork.n.04', 'synonyms': ['cork_(bottle_plug)', 'bottle_cork'], 'def': 'the plug in the mouth of a bottle (especially a wine bottle)', 'name': 'cork_(bottle_plug)'}, {'frequency': 'r', 'id': 310, 'synset': 'corkboard.n.01', 'synonyms': ['corkboard'], 'def': 'a sheet consisting of cork granules', 'name': 'corkboard'}, {'frequency': 'r', 'id': 311, 'synset': 'corkscrew.n.01', 'synonyms': ['corkscrew', 'bottle_screw'], 'def': 'a bottle opener that pulls corks', 'name': 'corkscrew'}, {'frequency': 'c', 'id': 312, 'synset': 'corn.n.03', 'synonyms': ['edible_corn', 'corn', 'maize'], 'def': 'ears of corn that can be prepared and served for human food', 'name': 'edible_corn'}, {'frequency': 'r', 'id': 313, 'synset': 'cornbread.n.01', 'synonyms': ['cornbread'], 'def': 'bread made primarily of cornmeal', 'name': 'cornbread'}, {'frequency': 'c', 'id': 314, 'synset': 'cornet.n.01', 'synonyms': ['cornet', 'horn', 'trumpet'], 'def': 'a brass musical instrument with a narrow tube and a flared bell and many valves', 'name': 'cornet'}, {'frequency': 'c', 'id': 315, 'synset': 'cornice.n.01', 'synonyms': ['cornice', 'valance', 'valance_board', 'pelmet'], 'def': 'a decorative framework to conceal curtain fixtures at the top of a window casing', 'name': 'cornice'}, {'frequency': 'r', 'id': 316, 'synset': 'cornmeal.n.01', 'synonyms': ['cornmeal'], 'def': 'coarsely ground corn', 'name': 'cornmeal'}, {'frequency': 'r', 'id': 317, 'synset': 'corset.n.01', 'synonyms': ['corset', 'girdle'], 'def': "a woman's close-fitting foundation garment", 'name': 'corset'}, {'frequency': 'r', 'id': 318, 'synset': 'cos.n.02', 'synonyms': ['romaine_lettuce'], 'def': 'lettuce with long dark-green leaves in a loosely packed elongated head', 'name': 'romaine_lettuce'}, {'frequency': 'c', 'id': 319, 'synset': 'costume.n.04', 'synonyms': ['costume'], 'def': 'the attire characteristic of a country or a time or a social class', 'name': 'costume'}, {'frequency': 'r', 'id': 320, 'synset': 'cougar.n.01', 'synonyms': ['cougar', 'puma', 'catamount', 'mountain_lion', 'panther'], 'def': 'large American feline resembling a lion', 'name': 'cougar'}, {'frequency': 'r', 'id': 321, 'synset': 'coverall.n.01', 'synonyms': ['coverall'], 'def': 'a loose-fitting protective garment that is worn over other clothing', 'name': 'coverall'}, {'frequency': 'r', 'id': 322, 'synset': 'cowbell.n.01', 'synonyms': ['cowbell'], 'def': 'a bell hung around the neck of cow so that the cow can be easily located', 'name': 'cowbell'}, {'frequency': 'f', 'id': 323, 'synset': 'cowboy_hat.n.01', 'synonyms': ['cowboy_hat', 'ten-gallon_hat'], 'def': 'a hat with a wide brim and a soft crown; worn by American ranch hands', 'name': 'cowboy_hat'}, {'frequency': 'r', 'id': 324, 'synset': 'crab.n.01', 'synonyms': ['crab_(animal)'], 'def': 'decapod having eyes on short stalks and a broad flattened shell and pincers', 'name': 'crab_(animal)'}, {'frequency': 'c', 'id': 325, 'synset': 'cracker.n.01', 'synonyms': ['cracker'], 'def': 'a thin crisp wafer', 'name': 'cracker'}, {'frequency': 'r', 'id': 326, 'synset': 'crape.n.01', 'synonyms': ['crape', 'crepe', 'French_pancake'], 'def': 'small very thin pancake', 'name': 'crape'}, {'frequency': 'f', 'id': 327, 'synset': 'crate.n.01', 'synonyms': ['crate'], 'def': 'a rugged box (usually made of wood); used for shipping', 'name': 'crate'}, {'frequency': 'r', 'id': 328, 'synset': 'crayon.n.01', 'synonyms': ['crayon', 'wax_crayon'], 'def': 'writing or drawing implement made of a colored stick of composition wax', 'name': 'crayon'}, {'frequency': 'r', 'id': 329, 'synset': 'cream_pitcher.n.01', 'synonyms': ['cream_pitcher'], 'def': 'a small pitcher for serving cream', 'name': 'cream_pitcher'}, {'frequency': 'r', 'id': 330, 'synset': 'credit_card.n.01', 'synonyms': ['credit_card', 'charge_card', 'debit_card'], 'def': 'a card, usually plastic, used to pay for goods and services', 'name': 'credit_card'}, {'frequency': 'c', 'id': 331, 'synset': 'crescent_roll.n.01', 'synonyms': ['crescent_roll', 'croissant'], 'def': 'very rich flaky crescent-shaped roll', 'name': 'crescent_roll'}, {'frequency': 'c', 'id': 332, 'synset': 'crib.n.01', 'synonyms': ['crib', 'cot'], 'def': 'baby bed with high sides made of slats', 'name': 'crib'}, {'frequency': 'c', 'id': 333, 'synset': 'crock.n.03', 'synonyms': ['crock_pot', 'earthenware_jar'], 'def': 'an earthen jar (made of baked clay)', 'name': 'crock_pot'}, {'frequency': 'f', 'id': 334, 'synset': 'crossbar.n.01', 'synonyms': ['crossbar'], 'def': 'a horizontal bar that goes across something', 'name': 'crossbar'}, {'frequency': 'r', 'id': 335, 'synset': 'crouton.n.01', 'synonyms': ['crouton'], 'def': 'a small piece of toasted or fried bread; served in soup or salads', 'name': 'crouton'}, {'frequency': 'r', 'id': 336, 'synset': 'crow.n.01', 'synonyms': ['crow'], 'def': 'black birds having a raucous call', 'name': 'crow'}, {'frequency': 'c', 'id': 337, 'synset': 'crown.n.04', 'synonyms': ['crown'], 'def': 'an ornamental jeweled headdress signifying sovereignty', 'name': 'crown'}, {'frequency': 'c', 'id': 338, 'synset': 'crucifix.n.01', 'synonyms': ['crucifix'], 'def': 'representation of the cross on which Jesus died', 'name': 'crucifix'}, {'frequency': 'c', 'id': 339, 'synset': 'cruise_ship.n.01', 'synonyms': ['cruise_ship', 'cruise_liner'], 'def': 'a passenger ship used commercially for pleasure cruises', 'name': 'cruise_ship'}, {'frequency': 'c', 'id': 340, 'synset': 'cruiser.n.01', 'synonyms': ['police_cruiser', 'patrol_car', 'police_car', 'squad_car'], 'def': 'a car in which policemen cruise the streets', 'name': 'police_cruiser'}, {'frequency': 'c', 'id': 341, 'synset': 'crumb.n.03', 'synonyms': ['crumb'], 'def': 'small piece of e.g. bread or cake', 'name': 'crumb'}, {'frequency': 'r', 'id': 342, 'synset': 'crutch.n.01', 'synonyms': ['crutch'], 'def': 'a wooden or metal staff that fits under the armpit and reaches to the ground', 'name': 'crutch'}, {'frequency': 'c', 'id': 343, 'synset': 'cub.n.03', 'synonyms': ['cub_(animal)'], 'def': 'the young of certain carnivorous mammals such as the bear or wolf or lion', 'name': 'cub_(animal)'}, {'frequency': 'r', 'id': 344, 'synset': 'cube.n.05', 'synonyms': ['cube', 'square_block'], 'def': 'a block in the (approximate) shape of a cube', 'name': 'cube'}, {'frequency': 'f', 'id': 345, 'synset': 'cucumber.n.02', 'synonyms': ['cucumber', 'cuke'], 'def': 'cylindrical green fruit with thin green rind and white flesh eaten as a vegetable', 'name': 'cucumber'}, {'frequency': 'c', 'id': 346, 'synset': 'cufflink.n.01', 'synonyms': ['cufflink'], 'def': 'jewelry consisting of linked buttons used to fasten the cuffs of a shirt', 'name': 'cufflink'}, {'frequency': 'f', 'id': 347, 'synset': 'cup.n.01', 'synonyms': ['cup'], 'def': 'a small open container usually used for drinking; usually has a handle', 'name': 'cup'}, {'frequency': 'c', 'id': 348, 'synset': 'cup.n.08', 'synonyms': ['trophy_cup'], 'def': 'a metal vessel with handles that is awarded as a trophy to a competition winner', 'name': 'trophy_cup'}, {'frequency': 'c', 'id': 349, 'synset': 'cupcake.n.01', 'synonyms': ['cupcake'], 'def': 'small cake baked in a muffin tin', 'name': 'cupcake'}, {'frequency': 'r', 'id': 350, 'synset': 'curler.n.01', 'synonyms': ['hair_curler', 'hair_roller', 'hair_crimper'], 'def': 'a cylindrical tube around which the hair is wound to curl it', 'name': 'hair_curler'}, {'frequency': 'r', 'id': 351, 'synset': 'curling_iron.n.01', 'synonyms': ['curling_iron'], 'def': 'a cylindrical home appliance that heats hair that has been curled around it', 'name': 'curling_iron'}, {'frequency': 'f', 'id': 352, 'synset': 'curtain.n.01', 'synonyms': ['curtain', 'drapery'], 'def': 'hanging cloth used as a blind (especially for a window)', 'name': 'curtain'}, {'frequency': 'f', 'id': 353, 'synset': 'cushion.n.03', 'synonyms': ['cushion'], 'def': 'a soft bag filled with air or padding such as feathers or foam rubber', 'name': 'cushion'}, {'frequency': 'r', 'id': 354, 'synset': 'custard.n.01', 'synonyms': ['custard'], 'def': 'sweetened mixture of milk and eggs baked or boiled or frozen', 'name': 'custard'}, {'frequency': 'c', 'id': 355, 'synset': 'cutter.n.06', 'synonyms': ['cutting_tool'], 'def': 'a cutting implement; a tool for cutting', 'name': 'cutting_tool'}, {'frequency': 'r', 'id': 356, 'synset': 'cylinder.n.04', 'synonyms': ['cylinder'], 'def': 'a cylindrical container', 'name': 'cylinder'}, {'frequency': 'r', 'id': 357, 'synset': 'cymbal.n.01', 'synonyms': ['cymbal'], 'def': 'a percussion instrument consisting of a concave brass disk', 'name': 'cymbal'}, {'frequency': 'r', 'id': 358, 'synset': 'dachshund.n.01', 'synonyms': ['dachshund', 'dachsie', 'badger_dog'], 'def': 'small long-bodied short-legged breed of dog having a short sleek coat and long drooping ears', 'name': 'dachshund'}, {'frequency': 'r', 'id': 359, 'synset': 'dagger.n.01', 'synonyms': ['dagger'], 'def': 'a short knife with a pointed blade used for piercing or stabbing', 'name': 'dagger'}, {'frequency': 'r', 'id': 360, 'synset': 'dartboard.n.01', 'synonyms': ['dartboard'], 'def': 'a circular board of wood or cork used as the target in the game of darts', 'name': 'dartboard'}, {'frequency': 'r', 'id': 361, 'synset': 'date.n.08', 'synonyms': ['date_(fruit)'], 'def': 'sweet edible fruit of the date palm with a single long woody seed', 'name': 'date_(fruit)'}, {'frequency': 'f', 'id': 362, 'synset': 'deck_chair.n.01', 'synonyms': ['deck_chair', 'beach_chair'], 'def': 'a folding chair for use outdoors; a wooden frame supports a length of canvas', 'name': 'deck_chair'}, {'frequency': 'c', 'id': 363, 'synset': 'deer.n.01', 'synonyms': ['deer', 'cervid'], 'def': "distinguished from Bovidae by the male's having solid deciduous antlers", 'name': 'deer'}, {'frequency': 'c', 'id': 364, 'synset': 'dental_floss.n.01', 'synonyms': ['dental_floss', 'floss'], 'def': 'a soft thread for cleaning the spaces between the teeth', 'name': 'dental_floss'}, {'frequency': 'f', 'id': 365, 'synset': 'desk.n.01', 'synonyms': ['desk'], 'def': 'a piece of furniture with a writing surface and usually drawers or other compartments', 'name': 'desk'}, {'frequency': 'r', 'id': 366, 'synset': 'detergent.n.01', 'synonyms': ['detergent'], 'def': 'a surface-active chemical widely used in industry and laundering', 'name': 'detergent'}, {'frequency': 'c', 'id': 367, 'synset': 'diaper.n.01', 'synonyms': ['diaper'], 'def': 'garment consisting of a folded cloth drawn up between the legs and fastened at the waist', 'name': 'diaper'}, {'frequency': 'r', 'id': 368, 'synset': 'diary.n.01', 'synonyms': ['diary', 'journal'], 'def': 'a daily written record of (usually personal) experiences and observations', 'name': 'diary'}, {'frequency': 'r', 'id': 369, 'synset': 'die.n.01', 'synonyms': ['die', 'dice'], 'def': 'a small cube with 1 to 6 spots on the six faces; used in gambling', 'name': 'die'}, {'frequency': 'r', 'id': 370, 'synset': 'dinghy.n.01', 'synonyms': ['dinghy', 'dory', 'rowboat'], 'def': 'a small boat of shallow draft with seats and oars with which it is propelled', 'name': 'dinghy'}, {'frequency': 'f', 'id': 371, 'synset': 'dining_table.n.01', 'synonyms': ['dining_table'], 'def': 'a table at which meals are served', 'name': 'dining_table'}, {'frequency': 'r', 'id': 372, 'synset': 'dinner_jacket.n.01', 'synonyms': ['tux', 'tuxedo'], 'def': 'semiformal evening dress for men', 'name': 'tux'}, {'frequency': 'c', 'id': 373, 'synset': 'dish.n.01', 'synonyms': ['dish'], 'def': 'a piece of dishware normally used as a container for holding or serving food', 'name': 'dish'}, {'frequency': 'c', 'id': 374, 'synset': 'dish.n.05', 'synonyms': ['dish_antenna'], 'def': 'directional antenna consisting of a parabolic reflector', 'name': 'dish_antenna'}, {'frequency': 'c', 'id': 375, 'synset': 'dishrag.n.01', 'synonyms': ['dishrag', 'dishcloth'], 'def': 'a cloth for washing dishes', 'name': 'dishrag'}, {'frequency': 'c', 'id': 376, 'synset': 'dishtowel.n.01', 'synonyms': ['dishtowel', 'tea_towel'], 'def': 'a towel for drying dishes', 'name': 'dishtowel'}, {'frequency': 'f', 'id': 377, 'synset': 'dishwasher.n.01', 'synonyms': ['dishwasher', 'dishwashing_machine'], 'def': 'a machine for washing dishes', 'name': 'dishwasher'}, {'frequency': 'r', 'id': 378, 'synset': 'dishwasher_detergent.n.01', 'synonyms': ['dishwasher_detergent', 'dishwashing_detergent', 'dishwashing_liquid'], 'def': 'a low-sudsing detergent designed for use in dishwashers', 'name': 'dishwasher_detergent'}, {'frequency': 'r', 'id': 379, 'synset': 'diskette.n.01', 'synonyms': ['diskette', 'floppy', 'floppy_disk'], 'def': 'a small plastic magnetic disk enclosed in a stiff envelope used to store data', 'name': 'diskette'}, {'frequency': 'c', 'id': 380, 'synset': 'dispenser.n.01', 'synonyms': ['dispenser'], 'def': 'a container so designed that the contents can be used in prescribed amounts', 'name': 'dispenser'}, {'frequency': 'c', 'id': 381, 'synset': 'dixie_cup.n.01', 'synonyms': ['Dixie_cup', 'paper_cup'], 'def': 'a disposable cup made of paper; for holding drinks', 'name': 'Dixie_cup'}, {'frequency': 'f', 'id': 382, 'synset': 'dog.n.01', 'synonyms': ['dog'], 'def': 'a common domesticated dog', 'name': 'dog'}, {'frequency': 'f', 'id': 383, 'synset': 'dog_collar.n.01', 'synonyms': ['dog_collar'], 'def': 'a collar for a dog', 'name': 'dog_collar'}, {'frequency': 'c', 'id': 384, 'synset': 'doll.n.01', 'synonyms': ['doll'], 'def': 'a toy replica of a HUMAN (NOT AN ANIMAL)', 'name': 'doll'}, {'frequency': 'r', 'id': 385, 'synset': 'dollar.n.02', 'synonyms': ['dollar', 'dollar_bill', 'one_dollar_bill'], 'def': 'a piece of paper money worth one dollar', 'name': 'dollar'}, {'frequency': 'r', 'id': 386, 'synset': 'dolphin.n.02', 'synonyms': ['dolphin'], 'def': 'any of various small toothed whales with a beaklike snout; larger than porpoises', 'name': 'dolphin'}, {'frequency': 'c', 'id': 387, 'synset': 'domestic_ass.n.01', 'synonyms': ['domestic_ass', 'donkey'], 'def': 'domestic beast of burden descended from the African wild ass; patient but stubborn', 'name': 'domestic_ass'}, {'frequency': 'r', 'id': 388, 'synset': 'domino.n.03', 'synonyms': ['eye_mask'], 'def': 'a mask covering the upper part of the face but with holes for the eyes', 'name': 'eye_mask'}, {'frequency': 'r', 'id': 389, 'synset': 'doorbell.n.01', 'synonyms': ['doorbell', 'buzzer'], 'def': 'a button at an outer door that gives a ringing or buzzing signal when pushed', 'name': 'doorbell'}, {'frequency': 'f', 'id': 390, 'synset': 'doorknob.n.01', 'synonyms': ['doorknob', 'doorhandle'], 'def': "a knob used to open a door (often called `doorhandle' in Great Britain)", 'name': 'doorknob'}, {'frequency': 'c', 'id': 391, 'synset': 'doormat.n.02', 'synonyms': ['doormat', 'welcome_mat'], 'def': 'a mat placed outside an exterior door for wiping the shoes before entering', 'name': 'doormat'}, {'frequency': 'f', 'id': 392, 'synset': 'doughnut.n.02', 'synonyms': ['doughnut', 'donut'], 'def': 'a small ring-shaped friedcake', 'name': 'doughnut'}, {'frequency': 'r', 'id': 393, 'synset': 'dove.n.01', 'synonyms': ['dove'], 'def': 'any of numerous small pigeons', 'name': 'dove'}, {'frequency': 'r', 'id': 394, 'synset': 'dragonfly.n.01', 'synonyms': ['dragonfly'], 'def': 'slender-bodied non-stinging insect having iridescent wings that are outspread at rest', 'name': 'dragonfly'}, {'frequency': 'f', 'id': 395, 'synset': 'drawer.n.01', 'synonyms': ['drawer'], 'def': 'a boxlike container in a piece of furniture; made so as to slide in and out', 'name': 'drawer'}, {'frequency': 'c', 'id': 396, 'synset': 'drawers.n.01', 'synonyms': ['underdrawers', 'boxers', 'boxershorts'], 'def': 'underpants worn by men', 'name': 'underdrawers'}, {'frequency': 'f', 'id': 397, 'synset': 'dress.n.01', 'synonyms': ['dress', 'frock'], 'def': 'a one-piece garment for a woman; has skirt and bodice', 'name': 'dress'}, {'frequency': 'c', 'id': 398, 'synset': 'dress_hat.n.01', 'synonyms': ['dress_hat', 'high_hat', 'opera_hat', 'silk_hat', 'top_hat'], 'def': "a man's hat with a tall crown; usually covered with silk or with beaver fur", 'name': 'dress_hat'}, {'frequency': 'c', 'id': 399, 'synset': 'dress_suit.n.01', 'synonyms': ['dress_suit'], 'def': 'formalwear consisting of full evening dress for men', 'name': 'dress_suit'}, {'frequency': 'c', 'id': 400, 'synset': 'dresser.n.05', 'synonyms': ['dresser'], 'def': 'a cabinet with shelves', 'name': 'dresser'}, {'frequency': 'c', 'id': 401, 'synset': 'drill.n.01', 'synonyms': ['drill'], 'def': 'a tool with a sharp rotating point for making holes in hard materials', 'name': 'drill'}, {'frequency': 'r', 'id': 402, 'synset': 'drinking_fountain.n.01', 'synonyms': ['drinking_fountain'], 'def': 'a public fountain to provide a jet of drinking water', 'name': 'drinking_fountain'}, {'frequency': 'r', 'id': 403, 'synset': 'drone.n.04', 'synonyms': ['drone'], 'def': 'an aircraft without a pilot that is operated by remote control', 'name': 'drone'}, {'frequency': 'r', 'id': 404, 'synset': 'dropper.n.01', 'synonyms': ['dropper', 'eye_dropper'], 'def': 'pipet consisting of a small tube with a vacuum bulb at one end for drawing liquid in and releasing it a drop at a time', 'name': 'dropper'}, {'frequency': 'c', 'id': 405, 'synset': 'drum.n.01', 'synonyms': ['drum_(musical_instrument)'], 'def': 'a musical percussion instrument; usually consists of a hollow cylinder with a membrane stretched across each end', 'name': 'drum_(musical_instrument)'}, {'frequency': 'r', 'id': 406, 'synset': 'drumstick.n.02', 'synonyms': ['drumstick'], 'def': 'a stick used for playing a drum', 'name': 'drumstick'}, {'frequency': 'f', 'id': 407, 'synset': 'duck.n.01', 'synonyms': ['duck'], 'def': 'small web-footed broad-billed swimming bird', 'name': 'duck'}, {'frequency': 'r', 'id': 408, 'synset': 'duckling.n.02', 'synonyms': ['duckling'], 'def': 'young duck', 'name': 'duckling'}, {'frequency': 'c', 'id': 409, 'synset': 'duct_tape.n.01', 'synonyms': ['duct_tape'], 'def': 'a wide silvery adhesive tape', 'name': 'duct_tape'}, {'frequency': 'f', 'id': 410, 'synset': 'duffel_bag.n.01', 'synonyms': ['duffel_bag', 'duffle_bag', 'duffel', 'duffle'], 'def': 'a large cylindrical bag of heavy cloth', 'name': 'duffel_bag'}, {'frequency': 'r', 'id': 411, 'synset': 'dumbbell.n.01', 'synonyms': ['dumbbell'], 'def': 'an exercising weight with two ball-like ends connected by a short handle', 'name': 'dumbbell'}, {'frequency': 'c', 'id': 412, 'synset': 'dumpster.n.01', 'synonyms': ['dumpster'], 'def': 'a container designed to receive and transport and dump waste', 'name': 'dumpster'}, {'frequency': 'r', 'id': 413, 'synset': 'dustpan.n.02', 'synonyms': ['dustpan'], 'def': 'a short-handled receptacle into which dust can be swept', 'name': 'dustpan'}, {'frequency': 'r', 'id': 414, 'synset': 'dutch_oven.n.02', 'synonyms': ['Dutch_oven'], 'def': 'iron or earthenware cooking pot; used for stews', 'name': 'Dutch_oven'}, {'frequency': 'c', 'id': 415, 'synset': 'eagle.n.01', 'synonyms': ['eagle'], 'def': 'large birds of prey noted for their broad wings and strong soaring flight', 'name': 'eagle'}, {'frequency': 'f', 'id': 416, 'synset': 'earphone.n.01', 'synonyms': ['earphone', 'earpiece', 'headphone'], 'def': 'device for listening to audio that is held over or inserted into the ear', 'name': 'earphone'}, {'frequency': 'r', 'id': 417, 'synset': 'earplug.n.01', 'synonyms': ['earplug'], 'def': 'a soft plug that is inserted into the ear canal to block sound', 'name': 'earplug'}, {'frequency': 'f', 'id': 418, 'synset': 'earring.n.01', 'synonyms': ['earring'], 'def': 'jewelry to ornament the ear', 'name': 'earring'}, {'frequency': 'c', 'id': 419, 'synset': 'easel.n.01', 'synonyms': ['easel'], 'def': "an upright tripod for displaying something (usually an artist's canvas)", 'name': 'easel'}, {'frequency': 'r', 'id': 420, 'synset': 'eclair.n.01', 'synonyms': ['eclair'], 'def': 'oblong cream puff', 'name': 'eclair'}, {'frequency': 'r', 'id': 421, 'synset': 'eel.n.01', 'synonyms': ['eel'], 'def': 'an elongate fish with fatty flesh', 'name': 'eel'}, {'frequency': 'f', 'id': 422, 'synset': 'egg.n.02', 'synonyms': ['egg', 'eggs'], 'def': 'oval reproductive body of a fowl (especially a hen) used as food', 'name': 'egg'}, {'frequency': 'r', 'id': 423, 'synset': 'egg_roll.n.01', 'synonyms': ['egg_roll', 'spring_roll'], 'def': 'minced vegetables and meat wrapped in a pancake and fried', 'name': 'egg_roll'}, {'frequency': 'c', 'id': 424, 'synset': 'egg_yolk.n.01', 'synonyms': ['egg_yolk', 'yolk_(egg)'], 'def': 'the yellow spherical part of an egg', 'name': 'egg_yolk'}, {'frequency': 'c', 'id': 425, 'synset': 'eggbeater.n.02', 'synonyms': ['eggbeater', 'eggwhisk'], 'def': 'a mixer for beating eggs or whipping cream', 'name': 'eggbeater'}, {'frequency': 'c', 'id': 426, 'synset': 'eggplant.n.01', 'synonyms': ['eggplant', 'aubergine'], 'def': 'egg-shaped vegetable having a shiny skin typically dark purple', 'name': 'eggplant'}, {'frequency': 'r', 'id': 427, 'synset': 'electric_chair.n.01', 'synonyms': ['electric_chair'], 'def': 'a chair-shaped instrument of execution by electrocution', 'name': 'electric_chair'}, {'frequency': 'f', 'id': 428, 'synset': 'electric_refrigerator.n.01', 'synonyms': ['refrigerator'], 'def': 'a refrigerator in which the coolant is pumped around by an electric motor', 'name': 'refrigerator'}, {'frequency': 'f', 'id': 429, 'synset': 'elephant.n.01', 'synonyms': ['elephant'], 'def': 'a common elephant', 'name': 'elephant'}, {'frequency': 'r', 'id': 430, 'synset': 'elk.n.01', 'synonyms': ['elk', 'moose'], 'def': 'large northern deer with enormous flattened antlers in the male', 'name': 'elk'}, {'frequency': 'c', 'id': 431, 'synset': 'envelope.n.01', 'synonyms': ['envelope'], 'def': 'a flat (usually rectangular) container for a letter, thin package, etc.', 'name': 'envelope'}, {'frequency': 'c', 'id': 432, 'synset': 'eraser.n.01', 'synonyms': ['eraser'], 'def': 'an implement used to erase something', 'name': 'eraser'}, {'frequency': 'r', 'id': 433, 'synset': 'escargot.n.01', 'synonyms': ['escargot'], 'def': 'edible snail usually served in the shell with a sauce of melted butter and garlic', 'name': 'escargot'}, {'frequency': 'r', 'id': 434, 'synset': 'eyepatch.n.01', 'synonyms': ['eyepatch'], 'def': 'a protective cloth covering for an injured eye', 'name': 'eyepatch'}, {'frequency': 'r', 'id': 435, 'synset': 'falcon.n.01', 'synonyms': ['falcon'], 'def': 'birds of prey having long pointed powerful wings adapted for swift flight', 'name': 'falcon'}, {'frequency': 'f', 'id': 436, 'synset': 'fan.n.01', 'synonyms': ['fan'], 'def': 'a device for creating a current of air by movement of a surface or surfaces', 'name': 'fan'}, {'frequency': 'f', 'id': 437, 'synset': 'faucet.n.01', 'synonyms': ['faucet', 'spigot', 'tap'], 'def': 'a regulator for controlling the flow of a liquid from a reservoir', 'name': 'faucet'}, {'frequency': 'r', 'id': 438, 'synset': 'fedora.n.01', 'synonyms': ['fedora'], 'def': 'a hat made of felt with a creased crown', 'name': 'fedora'}, {'frequency': 'r', 'id': 439, 'synset': 'ferret.n.02', 'synonyms': ['ferret'], 'def': 'domesticated albino variety of the European polecat bred for hunting rats and rabbits', 'name': 'ferret'}, {'frequency': 'c', 'id': 440, 'synset': 'ferris_wheel.n.01', 'synonyms': ['Ferris_wheel'], 'def': 'a large wheel with suspended seats that remain upright as the wheel rotates', 'name': 'Ferris_wheel'}, {'frequency': 'r', 'id': 441, 'synset': 'ferry.n.01', 'synonyms': ['ferry', 'ferryboat'], 'def': 'a boat that transports people or vehicles across a body of water and operates on a regular schedule', 'name': 'ferry'}, {'frequency': 'r', 'id': 442, 'synset': 'fig.n.04', 'synonyms': ['fig_(fruit)'], 'def': 'fleshy sweet pear-shaped yellowish or purple fruit eaten fresh or preserved or dried', 'name': 'fig_(fruit)'}, {'frequency': 'c', 'id': 443, 'synset': 'fighter.n.02', 'synonyms': ['fighter_jet', 'fighter_aircraft', 'attack_aircraft'], 'def': 'a high-speed military or naval airplane designed to destroy enemy targets', 'name': 'fighter_jet'}, {'frequency': 'f', 'id': 444, 'synset': 'figurine.n.01', 'synonyms': ['figurine'], 'def': 'a small carved or molded figure', 'name': 'figurine'}, {'frequency': 'c', 'id': 445, 'synset': 'file.n.03', 'synonyms': ['file_cabinet', 'filing_cabinet'], 'def': 'office furniture consisting of a container for keeping papers in order', 'name': 'file_cabinet'}, {'frequency': 'r', 'id': 446, 'synset': 'file.n.04', 'synonyms': ['file_(tool)'], 'def': 'a steel hand tool with small sharp teeth on some or all of its surfaces; used for smoothing wood or metal', 'name': 'file_(tool)'}, {'frequency': 'f', 'id': 447, 'synset': 'fire_alarm.n.02', 'synonyms': ['fire_alarm', 'smoke_alarm'], 'def': 'an alarm that is tripped off by fire or smoke', 'name': 'fire_alarm'}, {'frequency': 'c', 'id': 448, 'synset': 'fire_engine.n.01', 'synonyms': ['fire_engine', 'fire_truck'], 'def': 'large trucks that carry firefighters and equipment to the site of a fire', 'name': 'fire_engine'}, {'frequency': 'c', 'id': 449, 'synset': 'fire_extinguisher.n.01', 'synonyms': ['fire_extinguisher', 'extinguisher'], 'def': 'a manually operated device for extinguishing small fires', 'name': 'fire_extinguisher'}, {'frequency': 'c', 'id': 450, 'synset': 'fire_hose.n.01', 'synonyms': ['fire_hose'], 'def': 'a large hose that carries water from a fire hydrant to the site of the fire', 'name': 'fire_hose'}, {'frequency': 'f', 'id': 451, 'synset': 'fireplace.n.01', 'synonyms': ['fireplace'], 'def': 'an open recess in a wall at the base of a chimney where a fire can be built', 'name': 'fireplace'}, {'frequency': 'f', 'id': 452, 'synset': 'fireplug.n.01', 'synonyms': ['fireplug', 'fire_hydrant', 'hydrant'], 'def': 'an upright hydrant for drawing water to use in fighting a fire', 'name': 'fireplug'}, {'frequency': 'c', 'id': 453, 'synset': 'fish.n.01', 'synonyms': ['fish'], 'def': 'any of various mostly cold-blooded aquatic vertebrates usually having scales and breathing through gills', 'name': 'fish'}, {'frequency': 'r', 'id': 454, 'synset': 'fish.n.02', 'synonyms': ['fish_(food)'], 'def': 'the flesh of fish used as food', 'name': 'fish_(food)'}, {'frequency': 'r', 'id': 455, 'synset': 'fishbowl.n.02', 'synonyms': ['fishbowl', 'goldfish_bowl'], 'def': 'a transparent bowl in which small fish are kept', 'name': 'fishbowl'}, {'frequency': 'r', 'id': 456, 'synset': 'fishing_boat.n.01', 'synonyms': ['fishing_boat', 'fishing_vessel'], 'def': 'a vessel for fishing', 'name': 'fishing_boat'}, {'frequency': 'c', 'id': 457, 'synset': 'fishing_rod.n.01', 'synonyms': ['fishing_rod', 'fishing_pole'], 'def': 'a rod that is used in fishing to extend the fishing line', 'name': 'fishing_rod'}, {'frequency': 'f', 'id': 458, 'synset': 'flag.n.01', 'synonyms': ['flag'], 'def': 'emblem usually consisting of a rectangular piece of cloth of distinctive design (do not include pole)', 'name': 'flag'}, {'frequency': 'f', 'id': 459, 'synset': 'flagpole.n.02', 'synonyms': ['flagpole', 'flagstaff'], 'def': 'a tall staff or pole on which a flag is raised', 'name': 'flagpole'}, {'frequency': 'c', 'id': 460, 'synset': 'flamingo.n.01', 'synonyms': ['flamingo'], 'def': 'large pink web-footed bird with down-bent bill', 'name': 'flamingo'}, {'frequency': 'c', 'id': 461, 'synset': 'flannel.n.01', 'synonyms': ['flannel'], 'def': 'a soft light woolen fabric; used for clothing', 'name': 'flannel'}, {'frequency': 'r', 'id': 462, 'synset': 'flash.n.10', 'synonyms': ['flash', 'flashbulb'], 'def': 'a lamp for providing momentary light to take a photograph', 'name': 'flash'}, {'frequency': 'c', 'id': 463, 'synset': 'flashlight.n.01', 'synonyms': ['flashlight', 'torch'], 'def': 'a small portable battery-powered electric lamp', 'name': 'flashlight'}, {'frequency': 'r', 'id': 464, 'synset': 'fleece.n.03', 'synonyms': ['fleece'], 'def': 'a soft bulky fabric with deep pile; used chiefly for clothing', 'name': 'fleece'}, {'frequency': 'f', 'id': 465, 'synset': 'flip-flop.n.02', 'synonyms': ['flip-flop_(sandal)'], 'def': 'a backless sandal held to the foot by a thong between two toes', 'name': 'flip-flop_(sandal)'}, {'frequency': 'c', 'id': 466, 'synset': 'flipper.n.01', 'synonyms': ['flipper_(footwear)', 'fin_(footwear)'], 'def': 'a shoe to aid a person in swimming', 'name': 'flipper_(footwear)'}, {'frequency': 'f', 'id': 467, 'synset': 'flower_arrangement.n.01', 'synonyms': ['flower_arrangement', 'floral_arrangement'], 'def': 'a decorative arrangement of flowers', 'name': 'flower_arrangement'}, {'frequency': 'c', 'id': 468, 'synset': 'flute.n.02', 'synonyms': ['flute_glass', 'champagne_flute'], 'def': 'a tall narrow wineglass', 'name': 'flute_glass'}, {'frequency': 'r', 'id': 469, 'synset': 'foal.n.01', 'synonyms': ['foal'], 'def': 'a young horse', 'name': 'foal'}, {'frequency': 'c', 'id': 470, 'synset': 'folding_chair.n.01', 'synonyms': ['folding_chair'], 'def': 'a chair that can be folded flat for storage', 'name': 'folding_chair'}, {'frequency': 'c', 'id': 471, 'synset': 'food_processor.n.01', 'synonyms': ['food_processor'], 'def': 'a kitchen appliance for shredding, blending, chopping, or slicing food', 'name': 'food_processor'}, {'frequency': 'c', 'id': 472, 'synset': 'football.n.02', 'synonyms': ['football_(American)'], 'def': 'the inflated oblong ball used in playing American football', 'name': 'football_(American)'}, {'frequency': 'r', 'id': 473, 'synset': 'football_helmet.n.01', 'synonyms': ['football_helmet'], 'def': 'a padded helmet with a face mask to protect the head of football players', 'name': 'football_helmet'}, {'frequency': 'c', 'id': 474, 'synset': 'footstool.n.01', 'synonyms': ['footstool', 'footrest'], 'def': 'a low seat or a stool to rest the feet of a seated person', 'name': 'footstool'}, {'frequency': 'f', 'id': 475, 'synset': 'fork.n.01', 'synonyms': ['fork'], 'def': 'cutlery used for serving and eating food', 'name': 'fork'}, {'frequency': 'r', 'id': 476, 'synset': 'forklift.n.01', 'synonyms': ['forklift'], 'def': 'an industrial vehicle with a power operated fork in front that can be inserted under loads to lift and move them', 'name': 'forklift'}, {'frequency': 'r', 'id': 477, 'synset': 'freight_car.n.01', 'synonyms': ['freight_car'], 'def': 'a railway car that carries freight', 'name': 'freight_car'}, {'frequency': 'r', 'id': 478, 'synset': 'french_toast.n.01', 'synonyms': ['French_toast'], 'def': 'bread slice dipped in egg and milk and fried', 'name': 'French_toast'}, {'frequency': 'c', 'id': 479, 'synset': 'freshener.n.01', 'synonyms': ['freshener', 'air_freshener'], 'def': 'anything that freshens', 'name': 'freshener'}, {'frequency': 'f', 'id': 480, 'synset': 'frisbee.n.01', 'synonyms': ['frisbee'], 'def': 'a light, plastic disk propelled with a flip of the wrist for recreation or competition', 'name': 'frisbee'}, {'frequency': 'c', 'id': 481, 'synset': 'frog.n.01', 'synonyms': ['frog', 'toad', 'toad_frog'], 'def': 'a tailless stout-bodied amphibians with long hind limbs for leaping', 'name': 'frog'}, {'frequency': 'c', 'id': 482, 'synset': 'fruit_juice.n.01', 'synonyms': ['fruit_juice'], 'def': 'drink produced by squeezing or crushing fruit', 'name': 'fruit_juice'}, {'frequency': 'r', 'id': 483, 'synset': 'fruit_salad.n.01', 'synonyms': ['fruit_salad'], 'def': 'salad composed of fruits', 'name': 'fruit_salad'}, {'frequency': 'c', 'id': 484, 'synset': 'frying_pan.n.01', 'synonyms': ['frying_pan', 'frypan', 'skillet'], 'def': 'a pan used for frying foods', 'name': 'frying_pan'}, {'frequency': 'r', 'id': 485, 'synset': 'fudge.n.01', 'synonyms': ['fudge'], 'def': 'soft creamy candy', 'name': 'fudge'}, {'frequency': 'r', 'id': 486, 'synset': 'funnel.n.02', 'synonyms': ['funnel'], 'def': 'a cone-shaped utensil used to channel a substance into a container with a small mouth', 'name': 'funnel'}, {'frequency': 'c', 'id': 487, 'synset': 'futon.n.01', 'synonyms': ['futon'], 'def': 'a pad that is used for sleeping on the floor or on a raised frame', 'name': 'futon'}, {'frequency': 'r', 'id': 488, 'synset': 'gag.n.02', 'synonyms': ['gag', 'muzzle'], 'def': "restraint put into a person's mouth to prevent speaking or shouting", 'name': 'gag'}, {'frequency': 'r', 'id': 489, 'synset': 'garbage.n.03', 'synonyms': ['garbage'], 'def': 'a receptacle where waste can be discarded', 'name': 'garbage'}, {'frequency': 'c', 'id': 490, 'synset': 'garbage_truck.n.01', 'synonyms': ['garbage_truck'], 'def': 'a truck for collecting domestic refuse', 'name': 'garbage_truck'}, {'frequency': 'c', 'id': 491, 'synset': 'garden_hose.n.01', 'synonyms': ['garden_hose'], 'def': 'a hose used for watering a lawn or garden', 'name': 'garden_hose'}, {'frequency': 'c', 'id': 492, 'synset': 'gargle.n.01', 'synonyms': ['gargle', 'mouthwash'], 'def': 'a medicated solution used for gargling and rinsing the mouth', 'name': 'gargle'}, {'frequency': 'r', 'id': 493, 'synset': 'gargoyle.n.02', 'synonyms': ['gargoyle'], 'def': 'an ornament consisting of a grotesquely carved figure of a person or animal', 'name': 'gargoyle'}, {'frequency': 'c', 'id': 494, 'synset': 'garlic.n.02', 'synonyms': ['garlic', 'ail'], 'def': 'aromatic bulb used as seasoning', 'name': 'garlic'}, {'frequency': 'r', 'id': 495, 'synset': 'gasmask.n.01', 'synonyms': ['gasmask', 'respirator', 'gas_helmet'], 'def': 'a protective face mask with a filter', 'name': 'gasmask'}, {'frequency': 'r', 'id': 496, 'synset': 'gazelle.n.01', 'synonyms': ['gazelle'], 'def': 'small swift graceful antelope of Africa and Asia having lustrous eyes', 'name': 'gazelle'}, {'frequency': 'c', 'id': 497, 'synset': 'gelatin.n.02', 'synonyms': ['gelatin', 'jelly'], 'def': 'an edible jelly made with gelatin and used as a dessert or salad base or a coating for foods', 'name': 'gelatin'}, {'frequency': 'r', 'id': 498, 'synset': 'gem.n.02', 'synonyms': ['gemstone'], 'def': 'a crystalline rock that can be cut and polished for jewelry', 'name': 'gemstone'}, {'frequency': 'c', 'id': 499, 'synset': 'giant_panda.n.01', 'synonyms': ['giant_panda', 'panda', 'panda_bear'], 'def': 'large black-and-white herbivorous mammal of bamboo forests of China and Tibet', 'name': 'giant_panda'}, {'frequency': 'c', 'id': 500, 'synset': 'gift_wrap.n.01', 'synonyms': ['gift_wrap'], 'def': 'attractive wrapping paper suitable for wrapping gifts', 'name': 'gift_wrap'}, {'frequency': 'c', 'id': 501, 'synset': 'ginger.n.03', 'synonyms': ['ginger', 'gingerroot'], 'def': 'the root of the common ginger plant; used fresh as a seasoning', 'name': 'ginger'}, {'frequency': 'f', 'id': 502, 'synset': 'giraffe.n.01', 'synonyms': ['giraffe'], 'def': 'tall animal having a spotted coat and small horns and very long neck and legs', 'name': 'giraffe'}, {'frequency': 'c', 'id': 503, 'synset': 'girdle.n.02', 'synonyms': ['cincture', 'sash', 'waistband', 'waistcloth'], 'def': 'a band of material around the waist that strengthens a skirt or trousers', 'name': 'cincture'}, {'frequency': 'f', 'id': 504, 'synset': 'glass.n.02', 'synonyms': ['glass_(drink_container)', 'drinking_glass'], 'def': 'a container for holding liquids while drinking', 'name': 'glass_(drink_container)'}, {'frequency': 'c', 'id': 505, 'synset': 'globe.n.03', 'synonyms': ['globe'], 'def': 'a sphere on which a map (especially of the earth) is represented', 'name': 'globe'}, {'frequency': 'f', 'id': 506, 'synset': 'glove.n.02', 'synonyms': ['glove'], 'def': 'handwear covering the hand', 'name': 'glove'}, {'frequency': 'c', 'id': 507, 'synset': 'goat.n.01', 'synonyms': ['goat'], 'def': 'a common goat', 'name': 'goat'}, {'frequency': 'f', 'id': 508, 'synset': 'goggles.n.01', 'synonyms': ['goggles'], 'def': 'tight-fitting spectacles worn to protect the eyes', 'name': 'goggles'}, {'frequency': 'r', 'id': 509, 'synset': 'goldfish.n.01', 'synonyms': ['goldfish'], 'def': 'small golden or orange-red freshwater fishes used as pond or aquarium pets', 'name': 'goldfish'}, {'frequency': 'r', 'id': 510, 'synset': 'golf_club.n.02', 'synonyms': ['golf_club', 'golf-club'], 'def': 'golf equipment used by a golfer to hit a golf ball', 'name': 'golf_club'}, {'frequency': 'c', 'id': 511, 'synset': 'golfcart.n.01', 'synonyms': ['golfcart'], 'def': 'a small motor vehicle in which golfers can ride between shots', 'name': 'golfcart'}, {'frequency': 'r', 'id': 512, 'synset': 'gondola.n.02', 'synonyms': ['gondola_(boat)'], 'def': 'long narrow flat-bottomed boat propelled by sculling; traditionally used on canals of Venice', 'name': 'gondola_(boat)'}, {'frequency': 'c', 'id': 513, 'synset': 'goose.n.01', 'synonyms': ['goose'], 'def': 'loud, web-footed long-necked aquatic birds usually larger than ducks', 'name': 'goose'}, {'frequency': 'r', 'id': 514, 'synset': 'gorilla.n.01', 'synonyms': ['gorilla'], 'def': 'largest ape', 'name': 'gorilla'}, {'frequency': 'r', 'id': 515, 'synset': 'gourd.n.02', 'synonyms': ['gourd'], 'def': 'any of numerous inedible fruits with hard rinds', 'name': 'gourd'}, {'frequency': 'r', 'id': 516, 'synset': 'gown.n.04', 'synonyms': ['surgical_gown', 'scrubs_(surgical_clothing)'], 'def': 'protective garment worn by surgeons during operations', 'name': 'surgical_gown'}, {'frequency': 'f', 'id': 517, 'synset': 'grape.n.01', 'synonyms': ['grape'], 'def': 'any of various juicy fruit with green or purple skins; grow in clusters', 'name': 'grape'}, {'frequency': 'r', 'id': 518, 'synset': 'grasshopper.n.01', 'synonyms': ['grasshopper'], 'def': 'plant-eating insect with hind legs adapted for leaping', 'name': 'grasshopper'}, {'frequency': 'c', 'id': 519, 'synset': 'grater.n.01', 'synonyms': ['grater'], 'def': 'utensil with sharp perforations for shredding foods (as vegetables or cheese)', 'name': 'grater'}, {'frequency': 'c', 'id': 520, 'synset': 'gravestone.n.01', 'synonyms': ['gravestone', 'headstone', 'tombstone'], 'def': 'a stone that is used to mark a grave', 'name': 'gravestone'}, {'frequency': 'r', 'id': 521, 'synset': 'gravy_boat.n.01', 'synonyms': ['gravy_boat', 'gravy_holder'], 'def': 'a dish (often boat-shaped) for serving gravy or sauce', 'name': 'gravy_boat'}, {'frequency': 'c', 'id': 522, 'synset': 'green_bean.n.02', 'synonyms': ['green_bean'], 'def': 'a common bean plant cultivated for its slender green edible pods', 'name': 'green_bean'}, {'frequency': 'c', 'id': 523, 'synset': 'green_onion.n.01', 'synonyms': ['green_onion', 'spring_onion', 'scallion'], 'def': 'a young onion before the bulb has enlarged', 'name': 'green_onion'}, {'frequency': 'r', 'id': 524, 'synset': 'griddle.n.01', 'synonyms': ['griddle'], 'def': 'cooking utensil consisting of a flat heated surface on which food is cooked', 'name': 'griddle'}, {'frequency': 'r', 'id': 525, 'synset': 'grillroom.n.01', 'synonyms': ['grillroom', 'grill_(restaurant)'], 'def': 'a restaurant where food is cooked on a grill', 'name': 'grillroom'}, {'frequency': 'r', 'id': 526, 'synset': 'grinder.n.04', 'synonyms': ['grinder_(tool)'], 'def': 'a machine tool that polishes metal', 'name': 'grinder_(tool)'}, {'frequency': 'r', 'id': 527, 'synset': 'grits.n.01', 'synonyms': ['grits', 'hominy_grits'], 'def': 'coarsely ground corn boiled as a breakfast dish', 'name': 'grits'}, {'frequency': 'c', 'id': 528, 'synset': 'grizzly.n.01', 'synonyms': ['grizzly', 'grizzly_bear'], 'def': 'powerful brownish-yellow bear of the uplands of western North America', 'name': 'grizzly'}, {'frequency': 'c', 'id': 529, 'synset': 'grocery_bag.n.01', 'synonyms': ['grocery_bag'], 'def': "a sack for holding customer's groceries", 'name': 'grocery_bag'}, {'frequency': 'r', 'id': 530, 'synset': 'guacamole.n.01', 'synonyms': ['guacamole'], 'def': 'a dip made of mashed avocado mixed with chopped onions and other seasonings', 'name': 'guacamole'}, {'frequency': 'f', 'id': 531, 'synset': 'guitar.n.01', 'synonyms': ['guitar'], 'def': 'a stringed instrument usually having six strings; played by strumming or plucking', 'name': 'guitar'}, {'frequency': 'c', 'id': 532, 'synset': 'gull.n.02', 'synonyms': ['gull', 'seagull'], 'def': 'mostly white aquatic bird having long pointed wings and short legs', 'name': 'gull'}, {'frequency': 'c', 'id': 533, 'synset': 'gun.n.01', 'synonyms': ['gun'], 'def': 'a weapon that discharges a bullet at high velocity from a metal tube', 'name': 'gun'}, {'frequency': 'r', 'id': 534, 'synset': 'hair_spray.n.01', 'synonyms': ['hair_spray'], 'def': 'substance sprayed on the hair to hold it in place', 'name': 'hair_spray'}, {'frequency': 'c', 'id': 535, 'synset': 'hairbrush.n.01', 'synonyms': ['hairbrush'], 'def': "a brush used to groom a person's hair", 'name': 'hairbrush'}, {'frequency': 'c', 'id': 536, 'synset': 'hairnet.n.01', 'synonyms': ['hairnet'], 'def': 'a small net that someone wears over their hair to keep it in place', 'name': 'hairnet'}, {'frequency': 'c', 'id': 537, 'synset': 'hairpin.n.01', 'synonyms': ['hairpin'], 'def': "a double pronged pin used to hold women's hair in place", 'name': 'hairpin'}, {'frequency': 'f', 'id': 538, 'synset': 'ham.n.01', 'synonyms': ['ham', 'jambon', 'gammon'], 'def': 'meat cut from the thigh of a hog (usually smoked)', 'name': 'ham'}, {'frequency': 'c', 'id': 539, 'synset': 'hamburger.n.01', 'synonyms': ['hamburger', 'beefburger', 'burger'], 'def': 'a sandwich consisting of a patty of minced beef served on a bun', 'name': 'hamburger'}, {'frequency': 'c', 'id': 540, 'synset': 'hammer.n.02', 'synonyms': ['hammer'], 'def': 'a hand tool with a heavy head and a handle; used to deliver an impulsive force by striking', 'name': 'hammer'}, {'frequency': 'r', 'id': 541, 'synset': 'hammock.n.02', 'synonyms': ['hammock'], 'def': 'a hanging bed of canvas or rope netting (usually suspended between two trees)', 'name': 'hammock'}, {'frequency': 'r', 'id': 542, 'synset': 'hamper.n.02', 'synonyms': ['hamper'], 'def': 'a basket usually with a cover', 'name': 'hamper'}, {'frequency': 'r', 'id': 543, 'synset': 'hamster.n.01', 'synonyms': ['hamster'], 'def': 'short-tailed burrowing rodent with large cheek pouches', 'name': 'hamster'}, {'frequency': 'c', 'id': 544, 'synset': 'hand_blower.n.01', 'synonyms': ['hair_dryer'], 'def': 'a hand-held electric blower that can blow warm air onto the hair', 'name': 'hair_dryer'}, {'frequency': 'r', 'id': 545, 'synset': 'hand_glass.n.01', 'synonyms': ['hand_glass', 'hand_mirror'], 'def': 'a mirror intended to be held in the hand', 'name': 'hand_glass'}, {'frequency': 'f', 'id': 546, 'synset': 'hand_towel.n.01', 'synonyms': ['hand_towel', 'face_towel'], 'def': 'a small towel used to dry the hands or face', 'name': 'hand_towel'}, {'frequency': 'c', 'id': 547, 'synset': 'handcart.n.01', 'synonyms': ['handcart', 'pushcart', 'hand_truck'], 'def': 'wheeled vehicle that can be pushed by a person', 'name': 'handcart'}, {'frequency': 'r', 'id': 548, 'synset': 'handcuff.n.01', 'synonyms': ['handcuff'], 'def': 'shackle that consists of a metal loop that can be locked around the wrist', 'name': 'handcuff'}, {'frequency': 'c', 'id': 549, 'synset': 'handkerchief.n.01', 'synonyms': ['handkerchief'], 'def': 'a square piece of cloth used for wiping the eyes or nose or as a costume accessory', 'name': 'handkerchief'}, {'frequency': 'f', 'id': 550, 'synset': 'handle.n.01', 'synonyms': ['handle', 'grip', 'handgrip'], 'def': 'the appendage to an object that is designed to be held in order to use or move it', 'name': 'handle'}, {'frequency': 'r', 'id': 551, 'synset': 'handsaw.n.01', 'synonyms': ['handsaw', "carpenter's_saw"], 'def': 'a saw used with one hand for cutting wood', 'name': 'handsaw'}, {'frequency': 'r', 'id': 552, 'synset': 'hardback.n.01', 'synonyms': ['hardback_book', 'hardcover_book'], 'def': 'a book with cardboard or cloth or leather covers', 'name': 'hardback_book'}, {'frequency': 'r', 'id': 553, 'synset': 'harmonium.n.01', 'synonyms': ['harmonium', 'organ_(musical_instrument)', 'reed_organ_(musical_instrument)'], 'def': 'a free-reed instrument in which air is forced through the reeds by bellows', 'name': 'harmonium'}, {'frequency': 'f', 'id': 554, 'synset': 'hat.n.01', 'synonyms': ['hat'], 'def': 'headwear that protects the head from bad weather, sun, or worn for fashion', 'name': 'hat'}, {'frequency': 'r', 'id': 555, 'synset': 'hatbox.n.01', 'synonyms': ['hatbox'], 'def': 'a round piece of luggage for carrying hats', 'name': 'hatbox'}, {'frequency': 'r', 'id': 556, 'synset': 'hatch.n.03', 'synonyms': ['hatch'], 'def': 'a movable barrier covering a hatchway', 'name': 'hatch'}, {'frequency': 'c', 'id': 557, 'synset': 'head_covering.n.01', 'synonyms': ['veil'], 'def': 'a garment that covers the head and face', 'name': 'veil'}, {'frequency': 'f', 'id': 558, 'synset': 'headband.n.01', 'synonyms': ['headband'], 'def': 'a band worn around or over the head', 'name': 'headband'}, {'frequency': 'f', 'id': 559, 'synset': 'headboard.n.01', 'synonyms': ['headboard'], 'def': 'a vertical board or panel forming the head of a bedstead', 'name': 'headboard'}, {'frequency': 'f', 'id': 560, 'synset': 'headlight.n.01', 'synonyms': ['headlight', 'headlamp'], 'def': 'a powerful light with reflector; attached to the front of an automobile or locomotive', 'name': 'headlight'}, {'frequency': 'c', 'id': 561, 'synset': 'headscarf.n.01', 'synonyms': ['headscarf'], 'def': 'a kerchief worn over the head and tied under the chin', 'name': 'headscarf'}, {'frequency': 'r', 'id': 562, 'synset': 'headset.n.01', 'synonyms': ['headset'], 'def': 'receiver consisting of a pair of headphones', 'name': 'headset'}, {'frequency': 'c', 'id': 563, 'synset': 'headstall.n.01', 'synonyms': ['headstall_(for_horses)', 'headpiece_(for_horses)'], 'def': "the band that is the part of a bridle that fits around a horse's head", 'name': 'headstall_(for_horses)'}, {'frequency': 'r', 'id': 564, 'synset': 'hearing_aid.n.02', 'synonyms': ['hearing_aid'], 'def': 'an acoustic device used to direct sound to the ear of a hearing-impaired person', 'name': 'hearing_aid'}, {'frequency': 'c', 'id': 565, 'synset': 'heart.n.02', 'synonyms': ['heart'], 'def': 'a muscular organ; its contractions move the blood through the body', 'name': 'heart'}, {'frequency': 'c', 'id': 566, 'synset': 'heater.n.01', 'synonyms': ['heater', 'warmer'], 'def': 'device that heats water or supplies warmth to a room', 'name': 'heater'}, {'frequency': 'c', 'id': 567, 'synset': 'helicopter.n.01', 'synonyms': ['helicopter'], 'def': 'an aircraft without wings that obtains its lift from the rotation of overhead blades', 'name': 'helicopter'}, {'frequency': 'f', 'id': 568, 'synset': 'helmet.n.02', 'synonyms': ['helmet'], 'def': 'a protective headgear made of hard material to resist blows', 'name': 'helmet'}, {'frequency': 'r', 'id': 569, 'synset': 'heron.n.02', 'synonyms': ['heron'], 'def': 'grey or white wading bird with long neck and long legs and (usually) long bill', 'name': 'heron'}, {'frequency': 'c', 'id': 570, 'synset': 'highchair.n.01', 'synonyms': ['highchair', 'feeding_chair'], 'def': 'a chair for feeding a very young child', 'name': 'highchair'}, {'frequency': 'f', 'id': 571, 'synset': 'hinge.n.01', 'synonyms': ['hinge'], 'def': 'a joint that holds two parts together so that one can swing relative to the other', 'name': 'hinge'}, {'frequency': 'r', 'id': 572, 'synset': 'hippopotamus.n.01', 'synonyms': ['hippopotamus'], 'def': 'massive thick-skinned animal living in or around rivers of tropical Africa', 'name': 'hippopotamus'}, {'frequency': 'r', 'id': 573, 'synset': 'hockey_stick.n.01', 'synonyms': ['hockey_stick'], 'def': 'sports implement consisting of a stick used by hockey players to move the puck', 'name': 'hockey_stick'}, {'frequency': 'c', 'id': 574, 'synset': 'hog.n.03', 'synonyms': ['hog', 'pig'], 'def': 'domestic swine', 'name': 'hog'}, {'frequency': 'f', 'id': 575, 'synset': 'home_plate.n.01', 'synonyms': ['home_plate_(baseball)', 'home_base_(baseball)'], 'def': '(baseball) a rubber slab where the batter stands; it must be touched by a base runner in order to score', 'name': 'home_plate_(baseball)'}, {'frequency': 'c', 'id': 576, 'synset': 'honey.n.01', 'synonyms': ['honey'], 'def': 'a sweet yellow liquid produced by bees', 'name': 'honey'}, {'frequency': 'f', 'id': 577, 'synset': 'hood.n.06', 'synonyms': ['fume_hood', 'exhaust_hood'], 'def': 'metal covering leading to a vent that exhausts smoke or fumes', 'name': 'fume_hood'}, {'frequency': 'f', 'id': 578, 'synset': 'hook.n.05', 'synonyms': ['hook'], 'def': 'a curved or bent implement for suspending or pulling something', 'name': 'hook'}, {'frequency': 'f', 'id': 579, 'synset': 'horse.n.01', 'synonyms': ['horse'], 'def': 'a common horse', 'name': 'horse'}, {'frequency': 'f', 'id': 580, 'synset': 'hose.n.03', 'synonyms': ['hose', 'hosepipe'], 'def': 'a flexible pipe for conveying a liquid or gas', 'name': 'hose'}, {'frequency': 'r', 'id': 581, 'synset': 'hot-air_balloon.n.01', 'synonyms': ['hot-air_balloon'], 'def': 'balloon for travel through the air in a basket suspended below a large bag of heated air', 'name': 'hot-air_balloon'}, {'frequency': 'r', 'id': 582, 'synset': 'hot_plate.n.01', 'synonyms': ['hotplate'], 'def': 'a portable electric appliance for heating or cooking or keeping food warm', 'name': 'hotplate'}, {'frequency': 'c', 'id': 583, 'synset': 'hot_sauce.n.01', 'synonyms': ['hot_sauce'], 'def': 'a pungent peppery sauce', 'name': 'hot_sauce'}, {'frequency': 'r', 'id': 584, 'synset': 'hourglass.n.01', 'synonyms': ['hourglass'], 'def': 'a sandglass timer that runs for sixty minutes', 'name': 'hourglass'}, {'frequency': 'r', 'id': 585, 'synset': 'houseboat.n.01', 'synonyms': ['houseboat'], 'def': 'a barge that is designed and equipped for use as a dwelling', 'name': 'houseboat'}, {'frequency': 'r', 'id': 586, 'synset': 'hummingbird.n.01', 'synonyms': ['hummingbird'], 'def': 'tiny American bird having brilliant iridescent plumage and long slender bills', 'name': 'hummingbird'}, {'frequency': 'r', 'id': 587, 'synset': 'hummus.n.01', 'synonyms': ['hummus', 'humus', 'hommos', 'hoummos', 'humous'], 'def': 'a thick spread made from mashed chickpeas', 'name': 'hummus'}, {'frequency': 'c', 'id': 588, 'synset': 'ice_bear.n.01', 'synonyms': ['polar_bear'], 'def': 'white bear of Arctic regions', 'name': 'polar_bear'}, {'frequency': 'c', 'id': 589, 'synset': 'ice_cream.n.01', 'synonyms': ['icecream'], 'def': 'frozen dessert containing cream and sugar and flavoring', 'name': 'icecream'}, {'frequency': 'r', 'id': 590, 'synset': 'ice_lolly.n.01', 'synonyms': ['popsicle'], 'def': 'ice cream or water ice on a small wooden stick', 'name': 'popsicle'}, {'frequency': 'c', 'id': 591, 'synset': 'ice_maker.n.01', 'synonyms': ['ice_maker'], 'def': 'an appliance included in some electric refrigerators for making ice cubes', 'name': 'ice_maker'}, {'frequency': 'r', 'id': 592, 'synset': 'ice_pack.n.01', 'synonyms': ['ice_pack', 'ice_bag'], 'def': 'a waterproof bag filled with ice: applied to the body (especially the head) to cool or reduce swelling', 'name': 'ice_pack'}, {'frequency': 'r', 'id': 593, 'synset': 'ice_skate.n.01', 'synonyms': ['ice_skate'], 'def': 'skate consisting of a boot with a steel blade fitted to the sole', 'name': 'ice_skate'}, {'frequency': 'r', 'id': 594, 'synset': 'ice_tea.n.01', 'synonyms': ['ice_tea', 'iced_tea'], 'def': 'strong tea served over ice', 'name': 'ice_tea'}, {'frequency': 'c', 'id': 595, 'synset': 'igniter.n.01', 'synonyms': ['igniter', 'ignitor', 'lighter'], 'def': 'a substance or device used to start a fire', 'name': 'igniter'}, {'frequency': 'r', 'id': 596, 'synset': 'incense.n.01', 'synonyms': ['incense'], 'def': 'a substance that produces a fragrant odor when burned', 'name': 'incense'}, {'frequency': 'r', 'id': 597, 'synset': 'inhaler.n.01', 'synonyms': ['inhaler', 'inhalator'], 'def': 'a dispenser that produces a chemical vapor to be inhaled through mouth or nose', 'name': 'inhaler'}, {'frequency': 'c', 'id': 598, 'synset': 'ipod.n.01', 'synonyms': ['iPod'], 'def': 'a pocket-sized device used to play music files', 'name': 'iPod'}, {'frequency': 'c', 'id': 599, 'synset': 'iron.n.04', 'synonyms': ['iron_(for_clothing)', 'smoothing_iron_(for_clothing)'], 'def': 'home appliance consisting of a flat metal base that is heated and used to smooth cloth', 'name': 'iron_(for_clothing)'}, {'frequency': 'r', 'id': 600, 'synset': 'ironing_board.n.01', 'synonyms': ['ironing_board'], 'def': 'narrow padded board on collapsible supports; used for ironing clothes', 'name': 'ironing_board'}, {'frequency': 'f', 'id': 601, 'synset': 'jacket.n.01', 'synonyms': ['jacket'], 'def': 'a waist-length coat', 'name': 'jacket'}, {'frequency': 'r', 'id': 602, 'synset': 'jam.n.01', 'synonyms': ['jam'], 'def': 'preserve of crushed fruit', 'name': 'jam'}, {'frequency': 'f', 'id': 603, 'synset': 'jean.n.01', 'synonyms': ['jean', 'blue_jean', 'denim'], 'def': '(usually plural) close-fitting trousers of heavy denim for manual work or casual wear', 'name': 'jean'}, {'frequency': 'c', 'id': 604, 'synset': 'jeep.n.01', 'synonyms': ['jeep', 'landrover'], 'def': 'a car suitable for traveling over rough terrain', 'name': 'jeep'}, {'frequency': 'r', 'id': 605, 'synset': 'jelly_bean.n.01', 'synonyms': ['jelly_bean', 'jelly_egg'], 'def': 'sugar-glazed jellied candy', 'name': 'jelly_bean'}, {'frequency': 'f', 'id': 606, 'synset': 'jersey.n.03', 'synonyms': ['jersey', 'T-shirt', 'tee_shirt'], 'def': 'a close-fitting pullover shirt', 'name': 'jersey'}, {'frequency': 'c', 'id': 607, 'synset': 'jet.n.01', 'synonyms': ['jet_plane', 'jet-propelled_plane'], 'def': 'an airplane powered by one or more jet engines', 'name': 'jet_plane'}, {'frequency': 'c', 'id': 608, 'synset': 'jewelry.n.01', 'synonyms': ['jewelry', 'jewellery'], 'def': 'an adornment (as a bracelet or ring or necklace) made of precious metals and set with gems (or imitation gems)', 'name': 'jewelry'}, {'frequency': 'r', 'id': 609, 'synset': 'joystick.n.02', 'synonyms': ['joystick'], 'def': 'a control device for computers consisting of a vertical handle that can move freely in two directions', 'name': 'joystick'}, {'frequency': 'r', 'id': 610, 'synset': 'jump_suit.n.01', 'synonyms': ['jumpsuit'], 'def': "one-piece garment fashioned after a parachutist's uniform", 'name': 'jumpsuit'}, {'frequency': 'c', 'id': 611, 'synset': 'kayak.n.01', 'synonyms': ['kayak'], 'def': 'a small canoe consisting of a light frame made watertight with animal skins', 'name': 'kayak'}, {'frequency': 'r', 'id': 612, 'synset': 'keg.n.02', 'synonyms': ['keg'], 'def': 'small cask or barrel', 'name': 'keg'}, {'frequency': 'r', 'id': 613, 'synset': 'kennel.n.01', 'synonyms': ['kennel', 'doghouse'], 'def': 'outbuilding that serves as a shelter for a dog', 'name': 'kennel'}, {'frequency': 'c', 'id': 614, 'synset': 'kettle.n.01', 'synonyms': ['kettle', 'boiler'], 'def': 'a metal pot for stewing or boiling; usually has a lid', 'name': 'kettle'}, {'frequency': 'f', 'id': 615, 'synset': 'key.n.01', 'synonyms': ['key'], 'def': 'metal instrument used to unlock a lock', 'name': 'key'}, {'frequency': 'r', 'id': 616, 'synset': 'keycard.n.01', 'synonyms': ['keycard'], 'def': 'a plastic card used to gain access typically to a door', 'name': 'keycard'}, {'frequency': 'r', 'id': 617, 'synset': 'kilt.n.01', 'synonyms': ['kilt'], 'def': 'a knee-length pleated tartan skirt worn by men as part of the traditional dress in the Highlands of northern Scotland', 'name': 'kilt'}, {'frequency': 'c', 'id': 618, 'synset': 'kimono.n.01', 'synonyms': ['kimono'], 'def': 'a loose robe; imitated from robes originally worn by Japanese', 'name': 'kimono'}, {'frequency': 'f', 'id': 619, 'synset': 'kitchen_sink.n.01', 'synonyms': ['kitchen_sink'], 'def': 'a sink in a kitchen', 'name': 'kitchen_sink'}, {'frequency': 'c', 'id': 620, 'synset': 'kitchen_table.n.01', 'synonyms': ['kitchen_table'], 'def': 'a table in the kitchen', 'name': 'kitchen_table'}, {'frequency': 'f', 'id': 621, 'synset': 'kite.n.03', 'synonyms': ['kite'], 'def': 'plaything consisting of a light frame covered with tissue paper; flown in wind at end of a string', 'name': 'kite'}, {'frequency': 'c', 'id': 622, 'synset': 'kitten.n.01', 'synonyms': ['kitten', 'kitty'], 'def': 'young domestic cat', 'name': 'kitten'}, {'frequency': 'c', 'id': 623, 'synset': 'kiwi.n.03', 'synonyms': ['kiwi_fruit'], 'def': 'fuzzy brown egg-shaped fruit with slightly tart green flesh', 'name': 'kiwi_fruit'}, {'frequency': 'f', 'id': 624, 'synset': 'knee_pad.n.01', 'synonyms': ['knee_pad'], 'def': 'protective garment consisting of a pad worn by football or baseball or hockey players', 'name': 'knee_pad'}, {'frequency': 'f', 'id': 625, 'synset': 'knife.n.01', 'synonyms': ['knife'], 'def': 'tool with a blade and point used as a cutting instrument', 'name': 'knife'}, {'frequency': 'r', 'id': 626, 'synset': 'knight.n.02', 'synonyms': ['knight_(chess_piece)', 'horse_(chess_piece)'], 'def': 'a chess game piece shaped to resemble the head of a horse', 'name': 'knight_(chess_piece)'}, {'frequency': 'r', 'id': 627, 'synset': 'knitting_needle.n.01', 'synonyms': ['knitting_needle'], 'def': 'needle consisting of a slender rod with pointed ends; usually used in pairs', 'name': 'knitting_needle'}, {'frequency': 'f', 'id': 628, 'synset': 'knob.n.02', 'synonyms': ['knob'], 'def': 'a round handle often found on a door', 'name': 'knob'}, {'frequency': 'r', 'id': 629, 'synset': 'knocker.n.05', 'synonyms': ['knocker_(on_a_door)', 'doorknocker'], 'def': 'a device (usually metal and ornamental) attached by a hinge to a door', 'name': 'knocker_(on_a_door)'}, {'frequency': 'r', 'id': 630, 'synset': 'koala.n.01', 'synonyms': ['koala', 'koala_bear'], 'def': 'sluggish tailless Australian marsupial with grey furry ears and coat', 'name': 'koala'}, {'frequency': 'r', 'id': 631, 'synset': 'lab_coat.n.01', 'synonyms': ['lab_coat', 'laboratory_coat'], 'def': 'a light coat worn to protect clothing from substances used while working in a laboratory', 'name': 'lab_coat'}, {'frequency': 'f', 'id': 632, 'synset': 'ladder.n.01', 'synonyms': ['ladder'], 'def': 'steps consisting of two parallel members connected by rungs', 'name': 'ladder'}, {'frequency': 'c', 'id': 633, 'synset': 'ladle.n.01', 'synonyms': ['ladle'], 'def': 'a spoon-shaped vessel with a long handle frequently used to transfer liquids', 'name': 'ladle'}, {'frequency': 'r', 'id': 634, 'synset': 'ladybug.n.01', 'synonyms': ['ladybug', 'ladybeetle', 'ladybird_beetle'], 'def': 'small round bright-colored and spotted beetle, typically red and black', 'name': 'ladybug'}, {'frequency': 'c', 'id': 635, 'synset': 'lamb.n.01', 'synonyms': ['lamb_(animal)'], 'def': 'young sheep', 'name': 'lamb_(animal)'}, {'frequency': 'r', 'id': 636, 'synset': 'lamb_chop.n.01', 'synonyms': ['lamb-chop', 'lambchop'], 'def': 'chop cut from a lamb', 'name': 'lamb-chop'}, {'frequency': 'f', 'id': 637, 'synset': 'lamp.n.02', 'synonyms': ['lamp'], 'def': 'a piece of furniture holding one or more electric light bulbs', 'name': 'lamp'}, {'frequency': 'f', 'id': 638, 'synset': 'lamppost.n.01', 'synonyms': ['lamppost'], 'def': 'a metal post supporting an outdoor lamp (such as a streetlight)', 'name': 'lamppost'}, {'frequency': 'f', 'id': 639, 'synset': 'lampshade.n.01', 'synonyms': ['lampshade'], 'def': 'a protective ornamental shade used to screen a light bulb from direct view', 'name': 'lampshade'}, {'frequency': 'c', 'id': 640, 'synset': 'lantern.n.01', 'synonyms': ['lantern'], 'def': 'light in a transparent protective case', 'name': 'lantern'}, {'frequency': 'f', 'id': 641, 'synset': 'lanyard.n.02', 'synonyms': ['lanyard', 'laniard'], 'def': 'a cord worn around the neck to hold a knife or whistle, etc.', 'name': 'lanyard'}, {'frequency': 'f', 'id': 642, 'synset': 'laptop.n.01', 'synonyms': ['laptop_computer', 'notebook_computer'], 'def': 'a portable computer small enough to use in your lap', 'name': 'laptop_computer'}, {'frequency': 'r', 'id': 643, 'synset': 'lasagna.n.01', 'synonyms': ['lasagna', 'lasagne'], 'def': 'baked dish of layers of lasagna pasta with sauce and cheese and meat or vegetables', 'name': 'lasagna'}, {'frequency': 'c', 'id': 644, 'synset': 'latch.n.02', 'synonyms': ['latch'], 'def': 'a bar that can be lowered or slid into a groove to fasten a door or gate', 'name': 'latch'}, {'frequency': 'r', 'id': 645, 'synset': 'lawn_mower.n.01', 'synonyms': ['lawn_mower'], 'def': 'garden tool for mowing grass on lawns', 'name': 'lawn_mower'}, {'frequency': 'r', 'id': 646, 'synset': 'leather.n.01', 'synonyms': ['leather'], 'def': 'an animal skin made smooth and flexible by removing the hair and then tanning', 'name': 'leather'}, {'frequency': 'c', 'id': 647, 'synset': 'legging.n.01', 'synonyms': ['legging_(clothing)', 'leging_(clothing)', 'leg_covering'], 'def': 'a garment covering the leg (usually extending from the knee to the ankle)', 'name': 'legging_(clothing)'}, {'frequency': 'c', 'id': 648, 'synset': 'lego.n.01', 'synonyms': ['Lego', 'Lego_set'], 'def': "a child's plastic construction set for making models from blocks", 'name': 'Lego'}, {'frequency': 'f', 'id': 649, 'synset': 'lemon.n.01', 'synonyms': ['lemon'], 'def': 'yellow oval fruit with juicy acidic flesh', 'name': 'lemon'}, {'frequency': 'r', 'id': 650, 'synset': 'lemonade.n.01', 'synonyms': ['lemonade'], 'def': 'sweetened beverage of diluted lemon juice', 'name': 'lemonade'}, {'frequency': 'f', 'id': 651, 'synset': 'lettuce.n.02', 'synonyms': ['lettuce'], 'def': 'leafy plant commonly eaten in salad or on sandwiches', 'name': 'lettuce'}, {'frequency': 'f', 'id': 652, 'synset': 'license_plate.n.01', 'synonyms': ['license_plate', 'numberplate'], 'def': "a plate mounted on the front and back of car and bearing the car's registration number", 'name': 'license_plate'}, {'frequency': 'f', 'id': 653, 'synset': 'life_buoy.n.01', 'synonyms': ['life_buoy', 'lifesaver', 'life_belt', 'life_ring'], 'def': 'a ring-shaped life preserver used to prevent drowning (NOT a life-jacket or vest)', 'name': 'life_buoy'}, {'frequency': 'f', 'id': 654, 'synset': 'life_jacket.n.01', 'synonyms': ['life_jacket', 'life_vest'], 'def': 'life preserver consisting of a sleeveless jacket of buoyant or inflatable design', 'name': 'life_jacket'}, {'frequency': 'f', 'id': 655, 'synset': 'light_bulb.n.01', 'synonyms': ['lightbulb'], 'def': 'glass bulb or tube shaped electric device that emits light (DO NOT MARK LAMPS AS A WHOLE)', 'name': 'lightbulb'}, {'frequency': 'r', 'id': 656, 'synset': 'lightning_rod.n.02', 'synonyms': ['lightning_rod', 'lightning_conductor'], 'def': 'a metallic conductor that is attached to a high point and leads to the ground', 'name': 'lightning_rod'}, {'frequency': 'c', 'id': 657, 'synset': 'lime.n.06', 'synonyms': ['lime'], 'def': 'the green acidic fruit of any of various lime trees', 'name': 'lime'}, {'frequency': 'r', 'id': 658, 'synset': 'limousine.n.01', 'synonyms': ['limousine'], 'def': 'long luxurious car; usually driven by a chauffeur', 'name': 'limousine'}, {'frequency': 'r', 'id': 659, 'synset': 'linen.n.02', 'synonyms': ['linen_paper'], 'def': 'a high-quality paper made of linen fibers or with a linen finish', 'name': 'linen_paper'}, {'frequency': 'c', 'id': 660, 'synset': 'lion.n.01', 'synonyms': ['lion'], 'def': 'large gregarious predatory cat of Africa and India', 'name': 'lion'}, {'frequency': 'c', 'id': 661, 'synset': 'lip_balm.n.01', 'synonyms': ['lip_balm'], 'def': 'a balm applied to the lips', 'name': 'lip_balm'}, {'frequency': 'c', 'id': 662, 'synset': 'lipstick.n.01', 'synonyms': ['lipstick', 'lip_rouge'], 'def': 'makeup that is used to color the lips', 'name': 'lipstick'}, {'frequency': 'r', 'id': 663, 'synset': 'liquor.n.01', 'synonyms': ['liquor', 'spirits', 'hard_liquor', 'liqueur', 'cordial'], 'def': 'an alcoholic beverage that is distilled rather than fermented', 'name': 'liquor'}, {'frequency': 'r', 'id': 664, 'synset': 'lizard.n.01', 'synonyms': ['lizard'], 'def': 'a reptile with usually two pairs of legs and a tapering tail', 'name': 'lizard'}, {'frequency': 'r', 'id': 665, 'synset': 'loafer.n.02', 'synonyms': ['Loafer_(type_of_shoe)'], 'def': 'a low leather step-in shoe', 'name': 'Loafer_(type_of_shoe)'}, {'frequency': 'f', 'id': 666, 'synset': 'log.n.01', 'synonyms': ['log'], 'def': 'a segment of the trunk of a tree when stripped of branches', 'name': 'log'}, {'frequency': 'c', 'id': 667, 'synset': 'lollipop.n.02', 'synonyms': ['lollipop'], 'def': 'hard candy on a stick', 'name': 'lollipop'}, {'frequency': 'c', 'id': 668, 'synset': 'lotion.n.01', 'synonyms': ['lotion'], 'def': 'any of various cosmetic preparations that are applied to the skin', 'name': 'lotion'}, {'frequency': 'f', 'id': 669, 'synset': 'loudspeaker.n.01', 'synonyms': ['speaker_(stero_equipment)'], 'def': 'electronic device that produces sound often as part of a stereo system', 'name': 'speaker_(stero_equipment)'}, {'frequency': 'c', 'id': 670, 'synset': 'love_seat.n.01', 'synonyms': ['loveseat'], 'def': 'small sofa that seats two people', 'name': 'loveseat'}, {'frequency': 'r', 'id': 671, 'synset': 'machine_gun.n.01', 'synonyms': ['machine_gun'], 'def': 'a rapidly firing automatic gun', 'name': 'machine_gun'}, {'frequency': 'f', 'id': 672, 'synset': 'magazine.n.02', 'synonyms': ['magazine'], 'def': 'a paperback periodic publication', 'name': 'magazine'}, {'frequency': 'f', 'id': 673, 'synset': 'magnet.n.01', 'synonyms': ['magnet'], 'def': 'a device that attracts iron and produces a magnetic field', 'name': 'magnet'}, {'frequency': 'r', 'id': 674, 'synset': 'mail_slot.n.01', 'synonyms': ['mail_slot'], 'def': 'a slot (usually in a door) through which mail can be delivered', 'name': 'mail_slot'}, {'frequency': 'c', 'id': 675, 'synset': 'mailbox.n.01', 'synonyms': ['mailbox_(at_home)', 'letter_box_(at_home)'], 'def': 'a private box for delivery of mail', 'name': 'mailbox_(at_home)'}, {'frequency': 'r', 'id': 676, 'synset': 'mallet.n.01', 'synonyms': ['mallet'], 'def': 'a sports implement with a long handle and a hammer-like head used to hit a ball', 'name': 'mallet'}, {'frequency': 'r', 'id': 677, 'synset': 'mammoth.n.01', 'synonyms': ['mammoth'], 'def': 'any of numerous extinct elephants widely distributed in the Pleistocene', 'name': 'mammoth'}, {'frequency': 'c', 'id': 678, 'synset': 'mandarin.n.05', 'synonyms': ['mandarin_orange'], 'def': 'a somewhat flat reddish-orange loose skinned citrus of China', 'name': 'mandarin_orange'}, {'frequency': 'c', 'id': 679, 'synset': 'manger.n.01', 'synonyms': ['manger', 'trough'], 'def': 'a container (usually in a barn or stable) from which cattle or horses feed', 'name': 'manger'}, {'frequency': 'f', 'id': 680, 'synset': 'manhole.n.01', 'synonyms': ['manhole'], 'def': 'a hole (usually with a flush cover) through which a person can gain access to an underground structure', 'name': 'manhole'}, {'frequency': 'c', 'id': 681, 'synset': 'map.n.01', 'synonyms': ['map'], 'def': "a diagrammatic representation of the earth's surface (or part of it)", 'name': 'map'}, {'frequency': 'c', 'id': 682, 'synset': 'marker.n.03', 'synonyms': ['marker'], 'def': 'a writing implement for making a mark', 'name': 'marker'}, {'frequency': 'r', 'id': 683, 'synset': 'martini.n.01', 'synonyms': ['martini'], 'def': 'a cocktail made of gin (or vodka) with dry vermouth', 'name': 'martini'}, {'frequency': 'r', 'id': 684, 'synset': 'mascot.n.01', 'synonyms': ['mascot'], 'def': 'a person or animal that is adopted by a team or other group as a symbolic figure', 'name': 'mascot'}, {'frequency': 'c', 'id': 685, 'synset': 'mashed_potato.n.01', 'synonyms': ['mashed_potato'], 'def': 'potato that has been peeled and boiled and then mashed', 'name': 'mashed_potato'}, {'frequency': 'r', 'id': 686, 'synset': 'masher.n.02', 'synonyms': ['masher'], 'def': 'a kitchen utensil used for mashing (e.g. potatoes)', 'name': 'masher'}, {'frequency': 'f', 'id': 687, 'synset': 'mask.n.04', 'synonyms': ['mask', 'facemask'], 'def': 'a protective covering worn over the face', 'name': 'mask'}, {'frequency': 'f', 'id': 688, 'synset': 'mast.n.01', 'synonyms': ['mast'], 'def': 'a vertical spar for supporting sails', 'name': 'mast'}, {'frequency': 'c', 'id': 689, 'synset': 'mat.n.03', 'synonyms': ['mat_(gym_equipment)', 'gym_mat'], 'def': 'sports equipment consisting of a piece of thick padding on the floor for gymnastics', 'name': 'mat_(gym_equipment)'}, {'frequency': 'r', 'id': 690, 'synset': 'matchbox.n.01', 'synonyms': ['matchbox'], 'def': 'a box for holding matches', 'name': 'matchbox'}, {'frequency': 'f', 'id': 691, 'synset': 'mattress.n.01', 'synonyms': ['mattress'], 'def': 'a thick pad filled with resilient material used as a bed or part of a bed', 'name': 'mattress'}, {'frequency': 'c', 'id': 692, 'synset': 'measuring_cup.n.01', 'synonyms': ['measuring_cup'], 'def': 'graduated cup used to measure liquid or granular ingredients', 'name': 'measuring_cup'}, {'frequency': 'c', 'id': 693, 'synset': 'measuring_stick.n.01', 'synonyms': ['measuring_stick', 'ruler_(measuring_stick)', 'measuring_rod'], 'def': 'measuring instrument having a sequence of marks at regular intervals', 'name': 'measuring_stick'}, {'frequency': 'c', 'id': 694, 'synset': 'meatball.n.01', 'synonyms': ['meatball'], 'def': 'ground meat formed into a ball and fried or simmered in broth', 'name': 'meatball'}, {'frequency': 'c', 'id': 695, 'synset': 'medicine.n.02', 'synonyms': ['medicine'], 'def': 'something that treats or prevents or alleviates the symptoms of disease', 'name': 'medicine'}, {'frequency': 'r', 'id': 696, 'synset': 'melon.n.01', 'synonyms': ['melon'], 'def': 'fruit of the gourd family having a hard rind and sweet juicy flesh', 'name': 'melon'}, {'frequency': 'f', 'id': 697, 'synset': 'microphone.n.01', 'synonyms': ['microphone'], 'def': 'device for converting sound waves into electrical energy', 'name': 'microphone'}, {'frequency': 'r', 'id': 698, 'synset': 'microscope.n.01', 'synonyms': ['microscope'], 'def': 'magnifier of the image of small objects', 'name': 'microscope'}, {'frequency': 'f', 'id': 699, 'synset': 'microwave.n.02', 'synonyms': ['microwave_oven'], 'def': 'kitchen appliance that cooks food by passing an electromagnetic wave through it', 'name': 'microwave_oven'}, {'frequency': 'r', 'id': 700, 'synset': 'milestone.n.01', 'synonyms': ['milestone', 'milepost'], 'def': 'stone post at side of a road to show distances', 'name': 'milestone'}, {'frequency': 'c', 'id': 701, 'synset': 'milk.n.01', 'synonyms': ['milk'], 'def': 'a white nutritious liquid secreted by mammals and used as food by human beings', 'name': 'milk'}, {'frequency': 'f', 'id': 702, 'synset': 'minivan.n.01', 'synonyms': ['minivan'], 'def': 'a small box-shaped passenger van', 'name': 'minivan'}, {'frequency': 'r', 'id': 703, 'synset': 'mint.n.05', 'synonyms': ['mint_candy'], 'def': 'a candy that is flavored with a mint oil', 'name': 'mint_candy'}, {'frequency': 'f', 'id': 704, 'synset': 'mirror.n.01', 'synonyms': ['mirror'], 'def': 'polished surface that forms images by reflecting light', 'name': 'mirror'}, {'frequency': 'c', 'id': 705, 'synset': 'mitten.n.01', 'synonyms': ['mitten'], 'def': 'glove that encases the thumb separately and the other four fingers together', 'name': 'mitten'}, {'frequency': 'c', 'id': 706, 'synset': 'mixer.n.04', 'synonyms': ['mixer_(kitchen_tool)', 'stand_mixer'], 'def': 'a kitchen utensil that is used for mixing foods', 'name': 'mixer_(kitchen_tool)'}, {'frequency': 'c', 'id': 707, 'synset': 'money.n.03', 'synonyms': ['money'], 'def': 'the official currency issued by a government or national bank', 'name': 'money'}, {'frequency': 'f', 'id': 708, 'synset': 'monitor.n.04', 'synonyms': ['monitor_(computer_equipment) computer_monitor'], 'def': 'a computer monitor', 'name': 'monitor_(computer_equipment) computer_monitor'}, {'frequency': 'c', 'id': 709, 'synset': 'monkey.n.01', 'synonyms': ['monkey'], 'def': 'any of various long-tailed primates', 'name': 'monkey'}, {'frequency': 'f', 'id': 710, 'synset': 'motor.n.01', 'synonyms': ['motor'], 'def': 'machine that converts other forms of energy into mechanical energy and so imparts motion', 'name': 'motor'}, {'frequency': 'f', 'id': 711, 'synset': 'motor_scooter.n.01', 'synonyms': ['motor_scooter', 'scooter'], 'def': 'a wheeled vehicle with small wheels and a low-powered engine', 'name': 'motor_scooter'}, {'frequency': 'r', 'id': 712, 'synset': 'motor_vehicle.n.01', 'synonyms': ['motor_vehicle', 'automotive_vehicle'], 'def': 'a self-propelled wheeled vehicle that does not run on rails', 'name': 'motor_vehicle'}, {'frequency': 'r', 'id': 713, 'synset': 'motorboat.n.01', 'synonyms': ['motorboat', 'powerboat'], 'def': 'a boat propelled by an internal-combustion engine', 'name': 'motorboat'}, {'frequency': 'f', 'id': 714, 'synset': 'motorcycle.n.01', 'synonyms': ['motorcycle'], 'def': 'a motor vehicle with two wheels and a strong frame', 'name': 'motorcycle'}, {'frequency': 'f', 'id': 715, 'synset': 'mound.n.01', 'synonyms': ['mound_(baseball)', "pitcher's_mound"], 'def': '(baseball) the slight elevation on which the pitcher stands', 'name': 'mound_(baseball)'}, {'frequency': 'r', 'id': 716, 'synset': 'mouse.n.01', 'synonyms': ['mouse_(animal_rodent)'], 'def': 'a small rodent with pointed snouts and small ears on elongated bodies with slender usually hairless tails', 'name': 'mouse_(animal_rodent)'}, {'frequency': 'f', 'id': 717, 'synset': 'mouse.n.04', 'synonyms': ['mouse_(computer_equipment)', 'computer_mouse'], 'def': 'a computer input device that controls an on-screen pointer', 'name': 'mouse_(computer_equipment)'}, {'frequency': 'f', 'id': 718, 'synset': 'mousepad.n.01', 'synonyms': ['mousepad'], 'def': 'a small portable pad that provides an operating surface for a computer mouse', 'name': 'mousepad'}, {'frequency': 'c', 'id': 719, 'synset': 'muffin.n.01', 'synonyms': ['muffin'], 'def': 'a sweet quick bread baked in a cup-shaped pan', 'name': 'muffin'}, {'frequency': 'f', 'id': 720, 'synset': 'mug.n.04', 'synonyms': ['mug'], 'def': 'with handle and usually cylindrical', 'name': 'mug'}, {'frequency': 'f', 'id': 721, 'synset': 'mushroom.n.02', 'synonyms': ['mushroom'], 'def': 'a common mushroom', 'name': 'mushroom'}, {'frequency': 'r', 'id': 722, 'synset': 'music_stool.n.01', 'synonyms': ['music_stool', 'piano_stool'], 'def': 'a stool for piano players; usually adjustable in height', 'name': 'music_stool'}, {'frequency': 'r', 'id': 723, 'synset': 'musical_instrument.n.01', 'synonyms': ['musical_instrument', 'instrument_(musical)'], 'def': 'any of various devices or contrivances that can be used to produce musical tones or sounds', 'name': 'musical_instrument'}, {'frequency': 'r', 'id': 724, 'synset': 'nailfile.n.01', 'synonyms': ['nailfile'], 'def': 'a small flat file for shaping the nails', 'name': 'nailfile'}, {'frequency': 'r', 'id': 725, 'synset': 'nameplate.n.01', 'synonyms': ['nameplate'], 'def': 'a plate bearing a name', 'name': 'nameplate'}, {'frequency': 'f', 'id': 726, 'synset': 'napkin.n.01', 'synonyms': ['napkin', 'table_napkin', 'serviette'], 'def': 'a small piece of table linen or paper that is used to wipe the mouth and to cover the lap in order to protect clothing', 'name': 'napkin'}, {'frequency': 'r', 'id': 727, 'synset': 'neckerchief.n.01', 'synonyms': ['neckerchief'], 'def': 'a kerchief worn around the neck', 'name': 'neckerchief'}, {'frequency': 'f', 'id': 728, 'synset': 'necklace.n.01', 'synonyms': ['necklace'], 'def': 'jewelry consisting of a cord or chain (often bearing gems) worn about the neck as an ornament', 'name': 'necklace'}, {'frequency': 'f', 'id': 729, 'synset': 'necktie.n.01', 'synonyms': ['necktie', 'tie_(necktie)'], 'def': 'neckwear consisting of a long narrow piece of material worn under a collar and tied in knot at the front', 'name': 'necktie'}, {'frequency': 'r', 'id': 730, 'synset': 'needle.n.03', 'synonyms': ['needle'], 'def': 'a sharp pointed implement (usually metal)', 'name': 'needle'}, {'frequency': 'c', 'id': 731, 'synset': 'nest.n.01', 'synonyms': ['nest'], 'def': 'a structure in which animals lay eggs or give birth to their young', 'name': 'nest'}, {'frequency': 'r', 'id': 732, 'synset': 'newsstand.n.01', 'synonyms': ['newsstand'], 'def': 'a stall where newspapers and other periodicals are sold', 'name': 'newsstand'}, {'frequency': 'c', 'id': 733, 'synset': 'nightwear.n.01', 'synonyms': ['nightshirt', 'nightwear', 'sleepwear', 'nightclothes'], 'def': 'garments designed to be worn in bed', 'name': 'nightshirt'}, {'frequency': 'r', 'id': 734, 'synset': 'nosebag.n.01', 'synonyms': ['nosebag_(for_animals)', 'feedbag'], 'def': 'a canvas bag that is used to feed an animal (such as a horse); covers the muzzle and fastens at the top of the head', 'name': 'nosebag_(for_animals)'}, {'frequency': 'r', 'id': 735, 'synset': 'noseband.n.01', 'synonyms': ['noseband_(for_animals)', 'nosepiece_(for_animals)'], 'def': "a strap that is the part of a bridle that goes over the animal's nose", 'name': 'noseband_(for_animals)'}, {'frequency': 'f', 'id': 736, 'synset': 'notebook.n.01', 'synonyms': ['notebook'], 'def': 'a book with blank pages for recording notes or memoranda', 'name': 'notebook'}, {'frequency': 'c', 'id': 737, 'synset': 'notepad.n.01', 'synonyms': ['notepad'], 'def': 'a pad of paper for keeping notes', 'name': 'notepad'}, {'frequency': 'c', 'id': 738, 'synset': 'nut.n.03', 'synonyms': ['nut'], 'def': 'a small metal block (usually square or hexagonal) with internal screw thread to be fitted onto a bolt', 'name': 'nut'}, {'frequency': 'r', 'id': 739, 'synset': 'nutcracker.n.01', 'synonyms': ['nutcracker'], 'def': 'a hand tool used to crack nuts open', 'name': 'nutcracker'}, {'frequency': 'c', 'id': 740, 'synset': 'oar.n.01', 'synonyms': ['oar'], 'def': 'an implement used to propel or steer a boat', 'name': 'oar'}, {'frequency': 'r', 'id': 741, 'synset': 'octopus.n.01', 'synonyms': ['octopus_(food)'], 'def': 'tentacles of octopus prepared as food', 'name': 'octopus_(food)'}, {'frequency': 'r', 'id': 742, 'synset': 'octopus.n.02', 'synonyms': ['octopus_(animal)'], 'def': 'bottom-living cephalopod having a soft oval body with eight long tentacles', 'name': 'octopus_(animal)'}, {'frequency': 'c', 'id': 743, 'synset': 'oil_lamp.n.01', 'synonyms': ['oil_lamp', 'kerosene_lamp', 'kerosine_lamp'], 'def': 'a lamp that burns oil (as kerosine) for light', 'name': 'oil_lamp'}, {'frequency': 'c', 'id': 744, 'synset': 'olive_oil.n.01', 'synonyms': ['olive_oil'], 'def': 'oil from olives', 'name': 'olive_oil'}, {'frequency': 'r', 'id': 745, 'synset': 'omelet.n.01', 'synonyms': ['omelet', 'omelette'], 'def': 'beaten eggs cooked until just set; may be folded around e.g. ham or cheese or jelly', 'name': 'omelet'}, {'frequency': 'f', 'id': 746, 'synset': 'onion.n.01', 'synonyms': ['onion'], 'def': 'the bulb of an onion plant', 'name': 'onion'}, {'frequency': 'f', 'id': 747, 'synset': 'orange.n.01', 'synonyms': ['orange_(fruit)'], 'def': 'orange (FRUIT of an orange tree)', 'name': 'orange_(fruit)'}, {'frequency': 'c', 'id': 748, 'synset': 'orange_juice.n.01', 'synonyms': ['orange_juice'], 'def': 'bottled or freshly squeezed juice of oranges', 'name': 'orange_juice'}, {'frequency': 'r', 'id': 749, 'synset': 'oregano.n.01', 'synonyms': ['oregano', 'marjoram'], 'def': 'aromatic Eurasian perennial herb used in cooking and baking', 'name': 'oregano'}, {'frequency': 'c', 'id': 750, 'synset': 'ostrich.n.02', 'synonyms': ['ostrich'], 'def': 'fast-running African flightless bird with two-toed feet; largest living bird', 'name': 'ostrich'}, {'frequency': 'c', 'id': 751, 'synset': 'ottoman.n.03', 'synonyms': ['ottoman', 'pouf', 'pouffe', 'hassock'], 'def': 'thick cushion used as a seat', 'name': 'ottoman'}, {'frequency': 'c', 'id': 752, 'synset': 'overall.n.01', 'synonyms': ['overalls_(clothing)'], 'def': 'work clothing consisting of denim trousers usually with a bib and shoulder straps', 'name': 'overalls_(clothing)'}, {'frequency': 'c', 'id': 753, 'synset': 'owl.n.01', 'synonyms': ['owl'], 'def': 'nocturnal bird of prey with hawk-like beak and claws and large head with front-facing eyes', 'name': 'owl'}, {'frequency': 'c', 'id': 754, 'synset': 'packet.n.03', 'synonyms': ['packet'], 'def': 'a small package or bundle', 'name': 'packet'}, {'frequency': 'r', 'id': 755, 'synset': 'pad.n.03', 'synonyms': ['inkpad', 'inking_pad', 'stamp_pad'], 'def': 'absorbent material saturated with ink used to transfer ink evenly to a rubber stamp', 'name': 'inkpad'}, {'frequency': 'c', 'id': 756, 'synset': 'pad.n.04', 'synonyms': ['pad'], 'def': 'a flat mass of soft material used for protection, stuffing, or comfort', 'name': 'pad'}, {'frequency': 'c', 'id': 757, 'synset': 'paddle.n.04', 'synonyms': ['paddle', 'boat_paddle'], 'def': 'a short light oar used without an oarlock to propel a canoe or small boat', 'name': 'paddle'}, {'frequency': 'c', 'id': 758, 'synset': 'padlock.n.01', 'synonyms': ['padlock'], 'def': 'a detachable, portable lock', 'name': 'padlock'}, {'frequency': 'r', 'id': 759, 'synset': 'paintbox.n.01', 'synonyms': ['paintbox'], 'def': "a box containing a collection of cubes or tubes of artists' paint", 'name': 'paintbox'}, {'frequency': 'c', 'id': 760, 'synset': 'paintbrush.n.01', 'synonyms': ['paintbrush'], 'def': 'a brush used as an applicator to apply paint', 'name': 'paintbrush'}, {'frequency': 'f', 'id': 761, 'synset': 'painting.n.01', 'synonyms': ['painting'], 'def': 'graphic art consisting of an artistic composition made by applying paints to a surface', 'name': 'painting'}, {'frequency': 'c', 'id': 762, 'synset': 'pajama.n.02', 'synonyms': ['pajamas', 'pyjamas'], 'def': 'loose-fitting nightclothes worn for sleeping or lounging', 'name': 'pajamas'}, {'frequency': 'c', 'id': 763, 'synset': 'palette.n.02', 'synonyms': ['palette', 'pallet'], 'def': 'board that provides a flat surface on which artists mix paints and the range of colors used', 'name': 'palette'}, {'frequency': 'f', 'id': 764, 'synset': 'pan.n.01', 'synonyms': ['pan_(for_cooking)', 'cooking_pan'], 'def': 'cooking utensil consisting of a wide metal vessel', 'name': 'pan_(for_cooking)'}, {'frequency': 'r', 'id': 765, 'synset': 'pan.n.03', 'synonyms': ['pan_(metal_container)'], 'def': 'shallow container made of metal', 'name': 'pan_(metal_container)'}, {'frequency': 'c', 'id': 766, 'synset': 'pancake.n.01', 'synonyms': ['pancake'], 'def': 'a flat cake of thin batter fried on both sides on a griddle', 'name': 'pancake'}, {'frequency': 'r', 'id': 767, 'synset': 'pantyhose.n.01', 'synonyms': ['pantyhose'], 'def': "a woman's tights consisting of underpants and stockings", 'name': 'pantyhose'}, {'frequency': 'r', 'id': 768, 'synset': 'papaya.n.02', 'synonyms': ['papaya'], 'def': 'large oval melon-like tropical fruit with yellowish flesh', 'name': 'papaya'}, {'frequency': 'r', 'id': 769, 'synset': 'paper_clip.n.01', 'synonyms': ['paperclip'], 'def': 'a wire or plastic clip for holding sheets of paper together', 'name': 'paperclip'}, {'frequency': 'f', 'id': 770, 'synset': 'paper_plate.n.01', 'synonyms': ['paper_plate'], 'def': 'a disposable plate made of cardboard', 'name': 'paper_plate'}, {'frequency': 'f', 'id': 771, 'synset': 'paper_towel.n.01', 'synonyms': ['paper_towel'], 'def': 'a disposable towel made of absorbent paper', 'name': 'paper_towel'}, {'frequency': 'r', 'id': 772, 'synset': 'paperback_book.n.01', 'synonyms': ['paperback_book', 'paper-back_book', 'softback_book', 'soft-cover_book'], 'def': 'a book with paper covers', 'name': 'paperback_book'}, {'frequency': 'r', 'id': 773, 'synset': 'paperweight.n.01', 'synonyms': ['paperweight'], 'def': 'a weight used to hold down a stack of papers', 'name': 'paperweight'}, {'frequency': 'c', 'id': 774, 'synset': 'parachute.n.01', 'synonyms': ['parachute'], 'def': 'rescue equipment consisting of a device that fills with air and retards your fall', 'name': 'parachute'}, {'frequency': 'r', 'id': 775, 'synset': 'parakeet.n.01', 'synonyms': ['parakeet', 'parrakeet', 'parroket', 'paraquet', 'paroquet', 'parroquet'], 'def': 'any of numerous small slender long-tailed parrots', 'name': 'parakeet'}, {'frequency': 'c', 'id': 776, 'synset': 'parasail.n.01', 'synonyms': ['parasail_(sports)'], 'def': 'parachute that will lift a person up into the air when it is towed by a motorboat or a car', 'name': 'parasail_(sports)'}, {'frequency': 'r', 'id': 777, 'synset': 'parchment.n.01', 'synonyms': ['parchment'], 'def': 'a superior paper resembling sheepskin', 'name': 'parchment'}, {'frequency': 'r', 'id': 778, 'synset': 'parka.n.01', 'synonyms': ['parka', 'anorak'], 'def': "a kind of heavy jacket (`windcheater' is a British term)", 'name': 'parka'}, {'frequency': 'f', 'id': 779, 'synset': 'parking_meter.n.01', 'synonyms': ['parking_meter'], 'def': 'a coin-operated timer located next to a parking space', 'name': 'parking_meter'}, {'frequency': 'c', 'id': 780, 'synset': 'parrot.n.01', 'synonyms': ['parrot'], 'def': 'usually brightly colored tropical birds with short hooked beaks and the ability to mimic sounds', 'name': 'parrot'}, {'frequency': 'c', 'id': 781, 'synset': 'passenger_car.n.01', 'synonyms': ['passenger_car_(part_of_a_train)', 'coach_(part_of_a_train)'], 'def': 'a railcar where passengers ride', 'name': 'passenger_car_(part_of_a_train)'}, {'frequency': 'r', 'id': 782, 'synset': 'passenger_ship.n.01', 'synonyms': ['passenger_ship'], 'def': 'a ship built to carry passengers', 'name': 'passenger_ship'}, {'frequency': 'r', 'id': 783, 'synset': 'passport.n.02', 'synonyms': ['passport'], 'def': 'a document issued by a country to a citizen allowing that person to travel abroad and re-enter the home country', 'name': 'passport'}, {'frequency': 'f', 'id': 784, 'synset': 'pastry.n.02', 'synonyms': ['pastry'], 'def': 'any of various baked foods made of dough or batter', 'name': 'pastry'}, {'frequency': 'r', 'id': 785, 'synset': 'patty.n.01', 'synonyms': ['patty_(food)'], 'def': 'small flat mass of chopped food', 'name': 'patty_(food)'}, {'frequency': 'c', 'id': 786, 'synset': 'pea.n.01', 'synonyms': ['pea_(food)'], 'def': 'seed of a pea plant used for food', 'name': 'pea_(food)'}, {'frequency': 'c', 'id': 787, 'synset': 'peach.n.03', 'synonyms': ['peach'], 'def': 'downy juicy fruit with sweet yellowish or whitish flesh', 'name': 'peach'}, {'frequency': 'c', 'id': 788, 'synset': 'peanut_butter.n.01', 'synonyms': ['peanut_butter'], 'def': 'a spread made from ground peanuts', 'name': 'peanut_butter'}, {'frequency': 'c', 'id': 789, 'synset': 'pear.n.01', 'synonyms': ['pear'], 'def': 'sweet juicy gritty-textured fruit available in many varieties', 'name': 'pear'}, {'frequency': 'r', 'id': 790, 'synset': 'peeler.n.03', 'synonyms': ['peeler_(tool_for_fruit_and_vegetables)'], 'def': 'a device for peeling vegetables or fruits', 'name': 'peeler_(tool_for_fruit_and_vegetables)'}, {'frequency': 'r', 'id': 791, 'synset': 'pegboard.n.01', 'synonyms': ['pegboard'], 'def': 'a board perforated with regularly spaced holes into which pegs can be fitted', 'name': 'pegboard'}, {'frequency': 'c', 'id': 792, 'synset': 'pelican.n.01', 'synonyms': ['pelican'], 'def': 'large long-winged warm-water seabird having a large bill with a distensible pouch for fish', 'name': 'pelican'}, {'frequency': 'f', 'id': 793, 'synset': 'pen.n.01', 'synonyms': ['pen'], 'def': 'a writing implement with a point from which ink flows', 'name': 'pen'}, {'frequency': 'c', 'id': 794, 'synset': 'pencil.n.01', 'synonyms': ['pencil'], 'def': 'a thin cylindrical pointed writing implement made of wood and graphite', 'name': 'pencil'}, {'frequency': 'r', 'id': 795, 'synset': 'pencil_box.n.01', 'synonyms': ['pencil_box', 'pencil_case'], 'def': 'a box for holding pencils', 'name': 'pencil_box'}, {'frequency': 'r', 'id': 796, 'synset': 'pencil_sharpener.n.01', 'synonyms': ['pencil_sharpener'], 'def': 'a rotary implement for sharpening the point on pencils', 'name': 'pencil_sharpener'}, {'frequency': 'r', 'id': 797, 'synset': 'pendulum.n.01', 'synonyms': ['pendulum'], 'def': 'an apparatus consisting of an object mounted so that it swings freely under the influence of gravity', 'name': 'pendulum'}, {'frequency': 'c', 'id': 798, 'synset': 'penguin.n.01', 'synonyms': ['penguin'], 'def': 'short-legged flightless birds of cold southern regions having webbed feet and wings modified as flippers', 'name': 'penguin'}, {'frequency': 'r', 'id': 799, 'synset': 'pennant.n.02', 'synonyms': ['pennant'], 'def': 'a flag longer than it is wide (and often tapering)', 'name': 'pennant'}, {'frequency': 'r', 'id': 800, 'synset': 'penny.n.02', 'synonyms': ['penny_(coin)'], 'def': 'a coin worth one-hundredth of the value of the basic unit', 'name': 'penny_(coin)'}, {'frequency': 'c', 'id': 801, 'synset': 'pepper.n.03', 'synonyms': ['pepper', 'peppercorn'], 'def': 'pungent seasoning from the berry of the common pepper plant; whole or ground', 'name': 'pepper'}, {'frequency': 'c', 'id': 802, 'synset': 'pepper_mill.n.01', 'synonyms': ['pepper_mill', 'pepper_grinder'], 'def': 'a mill for grinding pepper', 'name': 'pepper_mill'}, {'frequency': 'c', 'id': 803, 'synset': 'perfume.n.02', 'synonyms': ['perfume'], 'def': 'a toiletry that emits and diffuses a fragrant odor', 'name': 'perfume'}, {'frequency': 'r', 'id': 804, 'synset': 'persimmon.n.02', 'synonyms': ['persimmon'], 'def': 'orange fruit resembling a plum; edible when fully ripe', 'name': 'persimmon'}, {'frequency': 'f', 'id': 805, 'synset': 'person.n.01', 'synonyms': ['baby', 'child', 'boy', 'girl', 'man', 'woman', 'person', 'human'], 'def': 'a human being', 'name': 'baby'}, {'frequency': 'r', 'id': 806, 'synset': 'pet.n.01', 'synonyms': ['pet'], 'def': 'a domesticated animal kept for companionship or amusement', 'name': 'pet'}, {'frequency': 'r', 'id': 807, 'synset': 'petfood.n.01', 'synonyms': ['petfood', 'pet-food'], 'def': 'food prepared for animal pets', 'name': 'petfood'}, {'frequency': 'r', 'id': 808, 'synset': 'pew.n.01', 'synonyms': ['pew_(church_bench)', 'church_bench'], 'def': 'long bench with backs; used in church by the congregation', 'name': 'pew_(church_bench)'}, {'frequency': 'r', 'id': 809, 'synset': 'phonebook.n.01', 'synonyms': ['phonebook', 'telephone_book', 'telephone_directory'], 'def': 'a directory containing an alphabetical list of telephone subscribers and their telephone numbers', 'name': 'phonebook'}, {'frequency': 'c', 'id': 810, 'synset': 'phonograph_record.n.01', 'synonyms': ['phonograph_record', 'phonograph_recording', 'record_(phonograph_recording)'], 'def': 'sound recording consisting of a typically black disk with a continuous groove', 'name': 'phonograph_record'}, {'frequency': 'c', 'id': 811, 'synset': 'piano.n.01', 'synonyms': ['piano'], 'def': 'a keyboard instrument that is played by depressing keys that cause hammers to strike tuned strings and produce sounds', 'name': 'piano'}, {'frequency': 'f', 'id': 812, 'synset': 'pickle.n.01', 'synonyms': ['pickle'], 'def': 'vegetables (especially cucumbers) preserved in brine or vinegar', 'name': 'pickle'}, {'frequency': 'f', 'id': 813, 'synset': 'pickup.n.01', 'synonyms': ['pickup_truck'], 'def': 'a light truck with an open body and low sides and a tailboard', 'name': 'pickup_truck'}, {'frequency': 'c', 'id': 814, 'synset': 'pie.n.01', 'synonyms': ['pie'], 'def': 'dish baked in pastry-lined pan often with a pastry top', 'name': 'pie'}, {'frequency': 'c', 'id': 815, 'synset': 'pigeon.n.01', 'synonyms': ['pigeon'], 'def': 'wild and domesticated birds having a heavy body and short legs', 'name': 'pigeon'}, {'frequency': 'r', 'id': 816, 'synset': 'piggy_bank.n.01', 'synonyms': ['piggy_bank', 'penny_bank'], 'def': "a child's coin bank (often shaped like a pig)", 'name': 'piggy_bank'}, {'frequency': 'f', 'id': 817, 'synset': 'pillow.n.01', 'synonyms': ['pillow'], 'def': 'a cushion to support the head of a sleeping person', 'name': 'pillow'}, {'frequency': 'r', 'id': 818, 'synset': 'pin.n.09', 'synonyms': ['pin_(non_jewelry)'], 'def': 'a small slender (often pointed) piece of wood or metal used to support or fasten or attach things', 'name': 'pin_(non_jewelry)'}, {'frequency': 'f', 'id': 819, 'synset': 'pineapple.n.02', 'synonyms': ['pineapple'], 'def': 'large sweet fleshy tropical fruit with a tuft of stiff leaves', 'name': 'pineapple'}, {'frequency': 'c', 'id': 820, 'synset': 'pinecone.n.01', 'synonyms': ['pinecone'], 'def': 'the seed-producing cone of a pine tree', 'name': 'pinecone'}, {'frequency': 'r', 'id': 821, 'synset': 'ping-pong_ball.n.01', 'synonyms': ['ping-pong_ball'], 'def': 'light hollow ball used in playing table tennis', 'name': 'ping-pong_ball'}, {'frequency': 'r', 'id': 822, 'synset': 'pinwheel.n.03', 'synonyms': ['pinwheel'], 'def': 'a toy consisting of vanes of colored paper or plastic that is pinned to a stick and spins when it is pointed into the wind', 'name': 'pinwheel'}, {'frequency': 'r', 'id': 823, 'synset': 'pipe.n.01', 'synonyms': ['tobacco_pipe'], 'def': 'a tube with a small bowl at one end; used for smoking tobacco', 'name': 'tobacco_pipe'}, {'frequency': 'f', 'id': 824, 'synset': 'pipe.n.02', 'synonyms': ['pipe', 'piping'], 'def': 'a long tube made of metal or plastic that is used to carry water or oil or gas etc.', 'name': 'pipe'}, {'frequency': 'r', 'id': 825, 'synset': 'pistol.n.01', 'synonyms': ['pistol', 'handgun'], 'def': 'a firearm that is held and fired with one hand', 'name': 'pistol'}, {'frequency': 'r', 'id': 826, 'synset': 'pita.n.01', 'synonyms': ['pita_(bread)', 'pocket_bread'], 'def': 'usually small round bread that can open into a pocket for filling', 'name': 'pita_(bread)'}, {'frequency': 'f', 'id': 827, 'synset': 'pitcher.n.02', 'synonyms': ['pitcher_(vessel_for_liquid)', 'ewer'], 'def': 'an open vessel with a handle and a spout for pouring', 'name': 'pitcher_(vessel_for_liquid)'}, {'frequency': 'r', 'id': 828, 'synset': 'pitchfork.n.01', 'synonyms': ['pitchfork'], 'def': 'a long-handled hand tool with sharp widely spaced prongs for lifting and pitching hay', 'name': 'pitchfork'}, {'frequency': 'f', 'id': 829, 'synset': 'pizza.n.01', 'synonyms': ['pizza'], 'def': 'Italian open pie made of thin bread dough spread with a spiced mixture of e.g. tomato sauce and cheese', 'name': 'pizza'}, {'frequency': 'f', 'id': 830, 'synset': 'place_mat.n.01', 'synonyms': ['place_mat'], 'def': 'a mat placed on a table for an individual place setting', 'name': 'place_mat'}, {'frequency': 'f', 'id': 831, 'synset': 'plate.n.04', 'synonyms': ['plate'], 'def': 'dish on which food is served or from which food is eaten', 'name': 'plate'}, {'frequency': 'c', 'id': 832, 'synset': 'platter.n.01', 'synonyms': ['platter'], 'def': 'a large shallow dish used for serving food', 'name': 'platter'}, {'frequency': 'r', 'id': 833, 'synset': 'playing_card.n.01', 'synonyms': ['playing_card'], 'def': 'one of a pack of cards that are used to play card games', 'name': 'playing_card'}, {'frequency': 'r', 'id': 834, 'synset': 'playpen.n.01', 'synonyms': ['playpen'], 'def': 'a portable enclosure in which babies may be left to play', 'name': 'playpen'}, {'frequency': 'c', 'id': 835, 'synset': 'pliers.n.01', 'synonyms': ['pliers', 'plyers'], 'def': 'a gripping hand tool with two hinged arms and (usually) serrated jaws', 'name': 'pliers'}, {'frequency': 'r', 'id': 836, 'synset': 'plow.n.01', 'synonyms': ['plow_(farm_equipment)', 'plough_(farm_equipment)'], 'def': 'a farm tool having one or more heavy blades to break the soil and cut a furrow prior to sowing', 'name': 'plow_(farm_equipment)'}, {'frequency': 'r', 'id': 837, 'synset': 'pocket_watch.n.01', 'synonyms': ['pocket_watch'], 'def': 'a watch that is carried in a small watch pocket', 'name': 'pocket_watch'}, {'frequency': 'c', 'id': 838, 'synset': 'pocketknife.n.01', 'synonyms': ['pocketknife'], 'def': 'a knife with a blade that folds into the handle; suitable for carrying in the pocket', 'name': 'pocketknife'}, {'frequency': 'c', 'id': 839, 'synset': 'poker.n.01', 'synonyms': ['poker_(fire_stirring_tool)', 'stove_poker', 'fire_hook'], 'def': 'fire iron consisting of a metal rod with a handle; used to stir a fire', 'name': 'poker_(fire_stirring_tool)'}, {'frequency': 'f', 'id': 840, 'synset': 'pole.n.01', 'synonyms': ['pole', 'post'], 'def': 'a long (usually round) rod of wood or metal or plastic', 'name': 'pole'}, {'frequency': 'r', 'id': 841, 'synset': 'police_van.n.01', 'synonyms': ['police_van', 'police_wagon', 'paddy_wagon', 'patrol_wagon'], 'def': 'van used by police to transport prisoners', 'name': 'police_van'}, {'frequency': 'f', 'id': 842, 'synset': 'polo_shirt.n.01', 'synonyms': ['polo_shirt', 'sport_shirt'], 'def': 'a shirt with short sleeves designed for comfort and casual wear', 'name': 'polo_shirt'}, {'frequency': 'r', 'id': 843, 'synset': 'poncho.n.01', 'synonyms': ['poncho'], 'def': 'a blanket-like cloak with a hole in the center for the head', 'name': 'poncho'}, {'frequency': 'c', 'id': 844, 'synset': 'pony.n.05', 'synonyms': ['pony'], 'def': 'any of various breeds of small gentle horses usually less than five feet high at the shoulder', 'name': 'pony'}, {'frequency': 'r', 'id': 845, 'synset': 'pool_table.n.01', 'synonyms': ['pool_table', 'billiard_table', 'snooker_table'], 'def': 'game equipment consisting of a heavy table on which pool is played', 'name': 'pool_table'}, {'frequency': 'f', 'id': 846, 'synset': 'pop.n.02', 'synonyms': ['pop_(soda)', 'soda_(pop)', 'tonic', 'soft_drink'], 'def': 'a sweet drink containing carbonated water and flavoring', 'name': 'pop_(soda)'}, {'frequency': 'r', 'id': 847, 'synset': 'portrait.n.02', 'synonyms': ['portrait', 'portrayal'], 'def': 'any likeness of a person, in any medium', 'name': 'portrait'}, {'frequency': 'c', 'id': 848, 'synset': 'postbox.n.01', 'synonyms': ['postbox_(public)', 'mailbox_(public)'], 'def': 'public box for deposit of mail', 'name': 'postbox_(public)'}, {'frequency': 'c', 'id': 849, 'synset': 'postcard.n.01', 'synonyms': ['postcard', 'postal_card', 'mailing-card'], 'def': 'a card for sending messages by post without an envelope', 'name': 'postcard'}, {'frequency': 'f', 'id': 850, 'synset': 'poster.n.01', 'synonyms': ['poster', 'placard'], 'def': 'a sign posted in a public place as an advertisement', 'name': 'poster'}, {'frequency': 'f', 'id': 851, 'synset': 'pot.n.01', 'synonyms': ['pot'], 'def': 'metal or earthenware cooking vessel that is usually round and deep; often has a handle and lid', 'name': 'pot'}, {'frequency': 'f', 'id': 852, 'synset': 'pot.n.04', 'synonyms': ['flowerpot'], 'def': 'a container in which plants are cultivated', 'name': 'flowerpot'}, {'frequency': 'f', 'id': 853, 'synset': 'potato.n.01', 'synonyms': ['potato'], 'def': 'an edible tuber native to South America', 'name': 'potato'}, {'frequency': 'c', 'id': 854, 'synset': 'potholder.n.01', 'synonyms': ['potholder'], 'def': 'an insulated pad for holding hot pots', 'name': 'potholder'}, {'frequency': 'c', 'id': 855, 'synset': 'pottery.n.01', 'synonyms': ['pottery', 'clayware'], 'def': 'ceramic ware made from clay and baked in a kiln', 'name': 'pottery'}, {'frequency': 'c', 'id': 856, 'synset': 'pouch.n.01', 'synonyms': ['pouch'], 'def': 'a small or medium size container for holding or carrying things', 'name': 'pouch'}, {'frequency': 'r', 'id': 857, 'synset': 'power_shovel.n.01', 'synonyms': ['power_shovel', 'excavator', 'digger'], 'def': 'a machine for excavating', 'name': 'power_shovel'}, {'frequency': 'c', 'id': 858, 'synset': 'prawn.n.01', 'synonyms': ['prawn', 'shrimp'], 'def': 'any of various edible decapod crustaceans', 'name': 'prawn'}, {'frequency': 'f', 'id': 859, 'synset': 'printer.n.03', 'synonyms': ['printer', 'printing_machine'], 'def': 'a machine that prints', 'name': 'printer'}, {'frequency': 'c', 'id': 860, 'synset': 'projectile.n.01', 'synonyms': ['projectile_(weapon)', 'missile'], 'def': 'a weapon that is forcibly thrown or projected at a targets', 'name': 'projectile_(weapon)'}, {'frequency': 'c', 'id': 861, 'synset': 'projector.n.02', 'synonyms': ['projector'], 'def': 'an optical instrument that projects an enlarged image onto a screen', 'name': 'projector'}, {'frequency': 'f', 'id': 862, 'synset': 'propeller.n.01', 'synonyms': ['propeller', 'propellor'], 'def': 'a mechanical device that rotates to push against air or water', 'name': 'propeller'}, {'frequency': 'r', 'id': 863, 'synset': 'prune.n.01', 'synonyms': ['prune'], 'def': 'dried plum', 'name': 'prune'}, {'frequency': 'r', 'id': 864, 'synset': 'pudding.n.01', 'synonyms': ['pudding'], 'def': 'any of various soft thick unsweetened baked dishes', 'name': 'pudding'}, {'frequency': 'r', 'id': 865, 'synset': 'puffer.n.02', 'synonyms': ['puffer_(fish)', 'pufferfish', 'blowfish', 'globefish'], 'def': 'fishes whose elongated spiny body can inflate itself with water or air to form a globe', 'name': 'puffer_(fish)'}, {'frequency': 'r', 'id': 866, 'synset': 'puffin.n.01', 'synonyms': ['puffin'], 'def': 'seabirds having short necks and brightly colored compressed bills', 'name': 'puffin'}, {'frequency': 'r', 'id': 867, 'synset': 'pug.n.01', 'synonyms': ['pug-dog'], 'def': 'small compact smooth-coated breed of Asiatic origin having a tightly curled tail and broad flat wrinkled muzzle', 'name': 'pug-dog'}, {'frequency': 'c', 'id': 868, 'synset': 'pumpkin.n.02', 'synonyms': ['pumpkin'], 'def': 'usually large pulpy deep-yellow round fruit of the squash family maturing in late summer or early autumn', 'name': 'pumpkin'}, {'frequency': 'r', 'id': 869, 'synset': 'punch.n.03', 'synonyms': ['puncher'], 'def': 'a tool for making holes or indentations', 'name': 'puncher'}, {'frequency': 'r', 'id': 870, 'synset': 'puppet.n.01', 'synonyms': ['puppet', 'marionette'], 'def': 'a small figure of a person operated from above with strings by a puppeteer', 'name': 'puppet'}, {'frequency': 'r', 'id': 871, 'synset': 'puppy.n.01', 'synonyms': ['puppy'], 'def': 'a young dog', 'name': 'puppy'}, {'frequency': 'r', 'id': 872, 'synset': 'quesadilla.n.01', 'synonyms': ['quesadilla'], 'def': 'a tortilla that is filled with cheese and heated', 'name': 'quesadilla'}, {'frequency': 'r', 'id': 873, 'synset': 'quiche.n.02', 'synonyms': ['quiche'], 'def': 'a tart filled with rich unsweetened custard; often contains other ingredients (as cheese or ham or seafood or vegetables)', 'name': 'quiche'}, {'frequency': 'f', 'id': 874, 'synset': 'quilt.n.01', 'synonyms': ['quilt', 'comforter'], 'def': 'bedding made of two layers of cloth filled with stuffing and stitched together', 'name': 'quilt'}, {'frequency': 'c', 'id': 875, 'synset': 'rabbit.n.01', 'synonyms': ['rabbit'], 'def': 'any of various burrowing animals of the family Leporidae having long ears and short tails', 'name': 'rabbit'}, {'frequency': 'r', 'id': 876, 'synset': 'racer.n.02', 'synonyms': ['race_car', 'racing_car'], 'def': 'a fast car that competes in races', 'name': 'race_car'}, {'frequency': 'c', 'id': 877, 'synset': 'racket.n.04', 'synonyms': ['racket', 'racquet'], 'def': 'a sports implement used to strike a ball in various games', 'name': 'racket'}, {'frequency': 'r', 'id': 878, 'synset': 'radar.n.01', 'synonyms': ['radar'], 'def': 'measuring instrument in which the echo of a pulse of microwave radiation is used to detect and locate distant objects', 'name': 'radar'}, {'frequency': 'c', 'id': 879, 'synset': 'radiator.n.03', 'synonyms': ['radiator'], 'def': 'a mechanism consisting of a metal honeycomb through which hot fluids circulate', 'name': 'radiator'}, {'frequency': 'c', 'id': 880, 'synset': 'radio_receiver.n.01', 'synonyms': ['radio_receiver', 'radio_set', 'radio', 'tuner_(radio)'], 'def': 'an electronic receiver that detects and demodulates and amplifies transmitted radio signals', 'name': 'radio_receiver'}, {'frequency': 'c', 'id': 881, 'synset': 'radish.n.03', 'synonyms': ['radish', 'daikon'], 'def': 'pungent edible root of any of various cultivated radish plants', 'name': 'radish'}, {'frequency': 'c', 'id': 882, 'synset': 'raft.n.01', 'synonyms': ['raft'], 'def': 'a flat float (usually made of logs or planks) that can be used for transport or as a platform for swimmers', 'name': 'raft'}, {'frequency': 'r', 'id': 883, 'synset': 'rag_doll.n.01', 'synonyms': ['rag_doll'], 'def': 'a cloth doll that is stuffed and (usually) painted', 'name': 'rag_doll'}, {'frequency': 'c', 'id': 884, 'synset': 'raincoat.n.01', 'synonyms': ['raincoat', 'waterproof_jacket'], 'def': 'a water-resistant coat', 'name': 'raincoat'}, {'frequency': 'c', 'id': 885, 'synset': 'ram.n.05', 'synonyms': ['ram_(animal)'], 'def': 'uncastrated adult male sheep', 'name': 'ram_(animal)'}, {'frequency': 'c', 'id': 886, 'synset': 'raspberry.n.02', 'synonyms': ['raspberry'], 'def': 'red or black edible aggregate berries usually smaller than the related blackberries', 'name': 'raspberry'}, {'frequency': 'r', 'id': 887, 'synset': 'rat.n.01', 'synonyms': ['rat'], 'def': 'any of various long-tailed rodents similar to but larger than a mouse', 'name': 'rat'}, {'frequency': 'c', 'id': 888, 'synset': 'razorblade.n.01', 'synonyms': ['razorblade'], 'def': 'a blade that has very sharp edge', 'name': 'razorblade'}, {'frequency': 'c', 'id': 889, 'synset': 'reamer.n.01', 'synonyms': ['reamer_(juicer)', 'juicer', 'juice_reamer'], 'def': 'a squeezer with a conical ridged center that is used for squeezing juice from citrus fruit', 'name': 'reamer_(juicer)'}, {'frequency': 'f', 'id': 890, 'synset': 'rearview_mirror.n.01', 'synonyms': ['rearview_mirror'], 'def': 'car mirror that reflects the view out of the rear window', 'name': 'rearview_mirror'}, {'frequency': 'c', 'id': 891, 'synset': 'receipt.n.02', 'synonyms': ['receipt'], 'def': 'an acknowledgment (usually tangible) that payment has been made', 'name': 'receipt'}, {'frequency': 'c', 'id': 892, 'synset': 'recliner.n.01', 'synonyms': ['recliner', 'reclining_chair', 'lounger_(chair)'], 'def': 'an armchair whose back can be lowered and foot can be raised to allow the sitter to recline in it', 'name': 'recliner'}, {'frequency': 'r', 'id': 893, 'synset': 'record_player.n.01', 'synonyms': ['record_player', 'phonograph_(record_player)', 'turntable'], 'def': 'machine in which rotating records cause a stylus to vibrate and the vibrations are amplified acoustically or electronically', 'name': 'record_player'}, {'frequency': 'r', 'id': 894, 'synset': 'red_cabbage.n.02', 'synonyms': ['red_cabbage'], 'def': 'compact head of purplish-red leaves', 'name': 'red_cabbage'}, {'frequency': 'f', 'id': 895, 'synset': 'reflector.n.01', 'synonyms': ['reflector'], 'def': 'device that reflects light, radiation, etc.', 'name': 'reflector'}, {'frequency': 'f', 'id': 896, 'synset': 'remote_control.n.01', 'synonyms': ['remote_control'], 'def': 'a device that can be used to control a machine or apparatus from a distance', 'name': 'remote_control'}, {'frequency': 'c', 'id': 897, 'synset': 'rhinoceros.n.01', 'synonyms': ['rhinoceros'], 'def': 'massive powerful herbivorous odd-toed ungulate of southeast Asia and Africa having very thick skin and one or two horns on the snout', 'name': 'rhinoceros'}, {'frequency': 'r', 'id': 898, 'synset': 'rib.n.03', 'synonyms': ['rib_(food)'], 'def': 'cut of meat including one or more ribs', 'name': 'rib_(food)'}, {'frequency': 'r', 'id': 899, 'synset': 'rifle.n.01', 'synonyms': ['rifle'], 'def': 'a shoulder firearm with a long barrel', 'name': 'rifle'}, {'frequency': 'f', 'id': 900, 'synset': 'ring.n.08', 'synonyms': ['ring'], 'def': 'jewelry consisting of a circlet of precious metal (often set with jewels) worn on the finger', 'name': 'ring'}, {'frequency': 'r', 'id': 901, 'synset': 'river_boat.n.01', 'synonyms': ['river_boat'], 'def': 'a boat used on rivers or to ply a river', 'name': 'river_boat'}, {'frequency': 'r', 'id': 902, 'synset': 'road_map.n.02', 'synonyms': ['road_map'], 'def': '(NOT A ROAD) a MAP showing roads (for automobile travel)', 'name': 'road_map'}, {'frequency': 'c', 'id': 903, 'synset': 'robe.n.01', 'synonyms': ['robe'], 'def': 'any loose flowing garment', 'name': 'robe'}, {'frequency': 'c', 'id': 904, 'synset': 'rocking_chair.n.01', 'synonyms': ['rocking_chair'], 'def': 'a chair mounted on rockers', 'name': 'rocking_chair'}, {'frequency': 'r', 'id': 905, 'synset': 'roller_skate.n.01', 'synonyms': ['roller_skate'], 'def': 'a shoe with pairs of rollers (small hard wheels) fixed to the sole', 'name': 'roller_skate'}, {'frequency': 'r', 'id': 906, 'synset': 'rollerblade.n.01', 'synonyms': ['Rollerblade'], 'def': 'an in-line variant of a roller skate', 'name': 'Rollerblade'}, {'frequency': 'c', 'id': 907, 'synset': 'rolling_pin.n.01', 'synonyms': ['rolling_pin'], 'def': 'utensil consisting of a cylinder (usually of wood) with a handle at each end; used to roll out dough', 'name': 'rolling_pin'}, {'frequency': 'r', 'id': 908, 'synset': 'root_beer.n.01', 'synonyms': ['root_beer'], 'def': 'carbonated drink containing extracts of roots and herbs', 'name': 'root_beer'}, {'frequency': 'c', 'id': 909, 'synset': 'router.n.02', 'synonyms': ['router_(computer_equipment)'], 'def': 'a device that forwards data packets between computer networks', 'name': 'router_(computer_equipment)'}, {'frequency': 'f', 'id': 910, 'synset': 'rubber_band.n.01', 'synonyms': ['rubber_band', 'elastic_band'], 'def': 'a narrow band of elastic rubber used to hold things (such as papers) together', 'name': 'rubber_band'}, {'frequency': 'c', 'id': 911, 'synset': 'runner.n.08', 'synonyms': ['runner_(carpet)'], 'def': 'a long narrow carpet', 'name': 'runner_(carpet)'}, {'frequency': 'f', 'id': 912, 'synset': 'sack.n.01', 'synonyms': ['plastic_bag', 'paper_bag'], 'def': "a bag made of paper or plastic for holding customer's purchases", 'name': 'plastic_bag'}, {'frequency': 'f', 'id': 913, 'synset': 'saddle.n.01', 'synonyms': ['saddle_(on_an_animal)'], 'def': 'a seat for the rider of a horse or camel', 'name': 'saddle_(on_an_animal)'}, {'frequency': 'f', 'id': 914, 'synset': 'saddle_blanket.n.01', 'synonyms': ['saddle_blanket', 'saddlecloth', 'horse_blanket'], 'def': 'stable gear consisting of a blanket placed under the saddle', 'name': 'saddle_blanket'}, {'frequency': 'c', 'id': 915, 'synset': 'saddlebag.n.01', 'synonyms': ['saddlebag'], 'def': 'a large bag (or pair of bags) hung over a saddle', 'name': 'saddlebag'}, {'frequency': 'r', 'id': 916, 'synset': 'safety_pin.n.01', 'synonyms': ['safety_pin'], 'def': 'a pin in the form of a clasp; has a guard so the point of the pin will not stick the user', 'name': 'safety_pin'}, {'frequency': 'c', 'id': 917, 'synset': 'sail.n.01', 'synonyms': ['sail'], 'def': 'a large piece of fabric by means of which wind is used to propel a sailing vessel', 'name': 'sail'}, {'frequency': 'c', 'id': 918, 'synset': 'salad.n.01', 'synonyms': ['salad'], 'def': 'food mixtures either arranged on a plate or tossed and served with a moist dressing; usually consisting of or including greens', 'name': 'salad'}, {'frequency': 'r', 'id': 919, 'synset': 'salad_plate.n.01', 'synonyms': ['salad_plate', 'salad_bowl'], 'def': 'a plate or bowl for individual servings of salad', 'name': 'salad_plate'}, {'frequency': 'r', 'id': 920, 'synset': 'salami.n.01', 'synonyms': ['salami'], 'def': 'highly seasoned fatty sausage of pork and beef usually dried', 'name': 'salami'}, {'frequency': 'r', 'id': 921, 'synset': 'salmon.n.01', 'synonyms': ['salmon_(fish)'], 'def': 'any of various large food and game fishes of northern waters', 'name': 'salmon_(fish)'}, {'frequency': 'r', 'id': 922, 'synset': 'salmon.n.03', 'synonyms': ['salmon_(food)'], 'def': 'flesh of any of various marine or freshwater fish of the family Salmonidae', 'name': 'salmon_(food)'}, {'frequency': 'r', 'id': 923, 'synset': 'salsa.n.01', 'synonyms': ['salsa'], 'def': 'spicy sauce of tomatoes and onions and chili peppers to accompany Mexican foods', 'name': 'salsa'}, {'frequency': 'f', 'id': 924, 'synset': 'saltshaker.n.01', 'synonyms': ['saltshaker'], 'def': 'a shaker with a perforated top for sprinkling salt', 'name': 'saltshaker'}, {'frequency': 'f', 'id': 925, 'synset': 'sandal.n.01', 'synonyms': ['sandal_(type_of_shoe)'], 'def': 'a shoe consisting of a sole fastened by straps to the foot', 'name': 'sandal_(type_of_shoe)'}, {'frequency': 'f', 'id': 926, 'synset': 'sandwich.n.01', 'synonyms': ['sandwich'], 'def': 'two (or more) slices of bread with a filling between them', 'name': 'sandwich'}, {'frequency': 'r', 'id': 927, 'synset': 'satchel.n.01', 'synonyms': ['satchel'], 'def': 'luggage consisting of a small case with a flat bottom and (usually) a shoulder strap', 'name': 'satchel'}, {'frequency': 'r', 'id': 928, 'synset': 'saucepan.n.01', 'synonyms': ['saucepan'], 'def': 'a deep pan with a handle; used for stewing or boiling', 'name': 'saucepan'}, {'frequency': 'f', 'id': 929, 'synset': 'saucer.n.02', 'synonyms': ['saucer'], 'def': 'a small shallow dish for holding a cup at the table', 'name': 'saucer'}, {'frequency': 'f', 'id': 930, 'synset': 'sausage.n.01', 'synonyms': ['sausage'], 'def': 'highly seasoned minced meat stuffed in casings', 'name': 'sausage'}, {'frequency': 'r', 'id': 931, 'synset': 'sawhorse.n.01', 'synonyms': ['sawhorse', 'sawbuck'], 'def': 'a framework for holding wood that is being sawed', 'name': 'sawhorse'}, {'frequency': 'r', 'id': 932, 'synset': 'sax.n.02', 'synonyms': ['saxophone'], 'def': "a wind instrument with a `J'-shaped form typically made of brass", 'name': 'saxophone'}, {'frequency': 'f', 'id': 933, 'synset': 'scale.n.07', 'synonyms': ['scale_(measuring_instrument)'], 'def': 'a measuring instrument for weighing; shows amount of mass', 'name': 'scale_(measuring_instrument)'}, {'frequency': 'r', 'id': 934, 'synset': 'scarecrow.n.01', 'synonyms': ['scarecrow', 'strawman'], 'def': 'an effigy in the shape of a man to frighten birds away from seeds', 'name': 'scarecrow'}, {'frequency': 'f', 'id': 935, 'synset': 'scarf.n.01', 'synonyms': ['scarf'], 'def': 'a garment worn around the head or neck or shoulders for warmth or decoration', 'name': 'scarf'}, {'frequency': 'c', 'id': 936, 'synset': 'school_bus.n.01', 'synonyms': ['school_bus'], 'def': 'a bus used to transport children to or from school', 'name': 'school_bus'}, {'frequency': 'f', 'id': 937, 'synset': 'scissors.n.01', 'synonyms': ['scissors'], 'def': 'a tool having two crossed pivoting blades with looped handles', 'name': 'scissors'}, {'frequency': 'c', 'id': 938, 'synset': 'scoreboard.n.01', 'synonyms': ['scoreboard'], 'def': 'a large board for displaying the score of a contest (and some other information)', 'name': 'scoreboard'}, {'frequency': 'c', 'id': 939, 'synset': 'scrambled_eggs.n.01', 'synonyms': ['scrambled_eggs'], 'def': 'eggs beaten and cooked to a soft firm consistency while stirring', 'name': 'scrambled_eggs'}, {'frequency': 'r', 'id': 940, 'synset': 'scraper.n.01', 'synonyms': ['scraper'], 'def': 'any of various hand tools for scraping', 'name': 'scraper'}, {'frequency': 'r', 'id': 941, 'synset': 'scratcher.n.03', 'synonyms': ['scratcher'], 'def': 'a device used for scratching', 'name': 'scratcher'}, {'frequency': 'c', 'id': 942, 'synset': 'screwdriver.n.01', 'synonyms': ['screwdriver'], 'def': 'a hand tool for driving screws; has a tip that fits into the head of a screw', 'name': 'screwdriver'}, {'frequency': 'c', 'id': 943, 'synset': 'scrub_brush.n.01', 'synonyms': ['scrubbing_brush'], 'def': 'a brush with short stiff bristles for heavy cleaning', 'name': 'scrubbing_brush'}, {'frequency': 'c', 'id': 944, 'synset': 'sculpture.n.01', 'synonyms': ['sculpture'], 'def': 'a three-dimensional work of art', 'name': 'sculpture'}, {'frequency': 'r', 'id': 945, 'synset': 'seabird.n.01', 'synonyms': ['seabird', 'seafowl'], 'def': 'a bird that frequents coastal waters and the open ocean: gulls; pelicans; gannets; cormorants; albatrosses; petrels; etc.', 'name': 'seabird'}, {'frequency': 'r', 'id': 946, 'synset': 'seahorse.n.02', 'synonyms': ['seahorse'], 'def': 'small fish with horse-like heads bent sharply downward and curled tails', 'name': 'seahorse'}, {'frequency': 'r', 'id': 947, 'synset': 'seaplane.n.01', 'synonyms': ['seaplane', 'hydroplane'], 'def': 'an airplane that can land on or take off from water', 'name': 'seaplane'}, {'frequency': 'c', 'id': 948, 'synset': 'seashell.n.01', 'synonyms': ['seashell'], 'def': 'the shell of a marine organism', 'name': 'seashell'}, {'frequency': 'r', 'id': 949, 'synset': 'seedling.n.01', 'synonyms': ['seedling'], 'def': 'young plant or tree grown from a seed', 'name': 'seedling'}, {'frequency': 'c', 'id': 950, 'synset': 'serving_dish.n.01', 'synonyms': ['serving_dish'], 'def': 'a dish used for serving food', 'name': 'serving_dish'}, {'frequency': 'r', 'id': 951, 'synset': 'sewing_machine.n.01', 'synonyms': ['sewing_machine'], 'def': 'a textile machine used as a home appliance for sewing', 'name': 'sewing_machine'}, {'frequency': 'r', 'id': 952, 'synset': 'shaker.n.03', 'synonyms': ['shaker'], 'def': 'a container in which something can be shaken', 'name': 'shaker'}, {'frequency': 'c', 'id': 953, 'synset': 'shampoo.n.01', 'synonyms': ['shampoo'], 'def': 'cleansing agent consisting of soaps or detergents used for washing the hair', 'name': 'shampoo'}, {'frequency': 'r', 'id': 954, 'synset': 'shark.n.01', 'synonyms': ['shark'], 'def': 'typically large carnivorous fishes with sharpe teeth', 'name': 'shark'}, {'frequency': 'r', 'id': 955, 'synset': 'sharpener.n.01', 'synonyms': ['sharpener'], 'def': 'any implement that is used to make something (an edge or a point) sharper', 'name': 'sharpener'}, {'frequency': 'r', 'id': 956, 'synset': 'sharpie.n.03', 'synonyms': ['Sharpie'], 'def': 'a pen with indelible ink that will write on any surface', 'name': 'Sharpie'}, {'frequency': 'r', 'id': 957, 'synset': 'shaver.n.03', 'synonyms': ['shaver_(electric)', 'electric_shaver', 'electric_razor'], 'def': 'a razor powered by an electric motor', 'name': 'shaver_(electric)'}, {'frequency': 'c', 'id': 958, 'synset': 'shaving_cream.n.01', 'synonyms': ['shaving_cream', 'shaving_soap'], 'def': 'toiletry consisting that forms a rich lather for softening the beard before shaving', 'name': 'shaving_cream'}, {'frequency': 'r', 'id': 959, 'synset': 'shawl.n.01', 'synonyms': ['shawl'], 'def': 'cloak consisting of an oblong piece of cloth used to cover the head and shoulders', 'name': 'shawl'}, {'frequency': 'r', 'id': 960, 'synset': 'shears.n.01', 'synonyms': ['shears'], 'def': 'large scissors with strong blades', 'name': 'shears'}, {'frequency': 'f', 'id': 961, 'synset': 'sheep.n.01', 'synonyms': ['sheep'], 'def': 'woolly usually horned ruminant mammal related to the goat', 'name': 'sheep'}, {'frequency': 'r', 'id': 962, 'synset': 'shepherd_dog.n.01', 'synonyms': ['shepherd_dog', 'sheepdog'], 'def': 'any of various usually long-haired breeds of dog reared to herd and guard sheep', 'name': 'shepherd_dog'}, {'frequency': 'r', 'id': 963, 'synset': 'sherbert.n.01', 'synonyms': ['sherbert', 'sherbet'], 'def': 'a frozen dessert made primarily of fruit juice and sugar', 'name': 'sherbert'}, {'frequency': 'r', 'id': 964, 'synset': 'shield.n.02', 'synonyms': ['shield'], 'def': 'armor carried on the arm to intercept blows', 'name': 'shield'}, {'frequency': 'f', 'id': 965, 'synset': 'shirt.n.01', 'synonyms': ['shirt'], 'def': 'a garment worn on the upper half of the body', 'name': 'shirt'}, {'frequency': 'f', 'id': 966, 'synset': 'shoe.n.01', 'synonyms': ['shoe', 'sneaker_(type_of_shoe)', 'tennis_shoe'], 'def': 'common footwear covering the foot', 'name': 'shoe'}, {'frequency': 'c', 'id': 967, 'synset': 'shopping_bag.n.01', 'synonyms': ['shopping_bag'], 'def': 'a bag made of plastic or strong paper (often with handles); used to transport goods after shopping', 'name': 'shopping_bag'}, {'frequency': 'c', 'id': 968, 'synset': 'shopping_cart.n.01', 'synonyms': ['shopping_cart'], 'def': 'a handcart that holds groceries or other goods while shopping', 'name': 'shopping_cart'}, {'frequency': 'f', 'id': 969, 'synset': 'short_pants.n.01', 'synonyms': ['short_pants', 'shorts_(clothing)', 'trunks_(clothing)'], 'def': 'trousers that end at or above the knee', 'name': 'short_pants'}, {'frequency': 'r', 'id': 970, 'synset': 'shot_glass.n.01', 'synonyms': ['shot_glass'], 'def': 'a small glass adequate to hold a single swallow of whiskey', 'name': 'shot_glass'}, {'frequency': 'c', 'id': 971, 'synset': 'shoulder_bag.n.01', 'synonyms': ['shoulder_bag'], 'def': 'a large handbag that can be carried by a strap looped over the shoulder', 'name': 'shoulder_bag'}, {'frequency': 'c', 'id': 972, 'synset': 'shovel.n.01', 'synonyms': ['shovel'], 'def': 'a hand tool for lifting loose material such as snow, dirt, etc.', 'name': 'shovel'}, {'frequency': 'f', 'id': 973, 'synset': 'shower.n.01', 'synonyms': ['shower_head'], 'def': 'a plumbing fixture that sprays water over you', 'name': 'shower_head'}, {'frequency': 'f', 'id': 974, 'synset': 'shower_curtain.n.01', 'synonyms': ['shower_curtain'], 'def': 'a curtain that keeps water from splashing out of the shower area', 'name': 'shower_curtain'}, {'frequency': 'r', 'id': 975, 'synset': 'shredder.n.01', 'synonyms': ['shredder_(for_paper)'], 'def': 'a device that shreds documents', 'name': 'shredder_(for_paper)'}, {'frequency': 'r', 'id': 976, 'synset': 'sieve.n.01', 'synonyms': ['sieve', 'screen_(sieve)'], 'def': 'a strainer for separating lumps from powdered material or grading particles', 'name': 'sieve'}, {'frequency': 'f', 'id': 977, 'synset': 'signboard.n.01', 'synonyms': ['signboard'], 'def': 'structure displaying a board on which advertisements can be posted', 'name': 'signboard'}, {'frequency': 'c', 'id': 978, 'synset': 'silo.n.01', 'synonyms': ['silo'], 'def': 'a cylindrical tower used for storing goods', 'name': 'silo'}, {'frequency': 'f', 'id': 979, 'synset': 'sink.n.01', 'synonyms': ['sink'], 'def': 'plumbing fixture consisting of a water basin fixed to a wall or floor and having a drainpipe', 'name': 'sink'}, {'frequency': 'f', 'id': 980, 'synset': 'skateboard.n.01', 'synonyms': ['skateboard'], 'def': 'a board with wheels that is ridden in a standing or crouching position and propelled by foot', 'name': 'skateboard'}, {'frequency': 'c', 'id': 981, 'synset': 'skewer.n.01', 'synonyms': ['skewer'], 'def': 'a long pin for holding meat in position while it is being roasted', 'name': 'skewer'}, {'frequency': 'f', 'id': 982, 'synset': 'ski.n.01', 'synonyms': ['ski'], 'def': 'sports equipment for skiing on snow', 'name': 'ski'}, {'frequency': 'f', 'id': 983, 'synset': 'ski_boot.n.01', 'synonyms': ['ski_boot'], 'def': 'a stiff boot that is fastened to a ski with a ski binding', 'name': 'ski_boot'}, {'frequency': 'f', 'id': 984, 'synset': 'ski_parka.n.01', 'synonyms': ['ski_parka', 'ski_jacket'], 'def': 'a parka to be worn while skiing', 'name': 'ski_parka'}, {'frequency': 'f', 'id': 985, 'synset': 'ski_pole.n.01', 'synonyms': ['ski_pole'], 'def': 'a pole with metal points used as an aid in skiing', 'name': 'ski_pole'}, {'frequency': 'f', 'id': 986, 'synset': 'skirt.n.02', 'synonyms': ['skirt'], 'def': 'a garment hanging from the waist; worn mainly by girls and women', 'name': 'skirt'}, {'frequency': 'c', 'id': 987, 'synset': 'sled.n.01', 'synonyms': ['sled', 'sledge', 'sleigh'], 'def': 'a vehicle or flat object for transportation over snow by sliding or pulled by dogs, etc.', 'name': 'sled'}, {'frequency': 'c', 'id': 988, 'synset': 'sleeping_bag.n.01', 'synonyms': ['sleeping_bag'], 'def': 'large padded bag designed to be slept in outdoors', 'name': 'sleeping_bag'}, {'frequency': 'r', 'id': 989, 'synset': 'sling.n.05', 'synonyms': ['sling_(bandage)', 'triangular_bandage'], 'def': 'bandage to support an injured forearm; slung over the shoulder or neck', 'name': 'sling_(bandage)'}, {'frequency': 'c', 'id': 990, 'synset': 'slipper.n.01', 'synonyms': ['slipper_(footwear)', 'carpet_slipper_(footwear)'], 'def': 'low footwear that can be slipped on and off easily; usually worn indoors', 'name': 'slipper_(footwear)'}, {'frequency': 'r', 'id': 991, 'synset': 'smoothie.n.02', 'synonyms': ['smoothie'], 'def': 'a thick smooth drink consisting of fresh fruit pureed with ice cream or yoghurt or milk', 'name': 'smoothie'}, {'frequency': 'r', 'id': 992, 'synset': 'snake.n.01', 'synonyms': ['snake', 'serpent'], 'def': 'limbless scaly elongate reptile; some are venomous', 'name': 'snake'}, {'frequency': 'f', 'id': 993, 'synset': 'snowboard.n.01', 'synonyms': ['snowboard'], 'def': 'a board that resembles a broad ski or a small surfboard; used in a standing position to slide down snow-covered slopes', 'name': 'snowboard'}, {'frequency': 'c', 'id': 994, 'synset': 'snowman.n.01', 'synonyms': ['snowman'], 'def': 'a figure of a person made of packed snow', 'name': 'snowman'}, {'frequency': 'c', 'id': 995, 'synset': 'snowmobile.n.01', 'synonyms': ['snowmobile'], 'def': 'tracked vehicle for travel on snow having skis in front', 'name': 'snowmobile'}, {'frequency': 'f', 'id': 996, 'synset': 'soap.n.01', 'synonyms': ['soap'], 'def': 'a cleansing agent made from the salts of vegetable or animal fats', 'name': 'soap'}, {'frequency': 'f', 'id': 997, 'synset': 'soccer_ball.n.01', 'synonyms': ['soccer_ball'], 'def': "an inflated ball used in playing soccer (called `football' outside of the United States)", 'name': 'soccer_ball'}, {'frequency': 'f', 'id': 998, 'synset': 'sock.n.01', 'synonyms': ['sock'], 'def': 'cloth covering for the foot; worn inside the shoe; reaches to between the ankle and the knee', 'name': 'sock'}, {'frequency': 'r', 'id': 999, 'synset': 'soda_fountain.n.02', 'synonyms': ['soda_fountain'], 'def': 'an apparatus for dispensing soda water', 'name': 'soda_fountain'}, {'frequency': 'r', 'id': 1000, 'synset': 'soda_water.n.01', 'synonyms': ['carbonated_water', 'club_soda', 'seltzer', 'sparkling_water'], 'def': 'effervescent beverage artificially charged with carbon dioxide', 'name': 'carbonated_water'}, {'frequency': 'f', 'id': 1001, 'synset': 'sofa.n.01', 'synonyms': ['sofa', 'couch', 'lounge'], 'def': 'an upholstered seat for more than one person', 'name': 'sofa'}, {'frequency': 'r', 'id': 1002, 'synset': 'softball.n.01', 'synonyms': ['softball'], 'def': 'ball used in playing softball', 'name': 'softball'}, {'frequency': 'c', 'id': 1003, 'synset': 'solar_array.n.01', 'synonyms': ['solar_array', 'solar_battery', 'solar_panel'], 'def': 'electrical device consisting of a large array of connected solar cells', 'name': 'solar_array'}, {'frequency': 'r', 'id': 1004, 'synset': 'sombrero.n.02', 'synonyms': ['sombrero'], 'def': 'a straw hat with a tall crown and broad brim; worn in American southwest and in Mexico', 'name': 'sombrero'}, {'frequency': 'c', 'id': 1005, 'synset': 'soup.n.01', 'synonyms': ['soup'], 'def': 'liquid food especially of meat or fish or vegetable stock often containing pieces of solid food', 'name': 'soup'}, {'frequency': 'r', 'id': 1006, 'synset': 'soup_bowl.n.01', 'synonyms': ['soup_bowl'], 'def': 'a bowl for serving soup', 'name': 'soup_bowl'}, {'frequency': 'c', 'id': 1007, 'synset': 'soupspoon.n.01', 'synonyms': ['soupspoon'], 'def': 'a spoon with a rounded bowl for eating soup', 'name': 'soupspoon'}, {'frequency': 'c', 'id': 1008, 'synset': 'sour_cream.n.01', 'synonyms': ['sour_cream', 'soured_cream'], 'def': 'soured light cream', 'name': 'sour_cream'}, {'frequency': 'r', 'id': 1009, 'synset': 'soya_milk.n.01', 'synonyms': ['soya_milk', 'soybean_milk', 'soymilk'], 'def': 'a milk substitute containing soybean flour and water; used in some infant formulas and in making tofu', 'name': 'soya_milk'}, {'frequency': 'r', 'id': 1010, 'synset': 'space_shuttle.n.01', 'synonyms': ['space_shuttle'], 'def': "a reusable spacecraft with wings for a controlled descent through the Earth's atmosphere", 'name': 'space_shuttle'}, {'frequency': 'r', 'id': 1011, 'synset': 'sparkler.n.02', 'synonyms': ['sparkler_(fireworks)'], 'def': 'a firework that burns slowly and throws out a shower of sparks', 'name': 'sparkler_(fireworks)'}, {'frequency': 'f', 'id': 1012, 'synset': 'spatula.n.02', 'synonyms': ['spatula'], 'def': 'a hand tool with a thin flexible blade used to mix or spread soft substances', 'name': 'spatula'}, {'frequency': 'r', 'id': 1013, 'synset': 'spear.n.01', 'synonyms': ['spear', 'lance'], 'def': 'a long pointed rod used as a tool or weapon', 'name': 'spear'}, {'frequency': 'f', 'id': 1014, 'synset': 'spectacles.n.01', 'synonyms': ['spectacles', 'specs', 'eyeglasses', 'glasses'], 'def': 'optical instrument consisting of a frame that holds a pair of lenses for correcting defective vision', 'name': 'spectacles'}, {'frequency': 'c', 'id': 1015, 'synset': 'spice_rack.n.01', 'synonyms': ['spice_rack'], 'def': 'a rack for displaying containers filled with spices', 'name': 'spice_rack'}, {'frequency': 'r', 'id': 1016, 'synset': 'spider.n.01', 'synonyms': ['spider'], 'def': 'predatory arachnid with eight legs, two poison fangs, two feelers, and usually two silk-spinning organs at the back end of the body', 'name': 'spider'}, {'frequency': 'c', 'id': 1017, 'synset': 'sponge.n.01', 'synonyms': ['sponge'], 'def': 'a porous mass usable to absorb water typically used for cleaning', 'name': 'sponge'}, {'frequency': 'f', 'id': 1018, 'synset': 'spoon.n.01', 'synonyms': ['spoon'], 'def': 'a piece of cutlery with a shallow bowl-shaped container and a handle', 'name': 'spoon'}, {'frequency': 'c', 'id': 1019, 'synset': 'sportswear.n.01', 'synonyms': ['sportswear', 'athletic_wear', 'activewear'], 'def': 'attire worn for sport or for casual wear', 'name': 'sportswear'}, {'frequency': 'c', 'id': 1020, 'synset': 'spotlight.n.02', 'synonyms': ['spotlight'], 'def': 'a lamp that produces a strong beam of light to illuminate a restricted area; used to focus attention of a stage performer', 'name': 'spotlight'}, {'frequency': 'r', 'id': 1021, 'synset': 'squirrel.n.01', 'synonyms': ['squirrel'], 'def': 'a kind of arboreal rodent having a long bushy tail', 'name': 'squirrel'}, {'frequency': 'c', 'id': 1022, 'synset': 'stapler.n.01', 'synonyms': ['stapler_(stapling_machine)'], 'def': 'a machine that inserts staples into sheets of paper in order to fasten them together', 'name': 'stapler_(stapling_machine)'}, {'frequency': 'r', 'id': 1023, 'synset': 'starfish.n.01', 'synonyms': ['starfish', 'sea_star'], 'def': 'echinoderms characterized by five arms extending from a central disk', 'name': 'starfish'}, {'frequency': 'f', 'id': 1024, 'synset': 'statue.n.01', 'synonyms': ['statue_(sculpture)'], 'def': 'a sculpture representing a human or animal', 'name': 'statue_(sculpture)'}, {'frequency': 'c', 'id': 1025, 'synset': 'steak.n.01', 'synonyms': ['steak_(food)'], 'def': 'a slice of meat cut from the fleshy part of an animal or large fish', 'name': 'steak_(food)'}, {'frequency': 'r', 'id': 1026, 'synset': 'steak_knife.n.01', 'synonyms': ['steak_knife'], 'def': 'a sharp table knife used in eating steak', 'name': 'steak_knife'}, {'frequency': 'r', 'id': 1027, 'synset': 'steamer.n.02', 'synonyms': ['steamer_(kitchen_appliance)'], 'def': 'a cooking utensil that can be used to cook food by steaming it', 'name': 'steamer_(kitchen_appliance)'}, {'frequency': 'f', 'id': 1028, 'synset': 'steering_wheel.n.01', 'synonyms': ['steering_wheel'], 'def': 'a handwheel that is used for steering', 'name': 'steering_wheel'}, {'frequency': 'r', 'id': 1029, 'synset': 'stencil.n.01', 'synonyms': ['stencil'], 'def': 'a sheet of material (metal, plastic, etc.) that has been perforated with a pattern; ink or paint can pass through the perforations to create the printed pattern on the surface below', 'name': 'stencil'}, {'frequency': 'r', 'id': 1030, 'synset': 'step_ladder.n.01', 'synonyms': ['stepladder'], 'def': 'a folding portable ladder hinged at the top', 'name': 'stepladder'}, {'frequency': 'c', 'id': 1031, 'synset': 'step_stool.n.01', 'synonyms': ['step_stool'], 'def': 'a stool that has one or two steps that fold under the seat', 'name': 'step_stool'}, {'frequency': 'c', 'id': 1032, 'synset': 'stereo.n.01', 'synonyms': ['stereo_(sound_system)'], 'def': 'electronic device for playing audio', 'name': 'stereo_(sound_system)'}, {'frequency': 'r', 'id': 1033, 'synset': 'stew.n.02', 'synonyms': ['stew'], 'def': 'food prepared by stewing especially meat or fish with vegetables', 'name': 'stew'}, {'frequency': 'r', 'id': 1034, 'synset': 'stirrer.n.02', 'synonyms': ['stirrer'], 'def': 'an implement used for stirring', 'name': 'stirrer'}, {'frequency': 'f', 'id': 1035, 'synset': 'stirrup.n.01', 'synonyms': ['stirrup'], 'def': "support consisting of metal loops into which rider's feet go", 'name': 'stirrup'}, {'frequency': 'c', 'id': 1036, 'synset': 'stocking.n.01', 'synonyms': ['stockings_(leg_wear)'], 'def': 'close-fitting hosiery to cover the foot and leg; come in matched pairs', 'name': 'stockings_(leg_wear)'}, {'frequency': 'f', 'id': 1037, 'synset': 'stool.n.01', 'synonyms': ['stool'], 'def': 'a simple seat without a back or arms', 'name': 'stool'}, {'frequency': 'f', 'id': 1038, 'synset': 'stop_sign.n.01', 'synonyms': ['stop_sign'], 'def': 'a traffic sign to notify drivers that they must come to a complete stop', 'name': 'stop_sign'}, {'frequency': 'f', 'id': 1039, 'synset': 'stoplight.n.01', 'synonyms': ['brake_light'], 'def': 'a red light on the rear of a motor vehicle that signals when the brakes are applied', 'name': 'brake_light'}, {'frequency': 'f', 'id': 1040, 'synset': 'stove.n.01', 'synonyms': ['stove', 'kitchen_stove', 'range_(kitchen_appliance)', 'kitchen_range', 'cooking_stove'], 'def': 'a kitchen appliance used for cooking food', 'name': 'stove'}, {'frequency': 'c', 'id': 1041, 'synset': 'strainer.n.01', 'synonyms': ['strainer'], 'def': 'a filter to retain larger pieces while smaller pieces and liquids pass through', 'name': 'strainer'}, {'frequency': 'f', 'id': 1042, 'synset': 'strap.n.01', 'synonyms': ['strap'], 'def': 'an elongated strip of material for binding things together or holding', 'name': 'strap'}, {'frequency': 'f', 'id': 1043, 'synset': 'straw.n.04', 'synonyms': ['straw_(for_drinking)', 'drinking_straw'], 'def': 'a thin paper or plastic tube used to suck liquids into the mouth', 'name': 'straw_(for_drinking)'}, {'frequency': 'f', 'id': 1044, 'synset': 'strawberry.n.01', 'synonyms': ['strawberry'], 'def': 'sweet fleshy red fruit', 'name': 'strawberry'}, {'frequency': 'f', 'id': 1045, 'synset': 'street_sign.n.01', 'synonyms': ['street_sign'], 'def': 'a sign visible from the street', 'name': 'street_sign'}, {'frequency': 'f', 'id': 1046, 'synset': 'streetlight.n.01', 'synonyms': ['streetlight', 'street_lamp'], 'def': 'a lamp supported on a lamppost; for illuminating a street', 'name': 'streetlight'}, {'frequency': 'r', 'id': 1047, 'synset': 'string_cheese.n.01', 'synonyms': ['string_cheese'], 'def': 'cheese formed in long strings twisted together', 'name': 'string_cheese'}, {'frequency': 'r', 'id': 1048, 'synset': 'stylus.n.02', 'synonyms': ['stylus'], 'def': 'a pointed tool for writing or drawing or engraving', 'name': 'stylus'}, {'frequency': 'r', 'id': 1049, 'synset': 'subwoofer.n.01', 'synonyms': ['subwoofer'], 'def': 'a loudspeaker that is designed to reproduce very low bass frequencies', 'name': 'subwoofer'}, {'frequency': 'r', 'id': 1050, 'synset': 'sugar_bowl.n.01', 'synonyms': ['sugar_bowl'], 'def': 'a dish in which sugar is served', 'name': 'sugar_bowl'}, {'frequency': 'r', 'id': 1051, 'synset': 'sugarcane.n.01', 'synonyms': ['sugarcane_(plant)'], 'def': 'juicy canes whose sap is a source of molasses and commercial sugar; fresh canes are sometimes chewed for the juice', 'name': 'sugarcane_(plant)'}, {'frequency': 'c', 'id': 1052, 'synset': 'suit.n.01', 'synonyms': ['suit_(clothing)'], 'def': 'a set of garments (usually including a jacket and trousers or skirt) for outerwear all of the same fabric and color', 'name': 'suit_(clothing)'}, {'frequency': 'c', 'id': 1053, 'synset': 'sunflower.n.01', 'synonyms': ['sunflower'], 'def': 'any plant of the genus Helianthus having large flower heads with dark disk florets and showy yellow rays', 'name': 'sunflower'}, {'frequency': 'f', 'id': 1054, 'synset': 'sunglasses.n.01', 'synonyms': ['sunglasses'], 'def': 'spectacles that are darkened or polarized to protect the eyes from the glare of the sun', 'name': 'sunglasses'}, {'frequency': 'c', 'id': 1055, 'synset': 'sunhat.n.01', 'synonyms': ['sunhat'], 'def': 'a hat with a broad brim that protects the face from direct exposure to the sun', 'name': 'sunhat'}, {'frequency': 'r', 'id': 1056, 'synset': 'sunscreen.n.01', 'synonyms': ['sunscreen', 'sunblock'], 'def': 'a cream spread on the skin; contains a chemical to filter out ultraviolet light and so protect from sunburn', 'name': 'sunscreen'}, {'frequency': 'f', 'id': 1057, 'synset': 'surfboard.n.01', 'synonyms': ['surfboard'], 'def': 'a narrow buoyant board for riding surf', 'name': 'surfboard'}, {'frequency': 'c', 'id': 1058, 'synset': 'sushi.n.01', 'synonyms': ['sushi'], 'def': 'rice (with raw fish) wrapped in seaweed', 'name': 'sushi'}, {'frequency': 'c', 'id': 1059, 'synset': 'swab.n.02', 'synonyms': ['mop'], 'def': 'cleaning implement consisting of absorbent material fastened to a handle; for cleaning floors', 'name': 'mop'}, {'frequency': 'c', 'id': 1060, 'synset': 'sweat_pants.n.01', 'synonyms': ['sweat_pants'], 'def': 'loose-fitting trousers with elastic cuffs; worn by athletes', 'name': 'sweat_pants'}, {'frequency': 'c', 'id': 1061, 'synset': 'sweatband.n.02', 'synonyms': ['sweatband'], 'def': 'a band of material tied around the forehead or wrist to absorb sweat', 'name': 'sweatband'}, {'frequency': 'f', 'id': 1062, 'synset': 'sweater.n.01', 'synonyms': ['sweater'], 'def': 'a crocheted or knitted garment covering the upper part of the body', 'name': 'sweater'}, {'frequency': 'f', 'id': 1063, 'synset': 'sweatshirt.n.01', 'synonyms': ['sweatshirt'], 'def': 'cotton knit pullover with long sleeves worn during athletic activity', 'name': 'sweatshirt'}, {'frequency': 'c', 'id': 1064, 'synset': 'sweet_potato.n.02', 'synonyms': ['sweet_potato'], 'def': 'the edible tuberous root of the sweet potato vine', 'name': 'sweet_potato'}, {'frequency': 'f', 'id': 1065, 'synset': 'swimsuit.n.01', 'synonyms': ['swimsuit', 'swimwear', 'bathing_suit', 'swimming_costume', 'bathing_costume', 'swimming_trunks', 'bathing_trunks'], 'def': 'garment worn for swimming', 'name': 'swimsuit'}, {'frequency': 'c', 'id': 1066, 'synset': 'sword.n.01', 'synonyms': ['sword'], 'def': 'a cutting or thrusting weapon that has a long metal blade', 'name': 'sword'}, {'frequency': 'r', 'id': 1067, 'synset': 'syringe.n.01', 'synonyms': ['syringe'], 'def': 'a medical instrument used to inject or withdraw fluids', 'name': 'syringe'}, {'frequency': 'r', 'id': 1068, 'synset': 'tabasco.n.02', 'synonyms': ['Tabasco_sauce'], 'def': 'very spicy sauce (trade name Tabasco) made from fully-aged red peppers', 'name': 'Tabasco_sauce'}, {'frequency': 'r', 'id': 1069, 'synset': 'table-tennis_table.n.01', 'synonyms': ['table-tennis_table', 'ping-pong_table'], 'def': 'a table used for playing table tennis', 'name': 'table-tennis_table'}, {'frequency': 'f', 'id': 1070, 'synset': 'table.n.02', 'synonyms': ['table'], 'def': 'a piece of furniture having a smooth flat top that is usually supported by one or more vertical legs', 'name': 'table'}, {'frequency': 'c', 'id': 1071, 'synset': 'table_lamp.n.01', 'synonyms': ['table_lamp'], 'def': 'a lamp that sits on a table', 'name': 'table_lamp'}, {'frequency': 'f', 'id': 1072, 'synset': 'tablecloth.n.01', 'synonyms': ['tablecloth'], 'def': 'a covering spread over a dining table', 'name': 'tablecloth'}, {'frequency': 'r', 'id': 1073, 'synset': 'tachometer.n.01', 'synonyms': ['tachometer'], 'def': 'measuring instrument for indicating speed of rotation', 'name': 'tachometer'}, {'frequency': 'r', 'id': 1074, 'synset': 'taco.n.02', 'synonyms': ['taco'], 'def': 'a small tortilla cupped around a filling', 'name': 'taco'}, {'frequency': 'f', 'id': 1075, 'synset': 'tag.n.02', 'synonyms': ['tag'], 'def': 'a label associated with something for the purpose of identification or information', 'name': 'tag'}, {'frequency': 'f', 'id': 1076, 'synset': 'taillight.n.01', 'synonyms': ['taillight', 'rear_light'], 'def': 'lamp (usually red) mounted at the rear of a motor vehicle', 'name': 'taillight'}, {'frequency': 'r', 'id': 1077, 'synset': 'tambourine.n.01', 'synonyms': ['tambourine'], 'def': 'a shallow drum with a single drumhead and with metallic disks in the sides', 'name': 'tambourine'}, {'frequency': 'r', 'id': 1078, 'synset': 'tank.n.01', 'synonyms': ['army_tank', 'armored_combat_vehicle', 'armoured_combat_vehicle'], 'def': 'an enclosed armored military vehicle; has a cannon and moves on caterpillar treads', 'name': 'army_tank'}, {'frequency': 'c', 'id': 1079, 'synset': 'tank.n.02', 'synonyms': ['tank_(storage_vessel)', 'storage_tank'], 'def': 'a large (usually metallic) vessel for holding gases or liquids', 'name': 'tank_(storage_vessel)'}, {'frequency': 'f', 'id': 1080, 'synset': 'tank_top.n.01', 'synonyms': ['tank_top_(clothing)'], 'def': 'a tight-fitting sleeveless shirt with wide shoulder straps and low neck and no front opening', 'name': 'tank_top_(clothing)'}, {'frequency': 'c', 'id': 1081, 'synset': 'tape.n.01', 'synonyms': ['tape_(sticky_cloth_or_paper)'], 'def': 'a long thin piece of cloth or paper as used for binding or fastening', 'name': 'tape_(sticky_cloth_or_paper)'}, {'frequency': 'c', 'id': 1082, 'synset': 'tape.n.04', 'synonyms': ['tape_measure', 'measuring_tape'], 'def': 'measuring instrument consisting of a narrow strip (cloth or metal) marked in inches or centimeters and used for measuring lengths', 'name': 'tape_measure'}, {'frequency': 'c', 'id': 1083, 'synset': 'tapestry.n.02', 'synonyms': ['tapestry'], 'def': 'a heavy textile with a woven design; used for curtains and upholstery', 'name': 'tapestry'}, {'frequency': 'f', 'id': 1084, 'synset': 'tarpaulin.n.01', 'synonyms': ['tarp'], 'def': 'waterproofed canvas', 'name': 'tarp'}, {'frequency': 'c', 'id': 1085, 'synset': 'tartan.n.01', 'synonyms': ['tartan', 'plaid'], 'def': 'a cloth having a crisscross design', 'name': 'tartan'}, {'frequency': 'c', 'id': 1086, 'synset': 'tassel.n.01', 'synonyms': ['tassel'], 'def': 'adornment consisting of a bunch of cords fastened at one end', 'name': 'tassel'}, {'frequency': 'r', 'id': 1087, 'synset': 'tea_bag.n.01', 'synonyms': ['tea_bag'], 'def': 'a measured amount of tea in a bag for an individual serving of tea', 'name': 'tea_bag'}, {'frequency': 'c', 'id': 1088, 'synset': 'teacup.n.02', 'synonyms': ['teacup'], 'def': 'a cup from which tea is drunk', 'name': 'teacup'}, {'frequency': 'c', 'id': 1089, 'synset': 'teakettle.n.01', 'synonyms': ['teakettle'], 'def': 'kettle for boiling water to make tea', 'name': 'teakettle'}, {'frequency': 'c', 'id': 1090, 'synset': 'teapot.n.01', 'synonyms': ['teapot'], 'def': 'pot for brewing tea; usually has a spout and handle', 'name': 'teapot'}, {'frequency': 'f', 'id': 1091, 'synset': 'teddy.n.01', 'synonyms': ['teddy_bear'], 'def': "plaything consisting of a child's toy bear (usually plush and stuffed with soft materials)", 'name': 'teddy_bear'}, {'frequency': 'f', 'id': 1092, 'synset': 'telephone.n.01', 'synonyms': ['telephone', 'phone', 'telephone_set'], 'def': 'electronic device for communicating by voice over long distances', 'name': 'telephone'}, {'frequency': 'c', 'id': 1093, 'synset': 'telephone_booth.n.01', 'synonyms': ['telephone_booth', 'phone_booth', 'call_box', 'telephone_box', 'telephone_kiosk'], 'def': 'booth for using a telephone', 'name': 'telephone_booth'}, {'frequency': 'f', 'id': 1094, 'synset': 'telephone_pole.n.01', 'synonyms': ['telephone_pole', 'telegraph_pole', 'telegraph_post'], 'def': 'tall pole supporting telephone wires', 'name': 'telephone_pole'}, {'frequency': 'r', 'id': 1095, 'synset': 'telephoto_lens.n.01', 'synonyms': ['telephoto_lens', 'zoom_lens'], 'def': 'a camera lens that magnifies the image', 'name': 'telephoto_lens'}, {'frequency': 'c', 'id': 1096, 'synset': 'television_camera.n.01', 'synonyms': ['television_camera', 'tv_camera'], 'def': 'television equipment for capturing and recording video', 'name': 'television_camera'}, {'frequency': 'f', 'id': 1097, 'synset': 'television_receiver.n.01', 'synonyms': ['television_set', 'tv', 'tv_set'], 'def': 'an electronic device that receives television signals and displays them on a screen', 'name': 'television_set'}, {'frequency': 'f', 'id': 1098, 'synset': 'tennis_ball.n.01', 'synonyms': ['tennis_ball'], 'def': 'ball about the size of a fist used in playing tennis', 'name': 'tennis_ball'}, {'frequency': 'f', 'id': 1099, 'synset': 'tennis_racket.n.01', 'synonyms': ['tennis_racket'], 'def': 'a racket used to play tennis', 'name': 'tennis_racket'}, {'frequency': 'r', 'id': 1100, 'synset': 'tequila.n.01', 'synonyms': ['tequila'], 'def': 'Mexican liquor made from fermented juices of an agave plant', 'name': 'tequila'}, {'frequency': 'c', 'id': 1101, 'synset': 'thermometer.n.01', 'synonyms': ['thermometer'], 'def': 'measuring instrument for measuring temperature', 'name': 'thermometer'}, {'frequency': 'c', 'id': 1102, 'synset': 'thermos.n.01', 'synonyms': ['thermos_bottle'], 'def': 'vacuum flask that preserves temperature of hot or cold drinks', 'name': 'thermos_bottle'}, {'frequency': 'c', 'id': 1103, 'synset': 'thermostat.n.01', 'synonyms': ['thermostat'], 'def': 'a regulator for automatically regulating temperature by starting or stopping the supply of heat', 'name': 'thermostat'}, {'frequency': 'r', 'id': 1104, 'synset': 'thimble.n.02', 'synonyms': ['thimble'], 'def': 'a small metal cap to protect the finger while sewing; can be used as a small container', 'name': 'thimble'}, {'frequency': 'c', 'id': 1105, 'synset': 'thread.n.01', 'synonyms': ['thread', 'yarn'], 'def': 'a fine cord of twisted fibers (of cotton or silk or wool or nylon etc.) used in sewing and weaving', 'name': 'thread'}, {'frequency': 'c', 'id': 1106, 'synset': 'thumbtack.n.01', 'synonyms': ['thumbtack', 'drawing_pin', 'pushpin'], 'def': 'a tack for attaching papers to a bulletin board or drawing board', 'name': 'thumbtack'}, {'frequency': 'c', 'id': 1107, 'synset': 'tiara.n.01', 'synonyms': ['tiara'], 'def': 'a jeweled headdress worn by women on formal occasions', 'name': 'tiara'}, {'frequency': 'c', 'id': 1108, 'synset': 'tiger.n.02', 'synonyms': ['tiger'], 'def': 'large feline of forests in most of Asia having a tawny coat with black stripes', 'name': 'tiger'}, {'frequency': 'c', 'id': 1109, 'synset': 'tights.n.01', 'synonyms': ['tights_(clothing)', 'leotards'], 'def': 'skintight knit hose covering the body from the waist to the feet worn by acrobats and dancers and as stockings by women and girls', 'name': 'tights_(clothing)'}, {'frequency': 'c', 'id': 1110, 'synset': 'timer.n.01', 'synonyms': ['timer', 'stopwatch'], 'def': 'a timepiece that measures a time interval and signals its end', 'name': 'timer'}, {'frequency': 'f', 'id': 1111, 'synset': 'tinfoil.n.01', 'synonyms': ['tinfoil'], 'def': 'foil made of tin or an alloy of tin and lead', 'name': 'tinfoil'}, {'frequency': 'r', 'id': 1112, 'synset': 'tinsel.n.01', 'synonyms': ['tinsel'], 'def': 'a showy decoration that is basically valueless', 'name': 'tinsel'}, {'frequency': 'f', 'id': 1113, 'synset': 'tissue.n.02', 'synonyms': ['tissue_paper'], 'def': 'a soft thin (usually translucent) paper', 'name': 'tissue_paper'}, {'frequency': 'c', 'id': 1114, 'synset': 'toast.n.01', 'synonyms': ['toast_(food)'], 'def': 'slice of bread that has been toasted', 'name': 'toast_(food)'}, {'frequency': 'f', 'id': 1115, 'synset': 'toaster.n.02', 'synonyms': ['toaster'], 'def': 'a kitchen appliance (usually electric) for toasting bread', 'name': 'toaster'}, {'frequency': 'c', 'id': 1116, 'synset': 'toaster_oven.n.01', 'synonyms': ['toaster_oven'], 'def': 'kitchen appliance consisting of a small electric oven for toasting or warming food', 'name': 'toaster_oven'}, {'frequency': 'f', 'id': 1117, 'synset': 'toilet.n.02', 'synonyms': ['toilet'], 'def': 'a plumbing fixture for defecation and urination', 'name': 'toilet'}, {'frequency': 'f', 'id': 1118, 'synset': 'toilet_tissue.n.01', 'synonyms': ['toilet_tissue', 'toilet_paper', 'bathroom_tissue'], 'def': 'a soft thin absorbent paper for use in toilets', 'name': 'toilet_tissue'}, {'frequency': 'f', 'id': 1119, 'synset': 'tomato.n.01', 'synonyms': ['tomato'], 'def': 'mildly acid red or yellow pulpy fruit eaten as a vegetable', 'name': 'tomato'}, {'frequency': 'c', 'id': 1120, 'synset': 'tongs.n.01', 'synonyms': ['tongs'], 'def': 'any of various devices for taking hold of objects; usually have two hinged legs with handles above and pointed hooks below', 'name': 'tongs'}, {'frequency': 'c', 'id': 1121, 'synset': 'toolbox.n.01', 'synonyms': ['toolbox'], 'def': 'a box or chest or cabinet for holding hand tools', 'name': 'toolbox'}, {'frequency': 'f', 'id': 1122, 'synset': 'toothbrush.n.01', 'synonyms': ['toothbrush'], 'def': 'small brush; has long handle; used to clean teeth', 'name': 'toothbrush'}, {'frequency': 'f', 'id': 1123, 'synset': 'toothpaste.n.01', 'synonyms': ['toothpaste'], 'def': 'a dentifrice in the form of a paste', 'name': 'toothpaste'}, {'frequency': 'c', 'id': 1124, 'synset': 'toothpick.n.01', 'synonyms': ['toothpick'], 'def': 'pick consisting of a small strip of wood or plastic; used to pick food from between the teeth', 'name': 'toothpick'}, {'frequency': 'c', 'id': 1125, 'synset': 'top.n.09', 'synonyms': ['cover'], 'def': 'covering for a hole (especially a hole in the top of a container)', 'name': 'cover'}, {'frequency': 'c', 'id': 1126, 'synset': 'tortilla.n.01', 'synonyms': ['tortilla'], 'def': 'thin unleavened pancake made from cornmeal or wheat flour', 'name': 'tortilla'}, {'frequency': 'c', 'id': 1127, 'synset': 'tow_truck.n.01', 'synonyms': ['tow_truck'], 'def': 'a truck equipped to hoist and pull wrecked cars (or to remove cars from no-parking zones)', 'name': 'tow_truck'}, {'frequency': 'f', 'id': 1128, 'synset': 'towel.n.01', 'synonyms': ['towel'], 'def': 'a rectangular piece of absorbent cloth (or paper) for drying or wiping', 'name': 'towel'}, {'frequency': 'f', 'id': 1129, 'synset': 'towel_rack.n.01', 'synonyms': ['towel_rack', 'towel_rail', 'towel_bar'], 'def': 'a rack consisting of one or more bars on which towels can be hung', 'name': 'towel_rack'}, {'frequency': 'f', 'id': 1130, 'synset': 'toy.n.03', 'synonyms': ['toy'], 'def': 'a device regarded as providing amusement', 'name': 'toy'}, {'frequency': 'c', 'id': 1131, 'synset': 'tractor.n.01', 'synonyms': ['tractor_(farm_equipment)'], 'def': 'a wheeled vehicle with large wheels; used in farming and other applications', 'name': 'tractor_(farm_equipment)'}, {'frequency': 'f', 'id': 1132, 'synset': 'traffic_light.n.01', 'synonyms': ['traffic_light'], 'def': 'a device to control vehicle traffic often consisting of three or more lights', 'name': 'traffic_light'}, {'frequency': 'r', 'id': 1133, 'synset': 'trail_bike.n.01', 'synonyms': ['dirt_bike'], 'def': 'a lightweight motorcycle equipped with rugged tires and suspension for off-road use', 'name': 'dirt_bike'}, {'frequency': 'c', 'id': 1134, 'synset': 'trailer_truck.n.01', 'synonyms': ['trailer_truck', 'tractor_trailer', 'trucking_rig', 'articulated_lorry', 'semi_truck'], 'def': 'a truck consisting of a tractor and trailer together', 'name': 'trailer_truck'}, {'frequency': 'f', 'id': 1135, 'synset': 'train.n.01', 'synonyms': ['train_(railroad_vehicle)', 'railroad_train'], 'def': 'public or private transport provided by a line of railway cars coupled together and drawn by a locomotive', 'name': 'train_(railroad_vehicle)'}, {'frequency': 'r', 'id': 1136, 'synset': 'trampoline.n.01', 'synonyms': ['trampoline'], 'def': 'gymnastic apparatus consisting of a strong canvas sheet attached with springs to a metal frame', 'name': 'trampoline'}, {'frequency': 'f', 'id': 1137, 'synset': 'tray.n.01', 'synonyms': ['tray'], 'def': 'an open receptacle for holding or displaying or serving articles or food', 'name': 'tray'}, {'frequency': 'r', 'id': 1138, 'synset': 'tree_house.n.01', 'synonyms': ['tree_house'], 'def': '(NOT A TREE) a PLAYHOUSE built in the branches of a tree', 'name': 'tree_house'}, {'frequency': 'r', 'id': 1139, 'synset': 'trench_coat.n.01', 'synonyms': ['trench_coat'], 'def': 'a military style raincoat; belted with deep pockets', 'name': 'trench_coat'}, {'frequency': 'r', 'id': 1140, 'synset': 'triangle.n.05', 'synonyms': ['triangle_(musical_instrument)'], 'def': 'a percussion instrument consisting of a metal bar bent in the shape of an open triangle', 'name': 'triangle_(musical_instrument)'}, {'frequency': 'r', 'id': 1141, 'synset': 'tricycle.n.01', 'synonyms': ['tricycle'], 'def': 'a vehicle with three wheels that is moved by foot pedals', 'name': 'tricycle'}, {'frequency': 'c', 'id': 1142, 'synset': 'tripod.n.01', 'synonyms': ['tripod'], 'def': 'a three-legged rack used for support', 'name': 'tripod'}, {'frequency': 'f', 'id': 1143, 'synset': 'trouser.n.01', 'synonyms': ['trousers', 'pants_(clothing)'], 'def': 'a garment extending from the waist to the knee or ankle, covering each leg separately', 'name': 'trousers'}, {'frequency': 'f', 'id': 1144, 'synset': 'truck.n.01', 'synonyms': ['truck'], 'def': 'an automotive vehicle suitable for hauling', 'name': 'truck'}, {'frequency': 'r', 'id': 1145, 'synset': 'truffle.n.03', 'synonyms': ['truffle_(chocolate)', 'chocolate_truffle'], 'def': 'creamy chocolate candy', 'name': 'truffle_(chocolate)'}, {'frequency': 'c', 'id': 1146, 'synset': 'trunk.n.02', 'synonyms': ['trunk'], 'def': 'luggage consisting of a large strong case used when traveling or for storage', 'name': 'trunk'}, {'frequency': 'r', 'id': 1147, 'synset': 'tub.n.02', 'synonyms': ['vat'], 'def': 'a large open vessel for holding or storing liquids', 'name': 'vat'}, {'frequency': 'c', 'id': 1148, 'synset': 'turban.n.01', 'synonyms': ['turban'], 'def': 'a traditional headdress consisting of a long scarf wrapped around the head', 'name': 'turban'}, {'frequency': 'r', 'id': 1149, 'synset': 'turkey.n.01', 'synonyms': ['turkey_(bird)'], 'def': 'large gallinaceous bird with fan-shaped tail; widely domesticated for food', 'name': 'turkey_(bird)'}, {'frequency': 'c', 'id': 1150, 'synset': 'turkey.n.04', 'synonyms': ['turkey_(food)'], 'def': 'flesh of large domesticated fowl usually roasted', 'name': 'turkey_(food)'}, {'frequency': 'r', 'id': 1151, 'synset': 'turnip.n.01', 'synonyms': ['turnip'], 'def': 'widely cultivated plant having a large fleshy edible white or yellow root', 'name': 'turnip'}, {'frequency': 'c', 'id': 1152, 'synset': 'turtle.n.02', 'synonyms': ['turtle'], 'def': 'any of various aquatic and land reptiles having a bony shell and flipper-like limbs for swimming', 'name': 'turtle'}, {'frequency': 'r', 'id': 1153, 'synset': 'turtleneck.n.01', 'synonyms': ['turtleneck_(clothing)', 'polo-neck'], 'def': 'a sweater or jersey with a high close-fitting collar', 'name': 'turtleneck_(clothing)'}, {'frequency': 'r', 'id': 1154, 'synset': 'typewriter.n.01', 'synonyms': ['typewriter'], 'def': 'hand-operated character printer for printing written messages one character at a time', 'name': 'typewriter'}, {'frequency': 'f', 'id': 1155, 'synset': 'umbrella.n.01', 'synonyms': ['umbrella'], 'def': 'a lightweight handheld collapsible canopy', 'name': 'umbrella'}, {'frequency': 'c', 'id': 1156, 'synset': 'underwear.n.01', 'synonyms': ['underwear', 'underclothes', 'underclothing', 'underpants'], 'def': 'undergarment worn next to the skin and under the outer garments', 'name': 'underwear'}, {'frequency': 'r', 'id': 1157, 'synset': 'unicycle.n.01', 'synonyms': ['unicycle'], 'def': 'a vehicle with a single wheel that is driven by pedals', 'name': 'unicycle'}, {'frequency': 'c', 'id': 1158, 'synset': 'urinal.n.01', 'synonyms': ['urinal'], 'def': 'a plumbing fixture (usually attached to the wall) used by men to urinate', 'name': 'urinal'}, {'frequency': 'r', 'id': 1159, 'synset': 'urn.n.01', 'synonyms': ['urn'], 'def': 'a large vase that usually has a pedestal or feet', 'name': 'urn'}, {'frequency': 'c', 'id': 1160, 'synset': 'vacuum.n.04', 'synonyms': ['vacuum_cleaner'], 'def': 'an electrical home appliance that cleans by suction', 'name': 'vacuum_cleaner'}, {'frequency': 'c', 'id': 1161, 'synset': 'valve.n.03', 'synonyms': ['valve'], 'def': 'control consisting of a mechanical device for controlling the flow of a fluid', 'name': 'valve'}, {'frequency': 'f', 'id': 1162, 'synset': 'vase.n.01', 'synonyms': ['vase'], 'def': 'an open jar of glass or porcelain used as an ornament or to hold flowers', 'name': 'vase'}, {'frequency': 'c', 'id': 1163, 'synset': 'vending_machine.n.01', 'synonyms': ['vending_machine'], 'def': 'a slot machine for selling goods', 'name': 'vending_machine'}, {'frequency': 'f', 'id': 1164, 'synset': 'vent.n.01', 'synonyms': ['vent', 'blowhole', 'air_vent'], 'def': 'a hole for the escape of gas or air', 'name': 'vent'}, {'frequency': 'c', 'id': 1165, 'synset': 'videotape.n.01', 'synonyms': ['videotape'], 'def': 'a video recording made on magnetic tape', 'name': 'videotape'}, {'frequency': 'r', 'id': 1166, 'synset': 'vinegar.n.01', 'synonyms': ['vinegar'], 'def': 'sour-tasting liquid produced usually by oxidation of the alcohol in wine or cider and used as a condiment or food preservative', 'name': 'vinegar'}, {'frequency': 'r', 'id': 1167, 'synset': 'violin.n.01', 'synonyms': ['violin', 'fiddle'], 'def': 'bowed stringed instrument that is the highest member of the violin family', 'name': 'violin'}, {'frequency': 'r', 'id': 1168, 'synset': 'vodka.n.01', 'synonyms': ['vodka'], 'def': 'unaged colorless liquor originating in Russia', 'name': 'vodka'}, {'frequency': 'r', 'id': 1169, 'synset': 'volleyball.n.02', 'synonyms': ['volleyball'], 'def': 'an inflated ball used in playing volleyball', 'name': 'volleyball'}, {'frequency': 'r', 'id': 1170, 'synset': 'vulture.n.01', 'synonyms': ['vulture'], 'def': 'any of various large birds of prey having naked heads and weak claws and feeding chiefly on carrion', 'name': 'vulture'}, {'frequency': 'c', 'id': 1171, 'synset': 'waffle.n.01', 'synonyms': ['waffle'], 'def': 'pancake batter baked in a waffle iron', 'name': 'waffle'}, {'frequency': 'r', 'id': 1172, 'synset': 'waffle_iron.n.01', 'synonyms': ['waffle_iron'], 'def': 'a kitchen appliance for baking waffles', 'name': 'waffle_iron'}, {'frequency': 'c', 'id': 1173, 'synset': 'wagon.n.01', 'synonyms': ['wagon'], 'def': 'any of various kinds of wheeled vehicles drawn by an animal or a tractor', 'name': 'wagon'}, {'frequency': 'c', 'id': 1174, 'synset': 'wagon_wheel.n.01', 'synonyms': ['wagon_wheel'], 'def': 'a wheel of a wagon', 'name': 'wagon_wheel'}, {'frequency': 'c', 'id': 1175, 'synset': 'walking_stick.n.01', 'synonyms': ['walking_stick'], 'def': 'a stick carried in the hand for support in walking', 'name': 'walking_stick'}, {'frequency': 'c', 'id': 1176, 'synset': 'wall_clock.n.01', 'synonyms': ['wall_clock'], 'def': 'a clock mounted on a wall', 'name': 'wall_clock'}, {'frequency': 'f', 'id': 1177, 'synset': 'wall_socket.n.01', 'synonyms': ['wall_socket', 'wall_plug', 'electric_outlet', 'electrical_outlet', 'outlet', 'electric_receptacle'], 'def': 'receptacle providing a place in a wiring system where current can be taken to run electrical devices', 'name': 'wall_socket'}, {'frequency': 'c', 'id': 1178, 'synset': 'wallet.n.01', 'synonyms': ['wallet', 'billfold'], 'def': 'a pocket-size case for holding papers and paper money', 'name': 'wallet'}, {'frequency': 'r', 'id': 1179, 'synset': 'walrus.n.01', 'synonyms': ['walrus'], 'def': 'either of two large northern marine mammals having ivory tusks and tough hide over thick blubber', 'name': 'walrus'}, {'frequency': 'r', 'id': 1180, 'synset': 'wardrobe.n.01', 'synonyms': ['wardrobe'], 'def': 'a tall piece of furniture that provides storage space for clothes; has a door and rails or hooks for hanging clothes', 'name': 'wardrobe'}, {'frequency': 'r', 'id': 1181, 'synset': 'wasabi.n.02', 'synonyms': ['wasabi'], 'def': 'the thick green root of the wasabi plant that the Japanese use in cooking and that tastes like strong horseradish', 'name': 'wasabi'}, {'frequency': 'c', 'id': 1182, 'synset': 'washer.n.03', 'synonyms': ['automatic_washer', 'washing_machine'], 'def': 'a home appliance for washing clothes and linens automatically', 'name': 'automatic_washer'}, {'frequency': 'f', 'id': 1183, 'synset': 'watch.n.01', 'synonyms': ['watch', 'wristwatch'], 'def': 'a small, portable timepiece', 'name': 'watch'}, {'frequency': 'f', 'id': 1184, 'synset': 'water_bottle.n.01', 'synonyms': ['water_bottle'], 'def': 'a bottle for holding water', 'name': 'water_bottle'}, {'frequency': 'c', 'id': 1185, 'synset': 'water_cooler.n.01', 'synonyms': ['water_cooler'], 'def': 'a device for cooling and dispensing drinking water', 'name': 'water_cooler'}, {'frequency': 'c', 'id': 1186, 'synset': 'water_faucet.n.01', 'synonyms': ['water_faucet', 'water_tap', 'tap_(water_faucet)'], 'def': 'a faucet for drawing water from a pipe or cask', 'name': 'water_faucet'}, {'frequency': 'r', 'id': 1187, 'synset': 'water_filter.n.01', 'synonyms': ['water_filter'], 'def': 'a filter to remove impurities from the water supply', 'name': 'water_filter'}, {'frequency': 'r', 'id': 1188, 'synset': 'water_heater.n.01', 'synonyms': ['water_heater', 'hot-water_heater'], 'def': 'a heater and storage tank to supply heated water', 'name': 'water_heater'}, {'frequency': 'r', 'id': 1189, 'synset': 'water_jug.n.01', 'synonyms': ['water_jug'], 'def': 'a jug that holds water', 'name': 'water_jug'}, {'frequency': 'r', 'id': 1190, 'synset': 'water_pistol.n.01', 'synonyms': ['water_gun', 'squirt_gun'], 'def': 'plaything consisting of a toy pistol that squirts water', 'name': 'water_gun'}, {'frequency': 'c', 'id': 1191, 'synset': 'water_scooter.n.01', 'synonyms': ['water_scooter', 'sea_scooter', 'jet_ski'], 'def': 'a motorboat resembling a motor scooter (NOT A SURFBOARD OR WATER SKI)', 'name': 'water_scooter'}, {'frequency': 'c', 'id': 1192, 'synset': 'water_ski.n.01', 'synonyms': ['water_ski'], 'def': 'broad ski for skimming over water towed by a speedboat (DO NOT MARK WATER)', 'name': 'water_ski'}, {'frequency': 'c', 'id': 1193, 'synset': 'water_tower.n.01', 'synonyms': ['water_tower'], 'def': 'a large reservoir for water', 'name': 'water_tower'}, {'frequency': 'c', 'id': 1194, 'synset': 'watering_can.n.01', 'synonyms': ['watering_can'], 'def': 'a container with a handle and a spout with a perforated nozzle; used to sprinkle water over plants', 'name': 'watering_can'}, {'frequency': 'c', 'id': 1195, 'synset': 'watermelon.n.02', 'synonyms': ['watermelon'], 'def': 'large oblong or roundish melon with a hard green rind and sweet watery red or occasionally yellowish pulp', 'name': 'watermelon'}, {'frequency': 'f', 'id': 1196, 'synset': 'weathervane.n.01', 'synonyms': ['weathervane', 'vane_(weathervane)', 'wind_vane'], 'def': 'mechanical device attached to an elevated structure; rotates freely to show the direction of the wind', 'name': 'weathervane'}, {'frequency': 'c', 'id': 1197, 'synset': 'webcam.n.01', 'synonyms': ['webcam'], 'def': 'a digital camera designed to take digital photographs and transmit them over the internet', 'name': 'webcam'}, {'frequency': 'c', 'id': 1198, 'synset': 'wedding_cake.n.01', 'synonyms': ['wedding_cake', 'bridecake'], 'def': 'a rich cake with two or more tiers and covered with frosting and decorations; served at a wedding reception', 'name': 'wedding_cake'}, {'frequency': 'c', 'id': 1199, 'synset': 'wedding_ring.n.01', 'synonyms': ['wedding_ring', 'wedding_band'], 'def': 'a ring given to the bride and/or groom at the wedding', 'name': 'wedding_ring'}, {'frequency': 'f', 'id': 1200, 'synset': 'wet_suit.n.01', 'synonyms': ['wet_suit'], 'def': 'a close-fitting garment made of a permeable material; worn in cold water to retain body heat', 'name': 'wet_suit'}, {'frequency': 'f', 'id': 1201, 'synset': 'wheel.n.01', 'synonyms': ['wheel'], 'def': 'a circular frame with spokes (or a solid disc) that can rotate on a shaft or axle', 'name': 'wheel'}, {'frequency': 'c', 'id': 1202, 'synset': 'wheelchair.n.01', 'synonyms': ['wheelchair'], 'def': 'a movable chair mounted on large wheels', 'name': 'wheelchair'}, {'frequency': 'c', 'id': 1203, 'synset': 'whipped_cream.n.01', 'synonyms': ['whipped_cream'], 'def': 'cream that has been beaten until light and fluffy', 'name': 'whipped_cream'}, {'frequency': 'r', 'id': 1204, 'synset': 'whiskey.n.01', 'synonyms': ['whiskey'], 'def': 'a liquor made from fermented mash of grain', 'name': 'whiskey'}, {'frequency': 'r', 'id': 1205, 'synset': 'whistle.n.03', 'synonyms': ['whistle'], 'def': 'a small wind instrument that produces a whistling sound by blowing into it', 'name': 'whistle'}, {'frequency': 'r', 'id': 1206, 'synset': 'wick.n.02', 'synonyms': ['wick'], 'def': 'a loosely woven cord in a candle or oil lamp that is lit on fire', 'name': 'wick'}, {'frequency': 'c', 'id': 1207, 'synset': 'wig.n.01', 'synonyms': ['wig'], 'def': 'hairpiece covering the head and made of real or synthetic hair', 'name': 'wig'}, {'frequency': 'c', 'id': 1208, 'synset': 'wind_chime.n.01', 'synonyms': ['wind_chime'], 'def': 'a decorative arrangement of pieces of metal or glass or pottery that hang together loosely so the wind can cause them to tinkle', 'name': 'wind_chime'}, {'frequency': 'c', 'id': 1209, 'synset': 'windmill.n.01', 'synonyms': ['windmill'], 'def': 'a mill that is powered by the wind', 'name': 'windmill'}, {'frequency': 'c', 'id': 1210, 'synset': 'window_box.n.01', 'synonyms': ['window_box_(for_plants)'], 'def': 'a container for growing plants on a windowsill', 'name': 'window_box_(for_plants)'}, {'frequency': 'f', 'id': 1211, 'synset': 'windshield_wiper.n.01', 'synonyms': ['windshield_wiper', 'windscreen_wiper', 'wiper_(for_windshield/screen)'], 'def': 'a mechanical device that cleans the windshield', 'name': 'windshield_wiper'}, {'frequency': 'c', 'id': 1212, 'synset': 'windsock.n.01', 'synonyms': ['windsock', 'air_sock', 'air-sleeve', 'wind_sleeve', 'wind_cone'], 'def': 'a truncated cloth cone mounted on a mast/pole; shows wind direction', 'name': 'windsock'}, {'frequency': 'f', 'id': 1213, 'synset': 'wine_bottle.n.01', 'synonyms': ['wine_bottle'], 'def': 'a bottle for holding wine', 'name': 'wine_bottle'}, {'frequency': 'r', 'id': 1214, 'synset': 'wine_bucket.n.01', 'synonyms': ['wine_bucket', 'wine_cooler'], 'def': 'a bucket of ice used to chill a bottle of wine', 'name': 'wine_bucket'}, {'frequency': 'f', 'id': 1215, 'synset': 'wineglass.n.01', 'synonyms': ['wineglass'], 'def': 'a glass that has a stem and in which wine is served', 'name': 'wineglass'}, {'frequency': 'r', 'id': 1216, 'synset': 'wing_chair.n.01', 'synonyms': ['wing_chair'], 'def': 'easy chair having wings on each side of a high back', 'name': 'wing_chair'}, {'frequency': 'c', 'id': 1217, 'synset': 'winker.n.02', 'synonyms': ['blinder_(for_horses)'], 'def': 'blinds that prevent a horse from seeing something on either side', 'name': 'blinder_(for_horses)'}, {'frequency': 'c', 'id': 1218, 'synset': 'wok.n.01', 'synonyms': ['wok'], 'def': 'pan with a convex bottom; used for frying in Chinese cooking', 'name': 'wok'}, {'frequency': 'r', 'id': 1219, 'synset': 'wolf.n.01', 'synonyms': ['wolf'], 'def': 'a wild carnivorous mammal of the dog family, living and hunting in packs', 'name': 'wolf'}, {'frequency': 'c', 'id': 1220, 'synset': 'wooden_spoon.n.02', 'synonyms': ['wooden_spoon'], 'def': 'a spoon made of wood', 'name': 'wooden_spoon'}, {'frequency': 'c', 'id': 1221, 'synset': 'wreath.n.01', 'synonyms': ['wreath'], 'def': 'an arrangement of flowers, leaves, or stems fastened in a ring', 'name': 'wreath'}, {'frequency': 'c', 'id': 1222, 'synset': 'wrench.n.03', 'synonyms': ['wrench', 'spanner'], 'def': 'a hand tool that is used to hold or twist a nut or bolt', 'name': 'wrench'}, {'frequency': 'c', 'id': 1223, 'synset': 'wristband.n.01', 'synonyms': ['wristband'], 'def': 'band consisting of a part of a sleeve that covers the wrist', 'name': 'wristband'}, {'frequency': 'f', 'id': 1224, 'synset': 'wristlet.n.01', 'synonyms': ['wristlet', 'wrist_band'], 'def': 'a band or bracelet worn around the wrist', 'name': 'wristlet'}, {'frequency': 'r', 'id': 1225, 'synset': 'yacht.n.01', 'synonyms': ['yacht'], 'def': 'an expensive vessel propelled by sail or power and used for cruising or racing', 'name': 'yacht'}, {'frequency': 'r', 'id': 1226, 'synset': 'yak.n.02', 'synonyms': ['yak'], 'def': 'large long-haired wild ox of Tibet often domesticated', 'name': 'yak'}, {'frequency': 'c', 'id': 1227, 'synset': 'yogurt.n.01', 'synonyms': ['yogurt', 'yoghurt', 'yoghourt'], 'def': 'a custard-like food made from curdled milk', 'name': 'yogurt'}, {'frequency': 'r', 'id': 1228, 'synset': 'yoke.n.07', 'synonyms': ['yoke_(animal_equipment)'], 'def': 'gear joining two animals at the neck; NOT egg yolk', 'name': 'yoke_(animal_equipment)'}, {'frequency': 'f', 'id': 1229, 'synset': 'zebra.n.01', 'synonyms': ['zebra'], 'def': 'any of several fleet black-and-white striped African equines', 'name': 'zebra'}, {'frequency': 'c', 'id': 1230, 'synset': 'zucchini.n.02', 'synonyms': ['zucchini', 'courgette'], 'def': 'small cucumber-shaped vegetable marrow; typically dark green', 'name': 'zucchini'}] # noqa
+# fmt: on
diff --git a/detectron2/detectron2/data/datasets/lvis_v1_categories.py b/detectron2/detectron2/data/datasets/lvis_v1_categories.py
new file mode 100755
index 0000000..7374e69
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/lvis_v1_categories.py
@@ -0,0 +1,16 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Autogen with
+# with open("lvis_v1_val.json", "r") as f:
+# a = json.load(f)
+# c = a["categories"]
+# for x in c:
+# del x["image_count"]
+# del x["instance_count"]
+# LVIS_CATEGORIES = repr(c) + " # noqa"
+# with open("/tmp/lvis_categories.py", "wt") as f:
+# f.write(f"LVIS_CATEGORIES = {LVIS_CATEGORIES}")
+# Then paste the contents of that file below
+
+# fmt: off
+LVIS_CATEGORIES = [{'frequency': 'c', 'synset': 'aerosol.n.02', 'synonyms': ['aerosol_can', 'spray_can'], 'id': 1, 'def': 'a dispenser that holds a substance under pressure', 'name': 'aerosol_can'}, {'frequency': 'f', 'synset': 'air_conditioner.n.01', 'synonyms': ['air_conditioner'], 'id': 2, 'def': 'a machine that keeps air cool and dry', 'name': 'air_conditioner'}, {'frequency': 'f', 'synset': 'airplane.n.01', 'synonyms': ['airplane', 'aeroplane'], 'id': 3, 'def': 'an aircraft that has a fixed wing and is powered by propellers or jets', 'name': 'airplane'}, {'frequency': 'f', 'synset': 'alarm_clock.n.01', 'synonyms': ['alarm_clock'], 'id': 4, 'def': 'a clock that wakes a sleeper at some preset time', 'name': 'alarm_clock'}, {'frequency': 'c', 'synset': 'alcohol.n.01', 'synonyms': ['alcohol', 'alcoholic_beverage'], 'id': 5, 'def': 'a liquor or brew containing alcohol as the active agent', 'name': 'alcohol'}, {'frequency': 'c', 'synset': 'alligator.n.02', 'synonyms': ['alligator', 'gator'], 'id': 6, 'def': 'amphibious reptiles related to crocodiles but with shorter broader snouts', 'name': 'alligator'}, {'frequency': 'c', 'synset': 'almond.n.02', 'synonyms': ['almond'], 'id': 7, 'def': 'oval-shaped edible seed of the almond tree', 'name': 'almond'}, {'frequency': 'c', 'synset': 'ambulance.n.01', 'synonyms': ['ambulance'], 'id': 8, 'def': 'a vehicle that takes people to and from hospitals', 'name': 'ambulance'}, {'frequency': 'c', 'synset': 'amplifier.n.01', 'synonyms': ['amplifier'], 'id': 9, 'def': 'electronic equipment that increases strength of signals', 'name': 'amplifier'}, {'frequency': 'c', 'synset': 'anklet.n.03', 'synonyms': ['anklet', 'ankle_bracelet'], 'id': 10, 'def': 'an ornament worn around the ankle', 'name': 'anklet'}, {'frequency': 'f', 'synset': 'antenna.n.01', 'synonyms': ['antenna', 'aerial', 'transmitting_aerial'], 'id': 11, 'def': 'an electrical device that sends or receives radio or television signals', 'name': 'antenna'}, {'frequency': 'f', 'synset': 'apple.n.01', 'synonyms': ['apple'], 'id': 12, 'def': 'fruit with red or yellow or green skin and sweet to tart crisp whitish flesh', 'name': 'apple'}, {'frequency': 'r', 'synset': 'applesauce.n.01', 'synonyms': ['applesauce'], 'id': 13, 'def': 'puree of stewed apples usually sweetened and spiced', 'name': 'applesauce'}, {'frequency': 'r', 'synset': 'apricot.n.02', 'synonyms': ['apricot'], 'id': 14, 'def': 'downy yellow to rosy-colored fruit resembling a small peach', 'name': 'apricot'}, {'frequency': 'f', 'synset': 'apron.n.01', 'synonyms': ['apron'], 'id': 15, 'def': 'a garment of cloth that is tied about the waist and worn to protect clothing', 'name': 'apron'}, {'frequency': 'c', 'synset': 'aquarium.n.01', 'synonyms': ['aquarium', 'fish_tank'], 'id': 16, 'def': 'a tank/pool/bowl filled with water for keeping live fish and underwater animals', 'name': 'aquarium'}, {'frequency': 'r', 'synset': 'arctic.n.02', 'synonyms': ['arctic_(type_of_shoe)', 'galosh', 'golosh', 'rubber_(type_of_shoe)', 'gumshoe'], 'id': 17, 'def': 'a waterproof overshoe that protects shoes from water or snow', 'name': 'arctic_(type_of_shoe)'}, {'frequency': 'c', 'synset': 'armband.n.02', 'synonyms': ['armband'], 'id': 18, 'def': 'a band worn around the upper arm', 'name': 'armband'}, {'frequency': 'f', 'synset': 'armchair.n.01', 'synonyms': ['armchair'], 'id': 19, 'def': 'chair with a support on each side for arms', 'name': 'armchair'}, {'frequency': 'r', 'synset': 'armoire.n.01', 'synonyms': ['armoire'], 'id': 20, 'def': 'a large wardrobe or cabinet', 'name': 'armoire'}, {'frequency': 'r', 'synset': 'armor.n.01', 'synonyms': ['armor', 'armour'], 'id': 21, 'def': 'protective covering made of metal and used in combat', 'name': 'armor'}, {'frequency': 'c', 'synset': 'artichoke.n.02', 'synonyms': ['artichoke'], 'id': 22, 'def': 'a thistlelike flower head with edible fleshy leaves and heart', 'name': 'artichoke'}, {'frequency': 'f', 'synset': 'ashcan.n.01', 'synonyms': ['trash_can', 'garbage_can', 'wastebin', 'dustbin', 'trash_barrel', 'trash_bin'], 'id': 23, 'def': 'a bin that holds rubbish until it is collected', 'name': 'trash_can'}, {'frequency': 'c', 'synset': 'ashtray.n.01', 'synonyms': ['ashtray'], 'id': 24, 'def': "a receptacle for the ash from smokers' cigars or cigarettes", 'name': 'ashtray'}, {'frequency': 'c', 'synset': 'asparagus.n.02', 'synonyms': ['asparagus'], 'id': 25, 'def': 'edible young shoots of the asparagus plant', 'name': 'asparagus'}, {'frequency': 'c', 'synset': 'atomizer.n.01', 'synonyms': ['atomizer', 'atomiser', 'spray', 'sprayer', 'nebulizer', 'nebuliser'], 'id': 26, 'def': 'a dispenser that turns a liquid (such as perfume) into a fine mist', 'name': 'atomizer'}, {'frequency': 'f', 'synset': 'avocado.n.01', 'synonyms': ['avocado'], 'id': 27, 'def': 'a pear-shaped fruit with green or blackish skin and rich yellowish pulp enclosing a single large seed', 'name': 'avocado'}, {'frequency': 'c', 'synset': 'award.n.02', 'synonyms': ['award', 'accolade'], 'id': 28, 'def': 'a tangible symbol signifying approval or distinction', 'name': 'award'}, {'frequency': 'f', 'synset': 'awning.n.01', 'synonyms': ['awning'], 'id': 29, 'def': 'a canopy made of canvas to shelter people or things from rain or sun', 'name': 'awning'}, {'frequency': 'r', 'synset': 'ax.n.01', 'synonyms': ['ax', 'axe'], 'id': 30, 'def': 'an edge tool with a heavy bladed head mounted across a handle', 'name': 'ax'}, {'frequency': 'r', 'synset': 'baboon.n.01', 'synonyms': ['baboon'], 'id': 31, 'def': 'large terrestrial monkeys having doglike muzzles', 'name': 'baboon'}, {'frequency': 'f', 'synset': 'baby_buggy.n.01', 'synonyms': ['baby_buggy', 'baby_carriage', 'perambulator', 'pram', 'stroller'], 'id': 32, 'def': 'a small vehicle with four wheels in which a baby or child is pushed around', 'name': 'baby_buggy'}, {'frequency': 'c', 'synset': 'backboard.n.01', 'synonyms': ['basketball_backboard'], 'id': 33, 'def': 'a raised vertical board with basket attached; used to play basketball', 'name': 'basketball_backboard'}, {'frequency': 'f', 'synset': 'backpack.n.01', 'synonyms': ['backpack', 'knapsack', 'packsack', 'rucksack', 'haversack'], 'id': 34, 'def': 'a bag carried by a strap on your back or shoulder', 'name': 'backpack'}, {'frequency': 'f', 'synset': 'bag.n.04', 'synonyms': ['handbag', 'purse', 'pocketbook'], 'id': 35, 'def': 'a container used for carrying money and small personal items or accessories', 'name': 'handbag'}, {'frequency': 'f', 'synset': 'bag.n.06', 'synonyms': ['suitcase', 'baggage', 'luggage'], 'id': 36, 'def': 'cases used to carry belongings when traveling', 'name': 'suitcase'}, {'frequency': 'c', 'synset': 'bagel.n.01', 'synonyms': ['bagel', 'beigel'], 'id': 37, 'def': 'glazed yeast-raised doughnut-shaped roll with hard crust', 'name': 'bagel'}, {'frequency': 'r', 'synset': 'bagpipe.n.01', 'synonyms': ['bagpipe'], 'id': 38, 'def': 'a tubular wind instrument; the player blows air into a bag and squeezes it out', 'name': 'bagpipe'}, {'frequency': 'r', 'synset': 'baguet.n.01', 'synonyms': ['baguet', 'baguette'], 'id': 39, 'def': 'narrow French stick loaf', 'name': 'baguet'}, {'frequency': 'r', 'synset': 'bait.n.02', 'synonyms': ['bait', 'lure'], 'id': 40, 'def': 'something used to lure fish or other animals into danger so they can be trapped or killed', 'name': 'bait'}, {'frequency': 'f', 'synset': 'ball.n.06', 'synonyms': ['ball'], 'id': 41, 'def': 'a spherical object used as a plaything', 'name': 'ball'}, {'frequency': 'r', 'synset': 'ballet_skirt.n.01', 'synonyms': ['ballet_skirt', 'tutu'], 'id': 42, 'def': 'very short skirt worn by ballerinas', 'name': 'ballet_skirt'}, {'frequency': 'f', 'synset': 'balloon.n.01', 'synonyms': ['balloon'], 'id': 43, 'def': 'large tough nonrigid bag filled with gas or heated air', 'name': 'balloon'}, {'frequency': 'c', 'synset': 'bamboo.n.02', 'synonyms': ['bamboo'], 'id': 44, 'def': 'woody tropical grass having hollow woody stems', 'name': 'bamboo'}, {'frequency': 'f', 'synset': 'banana.n.02', 'synonyms': ['banana'], 'id': 45, 'def': 'elongated crescent-shaped yellow fruit with soft sweet flesh', 'name': 'banana'}, {'frequency': 'c', 'synset': 'band_aid.n.01', 'synonyms': ['Band_Aid'], 'id': 46, 'def': 'trade name for an adhesive bandage to cover small cuts or blisters', 'name': 'Band_Aid'}, {'frequency': 'c', 'synset': 'bandage.n.01', 'synonyms': ['bandage'], 'id': 47, 'def': 'a piece of soft material that covers and protects an injured part of the body', 'name': 'bandage'}, {'frequency': 'f', 'synset': 'bandanna.n.01', 'synonyms': ['bandanna', 'bandana'], 'id': 48, 'def': 'large and brightly colored handkerchief; often used as a neckerchief', 'name': 'bandanna'}, {'frequency': 'r', 'synset': 'banjo.n.01', 'synonyms': ['banjo'], 'id': 49, 'def': 'a stringed instrument of the guitar family with a long neck and circular body', 'name': 'banjo'}, {'frequency': 'f', 'synset': 'banner.n.01', 'synonyms': ['banner', 'streamer'], 'id': 50, 'def': 'long strip of cloth or paper used for decoration or advertising', 'name': 'banner'}, {'frequency': 'r', 'synset': 'barbell.n.01', 'synonyms': ['barbell'], 'id': 51, 'def': 'a bar to which heavy discs are attached at each end; used in weightlifting', 'name': 'barbell'}, {'frequency': 'r', 'synset': 'barge.n.01', 'synonyms': ['barge'], 'id': 52, 'def': 'a flatbottom boat for carrying heavy loads (especially on canals)', 'name': 'barge'}, {'frequency': 'f', 'synset': 'barrel.n.02', 'synonyms': ['barrel', 'cask'], 'id': 53, 'def': 'a cylindrical container that holds liquids', 'name': 'barrel'}, {'frequency': 'c', 'synset': 'barrette.n.01', 'synonyms': ['barrette'], 'id': 54, 'def': "a pin for holding women's hair in place", 'name': 'barrette'}, {'frequency': 'c', 'synset': 'barrow.n.03', 'synonyms': ['barrow', 'garden_cart', 'lawn_cart', 'wheelbarrow'], 'id': 55, 'def': 'a cart for carrying small loads; has handles and one or more wheels', 'name': 'barrow'}, {'frequency': 'f', 'synset': 'base.n.03', 'synonyms': ['baseball_base'], 'id': 56, 'def': 'a place that the runner must touch before scoring', 'name': 'baseball_base'}, {'frequency': 'f', 'synset': 'baseball.n.02', 'synonyms': ['baseball'], 'id': 57, 'def': 'a ball used in playing baseball', 'name': 'baseball'}, {'frequency': 'f', 'synset': 'baseball_bat.n.01', 'synonyms': ['baseball_bat'], 'id': 58, 'def': 'an implement used in baseball by the batter', 'name': 'baseball_bat'}, {'frequency': 'f', 'synset': 'baseball_cap.n.01', 'synonyms': ['baseball_cap', 'jockey_cap', 'golf_cap'], 'id': 59, 'def': 'a cap with a bill', 'name': 'baseball_cap'}, {'frequency': 'f', 'synset': 'baseball_glove.n.01', 'synonyms': ['baseball_glove', 'baseball_mitt'], 'id': 60, 'def': 'the handwear used by fielders in playing baseball', 'name': 'baseball_glove'}, {'frequency': 'f', 'synset': 'basket.n.01', 'synonyms': ['basket', 'handbasket'], 'id': 61, 'def': 'a container that is usually woven and has handles', 'name': 'basket'}, {'frequency': 'c', 'synset': 'basketball.n.02', 'synonyms': ['basketball'], 'id': 62, 'def': 'an inflated ball used in playing basketball', 'name': 'basketball'}, {'frequency': 'r', 'synset': 'bass_horn.n.01', 'synonyms': ['bass_horn', 'sousaphone', 'tuba'], 'id': 63, 'def': 'the lowest brass wind instrument', 'name': 'bass_horn'}, {'frequency': 'c', 'synset': 'bat.n.01', 'synonyms': ['bat_(animal)'], 'id': 64, 'def': 'nocturnal mouselike mammal with forelimbs modified to form membranous wings', 'name': 'bat_(animal)'}, {'frequency': 'f', 'synset': 'bath_mat.n.01', 'synonyms': ['bath_mat'], 'id': 65, 'def': 'a heavy towel or mat to stand on while drying yourself after a bath', 'name': 'bath_mat'}, {'frequency': 'f', 'synset': 'bath_towel.n.01', 'synonyms': ['bath_towel'], 'id': 66, 'def': 'a large towel; to dry yourself after a bath', 'name': 'bath_towel'}, {'frequency': 'c', 'synset': 'bathrobe.n.01', 'synonyms': ['bathrobe'], 'id': 67, 'def': 'a loose-fitting robe of towelling; worn after a bath or swim', 'name': 'bathrobe'}, {'frequency': 'f', 'synset': 'bathtub.n.01', 'synonyms': ['bathtub', 'bathing_tub'], 'id': 68, 'def': 'a large open container that you fill with water and use to wash the body', 'name': 'bathtub'}, {'frequency': 'r', 'synset': 'batter.n.02', 'synonyms': ['batter_(food)'], 'id': 69, 'def': 'a liquid or semiliquid mixture, as of flour, eggs, and milk, used in cooking', 'name': 'batter_(food)'}, {'frequency': 'c', 'synset': 'battery.n.02', 'synonyms': ['battery'], 'id': 70, 'def': 'a portable device that produces electricity', 'name': 'battery'}, {'frequency': 'r', 'synset': 'beach_ball.n.01', 'synonyms': ['beachball'], 'id': 71, 'def': 'large and light ball; for play at the seaside', 'name': 'beachball'}, {'frequency': 'c', 'synset': 'bead.n.01', 'synonyms': ['bead'], 'id': 72, 'def': 'a small ball with a hole through the middle used for ornamentation, jewellery, etc.', 'name': 'bead'}, {'frequency': 'c', 'synset': 'bean_curd.n.01', 'synonyms': ['bean_curd', 'tofu'], 'id': 73, 'def': 'cheeselike food made of curdled soybean milk', 'name': 'bean_curd'}, {'frequency': 'c', 'synset': 'beanbag.n.01', 'synonyms': ['beanbag'], 'id': 74, 'def': 'a bag filled with dried beans or similar items; used in games or to sit on', 'name': 'beanbag'}, {'frequency': 'f', 'synset': 'beanie.n.01', 'synonyms': ['beanie', 'beany'], 'id': 75, 'def': 'a small skullcap; formerly worn by schoolboys and college freshmen', 'name': 'beanie'}, {'frequency': 'f', 'synset': 'bear.n.01', 'synonyms': ['bear'], 'id': 76, 'def': 'large carnivorous or omnivorous mammals with shaggy coats and claws', 'name': 'bear'}, {'frequency': 'f', 'synset': 'bed.n.01', 'synonyms': ['bed'], 'id': 77, 'def': 'a piece of furniture that provides a place to sleep', 'name': 'bed'}, {'frequency': 'r', 'synset': 'bedpan.n.01', 'synonyms': ['bedpan'], 'id': 78, 'def': 'a shallow vessel used by a bedridden patient for defecation and urination', 'name': 'bedpan'}, {'frequency': 'f', 'synset': 'bedspread.n.01', 'synonyms': ['bedspread', 'bedcover', 'bed_covering', 'counterpane', 'spread'], 'id': 79, 'def': 'decorative cover for a bed', 'name': 'bedspread'}, {'frequency': 'f', 'synset': 'beef.n.01', 'synonyms': ['cow'], 'id': 80, 'def': 'cattle/cow', 'name': 'cow'}, {'frequency': 'f', 'synset': 'beef.n.02', 'synonyms': ['beef_(food)', 'boeuf_(food)'], 'id': 81, 'def': 'meat from an adult domestic bovine', 'name': 'beef_(food)'}, {'frequency': 'r', 'synset': 'beeper.n.01', 'synonyms': ['beeper', 'pager'], 'id': 82, 'def': 'an device that beeps when the person carrying it is being paged', 'name': 'beeper'}, {'frequency': 'f', 'synset': 'beer_bottle.n.01', 'synonyms': ['beer_bottle'], 'id': 83, 'def': 'a bottle that holds beer', 'name': 'beer_bottle'}, {'frequency': 'c', 'synset': 'beer_can.n.01', 'synonyms': ['beer_can'], 'id': 84, 'def': 'a can that holds beer', 'name': 'beer_can'}, {'frequency': 'r', 'synset': 'beetle.n.01', 'synonyms': ['beetle'], 'id': 85, 'def': 'insect with hard wing covers', 'name': 'beetle'}, {'frequency': 'f', 'synset': 'bell.n.01', 'synonyms': ['bell'], 'id': 86, 'def': 'a hollow device made of metal that makes a ringing sound when struck', 'name': 'bell'}, {'frequency': 'f', 'synset': 'bell_pepper.n.02', 'synonyms': ['bell_pepper', 'capsicum'], 'id': 87, 'def': 'large bell-shaped sweet pepper in green or red or yellow or orange or black varieties', 'name': 'bell_pepper'}, {'frequency': 'f', 'synset': 'belt.n.02', 'synonyms': ['belt'], 'id': 88, 'def': 'a band to tie or buckle around the body (usually at the waist)', 'name': 'belt'}, {'frequency': 'f', 'synset': 'belt_buckle.n.01', 'synonyms': ['belt_buckle'], 'id': 89, 'def': 'the buckle used to fasten a belt', 'name': 'belt_buckle'}, {'frequency': 'f', 'synset': 'bench.n.01', 'synonyms': ['bench'], 'id': 90, 'def': 'a long seat for more than one person', 'name': 'bench'}, {'frequency': 'c', 'synset': 'beret.n.01', 'synonyms': ['beret'], 'id': 91, 'def': 'a cap with no brim or bill; made of soft cloth', 'name': 'beret'}, {'frequency': 'c', 'synset': 'bib.n.02', 'synonyms': ['bib'], 'id': 92, 'def': 'a napkin tied under the chin of a child while eating', 'name': 'bib'}, {'frequency': 'r', 'synset': 'bible.n.01', 'synonyms': ['Bible'], 'id': 93, 'def': 'the sacred writings of the Christian religions', 'name': 'Bible'}, {'frequency': 'f', 'synset': 'bicycle.n.01', 'synonyms': ['bicycle', 'bike_(bicycle)'], 'id': 94, 'def': 'a wheeled vehicle that has two wheels and is moved by foot pedals', 'name': 'bicycle'}, {'frequency': 'f', 'synset': 'bill.n.09', 'synonyms': ['visor', 'vizor'], 'id': 95, 'def': 'a brim that projects to the front to shade the eyes', 'name': 'visor'}, {'frequency': 'f', 'synset': 'billboard.n.01', 'synonyms': ['billboard'], 'id': 96, 'def': 'large outdoor signboard', 'name': 'billboard'}, {'frequency': 'c', 'synset': 'binder.n.03', 'synonyms': ['binder', 'ring-binder'], 'id': 97, 'def': 'holds loose papers or magazines', 'name': 'binder'}, {'frequency': 'c', 'synset': 'binoculars.n.01', 'synonyms': ['binoculars', 'field_glasses', 'opera_glasses'], 'id': 98, 'def': 'an optical instrument designed for simultaneous use by both eyes', 'name': 'binoculars'}, {'frequency': 'f', 'synset': 'bird.n.01', 'synonyms': ['bird'], 'id': 99, 'def': 'animal characterized by feathers and wings', 'name': 'bird'}, {'frequency': 'c', 'synset': 'bird_feeder.n.01', 'synonyms': ['birdfeeder'], 'id': 100, 'def': 'an outdoor device that supplies food for wild birds', 'name': 'birdfeeder'}, {'frequency': 'c', 'synset': 'birdbath.n.01', 'synonyms': ['birdbath'], 'id': 101, 'def': 'an ornamental basin (usually in a garden) for birds to bathe in', 'name': 'birdbath'}, {'frequency': 'c', 'synset': 'birdcage.n.01', 'synonyms': ['birdcage'], 'id': 102, 'def': 'a cage in which a bird can be kept', 'name': 'birdcage'}, {'frequency': 'c', 'synset': 'birdhouse.n.01', 'synonyms': ['birdhouse'], 'id': 103, 'def': 'a shelter for birds', 'name': 'birdhouse'}, {'frequency': 'f', 'synset': 'birthday_cake.n.01', 'synonyms': ['birthday_cake'], 'id': 104, 'def': 'decorated cake served at a birthday party', 'name': 'birthday_cake'}, {'frequency': 'r', 'synset': 'birthday_card.n.01', 'synonyms': ['birthday_card'], 'id': 105, 'def': 'a card expressing a birthday greeting', 'name': 'birthday_card'}, {'frequency': 'r', 'synset': 'black_flag.n.01', 'synonyms': ['pirate_flag'], 'id': 106, 'def': 'a flag usually bearing a white skull and crossbones on a black background', 'name': 'pirate_flag'}, {'frequency': 'c', 'synset': 'black_sheep.n.02', 'synonyms': ['black_sheep'], 'id': 107, 'def': 'sheep with a black coat', 'name': 'black_sheep'}, {'frequency': 'c', 'synset': 'blackberry.n.01', 'synonyms': ['blackberry'], 'id': 108, 'def': 'large sweet black or very dark purple edible aggregate fruit', 'name': 'blackberry'}, {'frequency': 'f', 'synset': 'blackboard.n.01', 'synonyms': ['blackboard', 'chalkboard'], 'id': 109, 'def': 'sheet of slate; for writing with chalk', 'name': 'blackboard'}, {'frequency': 'f', 'synset': 'blanket.n.01', 'synonyms': ['blanket'], 'id': 110, 'def': 'bedding that keeps a person warm in bed', 'name': 'blanket'}, {'frequency': 'c', 'synset': 'blazer.n.01', 'synonyms': ['blazer', 'sport_jacket', 'sport_coat', 'sports_jacket', 'sports_coat'], 'id': 111, 'def': 'lightweight jacket; often striped in the colors of a club or school', 'name': 'blazer'}, {'frequency': 'f', 'synset': 'blender.n.01', 'synonyms': ['blender', 'liquidizer', 'liquidiser'], 'id': 112, 'def': 'an electrically powered mixer that mix or chop or liquefy foods', 'name': 'blender'}, {'frequency': 'r', 'synset': 'blimp.n.02', 'synonyms': ['blimp'], 'id': 113, 'def': 'a small nonrigid airship used for observation or as a barrage balloon', 'name': 'blimp'}, {'frequency': 'f', 'synset': 'blinker.n.01', 'synonyms': ['blinker', 'flasher'], 'id': 114, 'def': 'a light that flashes on and off; used as a signal or to send messages', 'name': 'blinker'}, {'frequency': 'f', 'synset': 'blouse.n.01', 'synonyms': ['blouse'], 'id': 115, 'def': 'a top worn by women', 'name': 'blouse'}, {'frequency': 'f', 'synset': 'blueberry.n.02', 'synonyms': ['blueberry'], 'id': 116, 'def': 'sweet edible dark-blue berries of blueberry plants', 'name': 'blueberry'}, {'frequency': 'r', 'synset': 'board.n.09', 'synonyms': ['gameboard'], 'id': 117, 'def': 'a flat portable surface (usually rectangular) designed for board games', 'name': 'gameboard'}, {'frequency': 'f', 'synset': 'boat.n.01', 'synonyms': ['boat', 'ship_(boat)'], 'id': 118, 'def': 'a vessel for travel on water', 'name': 'boat'}, {'frequency': 'r', 'synset': 'bob.n.05', 'synonyms': ['bob', 'bobber', 'bobfloat'], 'id': 119, 'def': 'a small float usually made of cork; attached to a fishing line', 'name': 'bob'}, {'frequency': 'c', 'synset': 'bobbin.n.01', 'synonyms': ['bobbin', 'spool', 'reel'], 'id': 120, 'def': 'a thing around which thread/tape/film or other flexible materials can be wound', 'name': 'bobbin'}, {'frequency': 'c', 'synset': 'bobby_pin.n.01', 'synonyms': ['bobby_pin', 'hairgrip'], 'id': 121, 'def': 'a flat wire hairpin used to hold bobbed hair in place', 'name': 'bobby_pin'}, {'frequency': 'c', 'synset': 'boiled_egg.n.01', 'synonyms': ['boiled_egg', 'coddled_egg'], 'id': 122, 'def': 'egg cooked briefly in the shell in gently boiling water', 'name': 'boiled_egg'}, {'frequency': 'r', 'synset': 'bolo_tie.n.01', 'synonyms': ['bolo_tie', 'bolo', 'bola_tie', 'bola'], 'id': 123, 'def': 'a cord fastened around the neck with an ornamental clasp and worn as a necktie', 'name': 'bolo_tie'}, {'frequency': 'c', 'synset': 'bolt.n.03', 'synonyms': ['deadbolt'], 'id': 124, 'def': 'the part of a lock that is engaged or withdrawn with a key', 'name': 'deadbolt'}, {'frequency': 'f', 'synset': 'bolt.n.06', 'synonyms': ['bolt'], 'id': 125, 'def': 'a screw that screws into a nut to form a fastener', 'name': 'bolt'}, {'frequency': 'r', 'synset': 'bonnet.n.01', 'synonyms': ['bonnet'], 'id': 126, 'def': 'a hat tied under the chin', 'name': 'bonnet'}, {'frequency': 'f', 'synset': 'book.n.01', 'synonyms': ['book'], 'id': 127, 'def': 'a written work or composition that has been published', 'name': 'book'}, {'frequency': 'c', 'synset': 'bookcase.n.01', 'synonyms': ['bookcase'], 'id': 128, 'def': 'a piece of furniture with shelves for storing books', 'name': 'bookcase'}, {'frequency': 'c', 'synset': 'booklet.n.01', 'synonyms': ['booklet', 'brochure', 'leaflet', 'pamphlet'], 'id': 129, 'def': 'a small book usually having a paper cover', 'name': 'booklet'}, {'frequency': 'r', 'synset': 'bookmark.n.01', 'synonyms': ['bookmark', 'bookmarker'], 'id': 130, 'def': 'a marker (a piece of paper or ribbon) placed between the pages of a book', 'name': 'bookmark'}, {'frequency': 'r', 'synset': 'boom.n.04', 'synonyms': ['boom_microphone', 'microphone_boom'], 'id': 131, 'def': 'a pole carrying an overhead microphone projected over a film or tv set', 'name': 'boom_microphone'}, {'frequency': 'f', 'synset': 'boot.n.01', 'synonyms': ['boot'], 'id': 132, 'def': 'footwear that covers the whole foot and lower leg', 'name': 'boot'}, {'frequency': 'f', 'synset': 'bottle.n.01', 'synonyms': ['bottle'], 'id': 133, 'def': 'a glass or plastic vessel used for storing drinks or other liquids', 'name': 'bottle'}, {'frequency': 'c', 'synset': 'bottle_opener.n.01', 'synonyms': ['bottle_opener'], 'id': 134, 'def': 'an opener for removing caps or corks from bottles', 'name': 'bottle_opener'}, {'frequency': 'c', 'synset': 'bouquet.n.01', 'synonyms': ['bouquet'], 'id': 135, 'def': 'an arrangement of flowers that is usually given as a present', 'name': 'bouquet'}, {'frequency': 'r', 'synset': 'bow.n.04', 'synonyms': ['bow_(weapon)'], 'id': 136, 'def': 'a weapon for shooting arrows', 'name': 'bow_(weapon)'}, {'frequency': 'f', 'synset': 'bow.n.08', 'synonyms': ['bow_(decorative_ribbons)'], 'id': 137, 'def': 'a decorative interlacing of ribbons', 'name': 'bow_(decorative_ribbons)'}, {'frequency': 'f', 'synset': 'bow_tie.n.01', 'synonyms': ['bow-tie', 'bowtie'], 'id': 138, 'def': "a man's tie that ties in a bow", 'name': 'bow-tie'}, {'frequency': 'f', 'synset': 'bowl.n.03', 'synonyms': ['bowl'], 'id': 139, 'def': 'a dish that is round and open at the top for serving foods', 'name': 'bowl'}, {'frequency': 'r', 'synset': 'bowl.n.08', 'synonyms': ['pipe_bowl'], 'id': 140, 'def': 'a small round container that is open at the top for holding tobacco', 'name': 'pipe_bowl'}, {'frequency': 'c', 'synset': 'bowler_hat.n.01', 'synonyms': ['bowler_hat', 'bowler', 'derby_hat', 'derby', 'plug_hat'], 'id': 141, 'def': 'a felt hat that is round and hard with a narrow brim', 'name': 'bowler_hat'}, {'frequency': 'r', 'synset': 'bowling_ball.n.01', 'synonyms': ['bowling_ball'], 'id': 142, 'def': 'a large ball with finger holes used in the sport of bowling', 'name': 'bowling_ball'}, {'frequency': 'f', 'synset': 'box.n.01', 'synonyms': ['box'], 'id': 143, 'def': 'a (usually rectangular) container; may have a lid', 'name': 'box'}, {'frequency': 'r', 'synset': 'boxing_glove.n.01', 'synonyms': ['boxing_glove'], 'id': 144, 'def': 'large glove coverings the fists of a fighter worn for the sport of boxing', 'name': 'boxing_glove'}, {'frequency': 'c', 'synset': 'brace.n.06', 'synonyms': ['suspenders'], 'id': 145, 'def': 'elastic straps that hold trousers up (usually used in the plural)', 'name': 'suspenders'}, {'frequency': 'f', 'synset': 'bracelet.n.02', 'synonyms': ['bracelet', 'bangle'], 'id': 146, 'def': 'jewelry worn around the wrist for decoration', 'name': 'bracelet'}, {'frequency': 'r', 'synset': 'brass.n.07', 'synonyms': ['brass_plaque'], 'id': 147, 'def': 'a memorial made of brass', 'name': 'brass_plaque'}, {'frequency': 'c', 'synset': 'brassiere.n.01', 'synonyms': ['brassiere', 'bra', 'bandeau'], 'id': 148, 'def': 'an undergarment worn by women to support their breasts', 'name': 'brassiere'}, {'frequency': 'c', 'synset': 'bread-bin.n.01', 'synonyms': ['bread-bin', 'breadbox'], 'id': 149, 'def': 'a container used to keep bread or cake in', 'name': 'bread-bin'}, {'frequency': 'f', 'synset': 'bread.n.01', 'synonyms': ['bread'], 'id': 150, 'def': 'food made from dough of flour or meal and usually raised with yeast or baking powder and then baked', 'name': 'bread'}, {'frequency': 'r', 'synset': 'breechcloth.n.01', 'synonyms': ['breechcloth', 'breechclout', 'loincloth'], 'id': 151, 'def': 'a garment that provides covering for the loins', 'name': 'breechcloth'}, {'frequency': 'f', 'synset': 'bridal_gown.n.01', 'synonyms': ['bridal_gown', 'wedding_gown', 'wedding_dress'], 'id': 152, 'def': 'a gown worn by the bride at a wedding', 'name': 'bridal_gown'}, {'frequency': 'c', 'synset': 'briefcase.n.01', 'synonyms': ['briefcase'], 'id': 153, 'def': 'a case with a handle; for carrying papers or files or books', 'name': 'briefcase'}, {'frequency': 'f', 'synset': 'broccoli.n.01', 'synonyms': ['broccoli'], 'id': 154, 'def': 'plant with dense clusters of tight green flower buds', 'name': 'broccoli'}, {'frequency': 'r', 'synset': 'brooch.n.01', 'synonyms': ['broach'], 'id': 155, 'def': 'a decorative pin worn by women', 'name': 'broach'}, {'frequency': 'c', 'synset': 'broom.n.01', 'synonyms': ['broom'], 'id': 156, 'def': 'bundle of straws or twigs attached to a long handle; used for cleaning', 'name': 'broom'}, {'frequency': 'c', 'synset': 'brownie.n.03', 'synonyms': ['brownie'], 'id': 157, 'def': 'square or bar of very rich chocolate cake usually with nuts', 'name': 'brownie'}, {'frequency': 'c', 'synset': 'brussels_sprouts.n.01', 'synonyms': ['brussels_sprouts'], 'id': 158, 'def': 'the small edible cabbage-like buds growing along a stalk', 'name': 'brussels_sprouts'}, {'frequency': 'r', 'synset': 'bubble_gum.n.01', 'synonyms': ['bubble_gum'], 'id': 159, 'def': 'a kind of chewing gum that can be blown into bubbles', 'name': 'bubble_gum'}, {'frequency': 'f', 'synset': 'bucket.n.01', 'synonyms': ['bucket', 'pail'], 'id': 160, 'def': 'a roughly cylindrical vessel that is open at the top', 'name': 'bucket'}, {'frequency': 'r', 'synset': 'buggy.n.01', 'synonyms': ['horse_buggy'], 'id': 161, 'def': 'a small lightweight carriage; drawn by a single horse', 'name': 'horse_buggy'}, {'frequency': 'c', 'synset': 'bull.n.11', 'synonyms': ['horned_cow'], 'id': 162, 'def': 'a cow with horns', 'name': 'bull'}, {'frequency': 'c', 'synset': 'bulldog.n.01', 'synonyms': ['bulldog'], 'id': 163, 'def': 'a thickset short-haired dog with a large head and strong undershot lower jaw', 'name': 'bulldog'}, {'frequency': 'r', 'synset': 'bulldozer.n.01', 'synonyms': ['bulldozer', 'dozer'], 'id': 164, 'def': 'large powerful tractor; a large blade in front flattens areas of ground', 'name': 'bulldozer'}, {'frequency': 'c', 'synset': 'bullet_train.n.01', 'synonyms': ['bullet_train'], 'id': 165, 'def': 'a high-speed passenger train', 'name': 'bullet_train'}, {'frequency': 'c', 'synset': 'bulletin_board.n.02', 'synonyms': ['bulletin_board', 'notice_board'], 'id': 166, 'def': 'a board that hangs on a wall; displays announcements', 'name': 'bulletin_board'}, {'frequency': 'r', 'synset': 'bulletproof_vest.n.01', 'synonyms': ['bulletproof_vest'], 'id': 167, 'def': 'a vest capable of resisting the impact of a bullet', 'name': 'bulletproof_vest'}, {'frequency': 'c', 'synset': 'bullhorn.n.01', 'synonyms': ['bullhorn', 'megaphone'], 'id': 168, 'def': 'a portable loudspeaker with built-in microphone and amplifier', 'name': 'bullhorn'}, {'frequency': 'f', 'synset': 'bun.n.01', 'synonyms': ['bun', 'roll'], 'id': 169, 'def': 'small rounded bread either plain or sweet', 'name': 'bun'}, {'frequency': 'c', 'synset': 'bunk_bed.n.01', 'synonyms': ['bunk_bed'], 'id': 170, 'def': 'beds built one above the other', 'name': 'bunk_bed'}, {'frequency': 'f', 'synset': 'buoy.n.01', 'synonyms': ['buoy'], 'id': 171, 'def': 'a float attached by rope to the seabed to mark channels in a harbor or underwater hazards', 'name': 'buoy'}, {'frequency': 'r', 'synset': 'burrito.n.01', 'synonyms': ['burrito'], 'id': 172, 'def': 'a flour tortilla folded around a filling', 'name': 'burrito'}, {'frequency': 'f', 'synset': 'bus.n.01', 'synonyms': ['bus_(vehicle)', 'autobus', 'charabanc', 'double-decker', 'motorbus', 'motorcoach'], 'id': 173, 'def': 'a vehicle carrying many passengers; used for public transport', 'name': 'bus_(vehicle)'}, {'frequency': 'c', 'synset': 'business_card.n.01', 'synonyms': ['business_card'], 'id': 174, 'def': "a card on which are printed the person's name and business affiliation", 'name': 'business_card'}, {'frequency': 'f', 'synset': 'butter.n.01', 'synonyms': ['butter'], 'id': 175, 'def': 'an edible emulsion of fat globules made by churning milk or cream; for cooking and table use', 'name': 'butter'}, {'frequency': 'c', 'synset': 'butterfly.n.01', 'synonyms': ['butterfly'], 'id': 176, 'def': 'insect typically having a slender body with knobbed antennae and broad colorful wings', 'name': 'butterfly'}, {'frequency': 'f', 'synset': 'button.n.01', 'synonyms': ['button'], 'id': 177, 'def': 'a round fastener sewn to shirts and coats etc to fit through buttonholes', 'name': 'button'}, {'frequency': 'f', 'synset': 'cab.n.03', 'synonyms': ['cab_(taxi)', 'taxi', 'taxicab'], 'id': 178, 'def': 'a car that takes passengers where they want to go in exchange for money', 'name': 'cab_(taxi)'}, {'frequency': 'r', 'synset': 'cabana.n.01', 'synonyms': ['cabana'], 'id': 179, 'def': 'a small tent used as a dressing room beside the sea or a swimming pool', 'name': 'cabana'}, {'frequency': 'c', 'synset': 'cabin_car.n.01', 'synonyms': ['cabin_car', 'caboose'], 'id': 180, 'def': 'a car on a freight train for use of the train crew; usually the last car on the train', 'name': 'cabin_car'}, {'frequency': 'f', 'synset': 'cabinet.n.01', 'synonyms': ['cabinet'], 'id': 181, 'def': 'a piece of furniture resembling a cupboard with doors and shelves and drawers', 'name': 'cabinet'}, {'frequency': 'r', 'synset': 'cabinet.n.03', 'synonyms': ['locker', 'storage_locker'], 'id': 182, 'def': 'a storage compartment for clothes and valuables; usually it has a lock', 'name': 'locker'}, {'frequency': 'f', 'synset': 'cake.n.03', 'synonyms': ['cake'], 'id': 183, 'def': 'baked goods made from or based on a mixture of flour, sugar, eggs, and fat', 'name': 'cake'}, {'frequency': 'c', 'synset': 'calculator.n.02', 'synonyms': ['calculator'], 'id': 184, 'def': 'a small machine that is used for mathematical calculations', 'name': 'calculator'}, {'frequency': 'f', 'synset': 'calendar.n.02', 'synonyms': ['calendar'], 'id': 185, 'def': 'a list or register of events (appointments/social events/court cases, etc)', 'name': 'calendar'}, {'frequency': 'c', 'synset': 'calf.n.01', 'synonyms': ['calf'], 'id': 186, 'def': 'young of domestic cattle', 'name': 'calf'}, {'frequency': 'c', 'synset': 'camcorder.n.01', 'synonyms': ['camcorder'], 'id': 187, 'def': 'a portable television camera and videocassette recorder', 'name': 'camcorder'}, {'frequency': 'c', 'synset': 'camel.n.01', 'synonyms': ['camel'], 'id': 188, 'def': 'cud-chewing mammal used as a draft or saddle animal in desert regions', 'name': 'camel'}, {'frequency': 'f', 'synset': 'camera.n.01', 'synonyms': ['camera'], 'id': 189, 'def': 'equipment for taking photographs', 'name': 'camera'}, {'frequency': 'c', 'synset': 'camera_lens.n.01', 'synonyms': ['camera_lens'], 'id': 190, 'def': 'a lens that focuses the image in a camera', 'name': 'camera_lens'}, {'frequency': 'c', 'synset': 'camper.n.02', 'synonyms': ['camper_(vehicle)', 'camping_bus', 'motor_home'], 'id': 191, 'def': 'a recreational vehicle equipped for camping out while traveling', 'name': 'camper_(vehicle)'}, {'frequency': 'f', 'synset': 'can.n.01', 'synonyms': ['can', 'tin_can'], 'id': 192, 'def': 'airtight sealed metal container for food or drink or paint etc.', 'name': 'can'}, {'frequency': 'c', 'synset': 'can_opener.n.01', 'synonyms': ['can_opener', 'tin_opener'], 'id': 193, 'def': 'a device for cutting cans open', 'name': 'can_opener'}, {'frequency': 'f', 'synset': 'candle.n.01', 'synonyms': ['candle', 'candlestick'], 'id': 194, 'def': 'stick of wax with a wick in the middle', 'name': 'candle'}, {'frequency': 'f', 'synset': 'candlestick.n.01', 'synonyms': ['candle_holder'], 'id': 195, 'def': 'a holder with sockets for candles', 'name': 'candle_holder'}, {'frequency': 'r', 'synset': 'candy_bar.n.01', 'synonyms': ['candy_bar'], 'id': 196, 'def': 'a candy shaped as a bar', 'name': 'candy_bar'}, {'frequency': 'c', 'synset': 'candy_cane.n.01', 'synonyms': ['candy_cane'], 'id': 197, 'def': 'a hard candy in the shape of a rod (usually with stripes)', 'name': 'candy_cane'}, {'frequency': 'c', 'synset': 'cane.n.01', 'synonyms': ['walking_cane'], 'id': 198, 'def': 'a stick that people can lean on to help them walk', 'name': 'walking_cane'}, {'frequency': 'c', 'synset': 'canister.n.02', 'synonyms': ['canister', 'cannister'], 'id': 199, 'def': 'metal container for storing dry foods such as tea or flour', 'name': 'canister'}, {'frequency': 'c', 'synset': 'canoe.n.01', 'synonyms': ['canoe'], 'id': 200, 'def': 'small and light boat; pointed at both ends; propelled with a paddle', 'name': 'canoe'}, {'frequency': 'c', 'synset': 'cantaloup.n.02', 'synonyms': ['cantaloup', 'cantaloupe'], 'id': 201, 'def': 'the fruit of a cantaloup vine; small to medium-sized melon with yellowish flesh', 'name': 'cantaloup'}, {'frequency': 'r', 'synset': 'canteen.n.01', 'synonyms': ['canteen'], 'id': 202, 'def': 'a flask for carrying water; used by soldiers or travelers', 'name': 'canteen'}, {'frequency': 'f', 'synset': 'cap.n.01', 'synonyms': ['cap_(headwear)'], 'id': 203, 'def': 'a tight-fitting headwear', 'name': 'cap_(headwear)'}, {'frequency': 'f', 'synset': 'cap.n.02', 'synonyms': ['bottle_cap', 'cap_(container_lid)'], 'id': 204, 'def': 'a top (as for a bottle)', 'name': 'bottle_cap'}, {'frequency': 'c', 'synset': 'cape.n.02', 'synonyms': ['cape'], 'id': 205, 'def': 'a sleeveless garment like a cloak but shorter', 'name': 'cape'}, {'frequency': 'c', 'synset': 'cappuccino.n.01', 'synonyms': ['cappuccino', 'coffee_cappuccino'], 'id': 206, 'def': 'equal parts of espresso and steamed milk', 'name': 'cappuccino'}, {'frequency': 'f', 'synset': 'car.n.01', 'synonyms': ['car_(automobile)', 'auto_(automobile)', 'automobile'], 'id': 207, 'def': 'a motor vehicle with four wheels', 'name': 'car_(automobile)'}, {'frequency': 'f', 'synset': 'car.n.02', 'synonyms': ['railcar_(part_of_a_train)', 'railway_car_(part_of_a_train)', 'railroad_car_(part_of_a_train)'], 'id': 208, 'def': 'a wheeled vehicle adapted to the rails of railroad (mark each individual railcar separately)', 'name': 'railcar_(part_of_a_train)'}, {'frequency': 'r', 'synset': 'car.n.04', 'synonyms': ['elevator_car'], 'id': 209, 'def': 'where passengers ride up and down', 'name': 'elevator_car'}, {'frequency': 'r', 'synset': 'car_battery.n.01', 'synonyms': ['car_battery', 'automobile_battery'], 'id': 210, 'def': 'a battery in a motor vehicle', 'name': 'car_battery'}, {'frequency': 'c', 'synset': 'card.n.02', 'synonyms': ['identity_card'], 'id': 211, 'def': 'a card certifying the identity of the bearer', 'name': 'identity_card'}, {'frequency': 'c', 'synset': 'card.n.03', 'synonyms': ['card'], 'id': 212, 'def': 'a rectangular piece of paper used to send messages (e.g. greetings or pictures)', 'name': 'card'}, {'frequency': 'c', 'synset': 'cardigan.n.01', 'synonyms': ['cardigan'], 'id': 213, 'def': 'knitted jacket that is fastened up the front with buttons or a zipper', 'name': 'cardigan'}, {'frequency': 'r', 'synset': 'cargo_ship.n.01', 'synonyms': ['cargo_ship', 'cargo_vessel'], 'id': 214, 'def': 'a ship designed to carry cargo', 'name': 'cargo_ship'}, {'frequency': 'r', 'synset': 'carnation.n.01', 'synonyms': ['carnation'], 'id': 215, 'def': 'plant with pink to purple-red spice-scented usually double flowers', 'name': 'carnation'}, {'frequency': 'c', 'synset': 'carriage.n.02', 'synonyms': ['horse_carriage'], 'id': 216, 'def': 'a vehicle with wheels drawn by one or more horses', 'name': 'horse_carriage'}, {'frequency': 'f', 'synset': 'carrot.n.01', 'synonyms': ['carrot'], 'id': 217, 'def': 'deep orange edible root of the cultivated carrot plant', 'name': 'carrot'}, {'frequency': 'f', 'synset': 'carryall.n.01', 'synonyms': ['tote_bag'], 'id': 218, 'def': 'a capacious bag or basket', 'name': 'tote_bag'}, {'frequency': 'c', 'synset': 'cart.n.01', 'synonyms': ['cart'], 'id': 219, 'def': 'a heavy open wagon usually having two wheels and drawn by an animal', 'name': 'cart'}, {'frequency': 'c', 'synset': 'carton.n.02', 'synonyms': ['carton'], 'id': 220, 'def': 'a container made of cardboard for holding food or drink', 'name': 'carton'}, {'frequency': 'c', 'synset': 'cash_register.n.01', 'synonyms': ['cash_register', 'register_(for_cash_transactions)'], 'id': 221, 'def': 'a cashbox with an adding machine to register transactions', 'name': 'cash_register'}, {'frequency': 'r', 'synset': 'casserole.n.01', 'synonyms': ['casserole'], 'id': 222, 'def': 'food cooked and served in a casserole', 'name': 'casserole'}, {'frequency': 'r', 'synset': 'cassette.n.01', 'synonyms': ['cassette'], 'id': 223, 'def': 'a container that holds a magnetic tape used for recording or playing sound or video', 'name': 'cassette'}, {'frequency': 'c', 'synset': 'cast.n.05', 'synonyms': ['cast', 'plaster_cast', 'plaster_bandage'], 'id': 224, 'def': 'bandage consisting of a firm covering that immobilizes broken bones while they heal', 'name': 'cast'}, {'frequency': 'f', 'synset': 'cat.n.01', 'synonyms': ['cat'], 'id': 225, 'def': 'a domestic house cat', 'name': 'cat'}, {'frequency': 'f', 'synset': 'cauliflower.n.02', 'synonyms': ['cauliflower'], 'id': 226, 'def': 'edible compact head of white undeveloped flowers', 'name': 'cauliflower'}, {'frequency': 'c', 'synset': 'cayenne.n.02', 'synonyms': ['cayenne_(spice)', 'cayenne_pepper_(spice)', 'red_pepper_(spice)'], 'id': 227, 'def': 'ground pods and seeds of pungent red peppers of the genus Capsicum', 'name': 'cayenne_(spice)'}, {'frequency': 'c', 'synset': 'cd_player.n.01', 'synonyms': ['CD_player'], 'id': 228, 'def': 'electronic equipment for playing compact discs (CDs)', 'name': 'CD_player'}, {'frequency': 'f', 'synset': 'celery.n.01', 'synonyms': ['celery'], 'id': 229, 'def': 'widely cultivated herb with aromatic leaf stalks that are eaten raw or cooked', 'name': 'celery'}, {'frequency': 'f', 'synset': 'cellular_telephone.n.01', 'synonyms': ['cellular_telephone', 'cellular_phone', 'cellphone', 'mobile_phone', 'smart_phone'], 'id': 230, 'def': 'a hand-held mobile telephone', 'name': 'cellular_telephone'}, {'frequency': 'r', 'synset': 'chain_mail.n.01', 'synonyms': ['chain_mail', 'ring_mail', 'chain_armor', 'chain_armour', 'ring_armor', 'ring_armour'], 'id': 231, 'def': '(Middle Ages) flexible armor made of interlinked metal rings', 'name': 'chain_mail'}, {'frequency': 'f', 'synset': 'chair.n.01', 'synonyms': ['chair'], 'id': 232, 'def': 'a seat for one person, with a support for the back', 'name': 'chair'}, {'frequency': 'r', 'synset': 'chaise_longue.n.01', 'synonyms': ['chaise_longue', 'chaise', 'daybed'], 'id': 233, 'def': 'a long chair; for reclining', 'name': 'chaise_longue'}, {'frequency': 'r', 'synset': 'chalice.n.01', 'synonyms': ['chalice'], 'id': 234, 'def': 'a bowl-shaped drinking vessel; especially the Eucharistic cup', 'name': 'chalice'}, {'frequency': 'f', 'synset': 'chandelier.n.01', 'synonyms': ['chandelier'], 'id': 235, 'def': 'branched lighting fixture; often ornate; hangs from the ceiling', 'name': 'chandelier'}, {'frequency': 'r', 'synset': 'chap.n.04', 'synonyms': ['chap'], 'id': 236, 'def': 'leather leggings without a seat; worn over trousers by cowboys to protect their legs', 'name': 'chap'}, {'frequency': 'r', 'synset': 'checkbook.n.01', 'synonyms': ['checkbook', 'chequebook'], 'id': 237, 'def': 'a book issued to holders of checking accounts', 'name': 'checkbook'}, {'frequency': 'r', 'synset': 'checkerboard.n.01', 'synonyms': ['checkerboard'], 'id': 238, 'def': 'a board having 64 squares of two alternating colors', 'name': 'checkerboard'}, {'frequency': 'c', 'synset': 'cherry.n.03', 'synonyms': ['cherry'], 'id': 239, 'def': 'a red fruit with a single hard stone', 'name': 'cherry'}, {'frequency': 'r', 'synset': 'chessboard.n.01', 'synonyms': ['chessboard'], 'id': 240, 'def': 'a checkerboard used to play chess', 'name': 'chessboard'}, {'frequency': 'c', 'synset': 'chicken.n.02', 'synonyms': ['chicken_(animal)'], 'id': 241, 'def': 'a domestic fowl bred for flesh or eggs', 'name': 'chicken_(animal)'}, {'frequency': 'c', 'synset': 'chickpea.n.01', 'synonyms': ['chickpea', 'garbanzo'], 'id': 242, 'def': 'the seed of the chickpea plant; usually dried', 'name': 'chickpea'}, {'frequency': 'c', 'synset': 'chili.n.02', 'synonyms': ['chili_(vegetable)', 'chili_pepper_(vegetable)', 'chilli_(vegetable)', 'chilly_(vegetable)', 'chile_(vegetable)'], 'id': 243, 'def': 'very hot and finely tapering pepper of special pungency', 'name': 'chili_(vegetable)'}, {'frequency': 'r', 'synset': 'chime.n.01', 'synonyms': ['chime', 'gong'], 'id': 244, 'def': 'an instrument consisting of a set of bells that are struck with a hammer', 'name': 'chime'}, {'frequency': 'r', 'synset': 'chinaware.n.01', 'synonyms': ['chinaware'], 'id': 245, 'def': 'dishware made of high quality porcelain', 'name': 'chinaware'}, {'frequency': 'c', 'synset': 'chip.n.04', 'synonyms': ['crisp_(potato_chip)', 'potato_chip'], 'id': 246, 'def': 'a thin crisp slice of potato fried in deep fat', 'name': 'crisp_(potato_chip)'}, {'frequency': 'r', 'synset': 'chip.n.06', 'synonyms': ['poker_chip'], 'id': 247, 'def': 'a small disk-shaped counter used to represent money when gambling', 'name': 'poker_chip'}, {'frequency': 'c', 'synset': 'chocolate_bar.n.01', 'synonyms': ['chocolate_bar'], 'id': 248, 'def': 'a bar of chocolate candy', 'name': 'chocolate_bar'}, {'frequency': 'c', 'synset': 'chocolate_cake.n.01', 'synonyms': ['chocolate_cake'], 'id': 249, 'def': 'cake containing chocolate', 'name': 'chocolate_cake'}, {'frequency': 'r', 'synset': 'chocolate_milk.n.01', 'synonyms': ['chocolate_milk'], 'id': 250, 'def': 'milk flavored with chocolate syrup', 'name': 'chocolate_milk'}, {'frequency': 'r', 'synset': 'chocolate_mousse.n.01', 'synonyms': ['chocolate_mousse'], 'id': 251, 'def': 'dessert mousse made with chocolate', 'name': 'chocolate_mousse'}, {'frequency': 'f', 'synset': 'choker.n.03', 'synonyms': ['choker', 'collar', 'neckband'], 'id': 252, 'def': 'shirt collar, animal collar, or tight-fitting necklace', 'name': 'choker'}, {'frequency': 'f', 'synset': 'chopping_board.n.01', 'synonyms': ['chopping_board', 'cutting_board', 'chopping_block'], 'id': 253, 'def': 'a wooden board where meats or vegetables can be cut', 'name': 'chopping_board'}, {'frequency': 'f', 'synset': 'chopstick.n.01', 'synonyms': ['chopstick'], 'id': 254, 'def': 'one of a pair of slender sticks used as oriental tableware to eat food with', 'name': 'chopstick'}, {'frequency': 'f', 'synset': 'christmas_tree.n.05', 'synonyms': ['Christmas_tree'], 'id': 255, 'def': 'an ornamented evergreen used as a Christmas decoration', 'name': 'Christmas_tree'}, {'frequency': 'c', 'synset': 'chute.n.02', 'synonyms': ['slide'], 'id': 256, 'def': 'sloping channel through which things can descend', 'name': 'slide'}, {'frequency': 'r', 'synset': 'cider.n.01', 'synonyms': ['cider', 'cyder'], 'id': 257, 'def': 'a beverage made from juice pressed from apples', 'name': 'cider'}, {'frequency': 'r', 'synset': 'cigar_box.n.01', 'synonyms': ['cigar_box'], 'id': 258, 'def': 'a box for holding cigars', 'name': 'cigar_box'}, {'frequency': 'f', 'synset': 'cigarette.n.01', 'synonyms': ['cigarette'], 'id': 259, 'def': 'finely ground tobacco wrapped in paper; for smoking', 'name': 'cigarette'}, {'frequency': 'c', 'synset': 'cigarette_case.n.01', 'synonyms': ['cigarette_case', 'cigarette_pack'], 'id': 260, 'def': 'a small flat case for holding cigarettes', 'name': 'cigarette_case'}, {'frequency': 'f', 'synset': 'cistern.n.02', 'synonyms': ['cistern', 'water_tank'], 'id': 261, 'def': 'a tank that holds the water used to flush a toilet', 'name': 'cistern'}, {'frequency': 'r', 'synset': 'clarinet.n.01', 'synonyms': ['clarinet'], 'id': 262, 'def': 'a single-reed instrument with a straight tube', 'name': 'clarinet'}, {'frequency': 'c', 'synset': 'clasp.n.01', 'synonyms': ['clasp'], 'id': 263, 'def': 'a fastener (as a buckle or hook) that is used to hold two things together', 'name': 'clasp'}, {'frequency': 'c', 'synset': 'cleansing_agent.n.01', 'synonyms': ['cleansing_agent', 'cleanser', 'cleaner'], 'id': 264, 'def': 'a preparation used in cleaning something', 'name': 'cleansing_agent'}, {'frequency': 'r', 'synset': 'cleat.n.02', 'synonyms': ['cleat_(for_securing_rope)'], 'id': 265, 'def': 'a fastener (usually with two projecting horns) around which a rope can be secured', 'name': 'cleat_(for_securing_rope)'}, {'frequency': 'r', 'synset': 'clementine.n.01', 'synonyms': ['clementine'], 'id': 266, 'def': 'a variety of mandarin orange', 'name': 'clementine'}, {'frequency': 'c', 'synset': 'clip.n.03', 'synonyms': ['clip'], 'id': 267, 'def': 'any of various small fasteners used to hold loose articles together', 'name': 'clip'}, {'frequency': 'c', 'synset': 'clipboard.n.01', 'synonyms': ['clipboard'], 'id': 268, 'def': 'a small writing board with a clip at the top for holding papers', 'name': 'clipboard'}, {'frequency': 'r', 'synset': 'clipper.n.03', 'synonyms': ['clippers_(for_plants)'], 'id': 269, 'def': 'shears for cutting grass or shrubbery (often used in the plural)', 'name': 'clippers_(for_plants)'}, {'frequency': 'r', 'synset': 'cloak.n.02', 'synonyms': ['cloak'], 'id': 270, 'def': 'a loose outer garment', 'name': 'cloak'}, {'frequency': 'f', 'synset': 'clock.n.01', 'synonyms': ['clock', 'timepiece', 'timekeeper'], 'id': 271, 'def': 'a timepiece that shows the time of day', 'name': 'clock'}, {'frequency': 'f', 'synset': 'clock_tower.n.01', 'synonyms': ['clock_tower'], 'id': 272, 'def': 'a tower with a large clock visible high up on an outside face', 'name': 'clock_tower'}, {'frequency': 'c', 'synset': 'clothes_hamper.n.01', 'synonyms': ['clothes_hamper', 'laundry_basket', 'clothes_basket'], 'id': 273, 'def': 'a hamper that holds dirty clothes to be washed or wet clothes to be dried', 'name': 'clothes_hamper'}, {'frequency': 'c', 'synset': 'clothespin.n.01', 'synonyms': ['clothespin', 'clothes_peg'], 'id': 274, 'def': 'wood or plastic fastener; for holding clothes on a clothesline', 'name': 'clothespin'}, {'frequency': 'r', 'synset': 'clutch_bag.n.01', 'synonyms': ['clutch_bag'], 'id': 275, 'def': "a woman's strapless purse that is carried in the hand", 'name': 'clutch_bag'}, {'frequency': 'f', 'synset': 'coaster.n.03', 'synonyms': ['coaster'], 'id': 276, 'def': 'a covering (plate or mat) that protects the surface of a table', 'name': 'coaster'}, {'frequency': 'f', 'synset': 'coat.n.01', 'synonyms': ['coat'], 'id': 277, 'def': 'an outer garment that has sleeves and covers the body from shoulder down', 'name': 'coat'}, {'frequency': 'c', 'synset': 'coat_hanger.n.01', 'synonyms': ['coat_hanger', 'clothes_hanger', 'dress_hanger'], 'id': 278, 'def': "a hanger that is shaped like a person's shoulders", 'name': 'coat_hanger'}, {'frequency': 'c', 'synset': 'coatrack.n.01', 'synonyms': ['coatrack', 'hatrack'], 'id': 279, 'def': 'a rack with hooks for temporarily holding coats and hats', 'name': 'coatrack'}, {'frequency': 'c', 'synset': 'cock.n.04', 'synonyms': ['cock', 'rooster'], 'id': 280, 'def': 'adult male chicken', 'name': 'cock'}, {'frequency': 'r', 'synset': 'cockroach.n.01', 'synonyms': ['cockroach'], 'id': 281, 'def': 'any of numerous chiefly nocturnal insects; some are domestic pests', 'name': 'cockroach'}, {'frequency': 'r', 'synset': 'cocoa.n.01', 'synonyms': ['cocoa_(beverage)', 'hot_chocolate_(beverage)', 'drinking_chocolate'], 'id': 282, 'def': 'a beverage made from cocoa powder and milk and sugar; usually drunk hot', 'name': 'cocoa_(beverage)'}, {'frequency': 'c', 'synset': 'coconut.n.02', 'synonyms': ['coconut', 'cocoanut'], 'id': 283, 'def': 'large hard-shelled brown oval nut with a fibrous husk', 'name': 'coconut'}, {'frequency': 'f', 'synset': 'coffee_maker.n.01', 'synonyms': ['coffee_maker', 'coffee_machine'], 'id': 284, 'def': 'a kitchen appliance for brewing coffee automatically', 'name': 'coffee_maker'}, {'frequency': 'f', 'synset': 'coffee_table.n.01', 'synonyms': ['coffee_table', 'cocktail_table'], 'id': 285, 'def': 'low table where magazines can be placed and coffee or cocktails are served', 'name': 'coffee_table'}, {'frequency': 'c', 'synset': 'coffeepot.n.01', 'synonyms': ['coffeepot'], 'id': 286, 'def': 'tall pot in which coffee is brewed', 'name': 'coffeepot'}, {'frequency': 'r', 'synset': 'coil.n.05', 'synonyms': ['coil'], 'id': 287, 'def': 'tubing that is wound in a spiral', 'name': 'coil'}, {'frequency': 'c', 'synset': 'coin.n.01', 'synonyms': ['coin'], 'id': 288, 'def': 'a flat metal piece (usually a disc) used as money', 'name': 'coin'}, {'frequency': 'c', 'synset': 'colander.n.01', 'synonyms': ['colander', 'cullender'], 'id': 289, 'def': 'bowl-shaped strainer; used to wash or drain foods', 'name': 'colander'}, {'frequency': 'c', 'synset': 'coleslaw.n.01', 'synonyms': ['coleslaw', 'slaw'], 'id': 290, 'def': 'basically shredded cabbage', 'name': 'coleslaw'}, {'frequency': 'r', 'synset': 'coloring_material.n.01', 'synonyms': ['coloring_material', 'colouring_material'], 'id': 291, 'def': 'any material used for its color', 'name': 'coloring_material'}, {'frequency': 'r', 'synset': 'combination_lock.n.01', 'synonyms': ['combination_lock'], 'id': 292, 'def': 'lock that can be opened only by turning dials in a special sequence', 'name': 'combination_lock'}, {'frequency': 'c', 'synset': 'comforter.n.04', 'synonyms': ['pacifier', 'teething_ring'], 'id': 293, 'def': 'device used for an infant to suck or bite on', 'name': 'pacifier'}, {'frequency': 'r', 'synset': 'comic_book.n.01', 'synonyms': ['comic_book'], 'id': 294, 'def': 'a magazine devoted to comic strips', 'name': 'comic_book'}, {'frequency': 'r', 'synset': 'compass.n.01', 'synonyms': ['compass'], 'id': 295, 'def': 'navigational instrument for finding directions', 'name': 'compass'}, {'frequency': 'f', 'synset': 'computer_keyboard.n.01', 'synonyms': ['computer_keyboard', 'keyboard_(computer)'], 'id': 296, 'def': 'a keyboard that is a data input device for computers', 'name': 'computer_keyboard'}, {'frequency': 'f', 'synset': 'condiment.n.01', 'synonyms': ['condiment'], 'id': 297, 'def': 'a preparation (a sauce or relish or spice) to enhance flavor or enjoyment', 'name': 'condiment'}, {'frequency': 'f', 'synset': 'cone.n.01', 'synonyms': ['cone', 'traffic_cone'], 'id': 298, 'def': 'a cone-shaped object used to direct traffic', 'name': 'cone'}, {'frequency': 'f', 'synset': 'control.n.09', 'synonyms': ['control', 'controller'], 'id': 299, 'def': 'a mechanism that controls the operation of a machine', 'name': 'control'}, {'frequency': 'r', 'synset': 'convertible.n.01', 'synonyms': ['convertible_(automobile)'], 'id': 300, 'def': 'a car that has top that can be folded or removed', 'name': 'convertible_(automobile)'}, {'frequency': 'r', 'synset': 'convertible.n.03', 'synonyms': ['sofa_bed'], 'id': 301, 'def': 'a sofa that can be converted into a bed', 'name': 'sofa_bed'}, {'frequency': 'r', 'synset': 'cooker.n.01', 'synonyms': ['cooker'], 'id': 302, 'def': 'a utensil for cooking', 'name': 'cooker'}, {'frequency': 'f', 'synset': 'cookie.n.01', 'synonyms': ['cookie', 'cooky', 'biscuit_(cookie)'], 'id': 303, 'def': "any of various small flat sweet cakes (`biscuit' is the British term)", 'name': 'cookie'}, {'frequency': 'r', 'synset': 'cooking_utensil.n.01', 'synonyms': ['cooking_utensil'], 'id': 304, 'def': 'a kitchen utensil made of material that does not melt easily; used for cooking', 'name': 'cooking_utensil'}, {'frequency': 'f', 'synset': 'cooler.n.01', 'synonyms': ['cooler_(for_food)', 'ice_chest'], 'id': 305, 'def': 'an insulated box for storing food often with ice', 'name': 'cooler_(for_food)'}, {'frequency': 'f', 'synset': 'cork.n.04', 'synonyms': ['cork_(bottle_plug)', 'bottle_cork'], 'id': 306, 'def': 'the plug in the mouth of a bottle (especially a wine bottle)', 'name': 'cork_(bottle_plug)'}, {'frequency': 'r', 'synset': 'corkboard.n.01', 'synonyms': ['corkboard'], 'id': 307, 'def': 'a sheet consisting of cork granules', 'name': 'corkboard'}, {'frequency': 'c', 'synset': 'corkscrew.n.01', 'synonyms': ['corkscrew', 'bottle_screw'], 'id': 308, 'def': 'a bottle opener that pulls corks', 'name': 'corkscrew'}, {'frequency': 'f', 'synset': 'corn.n.03', 'synonyms': ['edible_corn', 'corn', 'maize'], 'id': 309, 'def': 'ears or kernels of corn that can be prepared and served for human food (only mark individual ears or kernels)', 'name': 'edible_corn'}, {'frequency': 'r', 'synset': 'cornbread.n.01', 'synonyms': ['cornbread'], 'id': 310, 'def': 'bread made primarily of cornmeal', 'name': 'cornbread'}, {'frequency': 'c', 'synset': 'cornet.n.01', 'synonyms': ['cornet', 'horn', 'trumpet'], 'id': 311, 'def': 'a brass musical instrument with a narrow tube and a flared bell and many valves', 'name': 'cornet'}, {'frequency': 'c', 'synset': 'cornice.n.01', 'synonyms': ['cornice', 'valance', 'valance_board', 'pelmet'], 'id': 312, 'def': 'a decorative framework to conceal curtain fixtures at the top of a window casing', 'name': 'cornice'}, {'frequency': 'r', 'synset': 'cornmeal.n.01', 'synonyms': ['cornmeal'], 'id': 313, 'def': 'coarsely ground corn', 'name': 'cornmeal'}, {'frequency': 'c', 'synset': 'corset.n.01', 'synonyms': ['corset', 'girdle'], 'id': 314, 'def': "a woman's close-fitting foundation garment", 'name': 'corset'}, {'frequency': 'c', 'synset': 'costume.n.04', 'synonyms': ['costume'], 'id': 315, 'def': 'the attire characteristic of a country or a time or a social class', 'name': 'costume'}, {'frequency': 'r', 'synset': 'cougar.n.01', 'synonyms': ['cougar', 'puma', 'catamount', 'mountain_lion', 'panther'], 'id': 316, 'def': 'large American feline resembling a lion', 'name': 'cougar'}, {'frequency': 'r', 'synset': 'coverall.n.01', 'synonyms': ['coverall'], 'id': 317, 'def': 'a loose-fitting protective garment that is worn over other clothing', 'name': 'coverall'}, {'frequency': 'c', 'synset': 'cowbell.n.01', 'synonyms': ['cowbell'], 'id': 318, 'def': 'a bell hung around the neck of cow so that the cow can be easily located', 'name': 'cowbell'}, {'frequency': 'f', 'synset': 'cowboy_hat.n.01', 'synonyms': ['cowboy_hat', 'ten-gallon_hat'], 'id': 319, 'def': 'a hat with a wide brim and a soft crown; worn by American ranch hands', 'name': 'cowboy_hat'}, {'frequency': 'c', 'synset': 'crab.n.01', 'synonyms': ['crab_(animal)'], 'id': 320, 'def': 'decapod having eyes on short stalks and a broad flattened shell and pincers', 'name': 'crab_(animal)'}, {'frequency': 'r', 'synset': 'crab.n.05', 'synonyms': ['crabmeat'], 'id': 321, 'def': 'the edible flesh of any of various crabs', 'name': 'crabmeat'}, {'frequency': 'c', 'synset': 'cracker.n.01', 'synonyms': ['cracker'], 'id': 322, 'def': 'a thin crisp wafer', 'name': 'cracker'}, {'frequency': 'r', 'synset': 'crape.n.01', 'synonyms': ['crape', 'crepe', 'French_pancake'], 'id': 323, 'def': 'small very thin pancake', 'name': 'crape'}, {'frequency': 'f', 'synset': 'crate.n.01', 'synonyms': ['crate'], 'id': 324, 'def': 'a rugged box (usually made of wood); used for shipping', 'name': 'crate'}, {'frequency': 'c', 'synset': 'crayon.n.01', 'synonyms': ['crayon', 'wax_crayon'], 'id': 325, 'def': 'writing or drawing implement made of a colored stick of composition wax', 'name': 'crayon'}, {'frequency': 'r', 'synset': 'cream_pitcher.n.01', 'synonyms': ['cream_pitcher'], 'id': 326, 'def': 'a small pitcher for serving cream', 'name': 'cream_pitcher'}, {'frequency': 'c', 'synset': 'crescent_roll.n.01', 'synonyms': ['crescent_roll', 'croissant'], 'id': 327, 'def': 'very rich flaky crescent-shaped roll', 'name': 'crescent_roll'}, {'frequency': 'c', 'synset': 'crib.n.01', 'synonyms': ['crib', 'cot'], 'id': 328, 'def': 'baby bed with high sides made of slats', 'name': 'crib'}, {'frequency': 'c', 'synset': 'crock.n.03', 'synonyms': ['crock_pot', 'earthenware_jar'], 'id': 329, 'def': 'an earthen jar (made of baked clay) or a modern electric crockpot', 'name': 'crock_pot'}, {'frequency': 'f', 'synset': 'crossbar.n.01', 'synonyms': ['crossbar'], 'id': 330, 'def': 'a horizontal bar that goes across something', 'name': 'crossbar'}, {'frequency': 'r', 'synset': 'crouton.n.01', 'synonyms': ['crouton'], 'id': 331, 'def': 'a small piece of toasted or fried bread; served in soup or salads', 'name': 'crouton'}, {'frequency': 'c', 'synset': 'crow.n.01', 'synonyms': ['crow'], 'id': 332, 'def': 'black birds having a raucous call', 'name': 'crow'}, {'frequency': 'r', 'synset': 'crowbar.n.01', 'synonyms': ['crowbar', 'wrecking_bar', 'pry_bar'], 'id': 333, 'def': 'a heavy iron lever with one end forged into a wedge', 'name': 'crowbar'}, {'frequency': 'c', 'synset': 'crown.n.04', 'synonyms': ['crown'], 'id': 334, 'def': 'an ornamental jeweled headdress signifying sovereignty', 'name': 'crown'}, {'frequency': 'c', 'synset': 'crucifix.n.01', 'synonyms': ['crucifix'], 'id': 335, 'def': 'representation of the cross on which Jesus died', 'name': 'crucifix'}, {'frequency': 'c', 'synset': 'cruise_ship.n.01', 'synonyms': ['cruise_ship', 'cruise_liner'], 'id': 336, 'def': 'a passenger ship used commercially for pleasure cruises', 'name': 'cruise_ship'}, {'frequency': 'c', 'synset': 'cruiser.n.01', 'synonyms': ['police_cruiser', 'patrol_car', 'police_car', 'squad_car'], 'id': 337, 'def': 'a car in which policemen cruise the streets', 'name': 'police_cruiser'}, {'frequency': 'f', 'synset': 'crumb.n.03', 'synonyms': ['crumb'], 'id': 338, 'def': 'small piece of e.g. bread or cake', 'name': 'crumb'}, {'frequency': 'c', 'synset': 'crutch.n.01', 'synonyms': ['crutch'], 'id': 339, 'def': 'a wooden or metal staff that fits under the armpit and reaches to the ground', 'name': 'crutch'}, {'frequency': 'c', 'synset': 'cub.n.03', 'synonyms': ['cub_(animal)'], 'id': 340, 'def': 'the young of certain carnivorous mammals such as the bear or wolf or lion', 'name': 'cub_(animal)'}, {'frequency': 'c', 'synset': 'cube.n.05', 'synonyms': ['cube', 'square_block'], 'id': 341, 'def': 'a block in the (approximate) shape of a cube', 'name': 'cube'}, {'frequency': 'f', 'synset': 'cucumber.n.02', 'synonyms': ['cucumber', 'cuke'], 'id': 342, 'def': 'cylindrical green fruit with thin green rind and white flesh eaten as a vegetable', 'name': 'cucumber'}, {'frequency': 'c', 'synset': 'cufflink.n.01', 'synonyms': ['cufflink'], 'id': 343, 'def': 'jewelry consisting of linked buttons used to fasten the cuffs of a shirt', 'name': 'cufflink'}, {'frequency': 'f', 'synset': 'cup.n.01', 'synonyms': ['cup'], 'id': 344, 'def': 'a small open container usually used for drinking; usually has a handle', 'name': 'cup'}, {'frequency': 'c', 'synset': 'cup.n.08', 'synonyms': ['trophy_cup'], 'id': 345, 'def': 'a metal award or cup-shaped vessel with handles that is awarded as a trophy to a competition winner', 'name': 'trophy_cup'}, {'frequency': 'f', 'synset': 'cupboard.n.01', 'synonyms': ['cupboard', 'closet'], 'id': 346, 'def': 'a small room (or recess) or cabinet used for storage space', 'name': 'cupboard'}, {'frequency': 'f', 'synset': 'cupcake.n.01', 'synonyms': ['cupcake'], 'id': 347, 'def': 'small cake baked in a muffin tin', 'name': 'cupcake'}, {'frequency': 'r', 'synset': 'curler.n.01', 'synonyms': ['hair_curler', 'hair_roller', 'hair_crimper'], 'id': 348, 'def': 'a cylindrical tube around which the hair is wound to curl it', 'name': 'hair_curler'}, {'frequency': 'r', 'synset': 'curling_iron.n.01', 'synonyms': ['curling_iron'], 'id': 349, 'def': 'a cylindrical home appliance that heats hair that has been curled around it', 'name': 'curling_iron'}, {'frequency': 'f', 'synset': 'curtain.n.01', 'synonyms': ['curtain', 'drapery'], 'id': 350, 'def': 'hanging cloth used as a blind (especially for a window)', 'name': 'curtain'}, {'frequency': 'f', 'synset': 'cushion.n.03', 'synonyms': ['cushion'], 'id': 351, 'def': 'a soft bag filled with air or padding such as feathers or foam rubber', 'name': 'cushion'}, {'frequency': 'r', 'synset': 'cylinder.n.04', 'synonyms': ['cylinder'], 'id': 352, 'def': 'a cylindrical container', 'name': 'cylinder'}, {'frequency': 'r', 'synset': 'cymbal.n.01', 'synonyms': ['cymbal'], 'id': 353, 'def': 'a percussion instrument consisting of a concave brass disk', 'name': 'cymbal'}, {'frequency': 'r', 'synset': 'dagger.n.01', 'synonyms': ['dagger'], 'id': 354, 'def': 'a short knife with a pointed blade used for piercing or stabbing', 'name': 'dagger'}, {'frequency': 'r', 'synset': 'dalmatian.n.02', 'synonyms': ['dalmatian'], 'id': 355, 'def': 'a large breed having a smooth white coat with black or brown spots', 'name': 'dalmatian'}, {'frequency': 'c', 'synset': 'dartboard.n.01', 'synonyms': ['dartboard'], 'id': 356, 'def': 'a circular board of wood or cork used as the target in the game of darts', 'name': 'dartboard'}, {'frequency': 'r', 'synset': 'date.n.08', 'synonyms': ['date_(fruit)'], 'id': 357, 'def': 'sweet edible fruit of the date palm with a single long woody seed', 'name': 'date_(fruit)'}, {'frequency': 'f', 'synset': 'deck_chair.n.01', 'synonyms': ['deck_chair', 'beach_chair'], 'id': 358, 'def': 'a folding chair for use outdoors; a wooden frame supports a length of canvas', 'name': 'deck_chair'}, {'frequency': 'c', 'synset': 'deer.n.01', 'synonyms': ['deer', 'cervid'], 'id': 359, 'def': "distinguished from Bovidae by the male's having solid deciduous antlers", 'name': 'deer'}, {'frequency': 'c', 'synset': 'dental_floss.n.01', 'synonyms': ['dental_floss', 'floss'], 'id': 360, 'def': 'a soft thread for cleaning the spaces between the teeth', 'name': 'dental_floss'}, {'frequency': 'f', 'synset': 'desk.n.01', 'synonyms': ['desk'], 'id': 361, 'def': 'a piece of furniture with a writing surface and usually drawers or other compartments', 'name': 'desk'}, {'frequency': 'r', 'synset': 'detergent.n.01', 'synonyms': ['detergent'], 'id': 362, 'def': 'a surface-active chemical widely used in industry and laundering', 'name': 'detergent'}, {'frequency': 'c', 'synset': 'diaper.n.01', 'synonyms': ['diaper'], 'id': 363, 'def': 'garment consisting of a folded cloth drawn up between the legs and fastened at the waist', 'name': 'diaper'}, {'frequency': 'r', 'synset': 'diary.n.01', 'synonyms': ['diary', 'journal'], 'id': 364, 'def': 'yearly planner book', 'name': 'diary'}, {'frequency': 'r', 'synset': 'die.n.01', 'synonyms': ['die', 'dice'], 'id': 365, 'def': 'a small cube with 1 to 6 spots on the six faces; used in gambling', 'name': 'die'}, {'frequency': 'r', 'synset': 'dinghy.n.01', 'synonyms': ['dinghy', 'dory', 'rowboat'], 'id': 366, 'def': 'a small boat of shallow draft with seats and oars with which it is propelled', 'name': 'dinghy'}, {'frequency': 'f', 'synset': 'dining_table.n.01', 'synonyms': ['dining_table'], 'id': 367, 'def': 'a table at which meals are served', 'name': 'dining_table'}, {'frequency': 'r', 'synset': 'dinner_jacket.n.01', 'synonyms': ['tux', 'tuxedo'], 'id': 368, 'def': 'semiformal evening dress for men', 'name': 'tux'}, {'frequency': 'f', 'synset': 'dish.n.01', 'synonyms': ['dish'], 'id': 369, 'def': 'a piece of dishware normally used as a container for holding or serving food', 'name': 'dish'}, {'frequency': 'c', 'synset': 'dish.n.05', 'synonyms': ['dish_antenna'], 'id': 370, 'def': 'directional antenna consisting of a parabolic reflector', 'name': 'dish_antenna'}, {'frequency': 'c', 'synset': 'dishrag.n.01', 'synonyms': ['dishrag', 'dishcloth'], 'id': 371, 'def': 'a cloth for washing dishes or cleaning in general', 'name': 'dishrag'}, {'frequency': 'f', 'synset': 'dishtowel.n.01', 'synonyms': ['dishtowel', 'tea_towel'], 'id': 372, 'def': 'a towel for drying dishes', 'name': 'dishtowel'}, {'frequency': 'f', 'synset': 'dishwasher.n.01', 'synonyms': ['dishwasher', 'dishwashing_machine'], 'id': 373, 'def': 'a machine for washing dishes', 'name': 'dishwasher'}, {'frequency': 'r', 'synset': 'dishwasher_detergent.n.01', 'synonyms': ['dishwasher_detergent', 'dishwashing_detergent', 'dishwashing_liquid', 'dishsoap'], 'id': 374, 'def': 'dishsoap or dish detergent designed for use in dishwashers', 'name': 'dishwasher_detergent'}, {'frequency': 'f', 'synset': 'dispenser.n.01', 'synonyms': ['dispenser'], 'id': 375, 'def': 'a container so designed that the contents can be used in prescribed amounts', 'name': 'dispenser'}, {'frequency': 'r', 'synset': 'diving_board.n.01', 'synonyms': ['diving_board'], 'id': 376, 'def': 'a springboard from which swimmers can dive', 'name': 'diving_board'}, {'frequency': 'f', 'synset': 'dixie_cup.n.01', 'synonyms': ['Dixie_cup', 'paper_cup'], 'id': 377, 'def': 'a disposable cup made of paper; for holding drinks', 'name': 'Dixie_cup'}, {'frequency': 'f', 'synset': 'dog.n.01', 'synonyms': ['dog'], 'id': 378, 'def': 'a common domesticated dog', 'name': 'dog'}, {'frequency': 'f', 'synset': 'dog_collar.n.01', 'synonyms': ['dog_collar'], 'id': 379, 'def': 'a collar for a dog', 'name': 'dog_collar'}, {'frequency': 'f', 'synset': 'doll.n.01', 'synonyms': ['doll'], 'id': 380, 'def': 'a toy replica of a HUMAN (NOT AN ANIMAL)', 'name': 'doll'}, {'frequency': 'r', 'synset': 'dollar.n.02', 'synonyms': ['dollar', 'dollar_bill', 'one_dollar_bill'], 'id': 381, 'def': 'a piece of paper money worth one dollar', 'name': 'dollar'}, {'frequency': 'r', 'synset': 'dollhouse.n.01', 'synonyms': ['dollhouse', "doll's_house"], 'id': 382, 'def': "a house so small that it is likened to a child's plaything", 'name': 'dollhouse'}, {'frequency': 'c', 'synset': 'dolphin.n.02', 'synonyms': ['dolphin'], 'id': 383, 'def': 'any of various small toothed whales with a beaklike snout; larger than porpoises', 'name': 'dolphin'}, {'frequency': 'c', 'synset': 'domestic_ass.n.01', 'synonyms': ['domestic_ass', 'donkey'], 'id': 384, 'def': 'domestic beast of burden descended from the African wild ass; patient but stubborn', 'name': 'domestic_ass'}, {'frequency': 'f', 'synset': 'doorknob.n.01', 'synonyms': ['doorknob', 'doorhandle'], 'id': 385, 'def': "a knob used to open a door (often called `doorhandle' in Great Britain)", 'name': 'doorknob'}, {'frequency': 'c', 'synset': 'doormat.n.02', 'synonyms': ['doormat', 'welcome_mat'], 'id': 386, 'def': 'a mat placed outside an exterior door for wiping the shoes before entering', 'name': 'doormat'}, {'frequency': 'f', 'synset': 'doughnut.n.02', 'synonyms': ['doughnut', 'donut'], 'id': 387, 'def': 'a small ring-shaped friedcake', 'name': 'doughnut'}, {'frequency': 'r', 'synset': 'dove.n.01', 'synonyms': ['dove'], 'id': 388, 'def': 'any of numerous small pigeons', 'name': 'dove'}, {'frequency': 'r', 'synset': 'dragonfly.n.01', 'synonyms': ['dragonfly'], 'id': 389, 'def': 'slender-bodied non-stinging insect having iridescent wings that are outspread at rest', 'name': 'dragonfly'}, {'frequency': 'f', 'synset': 'drawer.n.01', 'synonyms': ['drawer'], 'id': 390, 'def': 'a boxlike container in a piece of furniture; made so as to slide in and out', 'name': 'drawer'}, {'frequency': 'c', 'synset': 'drawers.n.01', 'synonyms': ['underdrawers', 'boxers', 'boxershorts'], 'id': 391, 'def': 'underpants worn by men', 'name': 'underdrawers'}, {'frequency': 'f', 'synset': 'dress.n.01', 'synonyms': ['dress', 'frock'], 'id': 392, 'def': 'a one-piece garment for a woman; has skirt and bodice', 'name': 'dress'}, {'frequency': 'c', 'synset': 'dress_hat.n.01', 'synonyms': ['dress_hat', 'high_hat', 'opera_hat', 'silk_hat', 'top_hat'], 'id': 393, 'def': "a man's hat with a tall crown; usually covered with silk or with beaver fur", 'name': 'dress_hat'}, {'frequency': 'f', 'synset': 'dress_suit.n.01', 'synonyms': ['dress_suit'], 'id': 394, 'def': 'formalwear consisting of full evening dress for men', 'name': 'dress_suit'}, {'frequency': 'f', 'synset': 'dresser.n.05', 'synonyms': ['dresser'], 'id': 395, 'def': 'a cabinet with shelves', 'name': 'dresser'}, {'frequency': 'c', 'synset': 'drill.n.01', 'synonyms': ['drill'], 'id': 396, 'def': 'a tool with a sharp rotating point for making holes in hard materials', 'name': 'drill'}, {'frequency': 'r', 'synset': 'drone.n.04', 'synonyms': ['drone'], 'id': 397, 'def': 'an aircraft without a pilot that is operated by remote control', 'name': 'drone'}, {'frequency': 'r', 'synset': 'dropper.n.01', 'synonyms': ['dropper', 'eye_dropper'], 'id': 398, 'def': 'pipet consisting of a small tube with a vacuum bulb at one end for drawing liquid in and releasing it a drop at a time', 'name': 'dropper'}, {'frequency': 'c', 'synset': 'drum.n.01', 'synonyms': ['drum_(musical_instrument)'], 'id': 399, 'def': 'a musical percussion instrument; usually consists of a hollow cylinder with a membrane stretched across each end', 'name': 'drum_(musical_instrument)'}, {'frequency': 'r', 'synset': 'drumstick.n.02', 'synonyms': ['drumstick'], 'id': 400, 'def': 'a stick used for playing a drum', 'name': 'drumstick'}, {'frequency': 'f', 'synset': 'duck.n.01', 'synonyms': ['duck'], 'id': 401, 'def': 'small web-footed broad-billed swimming bird', 'name': 'duck'}, {'frequency': 'c', 'synset': 'duckling.n.02', 'synonyms': ['duckling'], 'id': 402, 'def': 'young duck', 'name': 'duckling'}, {'frequency': 'c', 'synset': 'duct_tape.n.01', 'synonyms': ['duct_tape'], 'id': 403, 'def': 'a wide silvery adhesive tape', 'name': 'duct_tape'}, {'frequency': 'f', 'synset': 'duffel_bag.n.01', 'synonyms': ['duffel_bag', 'duffle_bag', 'duffel', 'duffle'], 'id': 404, 'def': 'a large cylindrical bag of heavy cloth (does not include suitcases)', 'name': 'duffel_bag'}, {'frequency': 'r', 'synset': 'dumbbell.n.01', 'synonyms': ['dumbbell'], 'id': 405, 'def': 'an exercising weight with two ball-like ends connected by a short handle', 'name': 'dumbbell'}, {'frequency': 'c', 'synset': 'dumpster.n.01', 'synonyms': ['dumpster'], 'id': 406, 'def': 'a container designed to receive and transport and dump waste', 'name': 'dumpster'}, {'frequency': 'r', 'synset': 'dustpan.n.02', 'synonyms': ['dustpan'], 'id': 407, 'def': 'a short-handled receptacle into which dust can be swept', 'name': 'dustpan'}, {'frequency': 'c', 'synset': 'eagle.n.01', 'synonyms': ['eagle'], 'id': 408, 'def': 'large birds of prey noted for their broad wings and strong soaring flight', 'name': 'eagle'}, {'frequency': 'f', 'synset': 'earphone.n.01', 'synonyms': ['earphone', 'earpiece', 'headphone'], 'id': 409, 'def': 'device for listening to audio that is held over or inserted into the ear', 'name': 'earphone'}, {'frequency': 'r', 'synset': 'earplug.n.01', 'synonyms': ['earplug'], 'id': 410, 'def': 'a soft plug that is inserted into the ear canal to block sound', 'name': 'earplug'}, {'frequency': 'f', 'synset': 'earring.n.01', 'synonyms': ['earring'], 'id': 411, 'def': 'jewelry to ornament the ear', 'name': 'earring'}, {'frequency': 'c', 'synset': 'easel.n.01', 'synonyms': ['easel'], 'id': 412, 'def': "an upright tripod for displaying something (usually an artist's canvas)", 'name': 'easel'}, {'frequency': 'r', 'synset': 'eclair.n.01', 'synonyms': ['eclair'], 'id': 413, 'def': 'oblong cream puff', 'name': 'eclair'}, {'frequency': 'r', 'synset': 'eel.n.01', 'synonyms': ['eel'], 'id': 414, 'def': 'an elongate fish with fatty flesh', 'name': 'eel'}, {'frequency': 'f', 'synset': 'egg.n.02', 'synonyms': ['egg', 'eggs'], 'id': 415, 'def': 'oval reproductive body of a fowl (especially a hen) used as food', 'name': 'egg'}, {'frequency': 'r', 'synset': 'egg_roll.n.01', 'synonyms': ['egg_roll', 'spring_roll'], 'id': 416, 'def': 'minced vegetables and meat wrapped in a pancake and fried', 'name': 'egg_roll'}, {'frequency': 'c', 'synset': 'egg_yolk.n.01', 'synonyms': ['egg_yolk', 'yolk_(egg)'], 'id': 417, 'def': 'the yellow spherical part of an egg', 'name': 'egg_yolk'}, {'frequency': 'c', 'synset': 'eggbeater.n.02', 'synonyms': ['eggbeater', 'eggwhisk'], 'id': 418, 'def': 'a mixer for beating eggs or whipping cream', 'name': 'eggbeater'}, {'frequency': 'c', 'synset': 'eggplant.n.01', 'synonyms': ['eggplant', 'aubergine'], 'id': 419, 'def': 'egg-shaped vegetable having a shiny skin typically dark purple', 'name': 'eggplant'}, {'frequency': 'r', 'synset': 'electric_chair.n.01', 'synonyms': ['electric_chair'], 'id': 420, 'def': 'a chair-shaped instrument of execution by electrocution', 'name': 'electric_chair'}, {'frequency': 'f', 'synset': 'electric_refrigerator.n.01', 'synonyms': ['refrigerator'], 'id': 421, 'def': 'a refrigerator in which the coolant is pumped around by an electric motor', 'name': 'refrigerator'}, {'frequency': 'f', 'synset': 'elephant.n.01', 'synonyms': ['elephant'], 'id': 422, 'def': 'a common elephant', 'name': 'elephant'}, {'frequency': 'c', 'synset': 'elk.n.01', 'synonyms': ['elk', 'moose'], 'id': 423, 'def': 'large northern deer with enormous flattened antlers in the male', 'name': 'elk'}, {'frequency': 'c', 'synset': 'envelope.n.01', 'synonyms': ['envelope'], 'id': 424, 'def': 'a flat (usually rectangular) container for a letter, thin package, etc.', 'name': 'envelope'}, {'frequency': 'c', 'synset': 'eraser.n.01', 'synonyms': ['eraser'], 'id': 425, 'def': 'an implement used to erase something', 'name': 'eraser'}, {'frequency': 'r', 'synset': 'escargot.n.01', 'synonyms': ['escargot'], 'id': 426, 'def': 'edible snail usually served in the shell with a sauce of melted butter and garlic', 'name': 'escargot'}, {'frequency': 'r', 'synset': 'eyepatch.n.01', 'synonyms': ['eyepatch'], 'id': 427, 'def': 'a protective cloth covering for an injured eye', 'name': 'eyepatch'}, {'frequency': 'r', 'synset': 'falcon.n.01', 'synonyms': ['falcon'], 'id': 428, 'def': 'birds of prey having long pointed powerful wings adapted for swift flight', 'name': 'falcon'}, {'frequency': 'f', 'synset': 'fan.n.01', 'synonyms': ['fan'], 'id': 429, 'def': 'a device for creating a current of air by movement of a surface or surfaces', 'name': 'fan'}, {'frequency': 'f', 'synset': 'faucet.n.01', 'synonyms': ['faucet', 'spigot', 'tap'], 'id': 430, 'def': 'a regulator for controlling the flow of a liquid from a reservoir', 'name': 'faucet'}, {'frequency': 'r', 'synset': 'fedora.n.01', 'synonyms': ['fedora'], 'id': 431, 'def': 'a hat made of felt with a creased crown', 'name': 'fedora'}, {'frequency': 'r', 'synset': 'ferret.n.02', 'synonyms': ['ferret'], 'id': 432, 'def': 'domesticated albino variety of the European polecat bred for hunting rats and rabbits', 'name': 'ferret'}, {'frequency': 'c', 'synset': 'ferris_wheel.n.01', 'synonyms': ['Ferris_wheel'], 'id': 433, 'def': 'a large wheel with suspended seats that remain upright as the wheel rotates', 'name': 'Ferris_wheel'}, {'frequency': 'c', 'synset': 'ferry.n.01', 'synonyms': ['ferry', 'ferryboat'], 'id': 434, 'def': 'a boat that transports people or vehicles across a body of water and operates on a regular schedule', 'name': 'ferry'}, {'frequency': 'r', 'synset': 'fig.n.04', 'synonyms': ['fig_(fruit)'], 'id': 435, 'def': 'fleshy sweet pear-shaped yellowish or purple fruit eaten fresh or preserved or dried', 'name': 'fig_(fruit)'}, {'frequency': 'c', 'synset': 'fighter.n.02', 'synonyms': ['fighter_jet', 'fighter_aircraft', 'attack_aircraft'], 'id': 436, 'def': 'a high-speed military or naval airplane designed to destroy enemy targets', 'name': 'fighter_jet'}, {'frequency': 'f', 'synset': 'figurine.n.01', 'synonyms': ['figurine'], 'id': 437, 'def': 'a small carved or molded figure', 'name': 'figurine'}, {'frequency': 'c', 'synset': 'file.n.03', 'synonyms': ['file_cabinet', 'filing_cabinet'], 'id': 438, 'def': 'office furniture consisting of a container for keeping papers in order', 'name': 'file_cabinet'}, {'frequency': 'r', 'synset': 'file.n.04', 'synonyms': ['file_(tool)'], 'id': 439, 'def': 'a steel hand tool with small sharp teeth on some or all of its surfaces; used for smoothing wood or metal', 'name': 'file_(tool)'}, {'frequency': 'f', 'synset': 'fire_alarm.n.02', 'synonyms': ['fire_alarm', 'smoke_alarm'], 'id': 440, 'def': 'an alarm that is tripped off by fire or smoke', 'name': 'fire_alarm'}, {'frequency': 'f', 'synset': 'fire_engine.n.01', 'synonyms': ['fire_engine', 'fire_truck'], 'id': 441, 'def': 'large trucks that carry firefighters and equipment to the site of a fire', 'name': 'fire_engine'}, {'frequency': 'f', 'synset': 'fire_extinguisher.n.01', 'synonyms': ['fire_extinguisher', 'extinguisher'], 'id': 442, 'def': 'a manually operated device for extinguishing small fires', 'name': 'fire_extinguisher'}, {'frequency': 'c', 'synset': 'fire_hose.n.01', 'synonyms': ['fire_hose'], 'id': 443, 'def': 'a large hose that carries water from a fire hydrant to the site of the fire', 'name': 'fire_hose'}, {'frequency': 'f', 'synset': 'fireplace.n.01', 'synonyms': ['fireplace'], 'id': 444, 'def': 'an open recess in a wall at the base of a chimney where a fire can be built', 'name': 'fireplace'}, {'frequency': 'f', 'synset': 'fireplug.n.01', 'synonyms': ['fireplug', 'fire_hydrant', 'hydrant'], 'id': 445, 'def': 'an upright hydrant for drawing water to use in fighting a fire', 'name': 'fireplug'}, {'frequency': 'r', 'synset': 'first-aid_kit.n.01', 'synonyms': ['first-aid_kit'], 'id': 446, 'def': 'kit consisting of a set of bandages and medicines for giving first aid', 'name': 'first-aid_kit'}, {'frequency': 'f', 'synset': 'fish.n.01', 'synonyms': ['fish'], 'id': 447, 'def': 'any of various mostly cold-blooded aquatic vertebrates usually having scales and breathing through gills', 'name': 'fish'}, {'frequency': 'c', 'synset': 'fish.n.02', 'synonyms': ['fish_(food)'], 'id': 448, 'def': 'the flesh of fish used as food', 'name': 'fish_(food)'}, {'frequency': 'r', 'synset': 'fishbowl.n.02', 'synonyms': ['fishbowl', 'goldfish_bowl'], 'id': 449, 'def': 'a transparent bowl in which small fish are kept', 'name': 'fishbowl'}, {'frequency': 'c', 'synset': 'fishing_rod.n.01', 'synonyms': ['fishing_rod', 'fishing_pole'], 'id': 450, 'def': 'a rod that is used in fishing to extend the fishing line', 'name': 'fishing_rod'}, {'frequency': 'f', 'synset': 'flag.n.01', 'synonyms': ['flag'], 'id': 451, 'def': 'emblem usually consisting of a rectangular piece of cloth of distinctive design (do not include pole)', 'name': 'flag'}, {'frequency': 'f', 'synset': 'flagpole.n.02', 'synonyms': ['flagpole', 'flagstaff'], 'id': 452, 'def': 'a tall staff or pole on which a flag is raised', 'name': 'flagpole'}, {'frequency': 'c', 'synset': 'flamingo.n.01', 'synonyms': ['flamingo'], 'id': 453, 'def': 'large pink web-footed bird with down-bent bill', 'name': 'flamingo'}, {'frequency': 'c', 'synset': 'flannel.n.01', 'synonyms': ['flannel'], 'id': 454, 'def': 'a soft light woolen fabric; used for clothing', 'name': 'flannel'}, {'frequency': 'c', 'synset': 'flap.n.01', 'synonyms': ['flap'], 'id': 455, 'def': 'any broad thin covering attached at one edge, such as a mud flap next to a wheel or a flap on an airplane wing', 'name': 'flap'}, {'frequency': 'r', 'synset': 'flash.n.10', 'synonyms': ['flash', 'flashbulb'], 'id': 456, 'def': 'a lamp for providing momentary light to take a photograph', 'name': 'flash'}, {'frequency': 'c', 'synset': 'flashlight.n.01', 'synonyms': ['flashlight', 'torch'], 'id': 457, 'def': 'a small portable battery-powered electric lamp', 'name': 'flashlight'}, {'frequency': 'r', 'synset': 'fleece.n.03', 'synonyms': ['fleece'], 'id': 458, 'def': 'a soft bulky fabric with deep pile; used chiefly for clothing', 'name': 'fleece'}, {'frequency': 'f', 'synset': 'flip-flop.n.02', 'synonyms': ['flip-flop_(sandal)'], 'id': 459, 'def': 'a backless sandal held to the foot by a thong between two toes', 'name': 'flip-flop_(sandal)'}, {'frequency': 'c', 'synset': 'flipper.n.01', 'synonyms': ['flipper_(footwear)', 'fin_(footwear)'], 'id': 460, 'def': 'a shoe to aid a person in swimming', 'name': 'flipper_(footwear)'}, {'frequency': 'f', 'synset': 'flower_arrangement.n.01', 'synonyms': ['flower_arrangement', 'floral_arrangement'], 'id': 461, 'def': 'a decorative arrangement of flowers', 'name': 'flower_arrangement'}, {'frequency': 'c', 'synset': 'flute.n.02', 'synonyms': ['flute_glass', 'champagne_flute'], 'id': 462, 'def': 'a tall narrow wineglass', 'name': 'flute_glass'}, {'frequency': 'c', 'synset': 'foal.n.01', 'synonyms': ['foal'], 'id': 463, 'def': 'a young horse', 'name': 'foal'}, {'frequency': 'c', 'synset': 'folding_chair.n.01', 'synonyms': ['folding_chair'], 'id': 464, 'def': 'a chair that can be folded flat for storage', 'name': 'folding_chair'}, {'frequency': 'c', 'synset': 'food_processor.n.01', 'synonyms': ['food_processor'], 'id': 465, 'def': 'a kitchen appliance for shredding, blending, chopping, or slicing food', 'name': 'food_processor'}, {'frequency': 'c', 'synset': 'football.n.02', 'synonyms': ['football_(American)'], 'id': 466, 'def': 'the inflated oblong ball used in playing American football', 'name': 'football_(American)'}, {'frequency': 'r', 'synset': 'football_helmet.n.01', 'synonyms': ['football_helmet'], 'id': 467, 'def': 'a padded helmet with a face mask to protect the head of football players', 'name': 'football_helmet'}, {'frequency': 'c', 'synset': 'footstool.n.01', 'synonyms': ['footstool', 'footrest'], 'id': 468, 'def': 'a low seat or a stool to rest the feet of a seated person', 'name': 'footstool'}, {'frequency': 'f', 'synset': 'fork.n.01', 'synonyms': ['fork'], 'id': 469, 'def': 'cutlery used for serving and eating food', 'name': 'fork'}, {'frequency': 'c', 'synset': 'forklift.n.01', 'synonyms': ['forklift'], 'id': 470, 'def': 'an industrial vehicle with a power operated fork in front that can be inserted under loads to lift and move them', 'name': 'forklift'}, {'frequency': 'c', 'synset': 'freight_car.n.01', 'synonyms': ['freight_car'], 'id': 471, 'def': 'a railway car that carries freight', 'name': 'freight_car'}, {'frequency': 'c', 'synset': 'french_toast.n.01', 'synonyms': ['French_toast'], 'id': 472, 'def': 'bread slice dipped in egg and milk and fried', 'name': 'French_toast'}, {'frequency': 'c', 'synset': 'freshener.n.01', 'synonyms': ['freshener', 'air_freshener'], 'id': 473, 'def': 'anything that freshens air by removing or covering odor', 'name': 'freshener'}, {'frequency': 'f', 'synset': 'frisbee.n.01', 'synonyms': ['frisbee'], 'id': 474, 'def': 'a light, plastic disk propelled with a flip of the wrist for recreation or competition', 'name': 'frisbee'}, {'frequency': 'c', 'synset': 'frog.n.01', 'synonyms': ['frog', 'toad', 'toad_frog'], 'id': 475, 'def': 'a tailless stout-bodied amphibians with long hind limbs for leaping', 'name': 'frog'}, {'frequency': 'c', 'synset': 'fruit_juice.n.01', 'synonyms': ['fruit_juice'], 'id': 476, 'def': 'drink produced by squeezing or crushing fruit', 'name': 'fruit_juice'}, {'frequency': 'f', 'synset': 'frying_pan.n.01', 'synonyms': ['frying_pan', 'frypan', 'skillet'], 'id': 477, 'def': 'a pan used for frying foods', 'name': 'frying_pan'}, {'frequency': 'r', 'synset': 'fudge.n.01', 'synonyms': ['fudge'], 'id': 478, 'def': 'soft creamy candy', 'name': 'fudge'}, {'frequency': 'r', 'synset': 'funnel.n.02', 'synonyms': ['funnel'], 'id': 479, 'def': 'a cone-shaped utensil used to channel a substance into a container with a small mouth', 'name': 'funnel'}, {'frequency': 'r', 'synset': 'futon.n.01', 'synonyms': ['futon'], 'id': 480, 'def': 'a pad that is used for sleeping on the floor or on a raised frame', 'name': 'futon'}, {'frequency': 'r', 'synset': 'gag.n.02', 'synonyms': ['gag', 'muzzle'], 'id': 481, 'def': "restraint put into a person's mouth to prevent speaking or shouting", 'name': 'gag'}, {'frequency': 'r', 'synset': 'garbage.n.03', 'synonyms': ['garbage'], 'id': 482, 'def': 'a receptacle where waste can be discarded', 'name': 'garbage'}, {'frequency': 'c', 'synset': 'garbage_truck.n.01', 'synonyms': ['garbage_truck'], 'id': 483, 'def': 'a truck for collecting domestic refuse', 'name': 'garbage_truck'}, {'frequency': 'c', 'synset': 'garden_hose.n.01', 'synonyms': ['garden_hose'], 'id': 484, 'def': 'a hose used for watering a lawn or garden', 'name': 'garden_hose'}, {'frequency': 'c', 'synset': 'gargle.n.01', 'synonyms': ['gargle', 'mouthwash'], 'id': 485, 'def': 'a medicated solution used for gargling and rinsing the mouth', 'name': 'gargle'}, {'frequency': 'r', 'synset': 'gargoyle.n.02', 'synonyms': ['gargoyle'], 'id': 486, 'def': 'an ornament consisting of a grotesquely carved figure of a person or animal', 'name': 'gargoyle'}, {'frequency': 'c', 'synset': 'garlic.n.02', 'synonyms': ['garlic', 'ail'], 'id': 487, 'def': 'aromatic bulb used as seasoning', 'name': 'garlic'}, {'frequency': 'r', 'synset': 'gasmask.n.01', 'synonyms': ['gasmask', 'respirator', 'gas_helmet'], 'id': 488, 'def': 'a protective face mask with a filter', 'name': 'gasmask'}, {'frequency': 'c', 'synset': 'gazelle.n.01', 'synonyms': ['gazelle'], 'id': 489, 'def': 'small swift graceful antelope of Africa and Asia having lustrous eyes', 'name': 'gazelle'}, {'frequency': 'c', 'synset': 'gelatin.n.02', 'synonyms': ['gelatin', 'jelly'], 'id': 490, 'def': 'an edible jelly made with gelatin and used as a dessert or salad base or a coating for foods', 'name': 'gelatin'}, {'frequency': 'r', 'synset': 'gem.n.02', 'synonyms': ['gemstone'], 'id': 491, 'def': 'a crystalline rock that can be cut and polished for jewelry', 'name': 'gemstone'}, {'frequency': 'r', 'synset': 'generator.n.02', 'synonyms': ['generator'], 'id': 492, 'def': 'engine that converts mechanical energy into electrical energy by electromagnetic induction', 'name': 'generator'}, {'frequency': 'c', 'synset': 'giant_panda.n.01', 'synonyms': ['giant_panda', 'panda', 'panda_bear'], 'id': 493, 'def': 'large black-and-white herbivorous mammal of bamboo forests of China and Tibet', 'name': 'giant_panda'}, {'frequency': 'c', 'synset': 'gift_wrap.n.01', 'synonyms': ['gift_wrap'], 'id': 494, 'def': 'attractive wrapping paper suitable for wrapping gifts', 'name': 'gift_wrap'}, {'frequency': 'c', 'synset': 'ginger.n.03', 'synonyms': ['ginger', 'gingerroot'], 'id': 495, 'def': 'the root of the common ginger plant; used fresh as a seasoning', 'name': 'ginger'}, {'frequency': 'f', 'synset': 'giraffe.n.01', 'synonyms': ['giraffe'], 'id': 496, 'def': 'tall animal having a spotted coat and small horns and very long neck and legs', 'name': 'giraffe'}, {'frequency': 'c', 'synset': 'girdle.n.02', 'synonyms': ['cincture', 'sash', 'waistband', 'waistcloth'], 'id': 497, 'def': 'a band of material around the waist that strengthens a skirt or trousers', 'name': 'cincture'}, {'frequency': 'f', 'synset': 'glass.n.02', 'synonyms': ['glass_(drink_container)', 'drinking_glass'], 'id': 498, 'def': 'a container for holding liquids while drinking', 'name': 'glass_(drink_container)'}, {'frequency': 'c', 'synset': 'globe.n.03', 'synonyms': ['globe'], 'id': 499, 'def': 'a sphere on which a map (especially of the earth) is represented', 'name': 'globe'}, {'frequency': 'f', 'synset': 'glove.n.02', 'synonyms': ['glove'], 'id': 500, 'def': 'handwear covering the hand', 'name': 'glove'}, {'frequency': 'c', 'synset': 'goat.n.01', 'synonyms': ['goat'], 'id': 501, 'def': 'a common goat', 'name': 'goat'}, {'frequency': 'f', 'synset': 'goggles.n.01', 'synonyms': ['goggles'], 'id': 502, 'def': 'tight-fitting spectacles worn to protect the eyes', 'name': 'goggles'}, {'frequency': 'r', 'synset': 'goldfish.n.01', 'synonyms': ['goldfish'], 'id': 503, 'def': 'small golden or orange-red freshwater fishes used as pond or aquarium pets', 'name': 'goldfish'}, {'frequency': 'c', 'synset': 'golf_club.n.02', 'synonyms': ['golf_club', 'golf-club'], 'id': 504, 'def': 'golf equipment used by a golfer to hit a golf ball', 'name': 'golf_club'}, {'frequency': 'c', 'synset': 'golfcart.n.01', 'synonyms': ['golfcart'], 'id': 505, 'def': 'a small motor vehicle in which golfers can ride between shots', 'name': 'golfcart'}, {'frequency': 'r', 'synset': 'gondola.n.02', 'synonyms': ['gondola_(boat)'], 'id': 506, 'def': 'long narrow flat-bottomed boat propelled by sculling; traditionally used on canals of Venice', 'name': 'gondola_(boat)'}, {'frequency': 'c', 'synset': 'goose.n.01', 'synonyms': ['goose'], 'id': 507, 'def': 'loud, web-footed long-necked aquatic birds usually larger than ducks', 'name': 'goose'}, {'frequency': 'r', 'synset': 'gorilla.n.01', 'synonyms': ['gorilla'], 'id': 508, 'def': 'largest ape', 'name': 'gorilla'}, {'frequency': 'r', 'synset': 'gourd.n.02', 'synonyms': ['gourd'], 'id': 509, 'def': 'any of numerous inedible fruits with hard rinds', 'name': 'gourd'}, {'frequency': 'f', 'synset': 'grape.n.01', 'synonyms': ['grape'], 'id': 510, 'def': 'any of various juicy fruit with green or purple skins; grow in clusters', 'name': 'grape'}, {'frequency': 'c', 'synset': 'grater.n.01', 'synonyms': ['grater'], 'id': 511, 'def': 'utensil with sharp perforations for shredding foods (as vegetables or cheese)', 'name': 'grater'}, {'frequency': 'c', 'synset': 'gravestone.n.01', 'synonyms': ['gravestone', 'headstone', 'tombstone'], 'id': 512, 'def': 'a stone that is used to mark a grave', 'name': 'gravestone'}, {'frequency': 'r', 'synset': 'gravy_boat.n.01', 'synonyms': ['gravy_boat', 'gravy_holder'], 'id': 513, 'def': 'a dish (often boat-shaped) for serving gravy or sauce', 'name': 'gravy_boat'}, {'frequency': 'f', 'synset': 'green_bean.n.02', 'synonyms': ['green_bean'], 'id': 514, 'def': 'a common bean plant cultivated for its slender green edible pods', 'name': 'green_bean'}, {'frequency': 'f', 'synset': 'green_onion.n.01', 'synonyms': ['green_onion', 'spring_onion', 'scallion'], 'id': 515, 'def': 'a young onion before the bulb has enlarged', 'name': 'green_onion'}, {'frequency': 'r', 'synset': 'griddle.n.01', 'synonyms': ['griddle'], 'id': 516, 'def': 'cooking utensil consisting of a flat heated surface on which food is cooked', 'name': 'griddle'}, {'frequency': 'f', 'synset': 'grill.n.02', 'synonyms': ['grill', 'grille', 'grillwork', 'radiator_grille'], 'id': 517, 'def': 'a framework of metal bars used as a partition or a grate', 'name': 'grill'}, {'frequency': 'r', 'synset': 'grits.n.01', 'synonyms': ['grits', 'hominy_grits'], 'id': 518, 'def': 'coarsely ground corn boiled as a breakfast dish', 'name': 'grits'}, {'frequency': 'c', 'synset': 'grizzly.n.01', 'synonyms': ['grizzly', 'grizzly_bear'], 'id': 519, 'def': 'powerful brownish-yellow bear of the uplands of western North America', 'name': 'grizzly'}, {'frequency': 'c', 'synset': 'grocery_bag.n.01', 'synonyms': ['grocery_bag'], 'id': 520, 'def': "a sack for holding customer's groceries", 'name': 'grocery_bag'}, {'frequency': 'f', 'synset': 'guitar.n.01', 'synonyms': ['guitar'], 'id': 521, 'def': 'a stringed instrument usually having six strings; played by strumming or plucking', 'name': 'guitar'}, {'frequency': 'c', 'synset': 'gull.n.02', 'synonyms': ['gull', 'seagull'], 'id': 522, 'def': 'mostly white aquatic bird having long pointed wings and short legs', 'name': 'gull'}, {'frequency': 'c', 'synset': 'gun.n.01', 'synonyms': ['gun'], 'id': 523, 'def': 'a weapon that discharges a bullet at high velocity from a metal tube', 'name': 'gun'}, {'frequency': 'f', 'synset': 'hairbrush.n.01', 'synonyms': ['hairbrush'], 'id': 524, 'def': "a brush used to groom a person's hair", 'name': 'hairbrush'}, {'frequency': 'c', 'synset': 'hairnet.n.01', 'synonyms': ['hairnet'], 'id': 525, 'def': 'a small net that someone wears over their hair to keep it in place', 'name': 'hairnet'}, {'frequency': 'c', 'synset': 'hairpin.n.01', 'synonyms': ['hairpin'], 'id': 526, 'def': "a double pronged pin used to hold women's hair in place", 'name': 'hairpin'}, {'frequency': 'r', 'synset': 'halter.n.03', 'synonyms': ['halter_top'], 'id': 527, 'def': "a woman's top that fastens behind the back and neck leaving the back and arms uncovered", 'name': 'halter_top'}, {'frequency': 'f', 'synset': 'ham.n.01', 'synonyms': ['ham', 'jambon', 'gammon'], 'id': 528, 'def': 'meat cut from the thigh of a hog (usually smoked)', 'name': 'ham'}, {'frequency': 'c', 'synset': 'hamburger.n.01', 'synonyms': ['hamburger', 'beefburger', 'burger'], 'id': 529, 'def': 'a sandwich consisting of a patty of minced beef served on a bun', 'name': 'hamburger'}, {'frequency': 'c', 'synset': 'hammer.n.02', 'synonyms': ['hammer'], 'id': 530, 'def': 'a hand tool with a heavy head and a handle; used to deliver an impulsive force by striking', 'name': 'hammer'}, {'frequency': 'c', 'synset': 'hammock.n.02', 'synonyms': ['hammock'], 'id': 531, 'def': 'a hanging bed of canvas or rope netting (usually suspended between two trees)', 'name': 'hammock'}, {'frequency': 'r', 'synset': 'hamper.n.02', 'synonyms': ['hamper'], 'id': 532, 'def': 'a basket usually with a cover', 'name': 'hamper'}, {'frequency': 'c', 'synset': 'hamster.n.01', 'synonyms': ['hamster'], 'id': 533, 'def': 'short-tailed burrowing rodent with large cheek pouches', 'name': 'hamster'}, {'frequency': 'f', 'synset': 'hand_blower.n.01', 'synonyms': ['hair_dryer'], 'id': 534, 'def': 'a hand-held electric blower that can blow warm air onto the hair', 'name': 'hair_dryer'}, {'frequency': 'r', 'synset': 'hand_glass.n.01', 'synonyms': ['hand_glass', 'hand_mirror'], 'id': 535, 'def': 'a mirror intended to be held in the hand', 'name': 'hand_glass'}, {'frequency': 'f', 'synset': 'hand_towel.n.01', 'synonyms': ['hand_towel', 'face_towel'], 'id': 536, 'def': 'a small towel used to dry the hands or face', 'name': 'hand_towel'}, {'frequency': 'c', 'synset': 'handcart.n.01', 'synonyms': ['handcart', 'pushcart', 'hand_truck'], 'id': 537, 'def': 'wheeled vehicle that can be pushed by a person', 'name': 'handcart'}, {'frequency': 'r', 'synset': 'handcuff.n.01', 'synonyms': ['handcuff'], 'id': 538, 'def': 'shackle that consists of a metal loop that can be locked around the wrist', 'name': 'handcuff'}, {'frequency': 'c', 'synset': 'handkerchief.n.01', 'synonyms': ['handkerchief'], 'id': 539, 'def': 'a square piece of cloth used for wiping the eyes or nose or as a costume accessory', 'name': 'handkerchief'}, {'frequency': 'f', 'synset': 'handle.n.01', 'synonyms': ['handle', 'grip', 'handgrip'], 'id': 540, 'def': 'the appendage to an object that is designed to be held in order to use or move it', 'name': 'handle'}, {'frequency': 'r', 'synset': 'handsaw.n.01', 'synonyms': ['handsaw', "carpenter's_saw"], 'id': 541, 'def': 'a saw used with one hand for cutting wood', 'name': 'handsaw'}, {'frequency': 'r', 'synset': 'hardback.n.01', 'synonyms': ['hardback_book', 'hardcover_book'], 'id': 542, 'def': 'a book with cardboard or cloth or leather covers', 'name': 'hardback_book'}, {'frequency': 'r', 'synset': 'harmonium.n.01', 'synonyms': ['harmonium', 'organ_(musical_instrument)', 'reed_organ_(musical_instrument)'], 'id': 543, 'def': 'a free-reed instrument in which air is forced through the reeds by bellows', 'name': 'harmonium'}, {'frequency': 'f', 'synset': 'hat.n.01', 'synonyms': ['hat'], 'id': 544, 'def': 'headwear that protects the head from bad weather, sun, or worn for fashion', 'name': 'hat'}, {'frequency': 'r', 'synset': 'hatbox.n.01', 'synonyms': ['hatbox'], 'id': 545, 'def': 'a round piece of luggage for carrying hats', 'name': 'hatbox'}, {'frequency': 'c', 'synset': 'head_covering.n.01', 'synonyms': ['veil'], 'id': 546, 'def': 'a garment that covers the head OR face', 'name': 'veil'}, {'frequency': 'f', 'synset': 'headband.n.01', 'synonyms': ['headband'], 'id': 547, 'def': 'a band worn around or over the head', 'name': 'headband'}, {'frequency': 'f', 'synset': 'headboard.n.01', 'synonyms': ['headboard'], 'id': 548, 'def': 'a vertical board or panel forming the head of a bedstead', 'name': 'headboard'}, {'frequency': 'f', 'synset': 'headlight.n.01', 'synonyms': ['headlight', 'headlamp'], 'id': 549, 'def': 'a powerful light with reflector; attached to the front of an automobile or locomotive', 'name': 'headlight'}, {'frequency': 'c', 'synset': 'headscarf.n.01', 'synonyms': ['headscarf'], 'id': 550, 'def': 'a kerchief worn over the head and tied under the chin', 'name': 'headscarf'}, {'frequency': 'r', 'synset': 'headset.n.01', 'synonyms': ['headset'], 'id': 551, 'def': 'receiver consisting of a pair of headphones', 'name': 'headset'}, {'frequency': 'c', 'synset': 'headstall.n.01', 'synonyms': ['headstall_(for_horses)', 'headpiece_(for_horses)'], 'id': 552, 'def': "the band that is the part of a bridle that fits around a horse's head", 'name': 'headstall_(for_horses)'}, {'frequency': 'c', 'synset': 'heart.n.02', 'synonyms': ['heart'], 'id': 553, 'def': 'a muscular organ; its contractions move the blood through the body', 'name': 'heart'}, {'frequency': 'c', 'synset': 'heater.n.01', 'synonyms': ['heater', 'warmer'], 'id': 554, 'def': 'device that heats water or supplies warmth to a room', 'name': 'heater'}, {'frequency': 'c', 'synset': 'helicopter.n.01', 'synonyms': ['helicopter'], 'id': 555, 'def': 'an aircraft without wings that obtains its lift from the rotation of overhead blades', 'name': 'helicopter'}, {'frequency': 'f', 'synset': 'helmet.n.02', 'synonyms': ['helmet'], 'id': 556, 'def': 'a protective headgear made of hard material to resist blows', 'name': 'helmet'}, {'frequency': 'r', 'synset': 'heron.n.02', 'synonyms': ['heron'], 'id': 557, 'def': 'grey or white wading bird with long neck and long legs and (usually) long bill', 'name': 'heron'}, {'frequency': 'c', 'synset': 'highchair.n.01', 'synonyms': ['highchair', 'feeding_chair'], 'id': 558, 'def': 'a chair for feeding a very young child', 'name': 'highchair'}, {'frequency': 'f', 'synset': 'hinge.n.01', 'synonyms': ['hinge'], 'id': 559, 'def': 'a joint that holds two parts together so that one can swing relative to the other', 'name': 'hinge'}, {'frequency': 'r', 'synset': 'hippopotamus.n.01', 'synonyms': ['hippopotamus'], 'id': 560, 'def': 'massive thick-skinned animal living in or around rivers of tropical Africa', 'name': 'hippopotamus'}, {'frequency': 'r', 'synset': 'hockey_stick.n.01', 'synonyms': ['hockey_stick'], 'id': 561, 'def': 'sports implement consisting of a stick used by hockey players to move the puck', 'name': 'hockey_stick'}, {'frequency': 'c', 'synset': 'hog.n.03', 'synonyms': ['hog', 'pig'], 'id': 562, 'def': 'domestic swine', 'name': 'hog'}, {'frequency': 'f', 'synset': 'home_plate.n.01', 'synonyms': ['home_plate_(baseball)', 'home_base_(baseball)'], 'id': 563, 'def': '(baseball) a rubber slab where the batter stands; it must be touched by a base runner in order to score', 'name': 'home_plate_(baseball)'}, {'frequency': 'c', 'synset': 'honey.n.01', 'synonyms': ['honey'], 'id': 564, 'def': 'a sweet yellow liquid produced by bees', 'name': 'honey'}, {'frequency': 'f', 'synset': 'hood.n.06', 'synonyms': ['fume_hood', 'exhaust_hood'], 'id': 565, 'def': 'metal covering leading to a vent that exhausts smoke or fumes', 'name': 'fume_hood'}, {'frequency': 'f', 'synset': 'hook.n.05', 'synonyms': ['hook'], 'id': 566, 'def': 'a curved or bent implement for suspending or pulling something', 'name': 'hook'}, {'frequency': 'r', 'synset': 'hookah.n.01', 'synonyms': ['hookah', 'narghile', 'nargileh', 'sheesha', 'shisha', 'water_pipe'], 'id': 567, 'def': 'a tobacco pipe with a long flexible tube connected to a container where the smoke is cooled by passing through water', 'name': 'hookah'}, {'frequency': 'r', 'synset': 'hornet.n.01', 'synonyms': ['hornet'], 'id': 568, 'def': 'large stinging wasp', 'name': 'hornet'}, {'frequency': 'f', 'synset': 'horse.n.01', 'synonyms': ['horse'], 'id': 569, 'def': 'a common horse', 'name': 'horse'}, {'frequency': 'f', 'synset': 'hose.n.03', 'synonyms': ['hose', 'hosepipe'], 'id': 570, 'def': 'a flexible pipe for conveying a liquid or gas', 'name': 'hose'}, {'frequency': 'r', 'synset': 'hot-air_balloon.n.01', 'synonyms': ['hot-air_balloon'], 'id': 571, 'def': 'balloon for travel through the air in a basket suspended below a large bag of heated air', 'name': 'hot-air_balloon'}, {'frequency': 'r', 'synset': 'hot_plate.n.01', 'synonyms': ['hotplate'], 'id': 572, 'def': 'a portable electric appliance for heating or cooking or keeping food warm', 'name': 'hotplate'}, {'frequency': 'c', 'synset': 'hot_sauce.n.01', 'synonyms': ['hot_sauce'], 'id': 573, 'def': 'a pungent peppery sauce', 'name': 'hot_sauce'}, {'frequency': 'r', 'synset': 'hourglass.n.01', 'synonyms': ['hourglass'], 'id': 574, 'def': 'a sandglass timer that runs for sixty minutes', 'name': 'hourglass'}, {'frequency': 'r', 'synset': 'houseboat.n.01', 'synonyms': ['houseboat'], 'id': 575, 'def': 'a barge that is designed and equipped for use as a dwelling', 'name': 'houseboat'}, {'frequency': 'c', 'synset': 'hummingbird.n.01', 'synonyms': ['hummingbird'], 'id': 576, 'def': 'tiny American bird having brilliant iridescent plumage and long slender bills', 'name': 'hummingbird'}, {'frequency': 'r', 'synset': 'hummus.n.01', 'synonyms': ['hummus', 'humus', 'hommos', 'hoummos', 'humous'], 'id': 577, 'def': 'a thick spread made from mashed chickpeas', 'name': 'hummus'}, {'frequency': 'f', 'synset': 'ice_bear.n.01', 'synonyms': ['polar_bear'], 'id': 578, 'def': 'white bear of Arctic regions', 'name': 'polar_bear'}, {'frequency': 'c', 'synset': 'ice_cream.n.01', 'synonyms': ['icecream'], 'id': 579, 'def': 'frozen dessert containing cream and sugar and flavoring', 'name': 'icecream'}, {'frequency': 'r', 'synset': 'ice_lolly.n.01', 'synonyms': ['popsicle'], 'id': 580, 'def': 'ice cream or water ice on a small wooden stick', 'name': 'popsicle'}, {'frequency': 'c', 'synset': 'ice_maker.n.01', 'synonyms': ['ice_maker'], 'id': 581, 'def': 'an appliance included in some electric refrigerators for making ice cubes', 'name': 'ice_maker'}, {'frequency': 'r', 'synset': 'ice_pack.n.01', 'synonyms': ['ice_pack', 'ice_bag'], 'id': 582, 'def': 'a waterproof bag filled with ice: applied to the body (especially the head) to cool or reduce swelling', 'name': 'ice_pack'}, {'frequency': 'r', 'synset': 'ice_skate.n.01', 'synonyms': ['ice_skate'], 'id': 583, 'def': 'skate consisting of a boot with a steel blade fitted to the sole', 'name': 'ice_skate'}, {'frequency': 'c', 'synset': 'igniter.n.01', 'synonyms': ['igniter', 'ignitor', 'lighter'], 'id': 584, 'def': 'a substance or device used to start a fire', 'name': 'igniter'}, {'frequency': 'r', 'synset': 'inhaler.n.01', 'synonyms': ['inhaler', 'inhalator'], 'id': 585, 'def': 'a dispenser that produces a chemical vapor to be inhaled through mouth or nose', 'name': 'inhaler'}, {'frequency': 'f', 'synset': 'ipod.n.01', 'synonyms': ['iPod'], 'id': 586, 'def': 'a pocket-sized device used to play music files', 'name': 'iPod'}, {'frequency': 'c', 'synset': 'iron.n.04', 'synonyms': ['iron_(for_clothing)', 'smoothing_iron_(for_clothing)'], 'id': 587, 'def': 'home appliance consisting of a flat metal base that is heated and used to smooth cloth', 'name': 'iron_(for_clothing)'}, {'frequency': 'c', 'synset': 'ironing_board.n.01', 'synonyms': ['ironing_board'], 'id': 588, 'def': 'narrow padded board on collapsible supports; used for ironing clothes', 'name': 'ironing_board'}, {'frequency': 'f', 'synset': 'jacket.n.01', 'synonyms': ['jacket'], 'id': 589, 'def': 'a waist-length coat', 'name': 'jacket'}, {'frequency': 'c', 'synset': 'jam.n.01', 'synonyms': ['jam'], 'id': 590, 'def': 'preserve of crushed fruit', 'name': 'jam'}, {'frequency': 'f', 'synset': 'jar.n.01', 'synonyms': ['jar'], 'id': 591, 'def': 'a vessel (usually cylindrical) with a wide mouth and without handles', 'name': 'jar'}, {'frequency': 'f', 'synset': 'jean.n.01', 'synonyms': ['jean', 'blue_jean', 'denim'], 'id': 592, 'def': '(usually plural) close-fitting trousers of heavy denim for manual work or casual wear', 'name': 'jean'}, {'frequency': 'c', 'synset': 'jeep.n.01', 'synonyms': ['jeep', 'landrover'], 'id': 593, 'def': 'a car suitable for traveling over rough terrain', 'name': 'jeep'}, {'frequency': 'r', 'synset': 'jelly_bean.n.01', 'synonyms': ['jelly_bean', 'jelly_egg'], 'id': 594, 'def': 'sugar-glazed jellied candy', 'name': 'jelly_bean'}, {'frequency': 'f', 'synset': 'jersey.n.03', 'synonyms': ['jersey', 'T-shirt', 'tee_shirt'], 'id': 595, 'def': 'a close-fitting pullover shirt', 'name': 'jersey'}, {'frequency': 'c', 'synset': 'jet.n.01', 'synonyms': ['jet_plane', 'jet-propelled_plane'], 'id': 596, 'def': 'an airplane powered by one or more jet engines', 'name': 'jet_plane'}, {'frequency': 'r', 'synset': 'jewel.n.01', 'synonyms': ['jewel', 'gem', 'precious_stone'], 'id': 597, 'def': 'a precious or semiprecious stone incorporated into a piece of jewelry', 'name': 'jewel'}, {'frequency': 'c', 'synset': 'jewelry.n.01', 'synonyms': ['jewelry', 'jewellery'], 'id': 598, 'def': 'an adornment (as a bracelet or ring or necklace) made of precious metals and set with gems (or imitation gems)', 'name': 'jewelry'}, {'frequency': 'r', 'synset': 'joystick.n.02', 'synonyms': ['joystick'], 'id': 599, 'def': 'a control device for computers consisting of a vertical handle that can move freely in two directions', 'name': 'joystick'}, {'frequency': 'c', 'synset': 'jump_suit.n.01', 'synonyms': ['jumpsuit'], 'id': 600, 'def': "one-piece garment fashioned after a parachutist's uniform", 'name': 'jumpsuit'}, {'frequency': 'c', 'synset': 'kayak.n.01', 'synonyms': ['kayak'], 'id': 601, 'def': 'a small canoe consisting of a light frame made watertight with animal skins', 'name': 'kayak'}, {'frequency': 'r', 'synset': 'keg.n.02', 'synonyms': ['keg'], 'id': 602, 'def': 'small cask or barrel', 'name': 'keg'}, {'frequency': 'r', 'synset': 'kennel.n.01', 'synonyms': ['kennel', 'doghouse'], 'id': 603, 'def': 'outbuilding that serves as a shelter for a dog', 'name': 'kennel'}, {'frequency': 'c', 'synset': 'kettle.n.01', 'synonyms': ['kettle', 'boiler'], 'id': 604, 'def': 'a metal pot for stewing or boiling; usually has a lid', 'name': 'kettle'}, {'frequency': 'f', 'synset': 'key.n.01', 'synonyms': ['key'], 'id': 605, 'def': 'metal instrument used to unlock a lock', 'name': 'key'}, {'frequency': 'r', 'synset': 'keycard.n.01', 'synonyms': ['keycard'], 'id': 606, 'def': 'a plastic card used to gain access typically to a door', 'name': 'keycard'}, {'frequency': 'c', 'synset': 'kilt.n.01', 'synonyms': ['kilt'], 'id': 607, 'def': 'a knee-length pleated tartan skirt worn by men as part of the traditional dress in the Highlands of northern Scotland', 'name': 'kilt'}, {'frequency': 'c', 'synset': 'kimono.n.01', 'synonyms': ['kimono'], 'id': 608, 'def': 'a loose robe; imitated from robes originally worn by Japanese', 'name': 'kimono'}, {'frequency': 'f', 'synset': 'kitchen_sink.n.01', 'synonyms': ['kitchen_sink'], 'id': 609, 'def': 'a sink in a kitchen', 'name': 'kitchen_sink'}, {'frequency': 'r', 'synset': 'kitchen_table.n.01', 'synonyms': ['kitchen_table'], 'id': 610, 'def': 'a table in the kitchen', 'name': 'kitchen_table'}, {'frequency': 'f', 'synset': 'kite.n.03', 'synonyms': ['kite'], 'id': 611, 'def': 'plaything consisting of a light frame covered with tissue paper; flown in wind at end of a string', 'name': 'kite'}, {'frequency': 'c', 'synset': 'kitten.n.01', 'synonyms': ['kitten', 'kitty'], 'id': 612, 'def': 'young domestic cat', 'name': 'kitten'}, {'frequency': 'c', 'synset': 'kiwi.n.03', 'synonyms': ['kiwi_fruit'], 'id': 613, 'def': 'fuzzy brown egg-shaped fruit with slightly tart green flesh', 'name': 'kiwi_fruit'}, {'frequency': 'f', 'synset': 'knee_pad.n.01', 'synonyms': ['knee_pad'], 'id': 614, 'def': 'protective garment consisting of a pad worn by football or baseball or hockey players', 'name': 'knee_pad'}, {'frequency': 'f', 'synset': 'knife.n.01', 'synonyms': ['knife'], 'id': 615, 'def': 'tool with a blade and point used as a cutting instrument', 'name': 'knife'}, {'frequency': 'r', 'synset': 'knitting_needle.n.01', 'synonyms': ['knitting_needle'], 'id': 616, 'def': 'needle consisting of a slender rod with pointed ends; usually used in pairs', 'name': 'knitting_needle'}, {'frequency': 'f', 'synset': 'knob.n.02', 'synonyms': ['knob'], 'id': 617, 'def': 'a round handle often found on a door', 'name': 'knob'}, {'frequency': 'r', 'synset': 'knocker.n.05', 'synonyms': ['knocker_(on_a_door)', 'doorknocker'], 'id': 618, 'def': 'a device (usually metal and ornamental) attached by a hinge to a door', 'name': 'knocker_(on_a_door)'}, {'frequency': 'r', 'synset': 'koala.n.01', 'synonyms': ['koala', 'koala_bear'], 'id': 619, 'def': 'sluggish tailless Australian marsupial with grey furry ears and coat', 'name': 'koala'}, {'frequency': 'r', 'synset': 'lab_coat.n.01', 'synonyms': ['lab_coat', 'laboratory_coat'], 'id': 620, 'def': 'a light coat worn to protect clothing from substances used while working in a laboratory', 'name': 'lab_coat'}, {'frequency': 'f', 'synset': 'ladder.n.01', 'synonyms': ['ladder'], 'id': 621, 'def': 'steps consisting of two parallel members connected by rungs', 'name': 'ladder'}, {'frequency': 'c', 'synset': 'ladle.n.01', 'synonyms': ['ladle'], 'id': 622, 'def': 'a spoon-shaped vessel with a long handle frequently used to transfer liquids', 'name': 'ladle'}, {'frequency': 'c', 'synset': 'ladybug.n.01', 'synonyms': ['ladybug', 'ladybeetle', 'ladybird_beetle'], 'id': 623, 'def': 'small round bright-colored and spotted beetle, typically red and black', 'name': 'ladybug'}, {'frequency': 'f', 'synset': 'lamb.n.01', 'synonyms': ['lamb_(animal)'], 'id': 624, 'def': 'young sheep', 'name': 'lamb_(animal)'}, {'frequency': 'r', 'synset': 'lamb_chop.n.01', 'synonyms': ['lamb-chop', 'lambchop'], 'id': 625, 'def': 'chop cut from a lamb', 'name': 'lamb-chop'}, {'frequency': 'f', 'synset': 'lamp.n.02', 'synonyms': ['lamp'], 'id': 626, 'def': 'a piece of furniture holding one or more electric light bulbs', 'name': 'lamp'}, {'frequency': 'f', 'synset': 'lamppost.n.01', 'synonyms': ['lamppost'], 'id': 627, 'def': 'a metal post supporting an outdoor lamp (such as a streetlight)', 'name': 'lamppost'}, {'frequency': 'f', 'synset': 'lampshade.n.01', 'synonyms': ['lampshade'], 'id': 628, 'def': 'a protective ornamental shade used to screen a light bulb from direct view', 'name': 'lampshade'}, {'frequency': 'c', 'synset': 'lantern.n.01', 'synonyms': ['lantern'], 'id': 629, 'def': 'light in a transparent protective case', 'name': 'lantern'}, {'frequency': 'f', 'synset': 'lanyard.n.02', 'synonyms': ['lanyard', 'laniard'], 'id': 630, 'def': 'a cord worn around the neck to hold a knife or whistle, etc.', 'name': 'lanyard'}, {'frequency': 'f', 'synset': 'laptop.n.01', 'synonyms': ['laptop_computer', 'notebook_computer'], 'id': 631, 'def': 'a portable computer small enough to use in your lap', 'name': 'laptop_computer'}, {'frequency': 'r', 'synset': 'lasagna.n.01', 'synonyms': ['lasagna', 'lasagne'], 'id': 632, 'def': 'baked dish of layers of lasagna pasta with sauce and cheese and meat or vegetables', 'name': 'lasagna'}, {'frequency': 'f', 'synset': 'latch.n.02', 'synonyms': ['latch'], 'id': 633, 'def': 'a bar that can be lowered or slid into a groove to fasten a door or gate', 'name': 'latch'}, {'frequency': 'r', 'synset': 'lawn_mower.n.01', 'synonyms': ['lawn_mower'], 'id': 634, 'def': 'garden tool for mowing grass on lawns', 'name': 'lawn_mower'}, {'frequency': 'r', 'synset': 'leather.n.01', 'synonyms': ['leather'], 'id': 635, 'def': 'an animal skin made smooth and flexible by removing the hair and then tanning', 'name': 'leather'}, {'frequency': 'c', 'synset': 'legging.n.01', 'synonyms': ['legging_(clothing)', 'leging_(clothing)', 'leg_covering'], 'id': 636, 'def': 'a garment covering the leg (usually extending from the knee to the ankle)', 'name': 'legging_(clothing)'}, {'frequency': 'c', 'synset': 'lego.n.01', 'synonyms': ['Lego', 'Lego_set'], 'id': 637, 'def': "a child's plastic construction set for making models from blocks", 'name': 'Lego'}, {'frequency': 'r', 'synset': 'legume.n.02', 'synonyms': ['legume'], 'id': 638, 'def': 'the fruit or seed of bean or pea plants', 'name': 'legume'}, {'frequency': 'f', 'synset': 'lemon.n.01', 'synonyms': ['lemon'], 'id': 639, 'def': 'yellow oval fruit with juicy acidic flesh', 'name': 'lemon'}, {'frequency': 'r', 'synset': 'lemonade.n.01', 'synonyms': ['lemonade'], 'id': 640, 'def': 'sweetened beverage of diluted lemon juice', 'name': 'lemonade'}, {'frequency': 'f', 'synset': 'lettuce.n.02', 'synonyms': ['lettuce'], 'id': 641, 'def': 'leafy plant commonly eaten in salad or on sandwiches', 'name': 'lettuce'}, {'frequency': 'f', 'synset': 'license_plate.n.01', 'synonyms': ['license_plate', 'numberplate'], 'id': 642, 'def': "a plate mounted on the front and back of car and bearing the car's registration number", 'name': 'license_plate'}, {'frequency': 'f', 'synset': 'life_buoy.n.01', 'synonyms': ['life_buoy', 'lifesaver', 'life_belt', 'life_ring'], 'id': 643, 'def': 'a ring-shaped life preserver used to prevent drowning (NOT a life-jacket or vest)', 'name': 'life_buoy'}, {'frequency': 'f', 'synset': 'life_jacket.n.01', 'synonyms': ['life_jacket', 'life_vest'], 'id': 644, 'def': 'life preserver consisting of a sleeveless jacket of buoyant or inflatable design', 'name': 'life_jacket'}, {'frequency': 'f', 'synset': 'light_bulb.n.01', 'synonyms': ['lightbulb'], 'id': 645, 'def': 'lightblub/source of light', 'name': 'lightbulb'}, {'frequency': 'r', 'synset': 'lightning_rod.n.02', 'synonyms': ['lightning_rod', 'lightning_conductor'], 'id': 646, 'def': 'a metallic conductor that is attached to a high point and leads to the ground', 'name': 'lightning_rod'}, {'frequency': 'f', 'synset': 'lime.n.06', 'synonyms': ['lime'], 'id': 647, 'def': 'the green acidic fruit of any of various lime trees', 'name': 'lime'}, {'frequency': 'r', 'synset': 'limousine.n.01', 'synonyms': ['limousine'], 'id': 648, 'def': 'long luxurious car; usually driven by a chauffeur', 'name': 'limousine'}, {'frequency': 'c', 'synset': 'lion.n.01', 'synonyms': ['lion'], 'id': 649, 'def': 'large gregarious predatory cat of Africa and India', 'name': 'lion'}, {'frequency': 'c', 'synset': 'lip_balm.n.01', 'synonyms': ['lip_balm'], 'id': 650, 'def': 'a balm applied to the lips', 'name': 'lip_balm'}, {'frequency': 'r', 'synset': 'liquor.n.01', 'synonyms': ['liquor', 'spirits', 'hard_liquor', 'liqueur', 'cordial'], 'id': 651, 'def': 'liquor or beer', 'name': 'liquor'}, {'frequency': 'c', 'synset': 'lizard.n.01', 'synonyms': ['lizard'], 'id': 652, 'def': 'a reptile with usually two pairs of legs and a tapering tail', 'name': 'lizard'}, {'frequency': 'f', 'synset': 'log.n.01', 'synonyms': ['log'], 'id': 653, 'def': 'a segment of the trunk of a tree when stripped of branches', 'name': 'log'}, {'frequency': 'c', 'synset': 'lollipop.n.02', 'synonyms': ['lollipop'], 'id': 654, 'def': 'hard candy on a stick', 'name': 'lollipop'}, {'frequency': 'f', 'synset': 'loudspeaker.n.01', 'synonyms': ['speaker_(stero_equipment)'], 'id': 655, 'def': 'electronic device that produces sound often as part of a stereo system', 'name': 'speaker_(stero_equipment)'}, {'frequency': 'c', 'synset': 'love_seat.n.01', 'synonyms': ['loveseat'], 'id': 656, 'def': 'small sofa that seats two people', 'name': 'loveseat'}, {'frequency': 'r', 'synset': 'machine_gun.n.01', 'synonyms': ['machine_gun'], 'id': 657, 'def': 'a rapidly firing automatic gun', 'name': 'machine_gun'}, {'frequency': 'f', 'synset': 'magazine.n.02', 'synonyms': ['magazine'], 'id': 658, 'def': 'a paperback periodic publication', 'name': 'magazine'}, {'frequency': 'f', 'synset': 'magnet.n.01', 'synonyms': ['magnet'], 'id': 659, 'def': 'a device that attracts iron and produces a magnetic field', 'name': 'magnet'}, {'frequency': 'c', 'synset': 'mail_slot.n.01', 'synonyms': ['mail_slot'], 'id': 660, 'def': 'a slot (usually in a door) through which mail can be delivered', 'name': 'mail_slot'}, {'frequency': 'f', 'synset': 'mailbox.n.01', 'synonyms': ['mailbox_(at_home)', 'letter_box_(at_home)'], 'id': 661, 'def': 'a private box for delivery of mail', 'name': 'mailbox_(at_home)'}, {'frequency': 'r', 'synset': 'mallard.n.01', 'synonyms': ['mallard'], 'id': 662, 'def': 'wild dabbling duck from which domestic ducks are descended', 'name': 'mallard'}, {'frequency': 'r', 'synset': 'mallet.n.01', 'synonyms': ['mallet'], 'id': 663, 'def': 'a sports implement with a long handle and a hammer-like head used to hit a ball', 'name': 'mallet'}, {'frequency': 'r', 'synset': 'mammoth.n.01', 'synonyms': ['mammoth'], 'id': 664, 'def': 'any of numerous extinct elephants widely distributed in the Pleistocene', 'name': 'mammoth'}, {'frequency': 'r', 'synset': 'manatee.n.01', 'synonyms': ['manatee'], 'id': 665, 'def': 'sirenian mammal of tropical coastal waters of America', 'name': 'manatee'}, {'frequency': 'c', 'synset': 'mandarin.n.05', 'synonyms': ['mandarin_orange'], 'id': 666, 'def': 'a somewhat flat reddish-orange loose skinned citrus of China', 'name': 'mandarin_orange'}, {'frequency': 'c', 'synset': 'manger.n.01', 'synonyms': ['manger', 'trough'], 'id': 667, 'def': 'a container (usually in a barn or stable) from which cattle or horses feed', 'name': 'manger'}, {'frequency': 'f', 'synset': 'manhole.n.01', 'synonyms': ['manhole'], 'id': 668, 'def': 'a hole (usually with a flush cover) through which a person can gain access to an underground structure', 'name': 'manhole'}, {'frequency': 'f', 'synset': 'map.n.01', 'synonyms': ['map'], 'id': 669, 'def': "a diagrammatic representation of the earth's surface (or part of it)", 'name': 'map'}, {'frequency': 'f', 'synset': 'marker.n.03', 'synonyms': ['marker'], 'id': 670, 'def': 'a writing implement for making a mark', 'name': 'marker'}, {'frequency': 'r', 'synset': 'martini.n.01', 'synonyms': ['martini'], 'id': 671, 'def': 'a cocktail made of gin (or vodka) with dry vermouth', 'name': 'martini'}, {'frequency': 'r', 'synset': 'mascot.n.01', 'synonyms': ['mascot'], 'id': 672, 'def': 'a person or animal that is adopted by a team or other group as a symbolic figure', 'name': 'mascot'}, {'frequency': 'c', 'synset': 'mashed_potato.n.01', 'synonyms': ['mashed_potato'], 'id': 673, 'def': 'potato that has been peeled and boiled and then mashed', 'name': 'mashed_potato'}, {'frequency': 'r', 'synset': 'masher.n.02', 'synonyms': ['masher'], 'id': 674, 'def': 'a kitchen utensil used for mashing (e.g. potatoes)', 'name': 'masher'}, {'frequency': 'f', 'synset': 'mask.n.04', 'synonyms': ['mask', 'facemask'], 'id': 675, 'def': 'a protective covering worn over the face', 'name': 'mask'}, {'frequency': 'f', 'synset': 'mast.n.01', 'synonyms': ['mast'], 'id': 676, 'def': 'a vertical spar for supporting sails', 'name': 'mast'}, {'frequency': 'c', 'synset': 'mat.n.03', 'synonyms': ['mat_(gym_equipment)', 'gym_mat'], 'id': 677, 'def': 'sports equipment consisting of a piece of thick padding on the floor for gymnastics', 'name': 'mat_(gym_equipment)'}, {'frequency': 'r', 'synset': 'matchbox.n.01', 'synonyms': ['matchbox'], 'id': 678, 'def': 'a box for holding matches', 'name': 'matchbox'}, {'frequency': 'f', 'synset': 'mattress.n.01', 'synonyms': ['mattress'], 'id': 679, 'def': 'a thick pad filled with resilient material used as a bed or part of a bed', 'name': 'mattress'}, {'frequency': 'c', 'synset': 'measuring_cup.n.01', 'synonyms': ['measuring_cup'], 'id': 680, 'def': 'graduated cup used to measure liquid or granular ingredients', 'name': 'measuring_cup'}, {'frequency': 'c', 'synset': 'measuring_stick.n.01', 'synonyms': ['measuring_stick', 'ruler_(measuring_stick)', 'measuring_rod'], 'id': 681, 'def': 'measuring instrument having a sequence of marks at regular intervals', 'name': 'measuring_stick'}, {'frequency': 'c', 'synset': 'meatball.n.01', 'synonyms': ['meatball'], 'id': 682, 'def': 'ground meat formed into a ball and fried or simmered in broth', 'name': 'meatball'}, {'frequency': 'c', 'synset': 'medicine.n.02', 'synonyms': ['medicine'], 'id': 683, 'def': 'something that treats or prevents or alleviates the symptoms of disease', 'name': 'medicine'}, {'frequency': 'c', 'synset': 'melon.n.01', 'synonyms': ['melon'], 'id': 684, 'def': 'fruit of the gourd family having a hard rind and sweet juicy flesh', 'name': 'melon'}, {'frequency': 'f', 'synset': 'microphone.n.01', 'synonyms': ['microphone'], 'id': 685, 'def': 'device for converting sound waves into electrical energy', 'name': 'microphone'}, {'frequency': 'r', 'synset': 'microscope.n.01', 'synonyms': ['microscope'], 'id': 686, 'def': 'magnifier of the image of small objects', 'name': 'microscope'}, {'frequency': 'f', 'synset': 'microwave.n.02', 'synonyms': ['microwave_oven'], 'id': 687, 'def': 'kitchen appliance that cooks food by passing an electromagnetic wave through it', 'name': 'microwave_oven'}, {'frequency': 'r', 'synset': 'milestone.n.01', 'synonyms': ['milestone', 'milepost'], 'id': 688, 'def': 'stone post at side of a road to show distances', 'name': 'milestone'}, {'frequency': 'f', 'synset': 'milk.n.01', 'synonyms': ['milk'], 'id': 689, 'def': 'a white nutritious liquid secreted by mammals and used as food by human beings', 'name': 'milk'}, {'frequency': 'r', 'synset': 'milk_can.n.01', 'synonyms': ['milk_can'], 'id': 690, 'def': 'can for transporting milk', 'name': 'milk_can'}, {'frequency': 'r', 'synset': 'milkshake.n.01', 'synonyms': ['milkshake'], 'id': 691, 'def': 'frothy drink of milk and flavoring and sometimes fruit or ice cream', 'name': 'milkshake'}, {'frequency': 'f', 'synset': 'minivan.n.01', 'synonyms': ['minivan'], 'id': 692, 'def': 'a small box-shaped passenger van', 'name': 'minivan'}, {'frequency': 'r', 'synset': 'mint.n.05', 'synonyms': ['mint_candy'], 'id': 693, 'def': 'a candy that is flavored with a mint oil', 'name': 'mint_candy'}, {'frequency': 'f', 'synset': 'mirror.n.01', 'synonyms': ['mirror'], 'id': 694, 'def': 'polished surface that forms images by reflecting light', 'name': 'mirror'}, {'frequency': 'c', 'synset': 'mitten.n.01', 'synonyms': ['mitten'], 'id': 695, 'def': 'glove that encases the thumb separately and the other four fingers together', 'name': 'mitten'}, {'frequency': 'c', 'synset': 'mixer.n.04', 'synonyms': ['mixer_(kitchen_tool)', 'stand_mixer'], 'id': 696, 'def': 'a kitchen utensil that is used for mixing foods', 'name': 'mixer_(kitchen_tool)'}, {'frequency': 'c', 'synset': 'money.n.03', 'synonyms': ['money'], 'id': 697, 'def': 'the official currency issued by a government or national bank', 'name': 'money'}, {'frequency': 'f', 'synset': 'monitor.n.04', 'synonyms': ['monitor_(computer_equipment) computer_monitor'], 'id': 698, 'def': 'a computer monitor', 'name': 'monitor_(computer_equipment) computer_monitor'}, {'frequency': 'c', 'synset': 'monkey.n.01', 'synonyms': ['monkey'], 'id': 699, 'def': 'any of various long-tailed primates', 'name': 'monkey'}, {'frequency': 'f', 'synset': 'motor.n.01', 'synonyms': ['motor'], 'id': 700, 'def': 'machine that converts other forms of energy into mechanical energy and so imparts motion', 'name': 'motor'}, {'frequency': 'f', 'synset': 'motor_scooter.n.01', 'synonyms': ['motor_scooter', 'scooter'], 'id': 701, 'def': 'a wheeled vehicle with small wheels and a low-powered engine', 'name': 'motor_scooter'}, {'frequency': 'r', 'synset': 'motor_vehicle.n.01', 'synonyms': ['motor_vehicle', 'automotive_vehicle'], 'id': 702, 'def': 'a self-propelled wheeled vehicle that does not run on rails', 'name': 'motor_vehicle'}, {'frequency': 'f', 'synset': 'motorcycle.n.01', 'synonyms': ['motorcycle'], 'id': 703, 'def': 'a motor vehicle with two wheels and a strong frame', 'name': 'motorcycle'}, {'frequency': 'f', 'synset': 'mound.n.01', 'synonyms': ['mound_(baseball)', "pitcher's_mound"], 'id': 704, 'def': '(baseball) the slight elevation on which the pitcher stands', 'name': 'mound_(baseball)'}, {'frequency': 'f', 'synset': 'mouse.n.04', 'synonyms': ['mouse_(computer_equipment)', 'computer_mouse'], 'id': 705, 'def': 'a computer input device that controls an on-screen pointer (does not include trackpads / touchpads)', 'name': 'mouse_(computer_equipment)'}, {'frequency': 'f', 'synset': 'mousepad.n.01', 'synonyms': ['mousepad'], 'id': 706, 'def': 'a small portable pad that provides an operating surface for a computer mouse', 'name': 'mousepad'}, {'frequency': 'c', 'synset': 'muffin.n.01', 'synonyms': ['muffin'], 'id': 707, 'def': 'a sweet quick bread baked in a cup-shaped pan', 'name': 'muffin'}, {'frequency': 'f', 'synset': 'mug.n.04', 'synonyms': ['mug'], 'id': 708, 'def': 'with handle and usually cylindrical', 'name': 'mug'}, {'frequency': 'f', 'synset': 'mushroom.n.02', 'synonyms': ['mushroom'], 'id': 709, 'def': 'a common mushroom', 'name': 'mushroom'}, {'frequency': 'r', 'synset': 'music_stool.n.01', 'synonyms': ['music_stool', 'piano_stool'], 'id': 710, 'def': 'a stool for piano players; usually adjustable in height', 'name': 'music_stool'}, {'frequency': 'c', 'synset': 'musical_instrument.n.01', 'synonyms': ['musical_instrument', 'instrument_(musical)'], 'id': 711, 'def': 'any of various devices or contrivances that can be used to produce musical tones or sounds', 'name': 'musical_instrument'}, {'frequency': 'r', 'synset': 'nailfile.n.01', 'synonyms': ['nailfile'], 'id': 712, 'def': 'a small flat file for shaping the nails', 'name': 'nailfile'}, {'frequency': 'f', 'synset': 'napkin.n.01', 'synonyms': ['napkin', 'table_napkin', 'serviette'], 'id': 713, 'def': 'a small piece of table linen or paper that is used to wipe the mouth and to cover the lap in order to protect clothing', 'name': 'napkin'}, {'frequency': 'r', 'synset': 'neckerchief.n.01', 'synonyms': ['neckerchief'], 'id': 714, 'def': 'a kerchief worn around the neck', 'name': 'neckerchief'}, {'frequency': 'f', 'synset': 'necklace.n.01', 'synonyms': ['necklace'], 'id': 715, 'def': 'jewelry consisting of a cord or chain (often bearing gems) worn about the neck as an ornament', 'name': 'necklace'}, {'frequency': 'f', 'synset': 'necktie.n.01', 'synonyms': ['necktie', 'tie_(necktie)'], 'id': 716, 'def': 'neckwear consisting of a long narrow piece of material worn under a collar and tied in knot at the front', 'name': 'necktie'}, {'frequency': 'c', 'synset': 'needle.n.03', 'synonyms': ['needle'], 'id': 717, 'def': 'a sharp pointed implement (usually metal)', 'name': 'needle'}, {'frequency': 'c', 'synset': 'nest.n.01', 'synonyms': ['nest'], 'id': 718, 'def': 'a structure in which animals lay eggs or give birth to their young', 'name': 'nest'}, {'frequency': 'f', 'synset': 'newspaper.n.01', 'synonyms': ['newspaper', 'paper_(newspaper)'], 'id': 719, 'def': 'a daily or weekly publication on folded sheets containing news, articles, and advertisements', 'name': 'newspaper'}, {'frequency': 'c', 'synset': 'newsstand.n.01', 'synonyms': ['newsstand'], 'id': 720, 'def': 'a stall where newspapers and other periodicals are sold', 'name': 'newsstand'}, {'frequency': 'c', 'synset': 'nightwear.n.01', 'synonyms': ['nightshirt', 'nightwear', 'sleepwear', 'nightclothes'], 'id': 721, 'def': 'garments designed to be worn in bed', 'name': 'nightshirt'}, {'frequency': 'r', 'synset': 'nosebag.n.01', 'synonyms': ['nosebag_(for_animals)', 'feedbag'], 'id': 722, 'def': 'a canvas bag that is used to feed an animal (such as a horse); covers the muzzle and fastens at the top of the head', 'name': 'nosebag_(for_animals)'}, {'frequency': 'c', 'synset': 'noseband.n.01', 'synonyms': ['noseband_(for_animals)', 'nosepiece_(for_animals)'], 'id': 723, 'def': "a strap that is the part of a bridle that goes over the animal's nose", 'name': 'noseband_(for_animals)'}, {'frequency': 'f', 'synset': 'notebook.n.01', 'synonyms': ['notebook'], 'id': 724, 'def': 'a book with blank pages for recording notes or memoranda', 'name': 'notebook'}, {'frequency': 'c', 'synset': 'notepad.n.01', 'synonyms': ['notepad'], 'id': 725, 'def': 'a pad of paper for keeping notes', 'name': 'notepad'}, {'frequency': 'f', 'synset': 'nut.n.03', 'synonyms': ['nut'], 'id': 726, 'def': 'a small metal block (usually square or hexagonal) with internal screw thread to be fitted onto a bolt', 'name': 'nut'}, {'frequency': 'r', 'synset': 'nutcracker.n.01', 'synonyms': ['nutcracker'], 'id': 727, 'def': 'a hand tool used to crack nuts open', 'name': 'nutcracker'}, {'frequency': 'f', 'synset': 'oar.n.01', 'synonyms': ['oar'], 'id': 728, 'def': 'an implement used to propel or steer a boat', 'name': 'oar'}, {'frequency': 'r', 'synset': 'octopus.n.01', 'synonyms': ['octopus_(food)'], 'id': 729, 'def': 'tentacles of octopus prepared as food', 'name': 'octopus_(food)'}, {'frequency': 'r', 'synset': 'octopus.n.02', 'synonyms': ['octopus_(animal)'], 'id': 730, 'def': 'bottom-living cephalopod having a soft oval body with eight long tentacles', 'name': 'octopus_(animal)'}, {'frequency': 'c', 'synset': 'oil_lamp.n.01', 'synonyms': ['oil_lamp', 'kerosene_lamp', 'kerosine_lamp'], 'id': 731, 'def': 'a lamp that burns oil (as kerosine) for light', 'name': 'oil_lamp'}, {'frequency': 'c', 'synset': 'olive_oil.n.01', 'synonyms': ['olive_oil'], 'id': 732, 'def': 'oil from olives', 'name': 'olive_oil'}, {'frequency': 'r', 'synset': 'omelet.n.01', 'synonyms': ['omelet', 'omelette'], 'id': 733, 'def': 'beaten eggs cooked until just set; may be folded around e.g. ham or cheese or jelly', 'name': 'omelet'}, {'frequency': 'f', 'synset': 'onion.n.01', 'synonyms': ['onion'], 'id': 734, 'def': 'the bulb of an onion plant', 'name': 'onion'}, {'frequency': 'f', 'synset': 'orange.n.01', 'synonyms': ['orange_(fruit)'], 'id': 735, 'def': 'orange (FRUIT of an orange tree)', 'name': 'orange_(fruit)'}, {'frequency': 'c', 'synset': 'orange_juice.n.01', 'synonyms': ['orange_juice'], 'id': 736, 'def': 'bottled or freshly squeezed juice of oranges', 'name': 'orange_juice'}, {'frequency': 'c', 'synset': 'ostrich.n.02', 'synonyms': ['ostrich'], 'id': 737, 'def': 'fast-running African flightless bird with two-toed feet; largest living bird', 'name': 'ostrich'}, {'frequency': 'f', 'synset': 'ottoman.n.03', 'synonyms': ['ottoman', 'pouf', 'pouffe', 'hassock'], 'id': 738, 'def': 'a thick standalone cushion used as a seat or footrest, often next to a chair', 'name': 'ottoman'}, {'frequency': 'f', 'synset': 'oven.n.01', 'synonyms': ['oven'], 'id': 739, 'def': 'kitchen appliance used for baking or roasting', 'name': 'oven'}, {'frequency': 'c', 'synset': 'overall.n.01', 'synonyms': ['overalls_(clothing)'], 'id': 740, 'def': 'work clothing consisting of denim trousers usually with a bib and shoulder straps', 'name': 'overalls_(clothing)'}, {'frequency': 'c', 'synset': 'owl.n.01', 'synonyms': ['owl'], 'id': 741, 'def': 'nocturnal bird of prey with hawk-like beak and claws and large head with front-facing eyes', 'name': 'owl'}, {'frequency': 'c', 'synset': 'packet.n.03', 'synonyms': ['packet'], 'id': 742, 'def': 'a small package or bundle', 'name': 'packet'}, {'frequency': 'r', 'synset': 'pad.n.03', 'synonyms': ['inkpad', 'inking_pad', 'stamp_pad'], 'id': 743, 'def': 'absorbent material saturated with ink used to transfer ink evenly to a rubber stamp', 'name': 'inkpad'}, {'frequency': 'c', 'synset': 'pad.n.04', 'synonyms': ['pad'], 'id': 744, 'def': 'mostly arm/knee pads labeled', 'name': 'pad'}, {'frequency': 'f', 'synset': 'paddle.n.04', 'synonyms': ['paddle', 'boat_paddle'], 'id': 745, 'def': 'a short light oar used without an oarlock to propel a canoe or small boat', 'name': 'paddle'}, {'frequency': 'c', 'synset': 'padlock.n.01', 'synonyms': ['padlock'], 'id': 746, 'def': 'a detachable, portable lock', 'name': 'padlock'}, {'frequency': 'c', 'synset': 'paintbrush.n.01', 'synonyms': ['paintbrush'], 'id': 747, 'def': 'a brush used as an applicator to apply paint', 'name': 'paintbrush'}, {'frequency': 'f', 'synset': 'painting.n.01', 'synonyms': ['painting'], 'id': 748, 'def': 'graphic art consisting of an artistic composition made by applying paints to a surface', 'name': 'painting'}, {'frequency': 'f', 'synset': 'pajama.n.02', 'synonyms': ['pajamas', 'pyjamas'], 'id': 749, 'def': 'loose-fitting nightclothes worn for sleeping or lounging', 'name': 'pajamas'}, {'frequency': 'c', 'synset': 'palette.n.02', 'synonyms': ['palette', 'pallet'], 'id': 750, 'def': 'board that provides a flat surface on which artists mix paints and the range of colors used', 'name': 'palette'}, {'frequency': 'f', 'synset': 'pan.n.01', 'synonyms': ['pan_(for_cooking)', 'cooking_pan'], 'id': 751, 'def': 'cooking utensil consisting of a wide metal vessel', 'name': 'pan_(for_cooking)'}, {'frequency': 'r', 'synset': 'pan.n.03', 'synonyms': ['pan_(metal_container)'], 'id': 752, 'def': 'shallow container made of metal', 'name': 'pan_(metal_container)'}, {'frequency': 'c', 'synset': 'pancake.n.01', 'synonyms': ['pancake'], 'id': 753, 'def': 'a flat cake of thin batter fried on both sides on a griddle', 'name': 'pancake'}, {'frequency': 'r', 'synset': 'pantyhose.n.01', 'synonyms': ['pantyhose'], 'id': 754, 'def': "a woman's tights consisting of underpants and stockings", 'name': 'pantyhose'}, {'frequency': 'r', 'synset': 'papaya.n.02', 'synonyms': ['papaya'], 'id': 755, 'def': 'large oval melon-like tropical fruit with yellowish flesh', 'name': 'papaya'}, {'frequency': 'f', 'synset': 'paper_plate.n.01', 'synonyms': ['paper_plate'], 'id': 756, 'def': 'a disposable plate made of cardboard', 'name': 'paper_plate'}, {'frequency': 'f', 'synset': 'paper_towel.n.01', 'synonyms': ['paper_towel'], 'id': 757, 'def': 'a disposable towel made of absorbent paper', 'name': 'paper_towel'}, {'frequency': 'r', 'synset': 'paperback_book.n.01', 'synonyms': ['paperback_book', 'paper-back_book', 'softback_book', 'soft-cover_book'], 'id': 758, 'def': 'a book with paper covers', 'name': 'paperback_book'}, {'frequency': 'r', 'synset': 'paperweight.n.01', 'synonyms': ['paperweight'], 'id': 759, 'def': 'a weight used to hold down a stack of papers', 'name': 'paperweight'}, {'frequency': 'c', 'synset': 'parachute.n.01', 'synonyms': ['parachute'], 'id': 760, 'def': 'rescue equipment consisting of a device that fills with air and retards your fall', 'name': 'parachute'}, {'frequency': 'c', 'synset': 'parakeet.n.01', 'synonyms': ['parakeet', 'parrakeet', 'parroket', 'paraquet', 'paroquet', 'parroquet'], 'id': 761, 'def': 'any of numerous small slender long-tailed parrots', 'name': 'parakeet'}, {'frequency': 'c', 'synset': 'parasail.n.01', 'synonyms': ['parasail_(sports)'], 'id': 762, 'def': 'parachute that will lift a person up into the air when it is towed by a motorboat or a car', 'name': 'parasail_(sports)'}, {'frequency': 'c', 'synset': 'parasol.n.01', 'synonyms': ['parasol', 'sunshade'], 'id': 763, 'def': 'a handheld collapsible source of shade', 'name': 'parasol'}, {'frequency': 'r', 'synset': 'parchment.n.01', 'synonyms': ['parchment'], 'id': 764, 'def': 'a superior paper resembling sheepskin', 'name': 'parchment'}, {'frequency': 'c', 'synset': 'parka.n.01', 'synonyms': ['parka', 'anorak'], 'id': 765, 'def': "a kind of heavy jacket (`windcheater' is a British term)", 'name': 'parka'}, {'frequency': 'f', 'synset': 'parking_meter.n.01', 'synonyms': ['parking_meter'], 'id': 766, 'def': 'a coin-operated timer located next to a parking space', 'name': 'parking_meter'}, {'frequency': 'c', 'synset': 'parrot.n.01', 'synonyms': ['parrot'], 'id': 767, 'def': 'usually brightly colored tropical birds with short hooked beaks and the ability to mimic sounds', 'name': 'parrot'}, {'frequency': 'c', 'synset': 'passenger_car.n.01', 'synonyms': ['passenger_car_(part_of_a_train)', 'coach_(part_of_a_train)'], 'id': 768, 'def': 'a railcar where passengers ride', 'name': 'passenger_car_(part_of_a_train)'}, {'frequency': 'r', 'synset': 'passenger_ship.n.01', 'synonyms': ['passenger_ship'], 'id': 769, 'def': 'a ship built to carry passengers', 'name': 'passenger_ship'}, {'frequency': 'c', 'synset': 'passport.n.02', 'synonyms': ['passport'], 'id': 770, 'def': 'a document issued by a country to a citizen allowing that person to travel abroad and re-enter the home country', 'name': 'passport'}, {'frequency': 'f', 'synset': 'pastry.n.02', 'synonyms': ['pastry'], 'id': 771, 'def': 'any of various baked foods made of dough or batter', 'name': 'pastry'}, {'frequency': 'r', 'synset': 'patty.n.01', 'synonyms': ['patty_(food)'], 'id': 772, 'def': 'small flat mass of chopped food', 'name': 'patty_(food)'}, {'frequency': 'c', 'synset': 'pea.n.01', 'synonyms': ['pea_(food)'], 'id': 773, 'def': 'seed of a pea plant used for food', 'name': 'pea_(food)'}, {'frequency': 'c', 'synset': 'peach.n.03', 'synonyms': ['peach'], 'id': 774, 'def': 'downy juicy fruit with sweet yellowish or whitish flesh', 'name': 'peach'}, {'frequency': 'c', 'synset': 'peanut_butter.n.01', 'synonyms': ['peanut_butter'], 'id': 775, 'def': 'a spread made from ground peanuts', 'name': 'peanut_butter'}, {'frequency': 'f', 'synset': 'pear.n.01', 'synonyms': ['pear'], 'id': 776, 'def': 'sweet juicy gritty-textured fruit available in many varieties', 'name': 'pear'}, {'frequency': 'c', 'synset': 'peeler.n.03', 'synonyms': ['peeler_(tool_for_fruit_and_vegetables)'], 'id': 777, 'def': 'a device for peeling vegetables or fruits', 'name': 'peeler_(tool_for_fruit_and_vegetables)'}, {'frequency': 'r', 'synset': 'peg.n.04', 'synonyms': ['wooden_leg', 'pegleg'], 'id': 778, 'def': 'a prosthesis that replaces a missing leg', 'name': 'wooden_leg'}, {'frequency': 'r', 'synset': 'pegboard.n.01', 'synonyms': ['pegboard'], 'id': 779, 'def': 'a board perforated with regularly spaced holes into which pegs can be fitted', 'name': 'pegboard'}, {'frequency': 'c', 'synset': 'pelican.n.01', 'synonyms': ['pelican'], 'id': 780, 'def': 'large long-winged warm-water seabird having a large bill with a distensible pouch for fish', 'name': 'pelican'}, {'frequency': 'f', 'synset': 'pen.n.01', 'synonyms': ['pen'], 'id': 781, 'def': 'a writing implement with a point from which ink flows', 'name': 'pen'}, {'frequency': 'f', 'synset': 'pencil.n.01', 'synonyms': ['pencil'], 'id': 782, 'def': 'a thin cylindrical pointed writing implement made of wood and graphite', 'name': 'pencil'}, {'frequency': 'r', 'synset': 'pencil_box.n.01', 'synonyms': ['pencil_box', 'pencil_case'], 'id': 783, 'def': 'a box for holding pencils', 'name': 'pencil_box'}, {'frequency': 'r', 'synset': 'pencil_sharpener.n.01', 'synonyms': ['pencil_sharpener'], 'id': 784, 'def': 'a rotary implement for sharpening the point on pencils', 'name': 'pencil_sharpener'}, {'frequency': 'r', 'synset': 'pendulum.n.01', 'synonyms': ['pendulum'], 'id': 785, 'def': 'an apparatus consisting of an object mounted so that it swings freely under the influence of gravity', 'name': 'pendulum'}, {'frequency': 'c', 'synset': 'penguin.n.01', 'synonyms': ['penguin'], 'id': 786, 'def': 'short-legged flightless birds of cold southern regions having webbed feet and wings modified as flippers', 'name': 'penguin'}, {'frequency': 'r', 'synset': 'pennant.n.02', 'synonyms': ['pennant'], 'id': 787, 'def': 'a flag longer than it is wide (and often tapering)', 'name': 'pennant'}, {'frequency': 'r', 'synset': 'penny.n.02', 'synonyms': ['penny_(coin)'], 'id': 788, 'def': 'a coin worth one-hundredth of the value of the basic unit', 'name': 'penny_(coin)'}, {'frequency': 'f', 'synset': 'pepper.n.03', 'synonyms': ['pepper', 'peppercorn'], 'id': 789, 'def': 'pungent seasoning from the berry of the common pepper plant; whole or ground', 'name': 'pepper'}, {'frequency': 'c', 'synset': 'pepper_mill.n.01', 'synonyms': ['pepper_mill', 'pepper_grinder'], 'id': 790, 'def': 'a mill for grinding pepper', 'name': 'pepper_mill'}, {'frequency': 'c', 'synset': 'perfume.n.02', 'synonyms': ['perfume'], 'id': 791, 'def': 'a toiletry that emits and diffuses a fragrant odor', 'name': 'perfume'}, {'frequency': 'r', 'synset': 'persimmon.n.02', 'synonyms': ['persimmon'], 'id': 792, 'def': 'orange fruit resembling a plum; edible when fully ripe', 'name': 'persimmon'}, {'frequency': 'f', 'synset': 'person.n.01', 'synonyms': ['person', 'baby', 'child', 'boy', 'girl', 'man', 'woman', 'human'], 'id': 793, 'def': 'a human being', 'name': 'person'}, {'frequency': 'c', 'synset': 'pet.n.01', 'synonyms': ['pet'], 'id': 794, 'def': 'a domesticated animal kept for companionship or amusement', 'name': 'pet'}, {'frequency': 'c', 'synset': 'pew.n.01', 'synonyms': ['pew_(church_bench)', 'church_bench'], 'id': 795, 'def': 'long bench with backs; used in church by the congregation', 'name': 'pew_(church_bench)'}, {'frequency': 'r', 'synset': 'phonebook.n.01', 'synonyms': ['phonebook', 'telephone_book', 'telephone_directory'], 'id': 796, 'def': 'a directory containing an alphabetical list of telephone subscribers and their telephone numbers', 'name': 'phonebook'}, {'frequency': 'c', 'synset': 'phonograph_record.n.01', 'synonyms': ['phonograph_record', 'phonograph_recording', 'record_(phonograph_recording)'], 'id': 797, 'def': 'sound recording consisting of a typically black disk with a continuous groove', 'name': 'phonograph_record'}, {'frequency': 'f', 'synset': 'piano.n.01', 'synonyms': ['piano'], 'id': 798, 'def': 'a keyboard instrument that is played by depressing keys that cause hammers to strike tuned strings and produce sounds', 'name': 'piano'}, {'frequency': 'f', 'synset': 'pickle.n.01', 'synonyms': ['pickle'], 'id': 799, 'def': 'vegetables (especially cucumbers) preserved in brine or vinegar', 'name': 'pickle'}, {'frequency': 'f', 'synset': 'pickup.n.01', 'synonyms': ['pickup_truck'], 'id': 800, 'def': 'a light truck with an open body and low sides and a tailboard', 'name': 'pickup_truck'}, {'frequency': 'c', 'synset': 'pie.n.01', 'synonyms': ['pie'], 'id': 801, 'def': 'dish baked in pastry-lined pan often with a pastry top', 'name': 'pie'}, {'frequency': 'c', 'synset': 'pigeon.n.01', 'synonyms': ['pigeon'], 'id': 802, 'def': 'wild and domesticated birds having a heavy body and short legs', 'name': 'pigeon'}, {'frequency': 'r', 'synset': 'piggy_bank.n.01', 'synonyms': ['piggy_bank', 'penny_bank'], 'id': 803, 'def': "a child's coin bank (often shaped like a pig)", 'name': 'piggy_bank'}, {'frequency': 'f', 'synset': 'pillow.n.01', 'synonyms': ['pillow'], 'id': 804, 'def': 'a cushion to support the head of a sleeping person', 'name': 'pillow'}, {'frequency': 'r', 'synset': 'pin.n.09', 'synonyms': ['pin_(non_jewelry)'], 'id': 805, 'def': 'a small slender (often pointed) piece of wood or metal used to support or fasten or attach things', 'name': 'pin_(non_jewelry)'}, {'frequency': 'f', 'synset': 'pineapple.n.02', 'synonyms': ['pineapple'], 'id': 806, 'def': 'large sweet fleshy tropical fruit with a tuft of stiff leaves', 'name': 'pineapple'}, {'frequency': 'c', 'synset': 'pinecone.n.01', 'synonyms': ['pinecone'], 'id': 807, 'def': 'the seed-producing cone of a pine tree', 'name': 'pinecone'}, {'frequency': 'r', 'synset': 'ping-pong_ball.n.01', 'synonyms': ['ping-pong_ball'], 'id': 808, 'def': 'light hollow ball used in playing table tennis', 'name': 'ping-pong_ball'}, {'frequency': 'r', 'synset': 'pinwheel.n.03', 'synonyms': ['pinwheel'], 'id': 809, 'def': 'a toy consisting of vanes of colored paper or plastic that is pinned to a stick and spins when it is pointed into the wind', 'name': 'pinwheel'}, {'frequency': 'r', 'synset': 'pipe.n.01', 'synonyms': ['tobacco_pipe'], 'id': 810, 'def': 'a tube with a small bowl at one end; used for smoking tobacco', 'name': 'tobacco_pipe'}, {'frequency': 'f', 'synset': 'pipe.n.02', 'synonyms': ['pipe', 'piping'], 'id': 811, 'def': 'a long tube made of metal or plastic that is used to carry water or oil or gas etc.', 'name': 'pipe'}, {'frequency': 'r', 'synset': 'pistol.n.01', 'synonyms': ['pistol', 'handgun'], 'id': 812, 'def': 'a firearm that is held and fired with one hand', 'name': 'pistol'}, {'frequency': 'c', 'synset': 'pita.n.01', 'synonyms': ['pita_(bread)', 'pocket_bread'], 'id': 813, 'def': 'usually small round bread that can open into a pocket for filling', 'name': 'pita_(bread)'}, {'frequency': 'f', 'synset': 'pitcher.n.02', 'synonyms': ['pitcher_(vessel_for_liquid)', 'ewer'], 'id': 814, 'def': 'an open vessel with a handle and a spout for pouring', 'name': 'pitcher_(vessel_for_liquid)'}, {'frequency': 'r', 'synset': 'pitchfork.n.01', 'synonyms': ['pitchfork'], 'id': 815, 'def': 'a long-handled hand tool with sharp widely spaced prongs for lifting and pitching hay', 'name': 'pitchfork'}, {'frequency': 'f', 'synset': 'pizza.n.01', 'synonyms': ['pizza'], 'id': 816, 'def': 'Italian open pie made of thin bread dough spread with a spiced mixture of e.g. tomato sauce and cheese', 'name': 'pizza'}, {'frequency': 'f', 'synset': 'place_mat.n.01', 'synonyms': ['place_mat'], 'id': 817, 'def': 'a mat placed on a table for an individual place setting', 'name': 'place_mat'}, {'frequency': 'f', 'synset': 'plate.n.04', 'synonyms': ['plate'], 'id': 818, 'def': 'dish on which food is served or from which food is eaten', 'name': 'plate'}, {'frequency': 'c', 'synset': 'platter.n.01', 'synonyms': ['platter'], 'id': 819, 'def': 'a large shallow dish used for serving food', 'name': 'platter'}, {'frequency': 'r', 'synset': 'playpen.n.01', 'synonyms': ['playpen'], 'id': 820, 'def': 'a portable enclosure in which babies may be left to play', 'name': 'playpen'}, {'frequency': 'c', 'synset': 'pliers.n.01', 'synonyms': ['pliers', 'plyers'], 'id': 821, 'def': 'a gripping hand tool with two hinged arms and (usually) serrated jaws', 'name': 'pliers'}, {'frequency': 'r', 'synset': 'plow.n.01', 'synonyms': ['plow_(farm_equipment)', 'plough_(farm_equipment)'], 'id': 822, 'def': 'a farm tool having one or more heavy blades to break the soil and cut a furrow prior to sowing', 'name': 'plow_(farm_equipment)'}, {'frequency': 'r', 'synset': 'plume.n.02', 'synonyms': ['plume'], 'id': 823, 'def': 'a feather or cluster of feathers worn as an ornament', 'name': 'plume'}, {'frequency': 'r', 'synset': 'pocket_watch.n.01', 'synonyms': ['pocket_watch'], 'id': 824, 'def': 'a watch that is carried in a small watch pocket', 'name': 'pocket_watch'}, {'frequency': 'c', 'synset': 'pocketknife.n.01', 'synonyms': ['pocketknife'], 'id': 825, 'def': 'a knife with a blade that folds into the handle; suitable for carrying in the pocket', 'name': 'pocketknife'}, {'frequency': 'c', 'synset': 'poker.n.01', 'synonyms': ['poker_(fire_stirring_tool)', 'stove_poker', 'fire_hook'], 'id': 826, 'def': 'fire iron consisting of a metal rod with a handle; used to stir a fire', 'name': 'poker_(fire_stirring_tool)'}, {'frequency': 'f', 'synset': 'pole.n.01', 'synonyms': ['pole', 'post'], 'id': 827, 'def': 'a long (usually round) rod of wood or metal or plastic', 'name': 'pole'}, {'frequency': 'f', 'synset': 'polo_shirt.n.01', 'synonyms': ['polo_shirt', 'sport_shirt'], 'id': 828, 'def': 'a shirt with short sleeves designed for comfort and casual wear', 'name': 'polo_shirt'}, {'frequency': 'r', 'synset': 'poncho.n.01', 'synonyms': ['poncho'], 'id': 829, 'def': 'a blanket-like cloak with a hole in the center for the head', 'name': 'poncho'}, {'frequency': 'c', 'synset': 'pony.n.05', 'synonyms': ['pony'], 'id': 830, 'def': 'any of various breeds of small gentle horses usually less than five feet high at the shoulder', 'name': 'pony'}, {'frequency': 'r', 'synset': 'pool_table.n.01', 'synonyms': ['pool_table', 'billiard_table', 'snooker_table'], 'id': 831, 'def': 'game equipment consisting of a heavy table on which pool is played', 'name': 'pool_table'}, {'frequency': 'f', 'synset': 'pop.n.02', 'synonyms': ['pop_(soda)', 'soda_(pop)', 'tonic', 'soft_drink'], 'id': 832, 'def': 'a sweet drink containing carbonated water and flavoring', 'name': 'pop_(soda)'}, {'frequency': 'c', 'synset': 'postbox.n.01', 'synonyms': ['postbox_(public)', 'mailbox_(public)'], 'id': 833, 'def': 'public box for deposit of mail', 'name': 'postbox_(public)'}, {'frequency': 'c', 'synset': 'postcard.n.01', 'synonyms': ['postcard', 'postal_card', 'mailing-card'], 'id': 834, 'def': 'a card for sending messages by post without an envelope', 'name': 'postcard'}, {'frequency': 'f', 'synset': 'poster.n.01', 'synonyms': ['poster', 'placard'], 'id': 835, 'def': 'a sign posted in a public place as an advertisement', 'name': 'poster'}, {'frequency': 'f', 'synset': 'pot.n.01', 'synonyms': ['pot'], 'id': 836, 'def': 'metal or earthenware cooking vessel that is usually round and deep; often has a handle and lid', 'name': 'pot'}, {'frequency': 'f', 'synset': 'pot.n.04', 'synonyms': ['flowerpot'], 'id': 837, 'def': 'a container in which plants are cultivated', 'name': 'flowerpot'}, {'frequency': 'f', 'synset': 'potato.n.01', 'synonyms': ['potato'], 'id': 838, 'def': 'an edible tuber native to South America', 'name': 'potato'}, {'frequency': 'c', 'synset': 'potholder.n.01', 'synonyms': ['potholder'], 'id': 839, 'def': 'an insulated pad for holding hot pots', 'name': 'potholder'}, {'frequency': 'c', 'synset': 'pottery.n.01', 'synonyms': ['pottery', 'clayware'], 'id': 840, 'def': 'ceramic ware made from clay and baked in a kiln', 'name': 'pottery'}, {'frequency': 'c', 'synset': 'pouch.n.01', 'synonyms': ['pouch'], 'id': 841, 'def': 'a small or medium size container for holding or carrying things', 'name': 'pouch'}, {'frequency': 'c', 'synset': 'power_shovel.n.01', 'synonyms': ['power_shovel', 'excavator', 'digger'], 'id': 842, 'def': 'a machine for excavating', 'name': 'power_shovel'}, {'frequency': 'c', 'synset': 'prawn.n.01', 'synonyms': ['prawn', 'shrimp'], 'id': 843, 'def': 'any of various edible decapod crustaceans', 'name': 'prawn'}, {'frequency': 'c', 'synset': 'pretzel.n.01', 'synonyms': ['pretzel'], 'id': 844, 'def': 'glazed and salted cracker typically in the shape of a loose knot', 'name': 'pretzel'}, {'frequency': 'f', 'synset': 'printer.n.03', 'synonyms': ['printer', 'printing_machine'], 'id': 845, 'def': 'a machine that prints', 'name': 'printer'}, {'frequency': 'c', 'synset': 'projectile.n.01', 'synonyms': ['projectile_(weapon)', 'missile'], 'id': 846, 'def': 'a weapon that is forcibly thrown or projected at a targets', 'name': 'projectile_(weapon)'}, {'frequency': 'c', 'synset': 'projector.n.02', 'synonyms': ['projector'], 'id': 847, 'def': 'an optical instrument that projects an enlarged image onto a screen', 'name': 'projector'}, {'frequency': 'f', 'synset': 'propeller.n.01', 'synonyms': ['propeller', 'propellor'], 'id': 848, 'def': 'a mechanical device that rotates to push against air or water', 'name': 'propeller'}, {'frequency': 'r', 'synset': 'prune.n.01', 'synonyms': ['prune'], 'id': 849, 'def': 'dried plum', 'name': 'prune'}, {'frequency': 'r', 'synset': 'pudding.n.01', 'synonyms': ['pudding'], 'id': 850, 'def': 'any of various soft thick unsweetened baked dishes', 'name': 'pudding'}, {'frequency': 'r', 'synset': 'puffer.n.02', 'synonyms': ['puffer_(fish)', 'pufferfish', 'blowfish', 'globefish'], 'id': 851, 'def': 'fishes whose elongated spiny body can inflate itself with water or air to form a globe', 'name': 'puffer_(fish)'}, {'frequency': 'r', 'synset': 'puffin.n.01', 'synonyms': ['puffin'], 'id': 852, 'def': 'seabirds having short necks and brightly colored compressed bills', 'name': 'puffin'}, {'frequency': 'r', 'synset': 'pug.n.01', 'synonyms': ['pug-dog'], 'id': 853, 'def': 'small compact smooth-coated breed of Asiatic origin having a tightly curled tail and broad flat wrinkled muzzle', 'name': 'pug-dog'}, {'frequency': 'c', 'synset': 'pumpkin.n.02', 'synonyms': ['pumpkin'], 'id': 854, 'def': 'usually large pulpy deep-yellow round fruit of the squash family maturing in late summer or early autumn', 'name': 'pumpkin'}, {'frequency': 'r', 'synset': 'punch.n.03', 'synonyms': ['puncher'], 'id': 855, 'def': 'a tool for making holes or indentations', 'name': 'puncher'}, {'frequency': 'r', 'synset': 'puppet.n.01', 'synonyms': ['puppet', 'marionette'], 'id': 856, 'def': 'a small figure of a person operated from above with strings by a puppeteer', 'name': 'puppet'}, {'frequency': 'c', 'synset': 'puppy.n.01', 'synonyms': ['puppy'], 'id': 857, 'def': 'a young dog', 'name': 'puppy'}, {'frequency': 'r', 'synset': 'quesadilla.n.01', 'synonyms': ['quesadilla'], 'id': 858, 'def': 'a tortilla that is filled with cheese and heated', 'name': 'quesadilla'}, {'frequency': 'r', 'synset': 'quiche.n.02', 'synonyms': ['quiche'], 'id': 859, 'def': 'a tart filled with rich unsweetened custard; often contains other ingredients (as cheese or ham or seafood or vegetables)', 'name': 'quiche'}, {'frequency': 'f', 'synset': 'quilt.n.01', 'synonyms': ['quilt', 'comforter'], 'id': 860, 'def': 'bedding made of two layers of cloth filled with stuffing and stitched together', 'name': 'quilt'}, {'frequency': 'c', 'synset': 'rabbit.n.01', 'synonyms': ['rabbit'], 'id': 861, 'def': 'any of various burrowing animals of the family Leporidae having long ears and short tails', 'name': 'rabbit'}, {'frequency': 'r', 'synset': 'racer.n.02', 'synonyms': ['race_car', 'racing_car'], 'id': 862, 'def': 'a fast car that competes in races', 'name': 'race_car'}, {'frequency': 'c', 'synset': 'racket.n.04', 'synonyms': ['racket', 'racquet'], 'id': 863, 'def': 'a sports implement used to strike a ball in various games', 'name': 'racket'}, {'frequency': 'r', 'synset': 'radar.n.01', 'synonyms': ['radar'], 'id': 864, 'def': 'measuring instrument in which the echo of a pulse of microwave radiation is used to detect and locate distant objects', 'name': 'radar'}, {'frequency': 'f', 'synset': 'radiator.n.03', 'synonyms': ['radiator'], 'id': 865, 'def': 'a mechanism consisting of a metal honeycomb through which hot fluids circulate', 'name': 'radiator'}, {'frequency': 'c', 'synset': 'radio_receiver.n.01', 'synonyms': ['radio_receiver', 'radio_set', 'radio', 'tuner_(radio)'], 'id': 866, 'def': 'an electronic receiver that detects and demodulates and amplifies transmitted radio signals', 'name': 'radio_receiver'}, {'frequency': 'c', 'synset': 'radish.n.03', 'synonyms': ['radish', 'daikon'], 'id': 867, 'def': 'pungent edible root of any of various cultivated radish plants', 'name': 'radish'}, {'frequency': 'c', 'synset': 'raft.n.01', 'synonyms': ['raft'], 'id': 868, 'def': 'a flat float (usually made of logs or planks) that can be used for transport or as a platform for swimmers', 'name': 'raft'}, {'frequency': 'r', 'synset': 'rag_doll.n.01', 'synonyms': ['rag_doll'], 'id': 869, 'def': 'a cloth doll that is stuffed and (usually) painted', 'name': 'rag_doll'}, {'frequency': 'c', 'synset': 'raincoat.n.01', 'synonyms': ['raincoat', 'waterproof_jacket'], 'id': 870, 'def': 'a water-resistant coat', 'name': 'raincoat'}, {'frequency': 'c', 'synset': 'ram.n.05', 'synonyms': ['ram_(animal)'], 'id': 871, 'def': 'uncastrated adult male sheep', 'name': 'ram_(animal)'}, {'frequency': 'c', 'synset': 'raspberry.n.02', 'synonyms': ['raspberry'], 'id': 872, 'def': 'red or black edible aggregate berries usually smaller than the related blackberries', 'name': 'raspberry'}, {'frequency': 'r', 'synset': 'rat.n.01', 'synonyms': ['rat'], 'id': 873, 'def': 'any of various long-tailed rodents similar to but larger than a mouse', 'name': 'rat'}, {'frequency': 'c', 'synset': 'razorblade.n.01', 'synonyms': ['razorblade'], 'id': 874, 'def': 'a blade that has very sharp edge', 'name': 'razorblade'}, {'frequency': 'c', 'synset': 'reamer.n.01', 'synonyms': ['reamer_(juicer)', 'juicer', 'juice_reamer'], 'id': 875, 'def': 'a squeezer with a conical ridged center that is used for squeezing juice from citrus fruit', 'name': 'reamer_(juicer)'}, {'frequency': 'f', 'synset': 'rearview_mirror.n.01', 'synonyms': ['rearview_mirror'], 'id': 876, 'def': 'vehicle mirror (side or rearview)', 'name': 'rearview_mirror'}, {'frequency': 'c', 'synset': 'receipt.n.02', 'synonyms': ['receipt'], 'id': 877, 'def': 'an acknowledgment (usually tangible) that payment has been made', 'name': 'receipt'}, {'frequency': 'c', 'synset': 'recliner.n.01', 'synonyms': ['recliner', 'reclining_chair', 'lounger_(chair)'], 'id': 878, 'def': 'an armchair whose back can be lowered and foot can be raised to allow the sitter to recline in it', 'name': 'recliner'}, {'frequency': 'c', 'synset': 'record_player.n.01', 'synonyms': ['record_player', 'phonograph_(record_player)', 'turntable'], 'id': 879, 'def': 'machine in which rotating records cause a stylus to vibrate and the vibrations are amplified acoustically or electronically', 'name': 'record_player'}, {'frequency': 'f', 'synset': 'reflector.n.01', 'synonyms': ['reflector'], 'id': 880, 'def': 'device that reflects light, radiation, etc.', 'name': 'reflector'}, {'frequency': 'f', 'synset': 'remote_control.n.01', 'synonyms': ['remote_control'], 'id': 881, 'def': 'a device that can be used to control a machine or apparatus from a distance', 'name': 'remote_control'}, {'frequency': 'c', 'synset': 'rhinoceros.n.01', 'synonyms': ['rhinoceros'], 'id': 882, 'def': 'massive powerful herbivorous odd-toed ungulate of southeast Asia and Africa having very thick skin and one or two horns on the snout', 'name': 'rhinoceros'}, {'frequency': 'r', 'synset': 'rib.n.03', 'synonyms': ['rib_(food)'], 'id': 883, 'def': 'cut of meat including one or more ribs', 'name': 'rib_(food)'}, {'frequency': 'c', 'synset': 'rifle.n.01', 'synonyms': ['rifle'], 'id': 884, 'def': 'a shoulder firearm with a long barrel', 'name': 'rifle'}, {'frequency': 'f', 'synset': 'ring.n.08', 'synonyms': ['ring'], 'id': 885, 'def': 'jewelry consisting of a circlet of precious metal (often set with jewels) worn on the finger', 'name': 'ring'}, {'frequency': 'r', 'synset': 'river_boat.n.01', 'synonyms': ['river_boat'], 'id': 886, 'def': 'a boat used on rivers or to ply a river', 'name': 'river_boat'}, {'frequency': 'r', 'synset': 'road_map.n.02', 'synonyms': ['road_map'], 'id': 887, 'def': '(NOT A ROAD) a MAP showing roads (for automobile travel)', 'name': 'road_map'}, {'frequency': 'c', 'synset': 'robe.n.01', 'synonyms': ['robe'], 'id': 888, 'def': 'any loose flowing garment', 'name': 'robe'}, {'frequency': 'c', 'synset': 'rocking_chair.n.01', 'synonyms': ['rocking_chair'], 'id': 889, 'def': 'a chair mounted on rockers', 'name': 'rocking_chair'}, {'frequency': 'r', 'synset': 'rodent.n.01', 'synonyms': ['rodent'], 'id': 890, 'def': 'relatively small placental mammals having a single pair of constantly growing incisor teeth specialized for gnawing', 'name': 'rodent'}, {'frequency': 'r', 'synset': 'roller_skate.n.01', 'synonyms': ['roller_skate'], 'id': 891, 'def': 'a shoe with pairs of rollers (small hard wheels) fixed to the sole', 'name': 'roller_skate'}, {'frequency': 'r', 'synset': 'rollerblade.n.01', 'synonyms': ['Rollerblade'], 'id': 892, 'def': 'an in-line variant of a roller skate', 'name': 'Rollerblade'}, {'frequency': 'c', 'synset': 'rolling_pin.n.01', 'synonyms': ['rolling_pin'], 'id': 893, 'def': 'utensil consisting of a cylinder (usually of wood) with a handle at each end; used to roll out dough', 'name': 'rolling_pin'}, {'frequency': 'r', 'synset': 'root_beer.n.01', 'synonyms': ['root_beer'], 'id': 894, 'def': 'carbonated drink containing extracts of roots and herbs', 'name': 'root_beer'}, {'frequency': 'c', 'synset': 'router.n.02', 'synonyms': ['router_(computer_equipment)'], 'id': 895, 'def': 'a device that forwards data packets between computer networks', 'name': 'router_(computer_equipment)'}, {'frequency': 'f', 'synset': 'rubber_band.n.01', 'synonyms': ['rubber_band', 'elastic_band'], 'id': 896, 'def': 'a narrow band of elastic rubber used to hold things (such as papers) together', 'name': 'rubber_band'}, {'frequency': 'c', 'synset': 'runner.n.08', 'synonyms': ['runner_(carpet)'], 'id': 897, 'def': 'a long narrow carpet', 'name': 'runner_(carpet)'}, {'frequency': 'f', 'synset': 'sack.n.01', 'synonyms': ['plastic_bag', 'paper_bag'], 'id': 898, 'def': "a bag made of paper or plastic for holding customer's purchases", 'name': 'plastic_bag'}, {'frequency': 'f', 'synset': 'saddle.n.01', 'synonyms': ['saddle_(on_an_animal)'], 'id': 899, 'def': 'a seat for the rider of a horse or camel', 'name': 'saddle_(on_an_animal)'}, {'frequency': 'f', 'synset': 'saddle_blanket.n.01', 'synonyms': ['saddle_blanket', 'saddlecloth', 'horse_blanket'], 'id': 900, 'def': 'stable gear consisting of a blanket placed under the saddle', 'name': 'saddle_blanket'}, {'frequency': 'c', 'synset': 'saddlebag.n.01', 'synonyms': ['saddlebag'], 'id': 901, 'def': 'a large bag (or pair of bags) hung over a saddle', 'name': 'saddlebag'}, {'frequency': 'r', 'synset': 'safety_pin.n.01', 'synonyms': ['safety_pin'], 'id': 902, 'def': 'a pin in the form of a clasp; has a guard so the point of the pin will not stick the user', 'name': 'safety_pin'}, {'frequency': 'f', 'synset': 'sail.n.01', 'synonyms': ['sail'], 'id': 903, 'def': 'a large piece of fabric by means of which wind is used to propel a sailing vessel', 'name': 'sail'}, {'frequency': 'f', 'synset': 'salad.n.01', 'synonyms': ['salad'], 'id': 904, 'def': 'food mixtures either arranged on a plate or tossed and served with a moist dressing; usually consisting of or including greens', 'name': 'salad'}, {'frequency': 'r', 'synset': 'salad_plate.n.01', 'synonyms': ['salad_plate', 'salad_bowl'], 'id': 905, 'def': 'a plate or bowl for individual servings of salad', 'name': 'salad_plate'}, {'frequency': 'c', 'synset': 'salami.n.01', 'synonyms': ['salami'], 'id': 906, 'def': 'highly seasoned fatty sausage of pork and beef usually dried', 'name': 'salami'}, {'frequency': 'c', 'synset': 'salmon.n.01', 'synonyms': ['salmon_(fish)'], 'id': 907, 'def': 'any of various large food and game fishes of northern waters', 'name': 'salmon_(fish)'}, {'frequency': 'r', 'synset': 'salmon.n.03', 'synonyms': ['salmon_(food)'], 'id': 908, 'def': 'flesh of any of various marine or freshwater fish of the family Salmonidae', 'name': 'salmon_(food)'}, {'frequency': 'c', 'synset': 'salsa.n.01', 'synonyms': ['salsa'], 'id': 909, 'def': 'spicy sauce of tomatoes and onions and chili peppers to accompany Mexican foods', 'name': 'salsa'}, {'frequency': 'f', 'synset': 'saltshaker.n.01', 'synonyms': ['saltshaker'], 'id': 910, 'def': 'a shaker with a perforated top for sprinkling salt', 'name': 'saltshaker'}, {'frequency': 'f', 'synset': 'sandal.n.01', 'synonyms': ['sandal_(type_of_shoe)'], 'id': 911, 'def': 'a shoe consisting of a sole fastened by straps to the foot', 'name': 'sandal_(type_of_shoe)'}, {'frequency': 'f', 'synset': 'sandwich.n.01', 'synonyms': ['sandwich'], 'id': 912, 'def': 'two (or more) slices of bread with a filling between them', 'name': 'sandwich'}, {'frequency': 'r', 'synset': 'satchel.n.01', 'synonyms': ['satchel'], 'id': 913, 'def': 'luggage consisting of a small case with a flat bottom and (usually) a shoulder strap', 'name': 'satchel'}, {'frequency': 'r', 'synset': 'saucepan.n.01', 'synonyms': ['saucepan'], 'id': 914, 'def': 'a deep pan with a handle; used for stewing or boiling', 'name': 'saucepan'}, {'frequency': 'f', 'synset': 'saucer.n.02', 'synonyms': ['saucer'], 'id': 915, 'def': 'a small shallow dish for holding a cup at the table', 'name': 'saucer'}, {'frequency': 'f', 'synset': 'sausage.n.01', 'synonyms': ['sausage'], 'id': 916, 'def': 'highly seasoned minced meat stuffed in casings', 'name': 'sausage'}, {'frequency': 'r', 'synset': 'sawhorse.n.01', 'synonyms': ['sawhorse', 'sawbuck'], 'id': 917, 'def': 'a framework for holding wood that is being sawed', 'name': 'sawhorse'}, {'frequency': 'r', 'synset': 'sax.n.02', 'synonyms': ['saxophone'], 'id': 918, 'def': "a wind instrument with a `J'-shaped form typically made of brass", 'name': 'saxophone'}, {'frequency': 'f', 'synset': 'scale.n.07', 'synonyms': ['scale_(measuring_instrument)'], 'id': 919, 'def': 'a measuring instrument for weighing; shows amount of mass', 'name': 'scale_(measuring_instrument)'}, {'frequency': 'r', 'synset': 'scarecrow.n.01', 'synonyms': ['scarecrow', 'strawman'], 'id': 920, 'def': 'an effigy in the shape of a man to frighten birds away from seeds', 'name': 'scarecrow'}, {'frequency': 'f', 'synset': 'scarf.n.01', 'synonyms': ['scarf'], 'id': 921, 'def': 'a garment worn around the head or neck or shoulders for warmth or decoration', 'name': 'scarf'}, {'frequency': 'c', 'synset': 'school_bus.n.01', 'synonyms': ['school_bus'], 'id': 922, 'def': 'a bus used to transport children to or from school', 'name': 'school_bus'}, {'frequency': 'f', 'synset': 'scissors.n.01', 'synonyms': ['scissors'], 'id': 923, 'def': 'a tool having two crossed pivoting blades with looped handles', 'name': 'scissors'}, {'frequency': 'f', 'synset': 'scoreboard.n.01', 'synonyms': ['scoreboard'], 'id': 924, 'def': 'a large board for displaying the score of a contest (and some other information)', 'name': 'scoreboard'}, {'frequency': 'r', 'synset': 'scraper.n.01', 'synonyms': ['scraper'], 'id': 925, 'def': 'any of various hand tools for scraping', 'name': 'scraper'}, {'frequency': 'c', 'synset': 'screwdriver.n.01', 'synonyms': ['screwdriver'], 'id': 926, 'def': 'a hand tool for driving screws; has a tip that fits into the head of a screw', 'name': 'screwdriver'}, {'frequency': 'f', 'synset': 'scrub_brush.n.01', 'synonyms': ['scrubbing_brush'], 'id': 927, 'def': 'a brush with short stiff bristles for heavy cleaning', 'name': 'scrubbing_brush'}, {'frequency': 'c', 'synset': 'sculpture.n.01', 'synonyms': ['sculpture'], 'id': 928, 'def': 'a three-dimensional work of art', 'name': 'sculpture'}, {'frequency': 'c', 'synset': 'seabird.n.01', 'synonyms': ['seabird', 'seafowl'], 'id': 929, 'def': 'a bird that frequents coastal waters and the open ocean: gulls; pelicans; gannets; cormorants; albatrosses; petrels; etc.', 'name': 'seabird'}, {'frequency': 'c', 'synset': 'seahorse.n.02', 'synonyms': ['seahorse'], 'id': 930, 'def': 'small fish with horse-like heads bent sharply downward and curled tails', 'name': 'seahorse'}, {'frequency': 'r', 'synset': 'seaplane.n.01', 'synonyms': ['seaplane', 'hydroplane'], 'id': 931, 'def': 'an airplane that can land on or take off from water', 'name': 'seaplane'}, {'frequency': 'c', 'synset': 'seashell.n.01', 'synonyms': ['seashell'], 'id': 932, 'def': 'the shell of a marine organism', 'name': 'seashell'}, {'frequency': 'c', 'synset': 'sewing_machine.n.01', 'synonyms': ['sewing_machine'], 'id': 933, 'def': 'a textile machine used as a home appliance for sewing', 'name': 'sewing_machine'}, {'frequency': 'c', 'synset': 'shaker.n.03', 'synonyms': ['shaker'], 'id': 934, 'def': 'a container in which something can be shaken', 'name': 'shaker'}, {'frequency': 'c', 'synset': 'shampoo.n.01', 'synonyms': ['shampoo'], 'id': 935, 'def': 'cleansing agent consisting of soaps or detergents used for washing the hair', 'name': 'shampoo'}, {'frequency': 'c', 'synset': 'shark.n.01', 'synonyms': ['shark'], 'id': 936, 'def': 'typically large carnivorous fishes with sharpe teeth', 'name': 'shark'}, {'frequency': 'r', 'synset': 'sharpener.n.01', 'synonyms': ['sharpener'], 'id': 937, 'def': 'any implement that is used to make something (an edge or a point) sharper', 'name': 'sharpener'}, {'frequency': 'r', 'synset': 'sharpie.n.03', 'synonyms': ['Sharpie'], 'id': 938, 'def': 'a pen with indelible ink that will write on any surface', 'name': 'Sharpie'}, {'frequency': 'r', 'synset': 'shaver.n.03', 'synonyms': ['shaver_(electric)', 'electric_shaver', 'electric_razor'], 'id': 939, 'def': 'a razor powered by an electric motor', 'name': 'shaver_(electric)'}, {'frequency': 'c', 'synset': 'shaving_cream.n.01', 'synonyms': ['shaving_cream', 'shaving_soap'], 'id': 940, 'def': 'toiletry consisting that forms a rich lather for softening the beard before shaving', 'name': 'shaving_cream'}, {'frequency': 'r', 'synset': 'shawl.n.01', 'synonyms': ['shawl'], 'id': 941, 'def': 'cloak consisting of an oblong piece of cloth used to cover the head and shoulders', 'name': 'shawl'}, {'frequency': 'r', 'synset': 'shears.n.01', 'synonyms': ['shears'], 'id': 942, 'def': 'large scissors with strong blades', 'name': 'shears'}, {'frequency': 'f', 'synset': 'sheep.n.01', 'synonyms': ['sheep'], 'id': 943, 'def': 'woolly usually horned ruminant mammal related to the goat', 'name': 'sheep'}, {'frequency': 'r', 'synset': 'shepherd_dog.n.01', 'synonyms': ['shepherd_dog', 'sheepdog'], 'id': 944, 'def': 'any of various usually long-haired breeds of dog reared to herd and guard sheep', 'name': 'shepherd_dog'}, {'frequency': 'r', 'synset': 'sherbert.n.01', 'synonyms': ['sherbert', 'sherbet'], 'id': 945, 'def': 'a frozen dessert made primarily of fruit juice and sugar', 'name': 'sherbert'}, {'frequency': 'c', 'synset': 'shield.n.02', 'synonyms': ['shield'], 'id': 946, 'def': 'armor carried on the arm to intercept blows', 'name': 'shield'}, {'frequency': 'f', 'synset': 'shirt.n.01', 'synonyms': ['shirt'], 'id': 947, 'def': 'a garment worn on the upper half of the body', 'name': 'shirt'}, {'frequency': 'f', 'synset': 'shoe.n.01', 'synonyms': ['shoe', 'sneaker_(type_of_shoe)', 'tennis_shoe'], 'id': 948, 'def': 'common footwear covering the foot', 'name': 'shoe'}, {'frequency': 'f', 'synset': 'shopping_bag.n.01', 'synonyms': ['shopping_bag'], 'id': 949, 'def': 'a bag made of plastic or strong paper (often with handles); used to transport goods after shopping', 'name': 'shopping_bag'}, {'frequency': 'c', 'synset': 'shopping_cart.n.01', 'synonyms': ['shopping_cart'], 'id': 950, 'def': 'a handcart that holds groceries or other goods while shopping', 'name': 'shopping_cart'}, {'frequency': 'f', 'synset': 'short_pants.n.01', 'synonyms': ['short_pants', 'shorts_(clothing)', 'trunks_(clothing)'], 'id': 951, 'def': 'trousers that end at or above the knee', 'name': 'short_pants'}, {'frequency': 'r', 'synset': 'shot_glass.n.01', 'synonyms': ['shot_glass'], 'id': 952, 'def': 'a small glass adequate to hold a single swallow of whiskey', 'name': 'shot_glass'}, {'frequency': 'f', 'synset': 'shoulder_bag.n.01', 'synonyms': ['shoulder_bag'], 'id': 953, 'def': 'a large handbag that can be carried by a strap looped over the shoulder', 'name': 'shoulder_bag'}, {'frequency': 'c', 'synset': 'shovel.n.01', 'synonyms': ['shovel'], 'id': 954, 'def': 'a hand tool for lifting loose material such as snow, dirt, etc.', 'name': 'shovel'}, {'frequency': 'f', 'synset': 'shower.n.01', 'synonyms': ['shower_head'], 'id': 955, 'def': 'a plumbing fixture that sprays water over you', 'name': 'shower_head'}, {'frequency': 'r', 'synset': 'shower_cap.n.01', 'synonyms': ['shower_cap'], 'id': 956, 'def': 'a tight cap worn to keep hair dry while showering', 'name': 'shower_cap'}, {'frequency': 'f', 'synset': 'shower_curtain.n.01', 'synonyms': ['shower_curtain'], 'id': 957, 'def': 'a curtain that keeps water from splashing out of the shower area', 'name': 'shower_curtain'}, {'frequency': 'r', 'synset': 'shredder.n.01', 'synonyms': ['shredder_(for_paper)'], 'id': 958, 'def': 'a device that shreds documents', 'name': 'shredder_(for_paper)'}, {'frequency': 'f', 'synset': 'signboard.n.01', 'synonyms': ['signboard'], 'id': 959, 'def': 'structure displaying a board on which advertisements can be posted', 'name': 'signboard'}, {'frequency': 'c', 'synset': 'silo.n.01', 'synonyms': ['silo'], 'id': 960, 'def': 'a cylindrical tower used for storing goods', 'name': 'silo'}, {'frequency': 'f', 'synset': 'sink.n.01', 'synonyms': ['sink'], 'id': 961, 'def': 'plumbing fixture consisting of a water basin fixed to a wall or floor and having a drainpipe', 'name': 'sink'}, {'frequency': 'f', 'synset': 'skateboard.n.01', 'synonyms': ['skateboard'], 'id': 962, 'def': 'a board with wheels that is ridden in a standing or crouching position and propelled by foot', 'name': 'skateboard'}, {'frequency': 'c', 'synset': 'skewer.n.01', 'synonyms': ['skewer'], 'id': 963, 'def': 'a long pin for holding meat in position while it is being roasted', 'name': 'skewer'}, {'frequency': 'f', 'synset': 'ski.n.01', 'synonyms': ['ski'], 'id': 964, 'def': 'sports equipment for skiing on snow', 'name': 'ski'}, {'frequency': 'f', 'synset': 'ski_boot.n.01', 'synonyms': ['ski_boot'], 'id': 965, 'def': 'a stiff boot that is fastened to a ski with a ski binding', 'name': 'ski_boot'}, {'frequency': 'f', 'synset': 'ski_parka.n.01', 'synonyms': ['ski_parka', 'ski_jacket'], 'id': 966, 'def': 'a parka to be worn while skiing', 'name': 'ski_parka'}, {'frequency': 'f', 'synset': 'ski_pole.n.01', 'synonyms': ['ski_pole'], 'id': 967, 'def': 'a pole with metal points used as an aid in skiing', 'name': 'ski_pole'}, {'frequency': 'f', 'synset': 'skirt.n.02', 'synonyms': ['skirt'], 'id': 968, 'def': 'a garment hanging from the waist; worn mainly by girls and women', 'name': 'skirt'}, {'frequency': 'r', 'synset': 'skullcap.n.01', 'synonyms': ['skullcap'], 'id': 969, 'def': 'rounded brimless cap fitting the crown of the head', 'name': 'skullcap'}, {'frequency': 'c', 'synset': 'sled.n.01', 'synonyms': ['sled', 'sledge', 'sleigh'], 'id': 970, 'def': 'a vehicle or flat object for transportation over snow by sliding or pulled by dogs, etc.', 'name': 'sled'}, {'frequency': 'c', 'synset': 'sleeping_bag.n.01', 'synonyms': ['sleeping_bag'], 'id': 971, 'def': 'large padded bag designed to be slept in outdoors', 'name': 'sleeping_bag'}, {'frequency': 'r', 'synset': 'sling.n.05', 'synonyms': ['sling_(bandage)', 'triangular_bandage'], 'id': 972, 'def': 'bandage to support an injured forearm; slung over the shoulder or neck', 'name': 'sling_(bandage)'}, {'frequency': 'c', 'synset': 'slipper.n.01', 'synonyms': ['slipper_(footwear)', 'carpet_slipper_(footwear)'], 'id': 973, 'def': 'low footwear that can be slipped on and off easily; usually worn indoors', 'name': 'slipper_(footwear)'}, {'frequency': 'r', 'synset': 'smoothie.n.02', 'synonyms': ['smoothie'], 'id': 974, 'def': 'a thick smooth drink consisting of fresh fruit pureed with ice cream or yoghurt or milk', 'name': 'smoothie'}, {'frequency': 'r', 'synset': 'snake.n.01', 'synonyms': ['snake', 'serpent'], 'id': 975, 'def': 'limbless scaly elongate reptile; some are venomous', 'name': 'snake'}, {'frequency': 'f', 'synset': 'snowboard.n.01', 'synonyms': ['snowboard'], 'id': 976, 'def': 'a board that resembles a broad ski or a small surfboard; used in a standing position to slide down snow-covered slopes', 'name': 'snowboard'}, {'frequency': 'c', 'synset': 'snowman.n.01', 'synonyms': ['snowman'], 'id': 977, 'def': 'a figure of a person made of packed snow', 'name': 'snowman'}, {'frequency': 'c', 'synset': 'snowmobile.n.01', 'synonyms': ['snowmobile'], 'id': 978, 'def': 'tracked vehicle for travel on snow having skis in front', 'name': 'snowmobile'}, {'frequency': 'f', 'synset': 'soap.n.01', 'synonyms': ['soap'], 'id': 979, 'def': 'a cleansing agent made from the salts of vegetable or animal fats', 'name': 'soap'}, {'frequency': 'f', 'synset': 'soccer_ball.n.01', 'synonyms': ['soccer_ball'], 'id': 980, 'def': "an inflated ball used in playing soccer (called `football' outside of the United States)", 'name': 'soccer_ball'}, {'frequency': 'f', 'synset': 'sock.n.01', 'synonyms': ['sock'], 'id': 981, 'def': 'cloth covering for the foot; worn inside the shoe; reaches to between the ankle and the knee', 'name': 'sock'}, {'frequency': 'f', 'synset': 'sofa.n.01', 'synonyms': ['sofa', 'couch', 'lounge'], 'id': 982, 'def': 'an upholstered seat for more than one person', 'name': 'sofa'}, {'frequency': 'r', 'synset': 'softball.n.01', 'synonyms': ['softball'], 'id': 983, 'def': 'ball used in playing softball', 'name': 'softball'}, {'frequency': 'c', 'synset': 'solar_array.n.01', 'synonyms': ['solar_array', 'solar_battery', 'solar_panel'], 'id': 984, 'def': 'electrical device consisting of a large array of connected solar cells', 'name': 'solar_array'}, {'frequency': 'r', 'synset': 'sombrero.n.02', 'synonyms': ['sombrero'], 'id': 985, 'def': 'a straw hat with a tall crown and broad brim; worn in American southwest and in Mexico', 'name': 'sombrero'}, {'frequency': 'f', 'synset': 'soup.n.01', 'synonyms': ['soup'], 'id': 986, 'def': 'liquid food especially of meat or fish or vegetable stock often containing pieces of solid food', 'name': 'soup'}, {'frequency': 'r', 'synset': 'soup_bowl.n.01', 'synonyms': ['soup_bowl'], 'id': 987, 'def': 'a bowl for serving soup', 'name': 'soup_bowl'}, {'frequency': 'c', 'synset': 'soupspoon.n.01', 'synonyms': ['soupspoon'], 'id': 988, 'def': 'a spoon with a rounded bowl for eating soup', 'name': 'soupspoon'}, {'frequency': 'c', 'synset': 'sour_cream.n.01', 'synonyms': ['sour_cream', 'soured_cream'], 'id': 989, 'def': 'soured light cream', 'name': 'sour_cream'}, {'frequency': 'r', 'synset': 'soya_milk.n.01', 'synonyms': ['soya_milk', 'soybean_milk', 'soymilk'], 'id': 990, 'def': 'a milk substitute containing soybean flour and water; used in some infant formulas and in making tofu', 'name': 'soya_milk'}, {'frequency': 'r', 'synset': 'space_shuttle.n.01', 'synonyms': ['space_shuttle'], 'id': 991, 'def': "a reusable spacecraft with wings for a controlled descent through the Earth's atmosphere", 'name': 'space_shuttle'}, {'frequency': 'r', 'synset': 'sparkler.n.02', 'synonyms': ['sparkler_(fireworks)'], 'id': 992, 'def': 'a firework that burns slowly and throws out a shower of sparks', 'name': 'sparkler_(fireworks)'}, {'frequency': 'f', 'synset': 'spatula.n.02', 'synonyms': ['spatula'], 'id': 993, 'def': 'a hand tool with a thin flexible blade used to mix or spread soft substances', 'name': 'spatula'}, {'frequency': 'r', 'synset': 'spear.n.01', 'synonyms': ['spear', 'lance'], 'id': 994, 'def': 'a long pointed rod used as a tool or weapon', 'name': 'spear'}, {'frequency': 'f', 'synset': 'spectacles.n.01', 'synonyms': ['spectacles', 'specs', 'eyeglasses', 'glasses'], 'id': 995, 'def': 'optical instrument consisting of a frame that holds a pair of lenses for correcting defective vision', 'name': 'spectacles'}, {'frequency': 'c', 'synset': 'spice_rack.n.01', 'synonyms': ['spice_rack'], 'id': 996, 'def': 'a rack for displaying containers filled with spices', 'name': 'spice_rack'}, {'frequency': 'c', 'synset': 'spider.n.01', 'synonyms': ['spider'], 'id': 997, 'def': 'predatory arachnid with eight legs, two poison fangs, two feelers, and usually two silk-spinning organs at the back end of the body', 'name': 'spider'}, {'frequency': 'r', 'synset': 'spiny_lobster.n.02', 'synonyms': ['crawfish', 'crayfish'], 'id': 998, 'def': 'large edible marine crustacean having a spiny carapace but lacking the large pincers of true lobsters', 'name': 'crawfish'}, {'frequency': 'c', 'synset': 'sponge.n.01', 'synonyms': ['sponge'], 'id': 999, 'def': 'a porous mass usable to absorb water typically used for cleaning', 'name': 'sponge'}, {'frequency': 'f', 'synset': 'spoon.n.01', 'synonyms': ['spoon'], 'id': 1000, 'def': 'a piece of cutlery with a shallow bowl-shaped container and a handle', 'name': 'spoon'}, {'frequency': 'c', 'synset': 'sportswear.n.01', 'synonyms': ['sportswear', 'athletic_wear', 'activewear'], 'id': 1001, 'def': 'attire worn for sport or for casual wear', 'name': 'sportswear'}, {'frequency': 'c', 'synset': 'spotlight.n.02', 'synonyms': ['spotlight'], 'id': 1002, 'def': 'a lamp that produces a strong beam of light to illuminate a restricted area; used to focus attention of a stage performer', 'name': 'spotlight'}, {'frequency': 'r', 'synset': 'squid.n.01', 'synonyms': ['squid_(food)', 'calamari', 'calamary'], 'id': 1003, 'def': '(Italian cuisine) squid prepared as food', 'name': 'squid_(food)'}, {'frequency': 'c', 'synset': 'squirrel.n.01', 'synonyms': ['squirrel'], 'id': 1004, 'def': 'a kind of arboreal rodent having a long bushy tail', 'name': 'squirrel'}, {'frequency': 'r', 'synset': 'stagecoach.n.01', 'synonyms': ['stagecoach'], 'id': 1005, 'def': 'a large coach-and-four formerly used to carry passengers and mail on regular routes between towns', 'name': 'stagecoach'}, {'frequency': 'c', 'synset': 'stapler.n.01', 'synonyms': ['stapler_(stapling_machine)'], 'id': 1006, 'def': 'a machine that inserts staples into sheets of paper in order to fasten them together', 'name': 'stapler_(stapling_machine)'}, {'frequency': 'c', 'synset': 'starfish.n.01', 'synonyms': ['starfish', 'sea_star'], 'id': 1007, 'def': 'echinoderms characterized by five arms extending from a central disk', 'name': 'starfish'}, {'frequency': 'f', 'synset': 'statue.n.01', 'synonyms': ['statue_(sculpture)'], 'id': 1008, 'def': 'a sculpture representing a human or animal', 'name': 'statue_(sculpture)'}, {'frequency': 'c', 'synset': 'steak.n.01', 'synonyms': ['steak_(food)'], 'id': 1009, 'def': 'a slice of meat cut from the fleshy part of an animal or large fish', 'name': 'steak_(food)'}, {'frequency': 'r', 'synset': 'steak_knife.n.01', 'synonyms': ['steak_knife'], 'id': 1010, 'def': 'a sharp table knife used in eating steak', 'name': 'steak_knife'}, {'frequency': 'f', 'synset': 'steering_wheel.n.01', 'synonyms': ['steering_wheel'], 'id': 1011, 'def': 'a handwheel that is used for steering', 'name': 'steering_wheel'}, {'frequency': 'r', 'synset': 'step_ladder.n.01', 'synonyms': ['stepladder'], 'id': 1012, 'def': 'a folding portable ladder hinged at the top', 'name': 'stepladder'}, {'frequency': 'c', 'synset': 'step_stool.n.01', 'synonyms': ['step_stool'], 'id': 1013, 'def': 'a stool that has one or two steps that fold under the seat', 'name': 'step_stool'}, {'frequency': 'c', 'synset': 'stereo.n.01', 'synonyms': ['stereo_(sound_system)'], 'id': 1014, 'def': 'electronic device for playing audio', 'name': 'stereo_(sound_system)'}, {'frequency': 'r', 'synset': 'stew.n.02', 'synonyms': ['stew'], 'id': 1015, 'def': 'food prepared by stewing especially meat or fish with vegetables', 'name': 'stew'}, {'frequency': 'r', 'synset': 'stirrer.n.02', 'synonyms': ['stirrer'], 'id': 1016, 'def': 'an implement used for stirring', 'name': 'stirrer'}, {'frequency': 'f', 'synset': 'stirrup.n.01', 'synonyms': ['stirrup'], 'id': 1017, 'def': "support consisting of metal loops into which rider's feet go", 'name': 'stirrup'}, {'frequency': 'f', 'synset': 'stool.n.01', 'synonyms': ['stool'], 'id': 1018, 'def': 'a simple seat without a back or arms', 'name': 'stool'}, {'frequency': 'f', 'synset': 'stop_sign.n.01', 'synonyms': ['stop_sign'], 'id': 1019, 'def': 'a traffic sign to notify drivers that they must come to a complete stop', 'name': 'stop_sign'}, {'frequency': 'f', 'synset': 'stoplight.n.01', 'synonyms': ['brake_light'], 'id': 1020, 'def': 'a red light on the rear of a motor vehicle that signals when the brakes are applied', 'name': 'brake_light'}, {'frequency': 'f', 'synset': 'stove.n.01', 'synonyms': ['stove', 'kitchen_stove', 'range_(kitchen_appliance)', 'kitchen_range', 'cooking_stove'], 'id': 1021, 'def': 'a kitchen appliance used for cooking food', 'name': 'stove'}, {'frequency': 'c', 'synset': 'strainer.n.01', 'synonyms': ['strainer'], 'id': 1022, 'def': 'a filter to retain larger pieces while smaller pieces and liquids pass through', 'name': 'strainer'}, {'frequency': 'f', 'synset': 'strap.n.01', 'synonyms': ['strap'], 'id': 1023, 'def': 'an elongated strip of material for binding things together or holding', 'name': 'strap'}, {'frequency': 'f', 'synset': 'straw.n.04', 'synonyms': ['straw_(for_drinking)', 'drinking_straw'], 'id': 1024, 'def': 'a thin paper or plastic tube used to suck liquids into the mouth', 'name': 'straw_(for_drinking)'}, {'frequency': 'f', 'synset': 'strawberry.n.01', 'synonyms': ['strawberry'], 'id': 1025, 'def': 'sweet fleshy red fruit', 'name': 'strawberry'}, {'frequency': 'f', 'synset': 'street_sign.n.01', 'synonyms': ['street_sign'], 'id': 1026, 'def': 'a sign visible from the street', 'name': 'street_sign'}, {'frequency': 'f', 'synset': 'streetlight.n.01', 'synonyms': ['streetlight', 'street_lamp'], 'id': 1027, 'def': 'a lamp supported on a lamppost; for illuminating a street', 'name': 'streetlight'}, {'frequency': 'r', 'synset': 'string_cheese.n.01', 'synonyms': ['string_cheese'], 'id': 1028, 'def': 'cheese formed in long strings twisted together', 'name': 'string_cheese'}, {'frequency': 'r', 'synset': 'stylus.n.02', 'synonyms': ['stylus'], 'id': 1029, 'def': 'a pointed tool for writing or drawing or engraving, including pens', 'name': 'stylus'}, {'frequency': 'r', 'synset': 'subwoofer.n.01', 'synonyms': ['subwoofer'], 'id': 1030, 'def': 'a loudspeaker that is designed to reproduce very low bass frequencies', 'name': 'subwoofer'}, {'frequency': 'r', 'synset': 'sugar_bowl.n.01', 'synonyms': ['sugar_bowl'], 'id': 1031, 'def': 'a dish in which sugar is served', 'name': 'sugar_bowl'}, {'frequency': 'r', 'synset': 'sugarcane.n.01', 'synonyms': ['sugarcane_(plant)'], 'id': 1032, 'def': 'juicy canes whose sap is a source of molasses and commercial sugar; fresh canes are sometimes chewed for the juice', 'name': 'sugarcane_(plant)'}, {'frequency': 'f', 'synset': 'suit.n.01', 'synonyms': ['suit_(clothing)'], 'id': 1033, 'def': 'a set of garments (usually including a jacket and trousers or skirt) for outerwear all of the same fabric and color', 'name': 'suit_(clothing)'}, {'frequency': 'c', 'synset': 'sunflower.n.01', 'synonyms': ['sunflower'], 'id': 1034, 'def': 'any plant of the genus Helianthus having large flower heads with dark disk florets and showy yellow rays', 'name': 'sunflower'}, {'frequency': 'f', 'synset': 'sunglasses.n.01', 'synonyms': ['sunglasses'], 'id': 1035, 'def': 'spectacles that are darkened or polarized to protect the eyes from the glare of the sun', 'name': 'sunglasses'}, {'frequency': 'c', 'synset': 'sunhat.n.01', 'synonyms': ['sunhat'], 'id': 1036, 'def': 'a hat with a broad brim that protects the face from direct exposure to the sun', 'name': 'sunhat'}, {'frequency': 'f', 'synset': 'surfboard.n.01', 'synonyms': ['surfboard'], 'id': 1037, 'def': 'a narrow buoyant board for riding surf', 'name': 'surfboard'}, {'frequency': 'c', 'synset': 'sushi.n.01', 'synonyms': ['sushi'], 'id': 1038, 'def': 'rice (with raw fish) wrapped in seaweed', 'name': 'sushi'}, {'frequency': 'c', 'synset': 'swab.n.02', 'synonyms': ['mop'], 'id': 1039, 'def': 'cleaning implement consisting of absorbent material fastened to a handle; for cleaning floors', 'name': 'mop'}, {'frequency': 'c', 'synset': 'sweat_pants.n.01', 'synonyms': ['sweat_pants'], 'id': 1040, 'def': 'loose-fitting trousers with elastic cuffs; worn by athletes', 'name': 'sweat_pants'}, {'frequency': 'c', 'synset': 'sweatband.n.02', 'synonyms': ['sweatband'], 'id': 1041, 'def': 'a band of material tied around the forehead or wrist to absorb sweat', 'name': 'sweatband'}, {'frequency': 'f', 'synset': 'sweater.n.01', 'synonyms': ['sweater'], 'id': 1042, 'def': 'a crocheted or knitted garment covering the upper part of the body', 'name': 'sweater'}, {'frequency': 'f', 'synset': 'sweatshirt.n.01', 'synonyms': ['sweatshirt'], 'id': 1043, 'def': 'cotton knit pullover with long sleeves worn during athletic activity', 'name': 'sweatshirt'}, {'frequency': 'c', 'synset': 'sweet_potato.n.02', 'synonyms': ['sweet_potato'], 'id': 1044, 'def': 'the edible tuberous root of the sweet potato vine', 'name': 'sweet_potato'}, {'frequency': 'f', 'synset': 'swimsuit.n.01', 'synonyms': ['swimsuit', 'swimwear', 'bathing_suit', 'swimming_costume', 'bathing_costume', 'swimming_trunks', 'bathing_trunks'], 'id': 1045, 'def': 'garment worn for swimming', 'name': 'swimsuit'}, {'frequency': 'c', 'synset': 'sword.n.01', 'synonyms': ['sword'], 'id': 1046, 'def': 'a cutting or thrusting weapon that has a long metal blade', 'name': 'sword'}, {'frequency': 'r', 'synset': 'syringe.n.01', 'synonyms': ['syringe'], 'id': 1047, 'def': 'a medical instrument used to inject or withdraw fluids', 'name': 'syringe'}, {'frequency': 'r', 'synset': 'tabasco.n.02', 'synonyms': ['Tabasco_sauce'], 'id': 1048, 'def': 'very spicy sauce (trade name Tabasco) made from fully-aged red peppers', 'name': 'Tabasco_sauce'}, {'frequency': 'r', 'synset': 'table-tennis_table.n.01', 'synonyms': ['table-tennis_table', 'ping-pong_table'], 'id': 1049, 'def': 'a table used for playing table tennis', 'name': 'table-tennis_table'}, {'frequency': 'f', 'synset': 'table.n.02', 'synonyms': ['table'], 'id': 1050, 'def': 'a piece of furniture having a smooth flat top that is usually supported by one or more vertical legs', 'name': 'table'}, {'frequency': 'c', 'synset': 'table_lamp.n.01', 'synonyms': ['table_lamp'], 'id': 1051, 'def': 'a lamp that sits on a table', 'name': 'table_lamp'}, {'frequency': 'f', 'synset': 'tablecloth.n.01', 'synonyms': ['tablecloth'], 'id': 1052, 'def': 'a covering spread over a dining table', 'name': 'tablecloth'}, {'frequency': 'r', 'synset': 'tachometer.n.01', 'synonyms': ['tachometer'], 'id': 1053, 'def': 'measuring instrument for indicating speed of rotation', 'name': 'tachometer'}, {'frequency': 'r', 'synset': 'taco.n.02', 'synonyms': ['taco'], 'id': 1054, 'def': 'a small tortilla cupped around a filling', 'name': 'taco'}, {'frequency': 'f', 'synset': 'tag.n.02', 'synonyms': ['tag'], 'id': 1055, 'def': 'a label associated with something for the purpose of identification or information', 'name': 'tag'}, {'frequency': 'f', 'synset': 'taillight.n.01', 'synonyms': ['taillight', 'rear_light'], 'id': 1056, 'def': 'lamp (usually red) mounted at the rear of a motor vehicle', 'name': 'taillight'}, {'frequency': 'r', 'synset': 'tambourine.n.01', 'synonyms': ['tambourine'], 'id': 1057, 'def': 'a shallow drum with a single drumhead and with metallic disks in the sides', 'name': 'tambourine'}, {'frequency': 'r', 'synset': 'tank.n.01', 'synonyms': ['army_tank', 'armored_combat_vehicle', 'armoured_combat_vehicle'], 'id': 1058, 'def': 'an enclosed armored military vehicle; has a cannon and moves on caterpillar treads', 'name': 'army_tank'}, {'frequency': 'f', 'synset': 'tank.n.02', 'synonyms': ['tank_(storage_vessel)', 'storage_tank'], 'id': 1059, 'def': 'a large (usually metallic) vessel for holding gases or liquids', 'name': 'tank_(storage_vessel)'}, {'frequency': 'f', 'synset': 'tank_top.n.01', 'synonyms': ['tank_top_(clothing)'], 'id': 1060, 'def': 'a tight-fitting sleeveless shirt with wide shoulder straps and low neck and no front opening', 'name': 'tank_top_(clothing)'}, {'frequency': 'f', 'synset': 'tape.n.01', 'synonyms': ['tape_(sticky_cloth_or_paper)'], 'id': 1061, 'def': 'a long thin piece of cloth or paper as used for binding or fastening', 'name': 'tape_(sticky_cloth_or_paper)'}, {'frequency': 'c', 'synset': 'tape.n.04', 'synonyms': ['tape_measure', 'measuring_tape'], 'id': 1062, 'def': 'measuring instrument consisting of a narrow strip (cloth or metal) marked in inches or centimeters and used for measuring lengths', 'name': 'tape_measure'}, {'frequency': 'c', 'synset': 'tapestry.n.02', 'synonyms': ['tapestry'], 'id': 1063, 'def': 'a heavy textile with a woven design; used for curtains and upholstery', 'name': 'tapestry'}, {'frequency': 'f', 'synset': 'tarpaulin.n.01', 'synonyms': ['tarp'], 'id': 1064, 'def': 'waterproofed canvas', 'name': 'tarp'}, {'frequency': 'c', 'synset': 'tartan.n.01', 'synonyms': ['tartan', 'plaid'], 'id': 1065, 'def': 'a cloth having a crisscross design', 'name': 'tartan'}, {'frequency': 'c', 'synset': 'tassel.n.01', 'synonyms': ['tassel'], 'id': 1066, 'def': 'adornment consisting of a bunch of cords fastened at one end', 'name': 'tassel'}, {'frequency': 'c', 'synset': 'tea_bag.n.01', 'synonyms': ['tea_bag'], 'id': 1067, 'def': 'a measured amount of tea in a bag for an individual serving of tea', 'name': 'tea_bag'}, {'frequency': 'c', 'synset': 'teacup.n.02', 'synonyms': ['teacup'], 'id': 1068, 'def': 'a cup from which tea is drunk', 'name': 'teacup'}, {'frequency': 'c', 'synset': 'teakettle.n.01', 'synonyms': ['teakettle'], 'id': 1069, 'def': 'kettle for boiling water to make tea', 'name': 'teakettle'}, {'frequency': 'f', 'synset': 'teapot.n.01', 'synonyms': ['teapot'], 'id': 1070, 'def': 'pot for brewing tea; usually has a spout and handle', 'name': 'teapot'}, {'frequency': 'f', 'synset': 'teddy.n.01', 'synonyms': ['teddy_bear'], 'id': 1071, 'def': "plaything consisting of a child's toy bear (usually plush and stuffed with soft materials)", 'name': 'teddy_bear'}, {'frequency': 'f', 'synset': 'telephone.n.01', 'synonyms': ['telephone', 'phone', 'telephone_set'], 'id': 1072, 'def': 'electronic device for communicating by voice over long distances (includes wired and wireless/cell phones)', 'name': 'telephone'}, {'frequency': 'c', 'synset': 'telephone_booth.n.01', 'synonyms': ['telephone_booth', 'phone_booth', 'call_box', 'telephone_box', 'telephone_kiosk'], 'id': 1073, 'def': 'booth for using a telephone', 'name': 'telephone_booth'}, {'frequency': 'f', 'synset': 'telephone_pole.n.01', 'synonyms': ['telephone_pole', 'telegraph_pole', 'telegraph_post'], 'id': 1074, 'def': 'tall pole supporting telephone wires', 'name': 'telephone_pole'}, {'frequency': 'r', 'synset': 'telephoto_lens.n.01', 'synonyms': ['telephoto_lens', 'zoom_lens'], 'id': 1075, 'def': 'a camera lens that magnifies the image', 'name': 'telephoto_lens'}, {'frequency': 'c', 'synset': 'television_camera.n.01', 'synonyms': ['television_camera', 'tv_camera'], 'id': 1076, 'def': 'television equipment for capturing and recording video', 'name': 'television_camera'}, {'frequency': 'f', 'synset': 'television_receiver.n.01', 'synonyms': ['television_set', 'tv', 'tv_set'], 'id': 1077, 'def': 'an electronic device that receives television signals and displays them on a screen', 'name': 'television_set'}, {'frequency': 'f', 'synset': 'tennis_ball.n.01', 'synonyms': ['tennis_ball'], 'id': 1078, 'def': 'ball about the size of a fist used in playing tennis', 'name': 'tennis_ball'}, {'frequency': 'f', 'synset': 'tennis_racket.n.01', 'synonyms': ['tennis_racket'], 'id': 1079, 'def': 'a racket used to play tennis', 'name': 'tennis_racket'}, {'frequency': 'r', 'synset': 'tequila.n.01', 'synonyms': ['tequila'], 'id': 1080, 'def': 'Mexican liquor made from fermented juices of an agave plant', 'name': 'tequila'}, {'frequency': 'c', 'synset': 'thermometer.n.01', 'synonyms': ['thermometer'], 'id': 1081, 'def': 'measuring instrument for measuring temperature', 'name': 'thermometer'}, {'frequency': 'c', 'synset': 'thermos.n.01', 'synonyms': ['thermos_bottle'], 'id': 1082, 'def': 'vacuum flask that preserves temperature of hot or cold drinks', 'name': 'thermos_bottle'}, {'frequency': 'f', 'synset': 'thermostat.n.01', 'synonyms': ['thermostat'], 'id': 1083, 'def': 'a regulator for automatically regulating temperature by starting or stopping the supply of heat', 'name': 'thermostat'}, {'frequency': 'r', 'synset': 'thimble.n.02', 'synonyms': ['thimble'], 'id': 1084, 'def': 'a small metal cap to protect the finger while sewing; can be used as a small container', 'name': 'thimble'}, {'frequency': 'c', 'synset': 'thread.n.01', 'synonyms': ['thread', 'yarn'], 'id': 1085, 'def': 'a fine cord of twisted fibers (of cotton or silk or wool or nylon etc.) used in sewing and weaving', 'name': 'thread'}, {'frequency': 'c', 'synset': 'thumbtack.n.01', 'synonyms': ['thumbtack', 'drawing_pin', 'pushpin'], 'id': 1086, 'def': 'a tack for attaching papers to a bulletin board or drawing board', 'name': 'thumbtack'}, {'frequency': 'c', 'synset': 'tiara.n.01', 'synonyms': ['tiara'], 'id': 1087, 'def': 'a jeweled headdress worn by women on formal occasions', 'name': 'tiara'}, {'frequency': 'c', 'synset': 'tiger.n.02', 'synonyms': ['tiger'], 'id': 1088, 'def': 'large feline of forests in most of Asia having a tawny coat with black stripes', 'name': 'tiger'}, {'frequency': 'c', 'synset': 'tights.n.01', 'synonyms': ['tights_(clothing)', 'leotards'], 'id': 1089, 'def': 'skintight knit hose covering the body from the waist to the feet worn by acrobats and dancers and as stockings by women and girls', 'name': 'tights_(clothing)'}, {'frequency': 'c', 'synset': 'timer.n.01', 'synonyms': ['timer', 'stopwatch'], 'id': 1090, 'def': 'a timepiece that measures a time interval and signals its end', 'name': 'timer'}, {'frequency': 'f', 'synset': 'tinfoil.n.01', 'synonyms': ['tinfoil'], 'id': 1091, 'def': 'foil made of tin or an alloy of tin and lead', 'name': 'tinfoil'}, {'frequency': 'c', 'synset': 'tinsel.n.01', 'synonyms': ['tinsel'], 'id': 1092, 'def': 'a showy decoration that is basically valueless', 'name': 'tinsel'}, {'frequency': 'f', 'synset': 'tissue.n.02', 'synonyms': ['tissue_paper'], 'id': 1093, 'def': 'a soft thin (usually translucent) paper', 'name': 'tissue_paper'}, {'frequency': 'c', 'synset': 'toast.n.01', 'synonyms': ['toast_(food)'], 'id': 1094, 'def': 'slice of bread that has been toasted', 'name': 'toast_(food)'}, {'frequency': 'f', 'synset': 'toaster.n.02', 'synonyms': ['toaster'], 'id': 1095, 'def': 'a kitchen appliance (usually electric) for toasting bread', 'name': 'toaster'}, {'frequency': 'f', 'synset': 'toaster_oven.n.01', 'synonyms': ['toaster_oven'], 'id': 1096, 'def': 'kitchen appliance consisting of a small electric oven for toasting or warming food', 'name': 'toaster_oven'}, {'frequency': 'f', 'synset': 'toilet.n.02', 'synonyms': ['toilet'], 'id': 1097, 'def': 'a plumbing fixture for defecation and urination', 'name': 'toilet'}, {'frequency': 'f', 'synset': 'toilet_tissue.n.01', 'synonyms': ['toilet_tissue', 'toilet_paper', 'bathroom_tissue'], 'id': 1098, 'def': 'a soft thin absorbent paper for use in toilets', 'name': 'toilet_tissue'}, {'frequency': 'f', 'synset': 'tomato.n.01', 'synonyms': ['tomato'], 'id': 1099, 'def': 'mildly acid red or yellow pulpy fruit eaten as a vegetable', 'name': 'tomato'}, {'frequency': 'f', 'synset': 'tongs.n.01', 'synonyms': ['tongs'], 'id': 1100, 'def': 'any of various devices for taking hold of objects; usually have two hinged legs with handles above and pointed hooks below', 'name': 'tongs'}, {'frequency': 'c', 'synset': 'toolbox.n.01', 'synonyms': ['toolbox'], 'id': 1101, 'def': 'a box or chest or cabinet for holding hand tools', 'name': 'toolbox'}, {'frequency': 'f', 'synset': 'toothbrush.n.01', 'synonyms': ['toothbrush'], 'id': 1102, 'def': 'small brush; has long handle; used to clean teeth', 'name': 'toothbrush'}, {'frequency': 'f', 'synset': 'toothpaste.n.01', 'synonyms': ['toothpaste'], 'id': 1103, 'def': 'a dentifrice in the form of a paste', 'name': 'toothpaste'}, {'frequency': 'f', 'synset': 'toothpick.n.01', 'synonyms': ['toothpick'], 'id': 1104, 'def': 'pick consisting of a small strip of wood or plastic; used to pick food from between the teeth', 'name': 'toothpick'}, {'frequency': 'f', 'synset': 'top.n.09', 'synonyms': ['cover'], 'id': 1105, 'def': 'covering for a hole (especially a hole in the top of a container)', 'name': 'cover'}, {'frequency': 'c', 'synset': 'tortilla.n.01', 'synonyms': ['tortilla'], 'id': 1106, 'def': 'thin unleavened pancake made from cornmeal or wheat flour', 'name': 'tortilla'}, {'frequency': 'c', 'synset': 'tow_truck.n.01', 'synonyms': ['tow_truck'], 'id': 1107, 'def': 'a truck equipped to hoist and pull wrecked cars (or to remove cars from no-parking zones)', 'name': 'tow_truck'}, {'frequency': 'f', 'synset': 'towel.n.01', 'synonyms': ['towel'], 'id': 1108, 'def': 'a rectangular piece of absorbent cloth (or paper) for drying or wiping', 'name': 'towel'}, {'frequency': 'f', 'synset': 'towel_rack.n.01', 'synonyms': ['towel_rack', 'towel_rail', 'towel_bar'], 'id': 1109, 'def': 'a rack consisting of one or more bars on which towels can be hung', 'name': 'towel_rack'}, {'frequency': 'f', 'synset': 'toy.n.03', 'synonyms': ['toy'], 'id': 1110, 'def': 'a device regarded as providing amusement', 'name': 'toy'}, {'frequency': 'c', 'synset': 'tractor.n.01', 'synonyms': ['tractor_(farm_equipment)'], 'id': 1111, 'def': 'a wheeled vehicle with large wheels; used in farming and other applications', 'name': 'tractor_(farm_equipment)'}, {'frequency': 'f', 'synset': 'traffic_light.n.01', 'synonyms': ['traffic_light'], 'id': 1112, 'def': 'a device to control vehicle traffic often consisting of three or more lights', 'name': 'traffic_light'}, {'frequency': 'c', 'synset': 'trail_bike.n.01', 'synonyms': ['dirt_bike'], 'id': 1113, 'def': 'a lightweight motorcycle equipped with rugged tires and suspension for off-road use', 'name': 'dirt_bike'}, {'frequency': 'f', 'synset': 'trailer_truck.n.01', 'synonyms': ['trailer_truck', 'tractor_trailer', 'trucking_rig', 'articulated_lorry', 'semi_truck'], 'id': 1114, 'def': 'a truck consisting of a tractor and trailer together', 'name': 'trailer_truck'}, {'frequency': 'f', 'synset': 'train.n.01', 'synonyms': ['train_(railroad_vehicle)', 'railroad_train'], 'id': 1115, 'def': 'public or private transport provided by a line of railway cars coupled together and drawn by a locomotive', 'name': 'train_(railroad_vehicle)'}, {'frequency': 'r', 'synset': 'trampoline.n.01', 'synonyms': ['trampoline'], 'id': 1116, 'def': 'gymnastic apparatus consisting of a strong canvas sheet attached with springs to a metal frame', 'name': 'trampoline'}, {'frequency': 'f', 'synset': 'tray.n.01', 'synonyms': ['tray'], 'id': 1117, 'def': 'an open receptacle for holding or displaying or serving articles or food', 'name': 'tray'}, {'frequency': 'r', 'synset': 'trench_coat.n.01', 'synonyms': ['trench_coat'], 'id': 1118, 'def': 'a military style raincoat; belted with deep pockets', 'name': 'trench_coat'}, {'frequency': 'r', 'synset': 'triangle.n.05', 'synonyms': ['triangle_(musical_instrument)'], 'id': 1119, 'def': 'a percussion instrument consisting of a metal bar bent in the shape of an open triangle', 'name': 'triangle_(musical_instrument)'}, {'frequency': 'c', 'synset': 'tricycle.n.01', 'synonyms': ['tricycle'], 'id': 1120, 'def': 'a vehicle with three wheels that is moved by foot pedals', 'name': 'tricycle'}, {'frequency': 'f', 'synset': 'tripod.n.01', 'synonyms': ['tripod'], 'id': 1121, 'def': 'a three-legged rack used for support', 'name': 'tripod'}, {'frequency': 'f', 'synset': 'trouser.n.01', 'synonyms': ['trousers', 'pants_(clothing)'], 'id': 1122, 'def': 'a garment extending from the waist to the knee or ankle, covering each leg separately', 'name': 'trousers'}, {'frequency': 'f', 'synset': 'truck.n.01', 'synonyms': ['truck'], 'id': 1123, 'def': 'an automotive vehicle suitable for hauling', 'name': 'truck'}, {'frequency': 'r', 'synset': 'truffle.n.03', 'synonyms': ['truffle_(chocolate)', 'chocolate_truffle'], 'id': 1124, 'def': 'creamy chocolate candy', 'name': 'truffle_(chocolate)'}, {'frequency': 'c', 'synset': 'trunk.n.02', 'synonyms': ['trunk'], 'id': 1125, 'def': 'luggage consisting of a large strong case used when traveling or for storage', 'name': 'trunk'}, {'frequency': 'r', 'synset': 'tub.n.02', 'synonyms': ['vat'], 'id': 1126, 'def': 'a large vessel for holding or storing liquids', 'name': 'vat'}, {'frequency': 'c', 'synset': 'turban.n.01', 'synonyms': ['turban'], 'id': 1127, 'def': 'a traditional headdress consisting of a long scarf wrapped around the head', 'name': 'turban'}, {'frequency': 'c', 'synset': 'turkey.n.04', 'synonyms': ['turkey_(food)'], 'id': 1128, 'def': 'flesh of large domesticated fowl usually roasted', 'name': 'turkey_(food)'}, {'frequency': 'r', 'synset': 'turnip.n.01', 'synonyms': ['turnip'], 'id': 1129, 'def': 'widely cultivated plant having a large fleshy edible white or yellow root', 'name': 'turnip'}, {'frequency': 'c', 'synset': 'turtle.n.02', 'synonyms': ['turtle'], 'id': 1130, 'def': 'any of various aquatic and land reptiles having a bony shell and flipper-like limbs for swimming', 'name': 'turtle'}, {'frequency': 'c', 'synset': 'turtleneck.n.01', 'synonyms': ['turtleneck_(clothing)', 'polo-neck'], 'id': 1131, 'def': 'a sweater or jersey with a high close-fitting collar', 'name': 'turtleneck_(clothing)'}, {'frequency': 'c', 'synset': 'typewriter.n.01', 'synonyms': ['typewriter'], 'id': 1132, 'def': 'hand-operated character printer for printing written messages one character at a time', 'name': 'typewriter'}, {'frequency': 'f', 'synset': 'umbrella.n.01', 'synonyms': ['umbrella'], 'id': 1133, 'def': 'a lightweight handheld collapsible canopy', 'name': 'umbrella'}, {'frequency': 'f', 'synset': 'underwear.n.01', 'synonyms': ['underwear', 'underclothes', 'underclothing', 'underpants'], 'id': 1134, 'def': 'undergarment worn next to the skin and under the outer garments', 'name': 'underwear'}, {'frequency': 'r', 'synset': 'unicycle.n.01', 'synonyms': ['unicycle'], 'id': 1135, 'def': 'a vehicle with a single wheel that is driven by pedals', 'name': 'unicycle'}, {'frequency': 'f', 'synset': 'urinal.n.01', 'synonyms': ['urinal'], 'id': 1136, 'def': 'a plumbing fixture (usually attached to the wall) used by men to urinate', 'name': 'urinal'}, {'frequency': 'c', 'synset': 'urn.n.01', 'synonyms': ['urn'], 'id': 1137, 'def': 'a large vase that usually has a pedestal or feet', 'name': 'urn'}, {'frequency': 'c', 'synset': 'vacuum.n.04', 'synonyms': ['vacuum_cleaner'], 'id': 1138, 'def': 'an electrical home appliance that cleans by suction', 'name': 'vacuum_cleaner'}, {'frequency': 'f', 'synset': 'vase.n.01', 'synonyms': ['vase'], 'id': 1139, 'def': 'an open jar of glass or porcelain used as an ornament or to hold flowers', 'name': 'vase'}, {'frequency': 'c', 'synset': 'vending_machine.n.01', 'synonyms': ['vending_machine'], 'id': 1140, 'def': 'a slot machine for selling goods', 'name': 'vending_machine'}, {'frequency': 'f', 'synset': 'vent.n.01', 'synonyms': ['vent', 'blowhole', 'air_vent'], 'id': 1141, 'def': 'a hole for the escape of gas or air', 'name': 'vent'}, {'frequency': 'f', 'synset': 'vest.n.01', 'synonyms': ['vest', 'waistcoat'], 'id': 1142, 'def': "a man's sleeveless garment worn underneath a coat", 'name': 'vest'}, {'frequency': 'c', 'synset': 'videotape.n.01', 'synonyms': ['videotape'], 'id': 1143, 'def': 'a video recording made on magnetic tape', 'name': 'videotape'}, {'frequency': 'r', 'synset': 'vinegar.n.01', 'synonyms': ['vinegar'], 'id': 1144, 'def': 'sour-tasting liquid produced usually by oxidation of the alcohol in wine or cider and used as a condiment or food preservative', 'name': 'vinegar'}, {'frequency': 'r', 'synset': 'violin.n.01', 'synonyms': ['violin', 'fiddle'], 'id': 1145, 'def': 'bowed stringed instrument that is the highest member of the violin family', 'name': 'violin'}, {'frequency': 'r', 'synset': 'vodka.n.01', 'synonyms': ['vodka'], 'id': 1146, 'def': 'unaged colorless liquor originating in Russia', 'name': 'vodka'}, {'frequency': 'c', 'synset': 'volleyball.n.02', 'synonyms': ['volleyball'], 'id': 1147, 'def': 'an inflated ball used in playing volleyball', 'name': 'volleyball'}, {'frequency': 'r', 'synset': 'vulture.n.01', 'synonyms': ['vulture'], 'id': 1148, 'def': 'any of various large birds of prey having naked heads and weak claws and feeding chiefly on carrion', 'name': 'vulture'}, {'frequency': 'c', 'synset': 'waffle.n.01', 'synonyms': ['waffle'], 'id': 1149, 'def': 'pancake batter baked in a waffle iron', 'name': 'waffle'}, {'frequency': 'r', 'synset': 'waffle_iron.n.01', 'synonyms': ['waffle_iron'], 'id': 1150, 'def': 'a kitchen appliance for baking waffles', 'name': 'waffle_iron'}, {'frequency': 'c', 'synset': 'wagon.n.01', 'synonyms': ['wagon'], 'id': 1151, 'def': 'any of various kinds of wheeled vehicles drawn by an animal or a tractor', 'name': 'wagon'}, {'frequency': 'c', 'synset': 'wagon_wheel.n.01', 'synonyms': ['wagon_wheel'], 'id': 1152, 'def': 'a wheel of a wagon', 'name': 'wagon_wheel'}, {'frequency': 'c', 'synset': 'walking_stick.n.01', 'synonyms': ['walking_stick'], 'id': 1153, 'def': 'a stick carried in the hand for support in walking', 'name': 'walking_stick'}, {'frequency': 'c', 'synset': 'wall_clock.n.01', 'synonyms': ['wall_clock'], 'id': 1154, 'def': 'a clock mounted on a wall', 'name': 'wall_clock'}, {'frequency': 'f', 'synset': 'wall_socket.n.01', 'synonyms': ['wall_socket', 'wall_plug', 'electric_outlet', 'electrical_outlet', 'outlet', 'electric_receptacle'], 'id': 1155, 'def': 'receptacle providing a place in a wiring system where current can be taken to run electrical devices', 'name': 'wall_socket'}, {'frequency': 'f', 'synset': 'wallet.n.01', 'synonyms': ['wallet', 'billfold'], 'id': 1156, 'def': 'a pocket-size case for holding papers and paper money', 'name': 'wallet'}, {'frequency': 'r', 'synset': 'walrus.n.01', 'synonyms': ['walrus'], 'id': 1157, 'def': 'either of two large northern marine mammals having ivory tusks and tough hide over thick blubber', 'name': 'walrus'}, {'frequency': 'r', 'synset': 'wardrobe.n.01', 'synonyms': ['wardrobe'], 'id': 1158, 'def': 'a tall piece of furniture that provides storage space for clothes; has a door and rails or hooks for hanging clothes', 'name': 'wardrobe'}, {'frequency': 'r', 'synset': 'washbasin.n.01', 'synonyms': ['washbasin', 'basin_(for_washing)', 'washbowl', 'washstand', 'handbasin'], 'id': 1159, 'def': 'a bathroom sink that is permanently installed and connected to a water supply and drainpipe; where you can wash your hands and face', 'name': 'washbasin'}, {'frequency': 'c', 'synset': 'washer.n.03', 'synonyms': ['automatic_washer', 'washing_machine'], 'id': 1160, 'def': 'a home appliance for washing clothes and linens automatically', 'name': 'automatic_washer'}, {'frequency': 'f', 'synset': 'watch.n.01', 'synonyms': ['watch', 'wristwatch'], 'id': 1161, 'def': 'a small, portable timepiece', 'name': 'watch'}, {'frequency': 'f', 'synset': 'water_bottle.n.01', 'synonyms': ['water_bottle'], 'id': 1162, 'def': 'a bottle for holding water', 'name': 'water_bottle'}, {'frequency': 'c', 'synset': 'water_cooler.n.01', 'synonyms': ['water_cooler'], 'id': 1163, 'def': 'a device for cooling and dispensing drinking water', 'name': 'water_cooler'}, {'frequency': 'c', 'synset': 'water_faucet.n.01', 'synonyms': ['water_faucet', 'water_tap', 'tap_(water_faucet)'], 'id': 1164, 'def': 'a faucet for drawing water from a pipe or cask', 'name': 'water_faucet'}, {'frequency': 'r', 'synset': 'water_heater.n.01', 'synonyms': ['water_heater', 'hot-water_heater'], 'id': 1165, 'def': 'a heater and storage tank to supply heated water', 'name': 'water_heater'}, {'frequency': 'c', 'synset': 'water_jug.n.01', 'synonyms': ['water_jug'], 'id': 1166, 'def': 'a jug that holds water', 'name': 'water_jug'}, {'frequency': 'r', 'synset': 'water_pistol.n.01', 'synonyms': ['water_gun', 'squirt_gun'], 'id': 1167, 'def': 'plaything consisting of a toy pistol that squirts water', 'name': 'water_gun'}, {'frequency': 'c', 'synset': 'water_scooter.n.01', 'synonyms': ['water_scooter', 'sea_scooter', 'jet_ski'], 'id': 1168, 'def': 'a motorboat resembling a motor scooter (NOT A SURFBOARD OR WATER SKI)', 'name': 'water_scooter'}, {'frequency': 'c', 'synset': 'water_ski.n.01', 'synonyms': ['water_ski'], 'id': 1169, 'def': 'broad ski for skimming over water towed by a speedboat (DO NOT MARK WATER)', 'name': 'water_ski'}, {'frequency': 'c', 'synset': 'water_tower.n.01', 'synonyms': ['water_tower'], 'id': 1170, 'def': 'a large reservoir for water', 'name': 'water_tower'}, {'frequency': 'c', 'synset': 'watering_can.n.01', 'synonyms': ['watering_can'], 'id': 1171, 'def': 'a container with a handle and a spout with a perforated nozzle; used to sprinkle water over plants', 'name': 'watering_can'}, {'frequency': 'f', 'synset': 'watermelon.n.02', 'synonyms': ['watermelon'], 'id': 1172, 'def': 'large oblong or roundish melon with a hard green rind and sweet watery red or occasionally yellowish pulp', 'name': 'watermelon'}, {'frequency': 'f', 'synset': 'weathervane.n.01', 'synonyms': ['weathervane', 'vane_(weathervane)', 'wind_vane'], 'id': 1173, 'def': 'mechanical device attached to an elevated structure; rotates freely to show the direction of the wind', 'name': 'weathervane'}, {'frequency': 'c', 'synset': 'webcam.n.01', 'synonyms': ['webcam'], 'id': 1174, 'def': 'a digital camera designed to take digital photographs and transmit them over the internet', 'name': 'webcam'}, {'frequency': 'c', 'synset': 'wedding_cake.n.01', 'synonyms': ['wedding_cake', 'bridecake'], 'id': 1175, 'def': 'a rich cake with two or more tiers and covered with frosting and decorations; served at a wedding reception', 'name': 'wedding_cake'}, {'frequency': 'c', 'synset': 'wedding_ring.n.01', 'synonyms': ['wedding_ring', 'wedding_band'], 'id': 1176, 'def': 'a ring given to the bride and/or groom at the wedding', 'name': 'wedding_ring'}, {'frequency': 'f', 'synset': 'wet_suit.n.01', 'synonyms': ['wet_suit'], 'id': 1177, 'def': 'a close-fitting garment made of a permeable material; worn in cold water to retain body heat', 'name': 'wet_suit'}, {'frequency': 'f', 'synset': 'wheel.n.01', 'synonyms': ['wheel'], 'id': 1178, 'def': 'a circular frame with spokes (or a solid disc) that can rotate on a shaft or axle', 'name': 'wheel'}, {'frequency': 'c', 'synset': 'wheelchair.n.01', 'synonyms': ['wheelchair'], 'id': 1179, 'def': 'a movable chair mounted on large wheels', 'name': 'wheelchair'}, {'frequency': 'c', 'synset': 'whipped_cream.n.01', 'synonyms': ['whipped_cream'], 'id': 1180, 'def': 'cream that has been beaten until light and fluffy', 'name': 'whipped_cream'}, {'frequency': 'c', 'synset': 'whistle.n.03', 'synonyms': ['whistle'], 'id': 1181, 'def': 'a small wind instrument that produces a whistling sound by blowing into it', 'name': 'whistle'}, {'frequency': 'c', 'synset': 'wig.n.01', 'synonyms': ['wig'], 'id': 1182, 'def': 'hairpiece covering the head and made of real or synthetic hair', 'name': 'wig'}, {'frequency': 'c', 'synset': 'wind_chime.n.01', 'synonyms': ['wind_chime'], 'id': 1183, 'def': 'a decorative arrangement of pieces of metal or glass or pottery that hang together loosely so the wind can cause them to tinkle', 'name': 'wind_chime'}, {'frequency': 'c', 'synset': 'windmill.n.01', 'synonyms': ['windmill'], 'id': 1184, 'def': 'A mill or turbine that is powered by wind', 'name': 'windmill'}, {'frequency': 'c', 'synset': 'window_box.n.01', 'synonyms': ['window_box_(for_plants)'], 'id': 1185, 'def': 'a container for growing plants on a windowsill', 'name': 'window_box_(for_plants)'}, {'frequency': 'f', 'synset': 'windshield_wiper.n.01', 'synonyms': ['windshield_wiper', 'windscreen_wiper', 'wiper_(for_windshield/screen)'], 'id': 1186, 'def': 'a mechanical device that cleans the windshield', 'name': 'windshield_wiper'}, {'frequency': 'c', 'synset': 'windsock.n.01', 'synonyms': ['windsock', 'air_sock', 'air-sleeve', 'wind_sleeve', 'wind_cone'], 'id': 1187, 'def': 'a truncated cloth cone mounted on a mast/pole; shows wind direction', 'name': 'windsock'}, {'frequency': 'f', 'synset': 'wine_bottle.n.01', 'synonyms': ['wine_bottle'], 'id': 1188, 'def': 'a bottle for holding wine', 'name': 'wine_bottle'}, {'frequency': 'c', 'synset': 'wine_bucket.n.01', 'synonyms': ['wine_bucket', 'wine_cooler'], 'id': 1189, 'def': 'a bucket of ice used to chill a bottle of wine', 'name': 'wine_bucket'}, {'frequency': 'f', 'synset': 'wineglass.n.01', 'synonyms': ['wineglass'], 'id': 1190, 'def': 'a glass that has a stem and in which wine is served', 'name': 'wineglass'}, {'frequency': 'f', 'synset': 'winker.n.02', 'synonyms': ['blinder_(for_horses)'], 'id': 1191, 'def': 'blinds that prevent a horse from seeing something on either side', 'name': 'blinder_(for_horses)'}, {'frequency': 'c', 'synset': 'wok.n.01', 'synonyms': ['wok'], 'id': 1192, 'def': 'pan with a convex bottom; used for frying in Chinese cooking', 'name': 'wok'}, {'frequency': 'r', 'synset': 'wolf.n.01', 'synonyms': ['wolf'], 'id': 1193, 'def': 'a wild carnivorous mammal of the dog family, living and hunting in packs', 'name': 'wolf'}, {'frequency': 'c', 'synset': 'wooden_spoon.n.02', 'synonyms': ['wooden_spoon'], 'id': 1194, 'def': 'a spoon made of wood', 'name': 'wooden_spoon'}, {'frequency': 'c', 'synset': 'wreath.n.01', 'synonyms': ['wreath'], 'id': 1195, 'def': 'an arrangement of flowers, leaves, or stems fastened in a ring', 'name': 'wreath'}, {'frequency': 'c', 'synset': 'wrench.n.03', 'synonyms': ['wrench', 'spanner'], 'id': 1196, 'def': 'a hand tool that is used to hold or twist a nut or bolt', 'name': 'wrench'}, {'frequency': 'f', 'synset': 'wristband.n.01', 'synonyms': ['wristband'], 'id': 1197, 'def': 'band consisting of a part of a sleeve that covers the wrist', 'name': 'wristband'}, {'frequency': 'f', 'synset': 'wristlet.n.01', 'synonyms': ['wristlet', 'wrist_band'], 'id': 1198, 'def': 'a band or bracelet worn around the wrist', 'name': 'wristlet'}, {'frequency': 'c', 'synset': 'yacht.n.01', 'synonyms': ['yacht'], 'id': 1199, 'def': 'an expensive vessel propelled by sail or power and used for cruising or racing', 'name': 'yacht'}, {'frequency': 'c', 'synset': 'yogurt.n.01', 'synonyms': ['yogurt', 'yoghurt', 'yoghourt'], 'id': 1200, 'def': 'a custard-like food made from curdled milk', 'name': 'yogurt'}, {'frequency': 'c', 'synset': 'yoke.n.07', 'synonyms': ['yoke_(animal_equipment)'], 'id': 1201, 'def': 'gear joining two animals at the neck; NOT egg yolk', 'name': 'yoke_(animal_equipment)'}, {'frequency': 'f', 'synset': 'zebra.n.01', 'synonyms': ['zebra'], 'id': 1202, 'def': 'any of several fleet black-and-white striped African equines', 'name': 'zebra'}, {'frequency': 'c', 'synset': 'zucchini.n.02', 'synonyms': ['zucchini', 'courgette'], 'id': 1203, 'def': 'small cucumber-shaped vegetable marrow; typically dark green', 'name': 'zucchini'}] # noqa
+# fmt: on
diff --git a/detectron2/detectron2/data/datasets/lvis_v1_category_image_count.py b/detectron2/detectron2/data/datasets/lvis_v1_category_image_count.py
new file mode 100755
index 0000000..31bf0cf
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/lvis_v1_category_image_count.py
@@ -0,0 +1,20 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Autogen with
+# with open("lvis_v1_train.json", "r") as f:
+# a = json.load(f)
+# c = a["categories"]
+# for x in c:
+# del x["name"]
+# del x["instance_count"]
+# del x["def"]
+# del x["synonyms"]
+# del x["frequency"]
+# del x["synset"]
+# LVIS_CATEGORY_IMAGE_COUNT = repr(c) + " # noqa"
+# with open("/tmp/lvis_category_image_count.py", "wt") as f:
+# f.write(f"LVIS_CATEGORY_IMAGE_COUNT = {LVIS_CATEGORY_IMAGE_COUNT}")
+# Then paste the contents of that file below
+
+# fmt: off
+LVIS_CATEGORY_IMAGE_COUNT = [{'id': 1, 'image_count': 64}, {'id': 2, 'image_count': 364}, {'id': 3, 'image_count': 1911}, {'id': 4, 'image_count': 149}, {'id': 5, 'image_count': 29}, {'id': 6, 'image_count': 26}, {'id': 7, 'image_count': 59}, {'id': 8, 'image_count': 22}, {'id': 9, 'image_count': 12}, {'id': 10, 'image_count': 28}, {'id': 11, 'image_count': 505}, {'id': 12, 'image_count': 1207}, {'id': 13, 'image_count': 4}, {'id': 14, 'image_count': 10}, {'id': 15, 'image_count': 500}, {'id': 16, 'image_count': 33}, {'id': 17, 'image_count': 3}, {'id': 18, 'image_count': 44}, {'id': 19, 'image_count': 561}, {'id': 20, 'image_count': 8}, {'id': 21, 'image_count': 9}, {'id': 22, 'image_count': 33}, {'id': 23, 'image_count': 1883}, {'id': 24, 'image_count': 98}, {'id': 25, 'image_count': 70}, {'id': 26, 'image_count': 46}, {'id': 27, 'image_count': 117}, {'id': 28, 'image_count': 41}, {'id': 29, 'image_count': 1395}, {'id': 30, 'image_count': 7}, {'id': 31, 'image_count': 1}, {'id': 32, 'image_count': 314}, {'id': 33, 'image_count': 31}, {'id': 34, 'image_count': 1905}, {'id': 35, 'image_count': 1859}, {'id': 36, 'image_count': 1623}, {'id': 37, 'image_count': 47}, {'id': 38, 'image_count': 3}, {'id': 39, 'image_count': 3}, {'id': 40, 'image_count': 1}, {'id': 41, 'image_count': 305}, {'id': 42, 'image_count': 6}, {'id': 43, 'image_count': 210}, {'id': 44, 'image_count': 36}, {'id': 45, 'image_count': 1787}, {'id': 46, 'image_count': 17}, {'id': 47, 'image_count': 51}, {'id': 48, 'image_count': 138}, {'id': 49, 'image_count': 3}, {'id': 50, 'image_count': 1470}, {'id': 51, 'image_count': 3}, {'id': 52, 'image_count': 2}, {'id': 53, 'image_count': 186}, {'id': 54, 'image_count': 76}, {'id': 55, 'image_count': 26}, {'id': 56, 'image_count': 303}, {'id': 57, 'image_count': 738}, {'id': 58, 'image_count': 1799}, {'id': 59, 'image_count': 1934}, {'id': 60, 'image_count': 1609}, {'id': 61, 'image_count': 1622}, {'id': 62, 'image_count': 41}, {'id': 63, 'image_count': 4}, {'id': 64, 'image_count': 11}, {'id': 65, 'image_count': 270}, {'id': 66, 'image_count': 349}, {'id': 67, 'image_count': 42}, {'id': 68, 'image_count': 823}, {'id': 69, 'image_count': 6}, {'id': 70, 'image_count': 48}, {'id': 71, 'image_count': 3}, {'id': 72, 'image_count': 42}, {'id': 73, 'image_count': 24}, {'id': 74, 'image_count': 16}, {'id': 75, 'image_count': 605}, {'id': 76, 'image_count': 646}, {'id': 77, 'image_count': 1765}, {'id': 78, 'image_count': 2}, {'id': 79, 'image_count': 125}, {'id': 80, 'image_count': 1420}, {'id': 81, 'image_count': 140}, {'id': 82, 'image_count': 4}, {'id': 83, 'image_count': 322}, {'id': 84, 'image_count': 60}, {'id': 85, 'image_count': 2}, {'id': 86, 'image_count': 231}, {'id': 87, 'image_count': 333}, {'id': 88, 'image_count': 1941}, {'id': 89, 'image_count': 367}, {'id': 90, 'image_count': 1922}, {'id': 91, 'image_count': 18}, {'id': 92, 'image_count': 81}, {'id': 93, 'image_count': 1}, {'id': 94, 'image_count': 1852}, {'id': 95, 'image_count': 430}, {'id': 96, 'image_count': 247}, {'id': 97, 'image_count': 94}, {'id': 98, 'image_count': 21}, {'id': 99, 'image_count': 1821}, {'id': 100, 'image_count': 16}, {'id': 101, 'image_count': 12}, {'id': 102, 'image_count': 25}, {'id': 103, 'image_count': 41}, {'id': 104, 'image_count': 244}, {'id': 105, 'image_count': 7}, {'id': 106, 'image_count': 1}, {'id': 107, 'image_count': 40}, {'id': 108, 'image_count': 40}, {'id': 109, 'image_count': 104}, {'id': 110, 'image_count': 1671}, {'id': 111, 'image_count': 49}, {'id': 112, 'image_count': 243}, {'id': 113, 'image_count': 2}, {'id': 114, 'image_count': 242}, {'id': 115, 'image_count': 271}, {'id': 116, 'image_count': 104}, {'id': 117, 'image_count': 8}, {'id': 118, 'image_count': 1758}, {'id': 119, 'image_count': 1}, {'id': 120, 'image_count': 48}, {'id': 121, 'image_count': 14}, {'id': 122, 'image_count': 40}, {'id': 123, 'image_count': 1}, {'id': 124, 'image_count': 37}, {'id': 125, 'image_count': 1510}, {'id': 126, 'image_count': 6}, {'id': 127, 'image_count': 1903}, {'id': 128, 'image_count': 70}, {'id': 129, 'image_count': 86}, {'id': 130, 'image_count': 7}, {'id': 131, 'image_count': 5}, {'id': 132, 'image_count': 1406}, {'id': 133, 'image_count': 1901}, {'id': 134, 'image_count': 15}, {'id': 135, 'image_count': 28}, {'id': 136, 'image_count': 6}, {'id': 137, 'image_count': 494}, {'id': 138, 'image_count': 234}, {'id': 139, 'image_count': 1922}, {'id': 140, 'image_count': 1}, {'id': 141, 'image_count': 35}, {'id': 142, 'image_count': 5}, {'id': 143, 'image_count': 1828}, {'id': 144, 'image_count': 8}, {'id': 145, 'image_count': 63}, {'id': 146, 'image_count': 1668}, {'id': 147, 'image_count': 4}, {'id': 148, 'image_count': 95}, {'id': 149, 'image_count': 17}, {'id': 150, 'image_count': 1567}, {'id': 151, 'image_count': 2}, {'id': 152, 'image_count': 103}, {'id': 153, 'image_count': 50}, {'id': 154, 'image_count': 1309}, {'id': 155, 'image_count': 6}, {'id': 156, 'image_count': 92}, {'id': 157, 'image_count': 19}, {'id': 158, 'image_count': 37}, {'id': 159, 'image_count': 4}, {'id': 160, 'image_count': 709}, {'id': 161, 'image_count': 9}, {'id': 162, 'image_count': 82}, {'id': 163, 'image_count': 15}, {'id': 164, 'image_count': 3}, {'id': 165, 'image_count': 61}, {'id': 166, 'image_count': 51}, {'id': 167, 'image_count': 5}, {'id': 168, 'image_count': 13}, {'id': 169, 'image_count': 642}, {'id': 170, 'image_count': 24}, {'id': 171, 'image_count': 255}, {'id': 172, 'image_count': 9}, {'id': 173, 'image_count': 1808}, {'id': 174, 'image_count': 31}, {'id': 175, 'image_count': 158}, {'id': 176, 'image_count': 80}, {'id': 177, 'image_count': 1884}, {'id': 178, 'image_count': 158}, {'id': 179, 'image_count': 2}, {'id': 180, 'image_count': 12}, {'id': 181, 'image_count': 1659}, {'id': 182, 'image_count': 7}, {'id': 183, 'image_count': 834}, {'id': 184, 'image_count': 57}, {'id': 185, 'image_count': 174}, {'id': 186, 'image_count': 95}, {'id': 187, 'image_count': 27}, {'id': 188, 'image_count': 22}, {'id': 189, 'image_count': 1391}, {'id': 190, 'image_count': 90}, {'id': 191, 'image_count': 40}, {'id': 192, 'image_count': 445}, {'id': 193, 'image_count': 21}, {'id': 194, 'image_count': 1132}, {'id': 195, 'image_count': 177}, {'id': 196, 'image_count': 4}, {'id': 197, 'image_count': 17}, {'id': 198, 'image_count': 84}, {'id': 199, 'image_count': 55}, {'id': 200, 'image_count': 30}, {'id': 201, 'image_count': 25}, {'id': 202, 'image_count': 2}, {'id': 203, 'image_count': 125}, {'id': 204, 'image_count': 1135}, {'id': 205, 'image_count': 19}, {'id': 206, 'image_count': 72}, {'id': 207, 'image_count': 1926}, {'id': 208, 'image_count': 159}, {'id': 209, 'image_count': 7}, {'id': 210, 'image_count': 1}, {'id': 211, 'image_count': 13}, {'id': 212, 'image_count': 35}, {'id': 213, 'image_count': 18}, {'id': 214, 'image_count': 8}, {'id': 215, 'image_count': 6}, {'id': 216, 'image_count': 35}, {'id': 217, 'image_count': 1222}, {'id': 218, 'image_count': 103}, {'id': 219, 'image_count': 28}, {'id': 220, 'image_count': 63}, {'id': 221, 'image_count': 28}, {'id': 222, 'image_count': 5}, {'id': 223, 'image_count': 7}, {'id': 224, 'image_count': 14}, {'id': 225, 'image_count': 1918}, {'id': 226, 'image_count': 133}, {'id': 227, 'image_count': 16}, {'id': 228, 'image_count': 27}, {'id': 229, 'image_count': 110}, {'id': 230, 'image_count': 1895}, {'id': 231, 'image_count': 4}, {'id': 232, 'image_count': 1927}, {'id': 233, 'image_count': 8}, {'id': 234, 'image_count': 1}, {'id': 235, 'image_count': 263}, {'id': 236, 'image_count': 10}, {'id': 237, 'image_count': 2}, {'id': 238, 'image_count': 3}, {'id': 239, 'image_count': 87}, {'id': 240, 'image_count': 9}, {'id': 241, 'image_count': 71}, {'id': 242, 'image_count': 13}, {'id': 243, 'image_count': 18}, {'id': 244, 'image_count': 2}, {'id': 245, 'image_count': 5}, {'id': 246, 'image_count': 45}, {'id': 247, 'image_count': 1}, {'id': 248, 'image_count': 23}, {'id': 249, 'image_count': 32}, {'id': 250, 'image_count': 4}, {'id': 251, 'image_count': 1}, {'id': 252, 'image_count': 858}, {'id': 253, 'image_count': 661}, {'id': 254, 'image_count': 168}, {'id': 255, 'image_count': 210}, {'id': 256, 'image_count': 65}, {'id': 257, 'image_count': 4}, {'id': 258, 'image_count': 2}, {'id': 259, 'image_count': 159}, {'id': 260, 'image_count': 31}, {'id': 261, 'image_count': 811}, {'id': 262, 'image_count': 1}, {'id': 263, 'image_count': 42}, {'id': 264, 'image_count': 27}, {'id': 265, 'image_count': 2}, {'id': 266, 'image_count': 5}, {'id': 267, 'image_count': 95}, {'id': 268, 'image_count': 32}, {'id': 269, 'image_count': 1}, {'id': 270, 'image_count': 1}, {'id': 271, 'image_count': 1844}, {'id': 272, 'image_count': 897}, {'id': 273, 'image_count': 31}, {'id': 274, 'image_count': 23}, {'id': 275, 'image_count': 1}, {'id': 276, 'image_count': 202}, {'id': 277, 'image_count': 746}, {'id': 278, 'image_count': 44}, {'id': 279, 'image_count': 14}, {'id': 280, 'image_count': 26}, {'id': 281, 'image_count': 1}, {'id': 282, 'image_count': 2}, {'id': 283, 'image_count': 25}, {'id': 284, 'image_count': 238}, {'id': 285, 'image_count': 592}, {'id': 286, 'image_count': 26}, {'id': 287, 'image_count': 5}, {'id': 288, 'image_count': 42}, {'id': 289, 'image_count': 13}, {'id': 290, 'image_count': 46}, {'id': 291, 'image_count': 1}, {'id': 292, 'image_count': 8}, {'id': 293, 'image_count': 34}, {'id': 294, 'image_count': 5}, {'id': 295, 'image_count': 1}, {'id': 296, 'image_count': 1871}, {'id': 297, 'image_count': 717}, {'id': 298, 'image_count': 1010}, {'id': 299, 'image_count': 679}, {'id': 300, 'image_count': 3}, {'id': 301, 'image_count': 4}, {'id': 302, 'image_count': 1}, {'id': 303, 'image_count': 166}, {'id': 304, 'image_count': 2}, {'id': 305, 'image_count': 266}, {'id': 306, 'image_count': 101}, {'id': 307, 'image_count': 6}, {'id': 308, 'image_count': 14}, {'id': 309, 'image_count': 133}, {'id': 310, 'image_count': 2}, {'id': 311, 'image_count': 38}, {'id': 312, 'image_count': 95}, {'id': 313, 'image_count': 1}, {'id': 314, 'image_count': 12}, {'id': 315, 'image_count': 49}, {'id': 316, 'image_count': 5}, {'id': 317, 'image_count': 5}, {'id': 318, 'image_count': 16}, {'id': 319, 'image_count': 216}, {'id': 320, 'image_count': 12}, {'id': 321, 'image_count': 1}, {'id': 322, 'image_count': 54}, {'id': 323, 'image_count': 5}, {'id': 324, 'image_count': 245}, {'id': 325, 'image_count': 12}, {'id': 326, 'image_count': 7}, {'id': 327, 'image_count': 35}, {'id': 328, 'image_count': 36}, {'id': 329, 'image_count': 32}, {'id': 330, 'image_count': 1027}, {'id': 331, 'image_count': 10}, {'id': 332, 'image_count': 12}, {'id': 333, 'image_count': 1}, {'id': 334, 'image_count': 67}, {'id': 335, 'image_count': 71}, {'id': 336, 'image_count': 30}, {'id': 337, 'image_count': 48}, {'id': 338, 'image_count': 249}, {'id': 339, 'image_count': 13}, {'id': 340, 'image_count': 29}, {'id': 341, 'image_count': 14}, {'id': 342, 'image_count': 236}, {'id': 343, 'image_count': 15}, {'id': 344, 'image_count': 1521}, {'id': 345, 'image_count': 25}, {'id': 346, 'image_count': 249}, {'id': 347, 'image_count': 139}, {'id': 348, 'image_count': 2}, {'id': 349, 'image_count': 2}, {'id': 350, 'image_count': 1890}, {'id': 351, 'image_count': 1240}, {'id': 352, 'image_count': 1}, {'id': 353, 'image_count': 9}, {'id': 354, 'image_count': 1}, {'id': 355, 'image_count': 3}, {'id': 356, 'image_count': 11}, {'id': 357, 'image_count': 4}, {'id': 358, 'image_count': 236}, {'id': 359, 'image_count': 44}, {'id': 360, 'image_count': 19}, {'id': 361, 'image_count': 1100}, {'id': 362, 'image_count': 7}, {'id': 363, 'image_count': 69}, {'id': 364, 'image_count': 2}, {'id': 365, 'image_count': 8}, {'id': 366, 'image_count': 5}, {'id': 367, 'image_count': 227}, {'id': 368, 'image_count': 6}, {'id': 369, 'image_count': 106}, {'id': 370, 'image_count': 81}, {'id': 371, 'image_count': 17}, {'id': 372, 'image_count': 134}, {'id': 373, 'image_count': 312}, {'id': 374, 'image_count': 8}, {'id': 375, 'image_count': 271}, {'id': 376, 'image_count': 2}, {'id': 377, 'image_count': 103}, {'id': 378, 'image_count': 1938}, {'id': 379, 'image_count': 574}, {'id': 380, 'image_count': 120}, {'id': 381, 'image_count': 2}, {'id': 382, 'image_count': 2}, {'id': 383, 'image_count': 13}, {'id': 384, 'image_count': 29}, {'id': 385, 'image_count': 1710}, {'id': 386, 'image_count': 66}, {'id': 387, 'image_count': 1008}, {'id': 388, 'image_count': 1}, {'id': 389, 'image_count': 3}, {'id': 390, 'image_count': 1942}, {'id': 391, 'image_count': 19}, {'id': 392, 'image_count': 1488}, {'id': 393, 'image_count': 46}, {'id': 394, 'image_count': 106}, {'id': 395, 'image_count': 115}, {'id': 396, 'image_count': 19}, {'id': 397, 'image_count': 2}, {'id': 398, 'image_count': 1}, {'id': 399, 'image_count': 28}, {'id': 400, 'image_count': 9}, {'id': 401, 'image_count': 192}, {'id': 402, 'image_count': 12}, {'id': 403, 'image_count': 21}, {'id': 404, 'image_count': 247}, {'id': 405, 'image_count': 6}, {'id': 406, 'image_count': 64}, {'id': 407, 'image_count': 7}, {'id': 408, 'image_count': 40}, {'id': 409, 'image_count': 542}, {'id': 410, 'image_count': 2}, {'id': 411, 'image_count': 1898}, {'id': 412, 'image_count': 36}, {'id': 413, 'image_count': 4}, {'id': 414, 'image_count': 1}, {'id': 415, 'image_count': 191}, {'id': 416, 'image_count': 6}, {'id': 417, 'image_count': 41}, {'id': 418, 'image_count': 39}, {'id': 419, 'image_count': 46}, {'id': 420, 'image_count': 1}, {'id': 421, 'image_count': 1451}, {'id': 422, 'image_count': 1878}, {'id': 423, 'image_count': 11}, {'id': 424, 'image_count': 82}, {'id': 425, 'image_count': 18}, {'id': 426, 'image_count': 1}, {'id': 427, 'image_count': 7}, {'id': 428, 'image_count': 3}, {'id': 429, 'image_count': 575}, {'id': 430, 'image_count': 1907}, {'id': 431, 'image_count': 8}, {'id': 432, 'image_count': 4}, {'id': 433, 'image_count': 32}, {'id': 434, 'image_count': 11}, {'id': 435, 'image_count': 4}, {'id': 436, 'image_count': 54}, {'id': 437, 'image_count': 202}, {'id': 438, 'image_count': 32}, {'id': 439, 'image_count': 3}, {'id': 440, 'image_count': 130}, {'id': 441, 'image_count': 119}, {'id': 442, 'image_count': 141}, {'id': 443, 'image_count': 29}, {'id': 444, 'image_count': 525}, {'id': 445, 'image_count': 1323}, {'id': 446, 'image_count': 2}, {'id': 447, 'image_count': 113}, {'id': 448, 'image_count': 16}, {'id': 449, 'image_count': 7}, {'id': 450, 'image_count': 35}, {'id': 451, 'image_count': 1908}, {'id': 452, 'image_count': 353}, {'id': 453, 'image_count': 18}, {'id': 454, 'image_count': 14}, {'id': 455, 'image_count': 77}, {'id': 456, 'image_count': 8}, {'id': 457, 'image_count': 37}, {'id': 458, 'image_count': 1}, {'id': 459, 'image_count': 346}, {'id': 460, 'image_count': 19}, {'id': 461, 'image_count': 1779}, {'id': 462, 'image_count': 23}, {'id': 463, 'image_count': 25}, {'id': 464, 'image_count': 67}, {'id': 465, 'image_count': 19}, {'id': 466, 'image_count': 28}, {'id': 467, 'image_count': 4}, {'id': 468, 'image_count': 27}, {'id': 469, 'image_count': 1861}, {'id': 470, 'image_count': 11}, {'id': 471, 'image_count': 13}, {'id': 472, 'image_count': 13}, {'id': 473, 'image_count': 32}, {'id': 474, 'image_count': 1767}, {'id': 475, 'image_count': 42}, {'id': 476, 'image_count': 17}, {'id': 477, 'image_count': 128}, {'id': 478, 'image_count': 1}, {'id': 479, 'image_count': 9}, {'id': 480, 'image_count': 10}, {'id': 481, 'image_count': 4}, {'id': 482, 'image_count': 9}, {'id': 483, 'image_count': 18}, {'id': 484, 'image_count': 41}, {'id': 485, 'image_count': 28}, {'id': 486, 'image_count': 3}, {'id': 487, 'image_count': 65}, {'id': 488, 'image_count': 9}, {'id': 489, 'image_count': 23}, {'id': 490, 'image_count': 24}, {'id': 491, 'image_count': 1}, {'id': 492, 'image_count': 2}, {'id': 493, 'image_count': 59}, {'id': 494, 'image_count': 48}, {'id': 495, 'image_count': 17}, {'id': 496, 'image_count': 1877}, {'id': 497, 'image_count': 18}, {'id': 498, 'image_count': 1920}, {'id': 499, 'image_count': 50}, {'id': 500, 'image_count': 1890}, {'id': 501, 'image_count': 99}, {'id': 502, 'image_count': 1530}, {'id': 503, 'image_count': 3}, {'id': 504, 'image_count': 11}, {'id': 505, 'image_count': 19}, {'id': 506, 'image_count': 3}, {'id': 507, 'image_count': 63}, {'id': 508, 'image_count': 5}, {'id': 509, 'image_count': 6}, {'id': 510, 'image_count': 233}, {'id': 511, 'image_count': 54}, {'id': 512, 'image_count': 36}, {'id': 513, 'image_count': 10}, {'id': 514, 'image_count': 124}, {'id': 515, 'image_count': 101}, {'id': 516, 'image_count': 3}, {'id': 517, 'image_count': 363}, {'id': 518, 'image_count': 3}, {'id': 519, 'image_count': 30}, {'id': 520, 'image_count': 18}, {'id': 521, 'image_count': 199}, {'id': 522, 'image_count': 97}, {'id': 523, 'image_count': 32}, {'id': 524, 'image_count': 121}, {'id': 525, 'image_count': 16}, {'id': 526, 'image_count': 12}, {'id': 527, 'image_count': 2}, {'id': 528, 'image_count': 214}, {'id': 529, 'image_count': 48}, {'id': 530, 'image_count': 26}, {'id': 531, 'image_count': 13}, {'id': 532, 'image_count': 4}, {'id': 533, 'image_count': 11}, {'id': 534, 'image_count': 123}, {'id': 535, 'image_count': 7}, {'id': 536, 'image_count': 200}, {'id': 537, 'image_count': 91}, {'id': 538, 'image_count': 9}, {'id': 539, 'image_count': 72}, {'id': 540, 'image_count': 1886}, {'id': 541, 'image_count': 4}, {'id': 542, 'image_count': 1}, {'id': 543, 'image_count': 1}, {'id': 544, 'image_count': 1932}, {'id': 545, 'image_count': 4}, {'id': 546, 'image_count': 56}, {'id': 547, 'image_count': 854}, {'id': 548, 'image_count': 755}, {'id': 549, 'image_count': 1843}, {'id': 550, 'image_count': 96}, {'id': 551, 'image_count': 7}, {'id': 552, 'image_count': 74}, {'id': 553, 'image_count': 66}, {'id': 554, 'image_count': 57}, {'id': 555, 'image_count': 44}, {'id': 556, 'image_count': 1905}, {'id': 557, 'image_count': 4}, {'id': 558, 'image_count': 90}, {'id': 559, 'image_count': 1635}, {'id': 560, 'image_count': 8}, {'id': 561, 'image_count': 5}, {'id': 562, 'image_count': 50}, {'id': 563, 'image_count': 545}, {'id': 564, 'image_count': 20}, {'id': 565, 'image_count': 193}, {'id': 566, 'image_count': 285}, {'id': 567, 'image_count': 3}, {'id': 568, 'image_count': 1}, {'id': 569, 'image_count': 1904}, {'id': 570, 'image_count': 294}, {'id': 571, 'image_count': 3}, {'id': 572, 'image_count': 5}, {'id': 573, 'image_count': 24}, {'id': 574, 'image_count': 2}, {'id': 575, 'image_count': 2}, {'id': 576, 'image_count': 16}, {'id': 577, 'image_count': 8}, {'id': 578, 'image_count': 154}, {'id': 579, 'image_count': 66}, {'id': 580, 'image_count': 1}, {'id': 581, 'image_count': 24}, {'id': 582, 'image_count': 1}, {'id': 583, 'image_count': 4}, {'id': 584, 'image_count': 75}, {'id': 585, 'image_count': 6}, {'id': 586, 'image_count': 126}, {'id': 587, 'image_count': 24}, {'id': 588, 'image_count': 22}, {'id': 589, 'image_count': 1872}, {'id': 590, 'image_count': 16}, {'id': 591, 'image_count': 423}, {'id': 592, 'image_count': 1927}, {'id': 593, 'image_count': 38}, {'id': 594, 'image_count': 3}, {'id': 595, 'image_count': 1945}, {'id': 596, 'image_count': 35}, {'id': 597, 'image_count': 1}, {'id': 598, 'image_count': 13}, {'id': 599, 'image_count': 9}, {'id': 600, 'image_count': 14}, {'id': 601, 'image_count': 37}, {'id': 602, 'image_count': 3}, {'id': 603, 'image_count': 4}, {'id': 604, 'image_count': 100}, {'id': 605, 'image_count': 195}, {'id': 606, 'image_count': 1}, {'id': 607, 'image_count': 12}, {'id': 608, 'image_count': 24}, {'id': 609, 'image_count': 489}, {'id': 610, 'image_count': 10}, {'id': 611, 'image_count': 1689}, {'id': 612, 'image_count': 42}, {'id': 613, 'image_count': 81}, {'id': 614, 'image_count': 894}, {'id': 615, 'image_count': 1868}, {'id': 616, 'image_count': 7}, {'id': 617, 'image_count': 1567}, {'id': 618, 'image_count': 10}, {'id': 619, 'image_count': 8}, {'id': 620, 'image_count': 7}, {'id': 621, 'image_count': 629}, {'id': 622, 'image_count': 89}, {'id': 623, 'image_count': 15}, {'id': 624, 'image_count': 134}, {'id': 625, 'image_count': 4}, {'id': 626, 'image_count': 1802}, {'id': 627, 'image_count': 595}, {'id': 628, 'image_count': 1210}, {'id': 629, 'image_count': 48}, {'id': 630, 'image_count': 418}, {'id': 631, 'image_count': 1846}, {'id': 632, 'image_count': 5}, {'id': 633, 'image_count': 221}, {'id': 634, 'image_count': 10}, {'id': 635, 'image_count': 7}, {'id': 636, 'image_count': 76}, {'id': 637, 'image_count': 22}, {'id': 638, 'image_count': 10}, {'id': 639, 'image_count': 341}, {'id': 640, 'image_count': 1}, {'id': 641, 'image_count': 705}, {'id': 642, 'image_count': 1900}, {'id': 643, 'image_count': 188}, {'id': 644, 'image_count': 227}, {'id': 645, 'image_count': 861}, {'id': 646, 'image_count': 6}, {'id': 647, 'image_count': 115}, {'id': 648, 'image_count': 5}, {'id': 649, 'image_count': 43}, {'id': 650, 'image_count': 14}, {'id': 651, 'image_count': 6}, {'id': 652, 'image_count': 15}, {'id': 653, 'image_count': 1167}, {'id': 654, 'image_count': 15}, {'id': 655, 'image_count': 994}, {'id': 656, 'image_count': 28}, {'id': 657, 'image_count': 2}, {'id': 658, 'image_count': 338}, {'id': 659, 'image_count': 334}, {'id': 660, 'image_count': 15}, {'id': 661, 'image_count': 102}, {'id': 662, 'image_count': 1}, {'id': 663, 'image_count': 8}, {'id': 664, 'image_count': 1}, {'id': 665, 'image_count': 1}, {'id': 666, 'image_count': 28}, {'id': 667, 'image_count': 91}, {'id': 668, 'image_count': 260}, {'id': 669, 'image_count': 131}, {'id': 670, 'image_count': 128}, {'id': 671, 'image_count': 3}, {'id': 672, 'image_count': 10}, {'id': 673, 'image_count': 39}, {'id': 674, 'image_count': 2}, {'id': 675, 'image_count': 925}, {'id': 676, 'image_count': 354}, {'id': 677, 'image_count': 31}, {'id': 678, 'image_count': 10}, {'id': 679, 'image_count': 215}, {'id': 680, 'image_count': 71}, {'id': 681, 'image_count': 43}, {'id': 682, 'image_count': 28}, {'id': 683, 'image_count': 34}, {'id': 684, 'image_count': 16}, {'id': 685, 'image_count': 273}, {'id': 686, 'image_count': 2}, {'id': 687, 'image_count': 999}, {'id': 688, 'image_count': 4}, {'id': 689, 'image_count': 107}, {'id': 690, 'image_count': 2}, {'id': 691, 'image_count': 1}, {'id': 692, 'image_count': 454}, {'id': 693, 'image_count': 9}, {'id': 694, 'image_count': 1901}, {'id': 695, 'image_count': 61}, {'id': 696, 'image_count': 91}, {'id': 697, 'image_count': 46}, {'id': 698, 'image_count': 1402}, {'id': 699, 'image_count': 74}, {'id': 700, 'image_count': 421}, {'id': 701, 'image_count': 226}, {'id': 702, 'image_count': 10}, {'id': 703, 'image_count': 1720}, {'id': 704, 'image_count': 261}, {'id': 705, 'image_count': 1337}, {'id': 706, 'image_count': 293}, {'id': 707, 'image_count': 62}, {'id': 708, 'image_count': 814}, {'id': 709, 'image_count': 407}, {'id': 710, 'image_count': 6}, {'id': 711, 'image_count': 16}, {'id': 712, 'image_count': 7}, {'id': 713, 'image_count': 1791}, {'id': 714, 'image_count': 2}, {'id': 715, 'image_count': 1915}, {'id': 716, 'image_count': 1940}, {'id': 717, 'image_count': 13}, {'id': 718, 'image_count': 16}, {'id': 719, 'image_count': 448}, {'id': 720, 'image_count': 12}, {'id': 721, 'image_count': 18}, {'id': 722, 'image_count': 4}, {'id': 723, 'image_count': 71}, {'id': 724, 'image_count': 189}, {'id': 725, 'image_count': 74}, {'id': 726, 'image_count': 103}, {'id': 727, 'image_count': 3}, {'id': 728, 'image_count': 110}, {'id': 729, 'image_count': 5}, {'id': 730, 'image_count': 9}, {'id': 731, 'image_count': 15}, {'id': 732, 'image_count': 25}, {'id': 733, 'image_count': 7}, {'id': 734, 'image_count': 647}, {'id': 735, 'image_count': 824}, {'id': 736, 'image_count': 100}, {'id': 737, 'image_count': 47}, {'id': 738, 'image_count': 121}, {'id': 739, 'image_count': 731}, {'id': 740, 'image_count': 73}, {'id': 741, 'image_count': 49}, {'id': 742, 'image_count': 23}, {'id': 743, 'image_count': 4}, {'id': 744, 'image_count': 62}, {'id': 745, 'image_count': 118}, {'id': 746, 'image_count': 99}, {'id': 747, 'image_count': 40}, {'id': 748, 'image_count': 1036}, {'id': 749, 'image_count': 105}, {'id': 750, 'image_count': 21}, {'id': 751, 'image_count': 229}, {'id': 752, 'image_count': 7}, {'id': 753, 'image_count': 72}, {'id': 754, 'image_count': 9}, {'id': 755, 'image_count': 10}, {'id': 756, 'image_count': 328}, {'id': 757, 'image_count': 468}, {'id': 758, 'image_count': 1}, {'id': 759, 'image_count': 2}, {'id': 760, 'image_count': 24}, {'id': 761, 'image_count': 11}, {'id': 762, 'image_count': 72}, {'id': 763, 'image_count': 17}, {'id': 764, 'image_count': 10}, {'id': 765, 'image_count': 17}, {'id': 766, 'image_count': 489}, {'id': 767, 'image_count': 47}, {'id': 768, 'image_count': 93}, {'id': 769, 'image_count': 1}, {'id': 770, 'image_count': 12}, {'id': 771, 'image_count': 228}, {'id': 772, 'image_count': 5}, {'id': 773, 'image_count': 76}, {'id': 774, 'image_count': 71}, {'id': 775, 'image_count': 30}, {'id': 776, 'image_count': 109}, {'id': 777, 'image_count': 14}, {'id': 778, 'image_count': 1}, {'id': 779, 'image_count': 8}, {'id': 780, 'image_count': 26}, {'id': 781, 'image_count': 339}, {'id': 782, 'image_count': 153}, {'id': 783, 'image_count': 2}, {'id': 784, 'image_count': 3}, {'id': 785, 'image_count': 8}, {'id': 786, 'image_count': 47}, {'id': 787, 'image_count': 8}, {'id': 788, 'image_count': 6}, {'id': 789, 'image_count': 116}, {'id': 790, 'image_count': 69}, {'id': 791, 'image_count': 13}, {'id': 792, 'image_count': 6}, {'id': 793, 'image_count': 1928}, {'id': 794, 'image_count': 79}, {'id': 795, 'image_count': 14}, {'id': 796, 'image_count': 7}, {'id': 797, 'image_count': 20}, {'id': 798, 'image_count': 114}, {'id': 799, 'image_count': 221}, {'id': 800, 'image_count': 502}, {'id': 801, 'image_count': 62}, {'id': 802, 'image_count': 87}, {'id': 803, 'image_count': 4}, {'id': 804, 'image_count': 1912}, {'id': 805, 'image_count': 7}, {'id': 806, 'image_count': 186}, {'id': 807, 'image_count': 18}, {'id': 808, 'image_count': 4}, {'id': 809, 'image_count': 3}, {'id': 810, 'image_count': 7}, {'id': 811, 'image_count': 1413}, {'id': 812, 'image_count': 7}, {'id': 813, 'image_count': 12}, {'id': 814, 'image_count': 248}, {'id': 815, 'image_count': 4}, {'id': 816, 'image_count': 1881}, {'id': 817, 'image_count': 529}, {'id': 818, 'image_count': 1932}, {'id': 819, 'image_count': 50}, {'id': 820, 'image_count': 3}, {'id': 821, 'image_count': 28}, {'id': 822, 'image_count': 10}, {'id': 823, 'image_count': 5}, {'id': 824, 'image_count': 5}, {'id': 825, 'image_count': 18}, {'id': 826, 'image_count': 14}, {'id': 827, 'image_count': 1890}, {'id': 828, 'image_count': 660}, {'id': 829, 'image_count': 8}, {'id': 830, 'image_count': 25}, {'id': 831, 'image_count': 10}, {'id': 832, 'image_count': 218}, {'id': 833, 'image_count': 36}, {'id': 834, 'image_count': 16}, {'id': 835, 'image_count': 808}, {'id': 836, 'image_count': 479}, {'id': 837, 'image_count': 1404}, {'id': 838, 'image_count': 307}, {'id': 839, 'image_count': 57}, {'id': 840, 'image_count': 28}, {'id': 841, 'image_count': 80}, {'id': 842, 'image_count': 11}, {'id': 843, 'image_count': 92}, {'id': 844, 'image_count': 20}, {'id': 845, 'image_count': 194}, {'id': 846, 'image_count': 23}, {'id': 847, 'image_count': 52}, {'id': 848, 'image_count': 673}, {'id': 849, 'image_count': 2}, {'id': 850, 'image_count': 2}, {'id': 851, 'image_count': 1}, {'id': 852, 'image_count': 2}, {'id': 853, 'image_count': 8}, {'id': 854, 'image_count': 80}, {'id': 855, 'image_count': 3}, {'id': 856, 'image_count': 3}, {'id': 857, 'image_count': 15}, {'id': 858, 'image_count': 2}, {'id': 859, 'image_count': 10}, {'id': 860, 'image_count': 386}, {'id': 861, 'image_count': 65}, {'id': 862, 'image_count': 3}, {'id': 863, 'image_count': 35}, {'id': 864, 'image_count': 5}, {'id': 865, 'image_count': 180}, {'id': 866, 'image_count': 99}, {'id': 867, 'image_count': 49}, {'id': 868, 'image_count': 28}, {'id': 869, 'image_count': 1}, {'id': 870, 'image_count': 52}, {'id': 871, 'image_count': 36}, {'id': 872, 'image_count': 70}, {'id': 873, 'image_count': 6}, {'id': 874, 'image_count': 29}, {'id': 875, 'image_count': 24}, {'id': 876, 'image_count': 1115}, {'id': 877, 'image_count': 61}, {'id': 878, 'image_count': 18}, {'id': 879, 'image_count': 18}, {'id': 880, 'image_count': 665}, {'id': 881, 'image_count': 1096}, {'id': 882, 'image_count': 29}, {'id': 883, 'image_count': 8}, {'id': 884, 'image_count': 14}, {'id': 885, 'image_count': 1622}, {'id': 886, 'image_count': 2}, {'id': 887, 'image_count': 3}, {'id': 888, 'image_count': 32}, {'id': 889, 'image_count': 55}, {'id': 890, 'image_count': 1}, {'id': 891, 'image_count': 10}, {'id': 892, 'image_count': 10}, {'id': 893, 'image_count': 47}, {'id': 894, 'image_count': 3}, {'id': 895, 'image_count': 29}, {'id': 896, 'image_count': 342}, {'id': 897, 'image_count': 25}, {'id': 898, 'image_count': 1469}, {'id': 899, 'image_count': 521}, {'id': 900, 'image_count': 347}, {'id': 901, 'image_count': 35}, {'id': 902, 'image_count': 7}, {'id': 903, 'image_count': 207}, {'id': 904, 'image_count': 108}, {'id': 905, 'image_count': 2}, {'id': 906, 'image_count': 34}, {'id': 907, 'image_count': 12}, {'id': 908, 'image_count': 10}, {'id': 909, 'image_count': 13}, {'id': 910, 'image_count': 361}, {'id': 911, 'image_count': 1023}, {'id': 912, 'image_count': 782}, {'id': 913, 'image_count': 2}, {'id': 914, 'image_count': 5}, {'id': 915, 'image_count': 247}, {'id': 916, 'image_count': 221}, {'id': 917, 'image_count': 4}, {'id': 918, 'image_count': 8}, {'id': 919, 'image_count': 158}, {'id': 920, 'image_count': 3}, {'id': 921, 'image_count': 752}, {'id': 922, 'image_count': 64}, {'id': 923, 'image_count': 707}, {'id': 924, 'image_count': 143}, {'id': 925, 'image_count': 1}, {'id': 926, 'image_count': 49}, {'id': 927, 'image_count': 126}, {'id': 928, 'image_count': 76}, {'id': 929, 'image_count': 11}, {'id': 930, 'image_count': 11}, {'id': 931, 'image_count': 4}, {'id': 932, 'image_count': 39}, {'id': 933, 'image_count': 11}, {'id': 934, 'image_count': 13}, {'id': 935, 'image_count': 91}, {'id': 936, 'image_count': 14}, {'id': 937, 'image_count': 5}, {'id': 938, 'image_count': 3}, {'id': 939, 'image_count': 10}, {'id': 940, 'image_count': 18}, {'id': 941, 'image_count': 9}, {'id': 942, 'image_count': 6}, {'id': 943, 'image_count': 951}, {'id': 944, 'image_count': 2}, {'id': 945, 'image_count': 1}, {'id': 946, 'image_count': 19}, {'id': 947, 'image_count': 1942}, {'id': 948, 'image_count': 1916}, {'id': 949, 'image_count': 139}, {'id': 950, 'image_count': 43}, {'id': 951, 'image_count': 1969}, {'id': 952, 'image_count': 5}, {'id': 953, 'image_count': 134}, {'id': 954, 'image_count': 74}, {'id': 955, 'image_count': 381}, {'id': 956, 'image_count': 1}, {'id': 957, 'image_count': 381}, {'id': 958, 'image_count': 6}, {'id': 959, 'image_count': 1826}, {'id': 960, 'image_count': 28}, {'id': 961, 'image_count': 1635}, {'id': 962, 'image_count': 1967}, {'id': 963, 'image_count': 16}, {'id': 964, 'image_count': 1926}, {'id': 965, 'image_count': 1789}, {'id': 966, 'image_count': 401}, {'id': 967, 'image_count': 1968}, {'id': 968, 'image_count': 1167}, {'id': 969, 'image_count': 1}, {'id': 970, 'image_count': 56}, {'id': 971, 'image_count': 17}, {'id': 972, 'image_count': 1}, {'id': 973, 'image_count': 58}, {'id': 974, 'image_count': 9}, {'id': 975, 'image_count': 8}, {'id': 976, 'image_count': 1124}, {'id': 977, 'image_count': 31}, {'id': 978, 'image_count': 16}, {'id': 979, 'image_count': 491}, {'id': 980, 'image_count': 432}, {'id': 981, 'image_count': 1945}, {'id': 982, 'image_count': 1899}, {'id': 983, 'image_count': 5}, {'id': 984, 'image_count': 28}, {'id': 985, 'image_count': 7}, {'id': 986, 'image_count': 146}, {'id': 987, 'image_count': 1}, {'id': 988, 'image_count': 25}, {'id': 989, 'image_count': 22}, {'id': 990, 'image_count': 1}, {'id': 991, 'image_count': 10}, {'id': 992, 'image_count': 9}, {'id': 993, 'image_count': 308}, {'id': 994, 'image_count': 4}, {'id': 995, 'image_count': 1969}, {'id': 996, 'image_count': 45}, {'id': 997, 'image_count': 12}, {'id': 998, 'image_count': 1}, {'id': 999, 'image_count': 85}, {'id': 1000, 'image_count': 1127}, {'id': 1001, 'image_count': 11}, {'id': 1002, 'image_count': 60}, {'id': 1003, 'image_count': 1}, {'id': 1004, 'image_count': 16}, {'id': 1005, 'image_count': 1}, {'id': 1006, 'image_count': 65}, {'id': 1007, 'image_count': 13}, {'id': 1008, 'image_count': 655}, {'id': 1009, 'image_count': 51}, {'id': 1010, 'image_count': 1}, {'id': 1011, 'image_count': 673}, {'id': 1012, 'image_count': 5}, {'id': 1013, 'image_count': 36}, {'id': 1014, 'image_count': 54}, {'id': 1015, 'image_count': 5}, {'id': 1016, 'image_count': 8}, {'id': 1017, 'image_count': 305}, {'id': 1018, 'image_count': 297}, {'id': 1019, 'image_count': 1053}, {'id': 1020, 'image_count': 223}, {'id': 1021, 'image_count': 1037}, {'id': 1022, 'image_count': 63}, {'id': 1023, 'image_count': 1881}, {'id': 1024, 'image_count': 507}, {'id': 1025, 'image_count': 333}, {'id': 1026, 'image_count': 1911}, {'id': 1027, 'image_count': 1765}, {'id': 1028, 'image_count': 1}, {'id': 1029, 'image_count': 5}, {'id': 1030, 'image_count': 1}, {'id': 1031, 'image_count': 9}, {'id': 1032, 'image_count': 2}, {'id': 1033, 'image_count': 151}, {'id': 1034, 'image_count': 82}, {'id': 1035, 'image_count': 1931}, {'id': 1036, 'image_count': 41}, {'id': 1037, 'image_count': 1895}, {'id': 1038, 'image_count': 24}, {'id': 1039, 'image_count': 22}, {'id': 1040, 'image_count': 35}, {'id': 1041, 'image_count': 69}, {'id': 1042, 'image_count': 962}, {'id': 1043, 'image_count': 588}, {'id': 1044, 'image_count': 21}, {'id': 1045, 'image_count': 825}, {'id': 1046, 'image_count': 52}, {'id': 1047, 'image_count': 5}, {'id': 1048, 'image_count': 5}, {'id': 1049, 'image_count': 5}, {'id': 1050, 'image_count': 1860}, {'id': 1051, 'image_count': 56}, {'id': 1052, 'image_count': 1582}, {'id': 1053, 'image_count': 7}, {'id': 1054, 'image_count': 2}, {'id': 1055, 'image_count': 1562}, {'id': 1056, 'image_count': 1885}, {'id': 1057, 'image_count': 1}, {'id': 1058, 'image_count': 5}, {'id': 1059, 'image_count': 137}, {'id': 1060, 'image_count': 1094}, {'id': 1061, 'image_count': 134}, {'id': 1062, 'image_count': 29}, {'id': 1063, 'image_count': 22}, {'id': 1064, 'image_count': 522}, {'id': 1065, 'image_count': 50}, {'id': 1066, 'image_count': 68}, {'id': 1067, 'image_count': 16}, {'id': 1068, 'image_count': 40}, {'id': 1069, 'image_count': 35}, {'id': 1070, 'image_count': 135}, {'id': 1071, 'image_count': 1413}, {'id': 1072, 'image_count': 772}, {'id': 1073, 'image_count': 50}, {'id': 1074, 'image_count': 1015}, {'id': 1075, 'image_count': 1}, {'id': 1076, 'image_count': 65}, {'id': 1077, 'image_count': 1900}, {'id': 1078, 'image_count': 1302}, {'id': 1079, 'image_count': 1977}, {'id': 1080, 'image_count': 2}, {'id': 1081, 'image_count': 29}, {'id': 1082, 'image_count': 36}, {'id': 1083, 'image_count': 138}, {'id': 1084, 'image_count': 4}, {'id': 1085, 'image_count': 67}, {'id': 1086, 'image_count': 26}, {'id': 1087, 'image_count': 25}, {'id': 1088, 'image_count': 33}, {'id': 1089, 'image_count': 37}, {'id': 1090, 'image_count': 50}, {'id': 1091, 'image_count': 270}, {'id': 1092, 'image_count': 12}, {'id': 1093, 'image_count': 316}, {'id': 1094, 'image_count': 41}, {'id': 1095, 'image_count': 224}, {'id': 1096, 'image_count': 105}, {'id': 1097, 'image_count': 1925}, {'id': 1098, 'image_count': 1021}, {'id': 1099, 'image_count': 1213}, {'id': 1100, 'image_count': 172}, {'id': 1101, 'image_count': 28}, {'id': 1102, 'image_count': 745}, {'id': 1103, 'image_count': 187}, {'id': 1104, 'image_count': 147}, {'id': 1105, 'image_count': 136}, {'id': 1106, 'image_count': 34}, {'id': 1107, 'image_count': 41}, {'id': 1108, 'image_count': 636}, {'id': 1109, 'image_count': 570}, {'id': 1110, 'image_count': 1149}, {'id': 1111, 'image_count': 61}, {'id': 1112, 'image_count': 1890}, {'id': 1113, 'image_count': 18}, {'id': 1114, 'image_count': 143}, {'id': 1115, 'image_count': 1517}, {'id': 1116, 'image_count': 7}, {'id': 1117, 'image_count': 943}, {'id': 1118, 'image_count': 6}, {'id': 1119, 'image_count': 1}, {'id': 1120, 'image_count': 11}, {'id': 1121, 'image_count': 101}, {'id': 1122, 'image_count': 1909}, {'id': 1123, 'image_count': 800}, {'id': 1124, 'image_count': 1}, {'id': 1125, 'image_count': 44}, {'id': 1126, 'image_count': 3}, {'id': 1127, 'image_count': 44}, {'id': 1128, 'image_count': 31}, {'id': 1129, 'image_count': 7}, {'id': 1130, 'image_count': 20}, {'id': 1131, 'image_count': 11}, {'id': 1132, 'image_count': 13}, {'id': 1133, 'image_count': 1924}, {'id': 1134, 'image_count': 113}, {'id': 1135, 'image_count': 2}, {'id': 1136, 'image_count': 139}, {'id': 1137, 'image_count': 12}, {'id': 1138, 'image_count': 37}, {'id': 1139, 'image_count': 1866}, {'id': 1140, 'image_count': 47}, {'id': 1141, 'image_count': 1468}, {'id': 1142, 'image_count': 729}, {'id': 1143, 'image_count': 24}, {'id': 1144, 'image_count': 1}, {'id': 1145, 'image_count': 10}, {'id': 1146, 'image_count': 3}, {'id': 1147, 'image_count': 14}, {'id': 1148, 'image_count': 4}, {'id': 1149, 'image_count': 29}, {'id': 1150, 'image_count': 4}, {'id': 1151, 'image_count': 70}, {'id': 1152, 'image_count': 46}, {'id': 1153, 'image_count': 14}, {'id': 1154, 'image_count': 48}, {'id': 1155, 'image_count': 1855}, {'id': 1156, 'image_count': 113}, {'id': 1157, 'image_count': 1}, {'id': 1158, 'image_count': 1}, {'id': 1159, 'image_count': 10}, {'id': 1160, 'image_count': 54}, {'id': 1161, 'image_count': 1923}, {'id': 1162, 'image_count': 630}, {'id': 1163, 'image_count': 31}, {'id': 1164, 'image_count': 69}, {'id': 1165, 'image_count': 7}, {'id': 1166, 'image_count': 11}, {'id': 1167, 'image_count': 1}, {'id': 1168, 'image_count': 30}, {'id': 1169, 'image_count': 50}, {'id': 1170, 'image_count': 45}, {'id': 1171, 'image_count': 28}, {'id': 1172, 'image_count': 114}, {'id': 1173, 'image_count': 193}, {'id': 1174, 'image_count': 21}, {'id': 1175, 'image_count': 91}, {'id': 1176, 'image_count': 31}, {'id': 1177, 'image_count': 1469}, {'id': 1178, 'image_count': 1924}, {'id': 1179, 'image_count': 87}, {'id': 1180, 'image_count': 77}, {'id': 1181, 'image_count': 11}, {'id': 1182, 'image_count': 47}, {'id': 1183, 'image_count': 21}, {'id': 1184, 'image_count': 47}, {'id': 1185, 'image_count': 70}, {'id': 1186, 'image_count': 1838}, {'id': 1187, 'image_count': 19}, {'id': 1188, 'image_count': 531}, {'id': 1189, 'image_count': 11}, {'id': 1190, 'image_count': 941}, {'id': 1191, 'image_count': 113}, {'id': 1192, 'image_count': 26}, {'id': 1193, 'image_count': 5}, {'id': 1194, 'image_count': 56}, {'id': 1195, 'image_count': 73}, {'id': 1196, 'image_count': 32}, {'id': 1197, 'image_count': 128}, {'id': 1198, 'image_count': 623}, {'id': 1199, 'image_count': 12}, {'id': 1200, 'image_count': 52}, {'id': 1201, 'image_count': 11}, {'id': 1202, 'image_count': 1674}, {'id': 1203, 'image_count': 81}] # noqa
+# fmt: on
diff --git a/detectron2/detectron2/data/datasets/pascal_voc.py b/detectron2/detectron2/data/datasets/pascal_voc.py
new file mode 100755
index 0000000..dbbf82c
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/pascal_voc.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import numpy as np
+import os
+import xml.etree.ElementTree as ET
+from typing import List, Tuple, Union
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+from detectron2.structures import BoxMode
+from detectron2.utils.file_io import PathManager
+
+__all__ = ["load_voc_instances", "register_pascal_voc"]
+
+
+# fmt: off
+CLASS_NAMES = (
+ "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat",
+ "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person",
+ "pottedplant", "sheep", "sofa", "train", "tvmonitor"
+)
+# fmt: on
+
+
+def load_voc_instances(dirname: str, split: str, class_names: Union[List[str], Tuple[str, ...]]):
+ """
+ Load Pascal VOC detection annotations to Detectron2 format.
+
+ Args:
+ dirname: Contain "Annotations", "ImageSets", "JPEGImages"
+ split (str): one of "train", "test", "val", "trainval"
+ class_names: list or tuple of class names
+ """
+ with PathManager.open(os.path.join(dirname, "ImageSets", "Main", split + ".txt")) as f:
+ fileids = np.loadtxt(f, dtype=np.str)
+
+ # Needs to read many small annotation files. Makes sense at local
+ annotation_dirname = PathManager.get_local_path(os.path.join(dirname, "Annotations/"))
+ dicts = []
+ for fileid in fileids:
+ anno_file = os.path.join(annotation_dirname, fileid + ".xml")
+ jpeg_file = os.path.join(dirname, "JPEGImages", fileid + ".jpg")
+
+ with PathManager.open(anno_file) as f:
+ tree = ET.parse(f)
+
+ r = {
+ "file_name": jpeg_file,
+ "image_id": fileid,
+ "height": int(tree.findall("./size/height")[0].text),
+ "width": int(tree.findall("./size/width")[0].text),
+ }
+ instances = []
+
+ for obj in tree.findall("object"):
+ cls = obj.find("name").text
+ # We include "difficult" samples in training.
+ # Based on limited experiments, they don't hurt accuracy.
+ # difficult = int(obj.find("difficult").text)
+ # if difficult == 1:
+ # continue
+ bbox = obj.find("bndbox")
+ bbox = [float(bbox.find(x).text) for x in ["xmin", "ymin", "xmax", "ymax"]]
+ # Original annotations are integers in the range [1, W or H]
+ # Assuming they mean 1-based pixel indices (inclusive),
+ # a box with annotation (xmin=1, xmax=W) covers the whole image.
+ # In coordinate space this is represented by (xmin=0, xmax=W)
+ bbox[0] -= 1.0
+ bbox[1] -= 1.0
+ instances.append(
+ {"category_id": class_names.index(cls), "bbox": bbox, "bbox_mode": BoxMode.XYXY_ABS}
+ )
+ r["annotations"] = instances
+ dicts.append(r)
+ return dicts
+
+
+def register_pascal_voc(name, dirname, split, year, class_names=CLASS_NAMES):
+ DatasetCatalog.register(name, lambda: load_voc_instances(dirname, split, class_names))
+ MetadataCatalog.get(name).set(
+ thing_classes=list(class_names), dirname=dirname, year=year, split=split
+ )
diff --git a/detectron2/detectron2/data/datasets/register_coco.py b/detectron2/detectron2/data/datasets/register_coco.py
new file mode 100755
index 0000000..e564438
--- /dev/null
+++ b/detectron2/detectron2/data/datasets/register_coco.py
@@ -0,0 +1,3 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .coco import register_coco_instances # noqa
+from .coco_panoptic import register_coco_panoptic_separated # noqa
diff --git a/detectron2/detectron2/data/detection_utils.py b/detectron2/detectron2/data/detection_utils.py
new file mode 100755
index 0000000..ada19bd
--- /dev/null
+++ b/detectron2/detectron2/data/detection_utils.py
@@ -0,0 +1,659 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+Common data processing utilities that are used in a
+typical object detection data pipeline.
+"""
+import logging
+import numpy as np
+from typing import List, Union
+import pycocotools.mask as mask_util
+import torch
+from PIL import Image
+
+from detectron2.structures import (
+ BitMasks,
+ Boxes,
+ BoxMode,
+ Instances,
+ Keypoints,
+ PolygonMasks,
+ RotatedBoxes,
+ polygons_to_bitmask,
+)
+from detectron2.utils.file_io import PathManager
+
+from . import transforms as T
+from .catalog import MetadataCatalog
+
+__all__ = [
+ "SizeMismatchError",
+ "convert_image_to_rgb",
+ "check_image_size",
+ "transform_proposals",
+ "transform_instance_annotations",
+ "annotations_to_instances",
+ "annotations_to_instances_rotated",
+ "build_augmentation",
+ "build_transform_gen",
+ "create_keypoint_hflip_indices",
+ "filter_empty_instances",
+ "read_image",
+]
+
+
+class SizeMismatchError(ValueError):
+ """
+ When loaded image has difference width/height compared with annotation.
+ """
+
+
+# https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
+_M_RGB2YUV = [[0.299, 0.587, 0.114], [-0.14713, -0.28886, 0.436], [0.615, -0.51499, -0.10001]]
+_M_YUV2RGB = [[1.0, 0.0, 1.13983], [1.0, -0.39465, -0.58060], [1.0, 2.03211, 0.0]]
+
+# https://www.exiv2.org/tags.html
+_EXIF_ORIENT = 274 # exif 'Orientation' tag
+
+
+def convert_PIL_to_numpy(image, format):
+ """
+ Convert PIL image to numpy array of target format.
+
+ Args:
+ image (PIL.Image): a PIL image
+ format (str): the format of output image
+
+ Returns:
+ (np.ndarray): also see `read_image`
+ """
+ if format is not None:
+ # PIL only supports RGB, so convert to RGB and flip channels over below
+ conversion_format = format
+ if format in ["BGR", "YUV-BT.601"]:
+ conversion_format = "RGB"
+ image = image.convert(conversion_format)
+ image = np.asarray(image)
+ # PIL squeezes out the channel dimension for "L", so make it HWC
+ if format == "L":
+ image = np.expand_dims(image, -1)
+
+ # handle formats not supported by PIL
+ elif format == "BGR":
+ # flip channels if needed
+ image = image[:, :, ::-1]
+ elif format == "YUV-BT.601":
+ image = image / 255.0
+ image = np.dot(image, np.array(_M_RGB2YUV).T)
+
+ return image
+
+
+def convert_image_to_rgb(image, format):
+ """
+ Convert an image from given format to RGB.
+
+ Args:
+ image (np.ndarray or Tensor): an HWC image
+ format (str): the format of input image, also see `read_image`
+
+ Returns:
+ (np.ndarray): (H,W,3) RGB image in 0-255 range, can be either float or uint8
+ """
+ if isinstance(image, torch.Tensor):
+ image = image.cpu().numpy()
+ if format == "BGR":
+ image = image[:, :, [2, 1, 0]]
+ elif format == "YUV-BT.601":
+ image = np.dot(image, np.array(_M_YUV2RGB).T)
+ image = image * 255.0
+ else:
+ if format == "L":
+ image = image[:, :, 0]
+ image = image.astype(np.uint8)
+ image = np.asarray(Image.fromarray(image, mode=format).convert("RGB"))
+ return image
+
+
+def _apply_exif_orientation(image):
+ """
+ Applies the exif orientation correctly.
+
+ This code exists per the bug:
+ https://github.com/python-pillow/Pillow/issues/3973
+ with the function `ImageOps.exif_transpose`. The Pillow source raises errors with
+ various methods, especially `tobytes`
+
+ Function based on:
+ https://github.com/wkentaro/labelme/blob/v4.5.4/labelme/utils/image.py#L59
+ https://github.com/python-pillow/Pillow/blob/7.1.2/src/PIL/ImageOps.py#L527
+
+ Args:
+ image (PIL.Image): a PIL image
+
+ Returns:
+ (PIL.Image): the PIL image with exif orientation applied, if applicable
+ """
+ if not hasattr(image, "getexif"):
+ return image
+
+ try:
+ exif = image.getexif()
+ except Exception: # https://github.com/facebookresearch/detectron2/issues/1885
+ exif = None
+
+ if exif is None:
+ return image
+
+ orientation = exif.get(_EXIF_ORIENT)
+
+ method = {
+ 2: Image.FLIP_LEFT_RIGHT,
+ 3: Image.ROTATE_180,
+ 4: Image.FLIP_TOP_BOTTOM,
+ 5: Image.TRANSPOSE,
+ 6: Image.ROTATE_270,
+ 7: Image.TRANSVERSE,
+ 8: Image.ROTATE_90,
+ }.get(orientation)
+
+ if method is not None:
+ return image.transpose(method)
+ return image
+
+
+def read_image(file_name, format=None):
+ """
+ Read an image into the given format.
+ Will apply rotation and flipping if the image has such exif information.
+
+ Args:
+ file_name (str): image file path
+ format (str): one of the supported image modes in PIL, or "BGR" or "YUV-BT.601".
+
+ Returns:
+ image (np.ndarray):
+ an HWC image in the given format, which is 0-255, uint8 for
+ supported image modes in PIL or "BGR"; float (0-1 for Y) for YUV-BT.601.
+ """
+ with PathManager.open(file_name, "rb") as f:
+ image = Image.open(f)
+
+ # work around this bug: https://github.com/python-pillow/Pillow/issues/3973
+ image = _apply_exif_orientation(image)
+ return convert_PIL_to_numpy(image, format)
+
+
+def check_image_size(dataset_dict, image):
+ """
+ Raise an error if the image does not match the size specified in the dict.
+ """
+ if "width" in dataset_dict or "height" in dataset_dict:
+ image_wh = (image.shape[1], image.shape[0])
+ expected_wh = (dataset_dict["width"], dataset_dict["height"])
+ if not image_wh == expected_wh:
+ raise SizeMismatchError(
+ "Mismatched image shape{}, got {}, expect {}.".format(
+ " for image " + dataset_dict["file_name"]
+ if "file_name" in dataset_dict
+ else "",
+ image_wh,
+ expected_wh,
+ )
+ + " Please check the width/height in your annotation."
+ )
+
+ # To ensure bbox always remap to original image size
+ if "width" not in dataset_dict:
+ dataset_dict["width"] = image.shape[1]
+ if "height" not in dataset_dict:
+ dataset_dict["height"] = image.shape[0]
+
+
+def transform_proposals(dataset_dict, image_shape, transforms, *, proposal_topk, min_box_size=0):
+ """
+ Apply transformations to the proposals in dataset_dict, if any.
+
+ Args:
+ dataset_dict (dict): a dict read from the dataset, possibly
+ contains fields "proposal_boxes", "proposal_objectness_logits", "proposal_bbox_mode"
+ image_shape (tuple): height, width
+ transforms (TransformList):
+ proposal_topk (int): only keep top-K scoring proposals
+ min_box_size (int): proposals with either side smaller than this
+ threshold are removed
+
+ The input dict is modified in-place, with abovementioned keys removed. A new
+ key "proposals" will be added. Its value is an `Instances`
+ object which contains the transformed proposals in its field
+ "proposal_boxes" and "objectness_logits".
+ """
+ if "proposal_boxes" in dataset_dict:
+ # Transform proposal boxes
+ boxes = transforms.apply_box(
+ BoxMode.convert(
+ dataset_dict.pop("proposal_boxes"),
+ dataset_dict.pop("proposal_bbox_mode"),
+ BoxMode.XYXY_ABS,
+ )
+ )
+ boxes = Boxes(boxes)
+ objectness_logits = torch.as_tensor(
+ dataset_dict.pop("proposal_objectness_logits").astype("float32")
+ )
+
+ boxes.clip(image_shape)
+ keep = boxes.nonempty(threshold=min_box_size)
+ boxes = boxes[keep]
+ objectness_logits = objectness_logits[keep]
+
+ proposals = Instances(image_shape)
+ proposals.proposal_boxes = boxes[:proposal_topk]
+ proposals.objectness_logits = objectness_logits[:proposal_topk]
+ dataset_dict["proposals"] = proposals
+
+
+def get_bbox(annotation):
+ """
+ Get bbox from data
+ Args:
+ annotation (dict): dict of instance annotations for a single instance.
+ Returns:
+ bbox (ndarray): x1, y1, x2, y2 coordinates
+ """
+ # bbox is 1d (per-instance bounding box)
+ bbox = BoxMode.convert(annotation["bbox"], annotation["bbox_mode"], BoxMode.XYXY_ABS)
+ return bbox
+
+
+def transform_instance_annotations(
+ annotation, transforms, image_size, *, keypoint_hflip_indices=None
+):
+ """
+ Apply transforms to box, segmentation and keypoints annotations of a single instance.
+
+ It will use `transforms.apply_box` for the box, and
+ `transforms.apply_coords` for segmentation polygons & keypoints.
+ If you need anything more specially designed for each data structure,
+ you'll need to implement your own version of this function or the transforms.
+
+ Args:
+ annotation (dict): dict of instance annotations for a single instance.
+ It will be modified in-place.
+ transforms (TransformList or list[Transform]):
+ image_size (tuple): the height, width of the transformed image
+ keypoint_hflip_indices (ndarray[int]): see `create_keypoint_hflip_indices`.
+
+ Returns:
+ dict:
+ the same input dict with fields "bbox", "segmentation", "keypoints"
+ transformed according to `transforms`.
+ The "bbox_mode" field will be set to XYXY_ABS.
+ """
+ if isinstance(transforms, (tuple, list)):
+ transforms = T.TransformList(transforms)
+ # bbox is 1d (per-instance bounding box)
+ bbox = BoxMode.convert(annotation["bbox"], annotation["bbox_mode"], BoxMode.XYXY_ABS)
+ # clip transformed bbox to image size
+ bbox = transforms.apply_box(np.array([bbox]))[0].clip(min=0)
+ annotation["bbox"] = np.minimum(bbox, list(image_size + image_size)[::-1])
+ annotation["bbox_mode"] = BoxMode.XYXY_ABS
+
+ if "segmentation" in annotation:
+ # each instance contains 1 or more polygons
+ segm = annotation["segmentation"]
+ if isinstance(segm, list):
+ # polygons
+ polygons = [np.asarray(p).reshape(-1, 2) for p in segm]
+ annotation["segmentation"] = [
+ p.reshape(-1) for p in transforms.apply_polygons(polygons)
+ ]
+ elif isinstance(segm, dict):
+ # RLE
+ mask = mask_util.decode(segm)
+ mask = transforms.apply_segmentation(mask)
+ assert tuple(mask.shape[:2]) == image_size
+ annotation["segmentation"] = mask
+ else:
+ raise ValueError(
+ "Cannot transform segmentation of type '{}'!"
+ "Supported types are: polygons as list[list[float] or ndarray],"
+ " COCO-style RLE as a dict.".format(type(segm))
+ )
+
+ if "keypoints" in annotation:
+ keypoints = transform_keypoint_annotations(
+ annotation["keypoints"], transforms, image_size, keypoint_hflip_indices
+ )
+ annotation["keypoints"] = keypoints
+
+ return annotation
+
+
+def transform_keypoint_annotations(keypoints, transforms, image_size, keypoint_hflip_indices=None):
+ """
+ Transform keypoint annotations of an image.
+ If a keypoint is transformed out of image boundary, it will be marked "unlabeled" (visibility=0)
+
+ Args:
+ keypoints (list[float]): Nx3 float in Detectron2's Dataset format.
+ Each point is represented by (x, y, visibility).
+ transforms (TransformList):
+ image_size (tuple): the height, width of the transformed image
+ keypoint_hflip_indices (ndarray[int]): see `create_keypoint_hflip_indices`.
+ When `transforms` includes horizontal flip, will use the index
+ mapping to flip keypoints.
+ """
+ # (N*3,) -> (N, 3)
+ keypoints = np.asarray(keypoints, dtype="float64").reshape(-1, 3)
+ keypoints_xy = transforms.apply_coords(keypoints[:, :2])
+
+ # Set all out-of-boundary points to "unlabeled"
+ inside = (keypoints_xy >= np.array([0, 0])) & (keypoints_xy <= np.array(image_size[::-1]))
+ inside = inside.all(axis=1)
+ keypoints[:, :2] = keypoints_xy
+ keypoints[:, 2][~inside] = 0
+
+ # This assumes that HorizFlipTransform is the only one that does flip
+ do_hflip = sum(isinstance(t, T.HFlipTransform) for t in transforms.transforms) % 2 == 1
+
+ # Alternative way: check if probe points was horizontally flipped.
+ # probe = np.asarray([[0.0, 0.0], [image_width, 0.0]])
+ # probe_aug = transforms.apply_coords(probe.copy())
+ # do_hflip = np.sign(probe[1][0] - probe[0][0]) != np.sign(probe_aug[1][0] - probe_aug[0][0]) # noqa
+
+ # If flipped, swap each keypoint with its opposite-handed equivalent
+ if do_hflip:
+ if keypoint_hflip_indices is None:
+ raise ValueError("Cannot flip keypoints without providing flip indices!")
+ if len(keypoints) != len(keypoint_hflip_indices):
+ raise ValueError(
+ "Keypoint data has {} points, but metadata "
+ "contains {} points!".format(len(keypoints), len(keypoint_hflip_indices))
+ )
+ keypoints = keypoints[np.asarray(keypoint_hflip_indices, dtype=np.int32), :]
+
+ # Maintain COCO convention that if visibility == 0 (unlabeled), then x, y = 0
+ keypoints[keypoints[:, 2] == 0] = 0
+ return keypoints
+
+
+def annotations_to_instances(annos, image_size, mask_format="polygon"):
+ """
+ Create an :class:`Instances` object used by the models,
+ from instance annotations in the dataset dict.
+
+ Args:
+ annos (list[dict]): a list of instance annotations in one image, each
+ element for one instance.
+ image_size (tuple): height, width
+
+ Returns:
+ Instances:
+ It will contain fields "gt_boxes", "gt_classes",
+ "gt_masks", "gt_keypoints", if they can be obtained from `annos`.
+ This is the format that builtin models expect.
+ """
+ boxes = (
+ np.stack(
+ [BoxMode.convert(obj["bbox"], obj["bbox_mode"], BoxMode.XYXY_ABS) for obj in annos]
+ )
+ if len(annos)
+ else np.zeros((0, 4))
+ )
+ target = Instances(image_size)
+ target.gt_boxes = Boxes(boxes)
+
+ classes = [int(obj["category_id"]) for obj in annos]
+ classes = torch.tensor(classes, dtype=torch.int64)
+ target.gt_classes = classes
+
+ if len(annos) and "segmentation" in annos[0]:
+ segms = [obj["segmentation"] for obj in annos]
+ if mask_format == "polygon":
+ try:
+ masks = PolygonMasks(segms)
+ except ValueError as e:
+ raise ValueError(
+ "Failed to use mask_format=='polygon' from the given annotations!"
+ ) from e
+ else:
+ assert mask_format == "bitmask", mask_format
+ masks = []
+ for segm in segms:
+ if isinstance(segm, list):
+ # polygon
+ masks.append(polygons_to_bitmask(segm, *image_size))
+ elif isinstance(segm, dict):
+ # COCO RLE
+ masks.append(mask_util.decode(segm))
+ elif isinstance(segm, np.ndarray):
+ assert segm.ndim == 2, "Expect segmentation of 2 dimensions, got {}.".format(
+ segm.ndim
+ )
+ # mask array
+ masks.append(segm)
+ else:
+ raise ValueError(
+ "Cannot convert segmentation of type '{}' to BitMasks!"
+ "Supported types are: polygons as list[list[float] or ndarray],"
+ " COCO-style RLE as a dict, or a binary segmentation mask "
+ " in a 2D numpy array of shape HxW.".format(type(segm))
+ )
+ # torch.from_numpy does not support array with negative stride.
+ masks = BitMasks(
+ torch.stack([torch.from_numpy(np.ascontiguousarray(x)) for x in masks])
+ )
+ target.gt_masks = masks
+
+ if len(annos) and "keypoints" in annos[0]:
+ kpts = [obj.get("keypoints", []) for obj in annos]
+ target.gt_keypoints = Keypoints(kpts)
+
+ return target
+
+
+def annotations_to_instances_rotated(annos, image_size):
+ """
+ Create an :class:`Instances` object used by the models,
+ from instance annotations in the dataset dict.
+ Compared to `annotations_to_instances`, this function is for rotated boxes only
+
+ Args:
+ annos (list[dict]): a list of instance annotations in one image, each
+ element for one instance.
+ image_size (tuple): height, width
+
+ Returns:
+ Instances:
+ Containing fields "gt_boxes", "gt_classes",
+ if they can be obtained from `annos`.
+ This is the format that builtin models expect.
+ """
+ boxes = [obj["bbox"] for obj in annos]
+ target = Instances(image_size)
+ boxes = target.gt_boxes = RotatedBoxes(boxes)
+ boxes.clip(image_size)
+
+ classes = [obj["category_id"] for obj in annos]
+ classes = torch.tensor(classes, dtype=torch.int64)
+ target.gt_classes = classes
+
+ return target
+
+
+def filter_empty_instances(
+ instances, by_box=True, by_mask=True, box_threshold=1e-5, return_mask=False
+):
+ """
+ Filter out empty instances in an `Instances` object.
+
+ Args:
+ instances (Instances):
+ by_box (bool): whether to filter out instances with empty boxes
+ by_mask (bool): whether to filter out instances with empty masks
+ box_threshold (float): minimum width and height to be considered non-empty
+ return_mask (bool): whether to return boolean mask of filtered instances
+
+ Returns:
+ Instances: the filtered instances.
+ tensor[bool], optional: boolean mask of filtered instances
+ """
+ assert by_box or by_mask
+ r = []
+ if by_box:
+ r.append(instances.gt_boxes.nonempty(threshold=box_threshold))
+ if instances.has("gt_masks") and by_mask:
+ r.append(instances.gt_masks.nonempty())
+
+ # TODO: can also filter visible keypoints
+
+ if not r:
+ return instances
+ m = r[0]
+ for x in r[1:]:
+ m = m & x
+ if return_mask:
+ return instances[m], m
+ return instances[m]
+
+
+def create_keypoint_hflip_indices(dataset_names: Union[str, List[str]]) -> List[int]:
+ """
+ Args:
+ dataset_names: list of dataset names
+
+ Returns:
+ list[int]: a list of size=#keypoints, storing the
+ horizontally-flipped keypoint indices.
+ """
+ if isinstance(dataset_names, str):
+ dataset_names = [dataset_names]
+
+ check_metadata_consistency("keypoint_names", dataset_names)
+ check_metadata_consistency("keypoint_flip_map", dataset_names)
+
+ meta = MetadataCatalog.get(dataset_names[0])
+ names = meta.keypoint_names
+ # TODO flip -> hflip
+ flip_map = dict(meta.keypoint_flip_map)
+ flip_map.update({v: k for k, v in flip_map.items()})
+ flipped_names = [i if i not in flip_map else flip_map[i] for i in names]
+ flip_indices = [names.index(i) for i in flipped_names]
+ return flip_indices
+
+
+def get_fed_loss_cls_weights(dataset_names: Union[str, List[str]], freq_weight_power=1.0):
+ """
+ Get frequency weight for each class sorted by class id.
+ We now calcualte freqency weight using image_count to the power freq_weight_power.
+
+ Args:
+ dataset_names: list of dataset names
+ freq_weight_power: power value
+ """
+ if isinstance(dataset_names, str):
+ dataset_names = [dataset_names]
+
+ check_metadata_consistency("class_image_count", dataset_names)
+
+ meta = MetadataCatalog.get(dataset_names[0])
+ class_freq_meta = meta.class_image_count
+ class_freq = torch.tensor(
+ [c["image_count"] for c in sorted(class_freq_meta, key=lambda x: x["id"])]
+ )
+ class_freq_weight = class_freq.float() ** freq_weight_power
+ return class_freq_weight
+
+
+def gen_crop_transform_with_instance(crop_size, image_size, instance):
+ """
+ Generate a CropTransform so that the cropping region contains
+ the center of the given instance.
+
+ Args:
+ crop_size (tuple): h, w in pixels
+ image_size (tuple): h, w
+ instance (dict): an annotation dict of one instance, in Detectron2's
+ dataset format.
+ """
+ crop_size = np.asarray(crop_size, dtype=np.int32)
+ bbox = BoxMode.convert(instance["bbox"], instance["bbox_mode"], BoxMode.XYXY_ABS)
+ center_yx = (bbox[1] + bbox[3]) * 0.5, (bbox[0] + bbox[2]) * 0.5
+ assert (
+ image_size[0] >= center_yx[0] and image_size[1] >= center_yx[1]
+ ), "The annotation bounding box is outside of the image!"
+ assert (
+ image_size[0] >= crop_size[0] and image_size[1] >= crop_size[1]
+ ), "Crop size is larger than image size!"
+
+ min_yx = np.maximum(np.floor(center_yx).astype(np.int32) - crop_size, 0)
+ max_yx = np.maximum(np.asarray(image_size, dtype=np.int32) - crop_size, 0)
+ max_yx = np.minimum(max_yx, np.ceil(center_yx).astype(np.int32))
+
+ y0 = np.random.randint(min_yx[0], max_yx[0] + 1)
+ x0 = np.random.randint(min_yx[1], max_yx[1] + 1)
+ return T.CropTransform(x0, y0, crop_size[1], crop_size[0])
+
+
+def check_metadata_consistency(key, dataset_names):
+ """
+ Check that the datasets have consistent metadata.
+
+ Args:
+ key (str): a metadata key
+ dataset_names (list[str]): a list of dataset names
+
+ Raises:
+ AttributeError: if the key does not exist in the metadata
+ ValueError: if the given datasets do not have the same metadata values defined by key
+ """
+ if len(dataset_names) == 0:
+ return
+ logger = logging.getLogger(__name__)
+ entries_per_dataset = [getattr(MetadataCatalog.get(d), key) for d in dataset_names]
+ for idx, entry in enumerate(entries_per_dataset):
+ if entry != entries_per_dataset[0]:
+ logger.error(
+ "Metadata '{}' for dataset '{}' is '{}'".format(key, dataset_names[idx], str(entry))
+ )
+ logger.error(
+ "Metadata '{}' for dataset '{}' is '{}'".format(
+ key, dataset_names[0], str(entries_per_dataset[0])
+ )
+ )
+ raise ValueError("Datasets have different metadata '{}'!".format(key))
+
+
+def build_augmentation(cfg, is_train):
+ """
+ Create a list of default :class:`Augmentation` from config.
+ Now it includes resizing and flipping.
+
+ Returns:
+ list[Augmentation]
+ """
+ if is_train:
+ min_size = cfg.INPUT.MIN_SIZE_TRAIN
+ max_size = cfg.INPUT.MAX_SIZE_TRAIN
+ sample_style = cfg.INPUT.MIN_SIZE_TRAIN_SAMPLING
+ else:
+ min_size = cfg.INPUT.MIN_SIZE_TEST
+ max_size = cfg.INPUT.MAX_SIZE_TEST
+ sample_style = "choice"
+ augmentation = [T.ResizeShortestEdge(min_size, max_size, sample_style)]
+ if is_train and cfg.INPUT.RANDOM_FLIP != "none":
+ augmentation.append(
+ T.RandomFlip(
+ horizontal=cfg.INPUT.RANDOM_FLIP == "horizontal",
+ vertical=cfg.INPUT.RANDOM_FLIP == "vertical",
+ )
+ )
+ return augmentation
+
+
+build_transform_gen = build_augmentation
+"""
+Alias for backward-compatibility.
+"""
diff --git a/detectron2/detectron2/data/samplers/__init__.py b/detectron2/detectron2/data/samplers/__init__.py
new file mode 100755
index 0000000..85c9f1a
--- /dev/null
+++ b/detectron2/detectron2/data/samplers/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .distributed_sampler import (
+ InferenceSampler,
+ RandomSubsetTrainingSampler,
+ RepeatFactorTrainingSampler,
+ TrainingSampler,
+)
+
+from .grouped_batch_sampler import GroupedBatchSampler
+
+__all__ = [
+ "GroupedBatchSampler",
+ "TrainingSampler",
+ "RandomSubsetTrainingSampler",
+ "InferenceSampler",
+ "RepeatFactorTrainingSampler",
+]
diff --git a/detectron2/detectron2/data/samplers/distributed_sampler.py b/detectron2/detectron2/data/samplers/distributed_sampler.py
new file mode 100755
index 0000000..a098e6a
--- /dev/null
+++ b/detectron2/detectron2/data/samplers/distributed_sampler.py
@@ -0,0 +1,278 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import logging
+import math
+from collections import defaultdict
+from typing import Optional
+import torch
+from torch.utils.data.sampler import Sampler
+
+from detectron2.utils import comm
+
+logger = logging.getLogger(__name__)
+
+
+class TrainingSampler(Sampler):
+ """
+ In training, we only care about the "infinite stream" of training data.
+ So this sampler produces an infinite stream of indices and
+ all workers cooperate to correctly shuffle the indices and sample different indices.
+
+ The samplers in each worker effectively produces `indices[worker_id::num_workers]`
+ where `indices` is an infinite stream of indices consisting of
+ `shuffle(range(size)) + shuffle(range(size)) + ...` (if shuffle is True)
+ or `range(size) + range(size) + ...` (if shuffle is False)
+
+ Note that this sampler does not shard based on pytorch DataLoader worker id.
+ A sampler passed to pytorch DataLoader is used only with map-style dataset
+ and will not be executed inside workers.
+ But if this sampler is used in a way that it gets execute inside a dataloader
+ worker, then extra work needs to be done to shard its outputs based on worker id.
+ This is required so that workers don't produce identical data.
+ :class:`ToIterableDataset` implements this logic.
+ This note is true for all samplers in detectron2.
+ """
+
+ def __init__(self, size: int, shuffle: bool = True, seed: Optional[int] = None):
+ """
+ Args:
+ size (int): the total number of data of the underlying dataset to sample from
+ shuffle (bool): whether to shuffle the indices or not
+ seed (int): the initial seed of the shuffle. Must be the same
+ across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ """
+ if not isinstance(size, int):
+ raise TypeError(f"TrainingSampler(size=) expects an int. Got type {type(size)}.")
+ if size <= 0:
+ raise ValueError(f"TrainingSampler(size=) expects a positive int. Got {size}.")
+ self._size = size
+ self._shuffle = shuffle
+ if seed is None:
+ seed = comm.shared_random_seed()
+ self._seed = int(seed)
+
+ self._rank = comm.get_rank()
+ self._world_size = comm.get_world_size()
+
+ def __iter__(self):
+ start = self._rank
+ yield from itertools.islice(self._infinite_indices(), start, None, self._world_size)
+
+ def _infinite_indices(self):
+ g = torch.Generator()
+ g.manual_seed(self._seed)
+ while True:
+ if self._shuffle:
+ yield from torch.randperm(self._size, generator=g).tolist()
+ else:
+ yield from torch.arange(self._size).tolist()
+
+
+class RandomSubsetTrainingSampler(TrainingSampler):
+ """
+ Similar to TrainingSampler, but only sample a random subset of indices.
+ This is useful when you want to estimate the accuracy vs data-number curves by
+ training the model with different subset_ratio.
+ """
+
+ def __init__(
+ self,
+ size: int,
+ subset_ratio: float,
+ shuffle: bool = True,
+ seed_shuffle: Optional[int] = None,
+ seed_subset: Optional[int] = None,
+ ):
+ """
+ Args:
+ size (int): the total number of data of the underlying dataset to sample from
+ subset_ratio (float): the ratio of subset data to sample from the underlying dataset
+ shuffle (bool): whether to shuffle the indices or not
+ seed_shuffle (int): the initial seed of the shuffle. Must be the same
+ across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ seed_subset (int): the seed to randomize the subset to be sampled.
+ Must be the same across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ """
+ super().__init__(size=size, shuffle=shuffle, seed=seed_shuffle)
+
+ assert 0.0 < subset_ratio <= 1.0
+ self._size_subset = int(size * subset_ratio)
+ assert self._size_subset > 0
+ if seed_subset is None:
+ seed_subset = comm.shared_random_seed()
+ self._seed_subset = int(seed_subset)
+
+ # randomly generate the subset indexes to be sampled from
+ g = torch.Generator()
+ g.manual_seed(self._seed_subset)
+ indexes_randperm = torch.randperm(self._size, generator=g)
+ self._indexes_subset = indexes_randperm[: self._size_subset]
+
+ logger.info("Using RandomSubsetTrainingSampler......")
+ logger.info(f"Randomly sample {self._size_subset} data from the original {self._size} data")
+
+ def _infinite_indices(self):
+ g = torch.Generator()
+ g.manual_seed(self._seed) # self._seed equals seed_shuffle from __init__()
+ while True:
+ if self._shuffle:
+ # generate a random permutation to shuffle self._indexes_subset
+ randperm = torch.randperm(self._size_subset, generator=g)
+ yield from self._indexes_subset[randperm].tolist()
+ else:
+ yield from self._indexes_subset.tolist()
+
+
+class RepeatFactorTrainingSampler(Sampler):
+ """
+ Similar to TrainingSampler, but a sample may appear more times than others based
+ on its "repeat factor". This is suitable for training on class imbalanced datasets like LVIS.
+ """
+
+ def __init__(self, repeat_factors, *, shuffle=True, seed=None):
+ """
+ Args:
+ repeat_factors (Tensor): a float vector, the repeat factor for each indice. When it's
+ full of ones, it is equivalent to ``TrainingSampler(len(repeat_factors), ...)``.
+ shuffle (bool): whether to shuffle the indices or not
+ seed (int): the initial seed of the shuffle. Must be the same
+ across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ """
+ self._shuffle = shuffle
+ if seed is None:
+ seed = comm.shared_random_seed()
+ self._seed = int(seed)
+
+ self._rank = comm.get_rank()
+ self._world_size = comm.get_world_size()
+
+ # Split into whole number (_int_part) and fractional (_frac_part) parts.
+ self._int_part = torch.trunc(repeat_factors)
+ self._frac_part = repeat_factors - self._int_part
+
+ @staticmethod
+ def repeat_factors_from_category_frequency(dataset_dicts, repeat_thresh):
+ """
+ Compute (fractional) per-image repeat factors based on category frequency.
+ The repeat factor for an image is a function of the frequency of the rarest
+ category labeled in that image. The "frequency of category c" in [0, 1] is defined
+ as the fraction of images in the training set (without repeats) in which category c
+ appears.
+ See :paper:`lvis` (>= v2) Appendix B.2.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 dataset format.
+ repeat_thresh (float): frequency threshold below which data is repeated.
+ If the frequency is half of `repeat_thresh`, the image will be
+ repeated twice.
+
+ Returns:
+ torch.Tensor:
+ the i-th element is the repeat factor for the dataset image at index i.
+ """
+ # 1. For each category c, compute the fraction of images that contain it: f(c)
+ category_freq = defaultdict(int)
+ for dataset_dict in dataset_dicts: # For each image (without repeats)
+ cat_ids = {ann["category_id"] for ann in dataset_dict["annotations"]}
+ for cat_id in cat_ids:
+ category_freq[cat_id] += 1
+ num_images = len(dataset_dicts)
+ for k, v in category_freq.items():
+ category_freq[k] = v / num_images
+
+ # 2. For each category c, compute the category-level repeat factor:
+ # r(c) = max(1, sqrt(t / f(c)))
+ category_rep = {
+ cat_id: max(1.0, math.sqrt(repeat_thresh / cat_freq))
+ for cat_id, cat_freq in category_freq.items()
+ }
+
+ # 3. For each image I, compute the image-level repeat factor:
+ # r(I) = max_{c in I} r(c)
+ rep_factors = []
+ for dataset_dict in dataset_dicts:
+ cat_ids = {ann["category_id"] for ann in dataset_dict["annotations"]}
+ rep_factor = max({category_rep[cat_id] for cat_id in cat_ids}, default=1.0)
+ rep_factors.append(rep_factor)
+
+ return torch.tensor(rep_factors, dtype=torch.float32)
+
+ def _get_epoch_indices(self, generator):
+ """
+ Create a list of dataset indices (with repeats) to use for one epoch.
+
+ Args:
+ generator (torch.Generator): pseudo random number generator used for
+ stochastic rounding.
+
+ Returns:
+ torch.Tensor: list of dataset indices to use in one epoch. Each index
+ is repeated based on its calculated repeat factor.
+ """
+ # Since repeat factors are fractional, we use stochastic rounding so
+ # that the target repeat factor is achieved in expectation over the
+ # course of training
+ rands = torch.rand(len(self._frac_part), generator=generator)
+ rep_factors = self._int_part + (rands < self._frac_part).float()
+ # Construct a list of indices in which we repeat images as specified
+ indices = []
+ for dataset_index, rep_factor in enumerate(rep_factors):
+ indices.extend([dataset_index] * int(rep_factor.item()))
+ return torch.tensor(indices, dtype=torch.int64)
+
+ def __iter__(self):
+ start = self._rank
+ yield from itertools.islice(self._infinite_indices(), start, None, self._world_size)
+
+ def _infinite_indices(self):
+ g = torch.Generator()
+ g.manual_seed(self._seed)
+ while True:
+ # Sample indices with repeats determined by stochastic rounding; each
+ # "epoch" may have a slightly different size due to the rounding.
+ indices = self._get_epoch_indices(g)
+ if self._shuffle:
+ randperm = torch.randperm(len(indices), generator=g)
+ yield from indices[randperm].tolist()
+ else:
+ yield from indices.tolist()
+
+
+class InferenceSampler(Sampler):
+ """
+ Produce indices for inference across all workers.
+ Inference needs to run on the __exact__ set of samples,
+ therefore when the total number of samples is not divisible by the number of workers,
+ this sampler produces different number of samples on different workers.
+ """
+
+ def __init__(self, size: int):
+ """
+ Args:
+ size (int): the total number of data of the underlying dataset to sample from
+ """
+ self._size = size
+ assert size > 0
+ self._rank = comm.get_rank()
+ self._world_size = comm.get_world_size()
+ self._local_indices = self._get_local_indices(size, self._world_size, self._rank)
+
+ @staticmethod
+ def _get_local_indices(total_size, world_size, rank):
+ shard_size = total_size // world_size
+ left = total_size % world_size
+ shard_sizes = [shard_size + int(r < left) for r in range(world_size)]
+
+ begin = sum(shard_sizes[:rank])
+ end = min(sum(shard_sizes[: rank + 1]), total_size)
+ return range(begin, end)
+
+ def __iter__(self):
+ yield from self._local_indices
+
+ def __len__(self):
+ return len(self._local_indices)
diff --git a/detectron2/detectron2/data/samplers/grouped_batch_sampler.py b/detectron2/detectron2/data/samplers/grouped_batch_sampler.py
new file mode 100755
index 0000000..5b24773
--- /dev/null
+++ b/detectron2/detectron2/data/samplers/grouped_batch_sampler.py
@@ -0,0 +1,47 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import numpy as np
+from torch.utils.data.sampler import BatchSampler, Sampler
+
+
+class GroupedBatchSampler(BatchSampler):
+ """
+ Wraps another sampler to yield a mini-batch of indices.
+ It enforces that the batch only contain elements from the same group.
+ It also tries to provide mini-batches which follows an ordering which is
+ as close as possible to the ordering from the original sampler.
+ """
+
+ def __init__(self, sampler, group_ids, batch_size):
+ """
+ Args:
+ sampler (Sampler): Base sampler.
+ group_ids (list[int]): If the sampler produces indices in range [0, N),
+ `group_ids` must be a list of `N` ints which contains the group id of each sample.
+ The group ids must be a set of integers in the range [0, num_groups).
+ batch_size (int): Size of mini-batch.
+ """
+ if not isinstance(sampler, Sampler):
+ raise ValueError(
+ "sampler should be an instance of "
+ "torch.utils.data.Sampler, but got sampler={}".format(sampler)
+ )
+ self.sampler = sampler
+ self.group_ids = np.asarray(group_ids)
+ assert self.group_ids.ndim == 1
+ self.batch_size = batch_size
+ groups = np.unique(self.group_ids).tolist()
+
+ # buffer the indices of each group until batch size is reached
+ self.buffer_per_group = {k: [] for k in groups}
+
+ def __iter__(self):
+ for idx in self.sampler:
+ group_id = self.group_ids[idx]
+ group_buffer = self.buffer_per_group[group_id]
+ group_buffer.append(idx)
+ if len(group_buffer) == self.batch_size:
+ yield group_buffer[:] # yield a copy of the list
+ del group_buffer[:]
+
+ def __len__(self):
+ raise NotImplementedError("len() of GroupedBatchSampler is not well-defined.")
diff --git a/detectron2/detectron2/data/transforms/__init__.py b/detectron2/detectron2/data/transforms/__init__.py
new file mode 100755
index 0000000..ab3c63b
--- /dev/null
+++ b/detectron2/detectron2/data/transforms/__init__.py
@@ -0,0 +1,14 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from fvcore.transforms.transform import Transform, TransformList # order them first
+from fvcore.transforms.transform import *
+from .transform import *
+from .augmentation import *
+from .augmentation_impl import *
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
+
+
+from detectron2.utils.env import fixup_module_metadata
+
+fixup_module_metadata(__name__, globals(), __all__)
+del fixup_module_metadata
diff --git a/detectron2/detectron2/data/transforms/augmentation.py b/detectron2/detectron2/data/transforms/augmentation.py
new file mode 100755
index 0000000..63dd41a
--- /dev/null
+++ b/detectron2/detectron2/data/transforms/augmentation.py
@@ -0,0 +1,380 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import inspect
+import numpy as np
+import pprint
+from typing import Any, List, Optional, Tuple, Union
+from fvcore.transforms.transform import Transform, TransformList
+
+"""
+See "Data Augmentation" tutorial for an overview of the system:
+https://detectron2.readthedocs.io/tutorials/augmentation.html
+"""
+
+
+__all__ = [
+ "Augmentation",
+ "AugmentationList",
+ "AugInput",
+ "TransformGen",
+ "apply_transform_gens",
+ "StandardAugInput",
+ "apply_augmentations",
+]
+
+
+def _check_img_dtype(img):
+ assert isinstance(img, np.ndarray), "[Augmentation] Needs an numpy array, but got a {}!".format(
+ type(img)
+ )
+ assert not isinstance(img.dtype, np.integer) or (
+ img.dtype == np.uint8
+ ), "[Augmentation] Got image of type {}, use uint8 or floating points instead!".format(
+ img.dtype
+ )
+ assert img.ndim in [2, 3], img.ndim
+
+
+def _get_aug_input_args(aug, aug_input) -> List[Any]:
+ """
+ Get the arguments to be passed to ``aug.get_transform`` from the input ``aug_input``.
+ """
+ if aug.input_args is None:
+ # Decide what attributes are needed automatically
+ prms = list(inspect.signature(aug.get_transform).parameters.items())
+ # The default behavior is: if there is one parameter, then its "image"
+ # (work automatically for majority of use cases, and also avoid BC breaking),
+ # Otherwise, use the argument names.
+ if len(prms) == 1:
+ names = ("image",)
+ else:
+ names = []
+ for name, prm in prms:
+ if prm.kind in (
+ inspect.Parameter.VAR_POSITIONAL,
+ inspect.Parameter.VAR_KEYWORD,
+ ):
+ raise TypeError(
+ f""" \
+The default implementation of `{type(aug)}.__call__` does not allow \
+`{type(aug)}.get_transform` to use variable-length arguments (*args, **kwargs)! \
+If arguments are unknown, reimplement `__call__` instead. \
+"""
+ )
+ names.append(name)
+ aug.input_args = tuple(names)
+
+ args = []
+ for f in aug.input_args:
+ try:
+ args.append(getattr(aug_input, f))
+ except AttributeError as e:
+ raise AttributeError(
+ f"{type(aug)}.get_transform needs input attribute '{f}', "
+ f"but it is not an attribute of {type(aug_input)}!"
+ ) from e
+ return args
+
+
+class Augmentation:
+ """
+ Augmentation defines (often random) policies/strategies to generate :class:`Transform`
+ from data. It is often used for pre-processing of input data.
+
+ A "policy" that generates a :class:`Transform` may, in the most general case,
+ need arbitrary information from input data in order to determine what transforms
+ to apply. Therefore, each :class:`Augmentation` instance defines the arguments
+ needed by its :meth:`get_transform` method. When called with the positional arguments,
+ the :meth:`get_transform` method executes the policy.
+
+ Note that :class:`Augmentation` defines the policies to create a :class:`Transform`,
+ but not how to execute the actual transform operations to those data.
+ Its :meth:`__call__` method will use :meth:`AugInput.transform` to execute the transform.
+
+ The returned `Transform` object is meant to describe deterministic transformation, which means
+ it can be re-applied on associated data, e.g. the geometry of an image and its segmentation
+ masks need to be transformed together.
+ (If such re-application is not needed, then determinism is not a crucial requirement.)
+ """
+
+ input_args: Optional[Tuple[str]] = None
+ """
+ Stores the attribute names needed by :meth:`get_transform`, e.g. ``("image", "sem_seg")``.
+ By default, it is just a tuple of argument names in :meth:`self.get_transform`, which often only
+ contain "image". As long as the argument name convention is followed, there is no need for
+ users to touch this attribute.
+ """
+
+ def _init(self, params=None):
+ if params:
+ for k, v in params.items():
+ if k != "self" and not k.startswith("_"):
+ setattr(self, k, v)
+
+ def get_transform(self, *args) -> Transform:
+ """
+ Execute the policy based on input data, and decide what transform to apply to inputs.
+
+ Args:
+ args: Any fixed-length positional arguments. By default, the name of the arguments
+ should exist in the :class:`AugInput` to be used.
+
+ Returns:
+ Transform: Returns the deterministic transform to apply to the input.
+
+ Examples:
+ ::
+ class MyAug:
+ # if a policy needs to know both image and semantic segmentation
+ def get_transform(image, sem_seg) -> T.Transform:
+ pass
+ tfm: Transform = MyAug().get_transform(image, sem_seg)
+ new_image = tfm.apply_image(image)
+
+ Notes:
+ Users can freely use arbitrary new argument names in custom
+ :meth:`get_transform` method, as long as they are available in the
+ input data. In detectron2 we use the following convention:
+
+ * image: (H,W) or (H,W,C) ndarray of type uint8 in range [0, 255], or
+ floating point in range [0, 1] or [0, 255].
+ * boxes: (N,4) ndarray of float32. It represents the instance bounding boxes
+ of N instances. Each is in XYXY format in unit of absolute coordinates.
+ * sem_seg: (H,W) ndarray of type uint8. Each element is an integer label of pixel.
+
+ We do not specify convention for other types and do not include builtin
+ :class:`Augmentation` that uses other types in detectron2.
+ """
+ raise NotImplementedError
+
+ def __call__(self, aug_input) -> Transform:
+ """
+ Augment the given `aug_input` **in-place**, and return the transform that's used.
+
+ This method will be called to apply the augmentation. In most augmentation, it
+ is enough to use the default implementation, which calls :meth:`get_transform`
+ using the inputs. But a subclass can overwrite it to have more complicated logic.
+
+ Args:
+ aug_input (AugInput): an object that has attributes needed by this augmentation
+ (defined by ``self.get_transform``). Its ``transform`` method will be called
+ to in-place transform it.
+
+ Returns:
+ Transform: the transform that is applied on the input.
+ """
+ args = _get_aug_input_args(self, aug_input)
+ tfm = self.get_transform(*args)
+ assert isinstance(tfm, (Transform, TransformList)), (
+ f"{type(self)}.get_transform must return an instance of Transform! "
+ f"Got {type(tfm)} instead."
+ )
+ aug_input.transform(tfm)
+ return tfm
+
+ def _rand_range(self, low=1.0, high=None, size=None):
+ """
+ Uniform float random number between low and high.
+ """
+ if high is None:
+ low, high = 0, low
+ if size is None:
+ size = []
+ return np.random.uniform(low, high, size)
+
+ def __repr__(self):
+ """
+ Produce something like:
+ "MyAugmentation(field1={self.field1}, field2={self.field2})"
+ """
+ try:
+ sig = inspect.signature(self.__init__)
+ classname = type(self).__name__
+ argstr = []
+ for name, param in sig.parameters.items():
+ assert (
+ param.kind != param.VAR_POSITIONAL and param.kind != param.VAR_KEYWORD
+ ), "The default __repr__ doesn't support *args or **kwargs"
+ assert hasattr(self, name), (
+ "Attribute {} not found! "
+ "Default __repr__ only works if attributes match the constructor.".format(name)
+ )
+ attr = getattr(self, name)
+ default = param.default
+ if default is attr:
+ continue
+ attr_str = pprint.pformat(attr)
+ if "\n" in attr_str:
+ # don't show it if pformat decides to use >1 lines
+ attr_str = "..."
+ argstr.append("{}={}".format(name, attr_str))
+ return "{}({})".format(classname, ", ".join(argstr))
+ except AssertionError:
+ return super().__repr__()
+
+ __str__ = __repr__
+
+
+class _TransformToAug(Augmentation):
+ def __init__(self, tfm: Transform):
+ self.tfm = tfm
+
+ def get_transform(self, *args):
+ return self.tfm
+
+ def __repr__(self):
+ return repr(self.tfm)
+
+ __str__ = __repr__
+
+
+def _transform_to_aug(tfm_or_aug):
+ """
+ Wrap Transform into Augmentation.
+ Private, used internally to implement augmentations.
+ """
+ assert isinstance(tfm_or_aug, (Transform, Augmentation)), tfm_or_aug
+ if isinstance(tfm_or_aug, Augmentation):
+ return tfm_or_aug
+ else:
+ return _TransformToAug(tfm_or_aug)
+
+
+class AugmentationList(Augmentation):
+ """
+ Apply a sequence of augmentations.
+
+ It has ``__call__`` method to apply the augmentations.
+
+ Note that :meth:`get_transform` method is impossible (will throw error if called)
+ for :class:`AugmentationList`, because in order to apply a sequence of augmentations,
+ the kth augmentation must be applied first, to provide inputs needed by the (k+1)th
+ augmentation.
+ """
+
+ def __init__(self, augs):
+ """
+ Args:
+ augs (list[Augmentation or Transform]):
+ """
+ super().__init__()
+ self.augs = [_transform_to_aug(x) for x in augs]
+
+ def __call__(self, aug_input) -> TransformList:
+ tfms = []
+ for x in self.augs:
+ tfm = x(aug_input)
+ tfms.append(tfm)
+ return TransformList(tfms)
+
+ def __repr__(self):
+ msgs = [str(x) for x in self.augs]
+ return "AugmentationList[{}]".format(", ".join(msgs))
+
+ __str__ = __repr__
+
+
+class AugInput:
+ """
+ Input that can be used with :meth:`Augmentation.__call__`.
+ This is a standard implementation for the majority of use cases.
+ This class provides the standard attributes **"image", "boxes", "sem_seg"**
+ defined in :meth:`__init__` and they may be needed by different augmentations.
+ Most augmentation policies do not need attributes beyond these three.
+
+ After applying augmentations to these attributes (using :meth:`AugInput.transform`),
+ the returned transforms can then be used to transform other data structures that users have.
+
+ Examples:
+ ::
+ input = AugInput(image, boxes=boxes)
+ tfms = augmentation(input)
+ transformed_image = input.image
+ transformed_boxes = input.boxes
+ transformed_other_data = tfms.apply_other(other_data)
+
+ An extended project that works with new data types may implement augmentation policies
+ that need other inputs. An algorithm may need to transform inputs in a way different
+ from the standard approach defined in this class. In those rare situations, users can
+ implement a class similar to this class, that satify the following condition:
+
+ * The input must provide access to these data in the form of attribute access
+ (``getattr``). For example, if an :class:`Augmentation` to be applied needs "image"
+ and "sem_seg" arguments, its input must have the attribute "image" and "sem_seg".
+ * The input must have a ``transform(tfm: Transform) -> None`` method which
+ in-place transforms all its attributes.
+ """
+
+ # TODO maybe should support more builtin data types here
+ def __init__(
+ self,
+ image: np.ndarray,
+ *,
+ boxes: Optional[np.ndarray] = None,
+ sem_seg: Optional[np.ndarray] = None,
+ ):
+ """
+ Args:
+ image (ndarray): (H,W) or (H,W,C) ndarray of type uint8 in range [0, 255], or
+ floating point in range [0, 1] or [0, 255]. The meaning of C is up
+ to users.
+ boxes (ndarray or None): Nx4 float32 boxes in XYXY_ABS mode
+ sem_seg (ndarray or None): HxW uint8 semantic segmentation mask. Each element
+ is an integer label of pixel.
+ """
+ _check_img_dtype(image)
+ self.image = image
+ self.boxes = boxes
+ self.sem_seg = sem_seg
+
+ def transform(self, tfm: Transform) -> None:
+ """
+ In-place transform all attributes of this class.
+
+ By "in-place", it means after calling this method, accessing an attribute such
+ as ``self.image`` will return transformed data.
+ """
+ self.image = tfm.apply_image(self.image)
+ if self.boxes is not None:
+ self.boxes = tfm.apply_box(self.boxes)
+ if self.sem_seg is not None:
+ self.sem_seg = tfm.apply_segmentation(self.sem_seg)
+
+ def apply_augmentations(
+ self, augmentations: List[Union[Augmentation, Transform]]
+ ) -> TransformList:
+ """
+ Equivalent of ``AugmentationList(augmentations)(self)``
+ """
+ return AugmentationList(augmentations)(self)
+
+
+def apply_augmentations(augmentations: List[Union[Transform, Augmentation]], inputs):
+ """
+ Use ``T.AugmentationList(augmentations)(inputs)`` instead.
+ """
+ if isinstance(inputs, np.ndarray):
+ # handle the common case of image-only Augmentation, also for backward compatibility
+ image_only = True
+ inputs = AugInput(inputs)
+ else:
+ image_only = False
+ tfms = inputs.apply_augmentations(augmentations)
+ return inputs.image if image_only else inputs, tfms
+
+
+apply_transform_gens = apply_augmentations
+"""
+Alias for backward-compatibility.
+"""
+
+TransformGen = Augmentation
+"""
+Alias for Augmentation, since it is something that generates :class:`Transform`s
+"""
+
+StandardAugInput = AugInput
+"""
+Alias for compatibility. It's not worth the complexity to have two classes.
+"""
diff --git a/detectron2/detectron2/data/transforms/augmentation_impl.py b/detectron2/detectron2/data/transforms/augmentation_impl.py
new file mode 100755
index 0000000..cc270cd
--- /dev/null
+++ b/detectron2/detectron2/data/transforms/augmentation_impl.py
@@ -0,0 +1,736 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+"""
+Implement many useful :class:`Augmentation`.
+"""
+import numpy as np
+import sys
+from numpy import random
+from typing import Tuple
+import torch
+from fvcore.transforms.transform import (
+ BlendTransform,
+ CropTransform,
+ HFlipTransform,
+ NoOpTransform,
+ PadTransform,
+ Transform,
+ TransformList,
+ VFlipTransform,
+)
+from PIL import Image
+
+from detectron2.structures import Boxes, pairwise_iou
+
+from .augmentation import Augmentation, _transform_to_aug
+from .transform import ExtentTransform, ResizeTransform, RotationTransform
+
+__all__ = [
+ "FixedSizeCrop",
+ "RandomApply",
+ "RandomBrightness",
+ "RandomContrast",
+ "RandomCrop",
+ "RandomExtent",
+ "RandomFlip",
+ "RandomSaturation",
+ "RandomLighting",
+ "RandomRotation",
+ "Resize",
+ "ResizeScale",
+ "ResizeShortestEdge",
+ "RandomCrop_CategoryAreaConstraint",
+ "RandomResize",
+ "MinIoURandomCrop",
+]
+
+
+class RandomApply(Augmentation):
+ """
+ Randomly apply an augmentation with a given probability.
+ """
+
+ def __init__(self, tfm_or_aug, prob=0.5):
+ """
+ Args:
+ tfm_or_aug (Transform, Augmentation): the transform or augmentation
+ to be applied. It can either be a `Transform` or `Augmentation`
+ instance.
+ prob (float): probability between 0.0 and 1.0 that
+ the wrapper transformation is applied
+ """
+ super().__init__()
+ self.aug = _transform_to_aug(tfm_or_aug)
+ assert 0.0 <= prob <= 1.0, f"Probablity must be between 0.0 and 1.0 (given: {prob})"
+ self.prob = prob
+
+ def get_transform(self, *args):
+ do = self._rand_range() < self.prob
+ if do:
+ return self.aug.get_transform(*args)
+ else:
+ return NoOpTransform()
+
+ def __call__(self, aug_input):
+ do = self._rand_range() < self.prob
+ if do:
+ return self.aug(aug_input)
+ else:
+ return NoOpTransform()
+
+
+class RandomFlip(Augmentation):
+ """
+ Flip the image horizontally or vertically with the given probability.
+ """
+
+ def __init__(self, prob=0.5, *, horizontal=True, vertical=False):
+ """
+ Args:
+ prob (float): probability of flip.
+ horizontal (boolean): whether to apply horizontal flipping
+ vertical (boolean): whether to apply vertical flipping
+ """
+ super().__init__()
+
+ if horizontal and vertical:
+ raise ValueError("Cannot do both horiz and vert. Please use two Flip instead.")
+ if not horizontal and not vertical:
+ raise ValueError("At least one of horiz or vert has to be True!")
+ self._init(locals())
+
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ do = self._rand_range() < self.prob
+ if do:
+ if self.horizontal:
+ return HFlipTransform(w)
+ elif self.vertical:
+ return VFlipTransform(h)
+ else:
+ return NoOpTransform()
+
+
+class Resize(Augmentation):
+ """Resize image to a fixed target size"""
+
+ def __init__(self, shape, interp=Image.BILINEAR):
+ """
+ Args:
+ shape: (h, w) tuple or a int
+ interp: PIL interpolation method
+ """
+ if isinstance(shape, int):
+ shape = (shape, shape)
+ shape = tuple(shape)
+ self._init(locals())
+
+ def get_transform(self, image):
+ return ResizeTransform(
+ image.shape[0], image.shape[1], self.shape[0], self.shape[1], self.interp
+ )
+
+
+class ResizeShortestEdge(Augmentation):
+ """
+ Resize the image while keeping the aspect ratio unchanged.
+ It attempts to scale the shorter edge to the given `short_edge_length`,
+ as long as the longer edge does not exceed `max_size`.
+ If `max_size` is reached, then downscale so that the longer edge does not exceed max_size.
+ """
+
+ @torch.jit.unused
+ def __init__(
+ self, short_edge_length, max_size=sys.maxsize, sample_style="range", interp=Image.BILINEAR
+ ):
+ """
+ Args:
+ short_edge_length (list[int]): If ``sample_style=="range"``,
+ a [min, max] interval from which to sample the shortest edge length.
+ If ``sample_style=="choice"``, a list of shortest edge lengths to sample from.
+ max_size (int): maximum allowed longest edge length.
+ sample_style (str): either "range" or "choice".
+ """
+ super().__init__()
+ assert sample_style in ["range", "choice"], sample_style
+
+ self.is_range = sample_style == "range"
+ if isinstance(short_edge_length, int):
+ short_edge_length = (short_edge_length, short_edge_length)
+ if self.is_range:
+ assert len(short_edge_length) == 2, (
+ "short_edge_length must be two values using 'range' sample style."
+ f" Got {short_edge_length}!"
+ )
+ self._init(locals())
+
+ @torch.jit.unused
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ if self.is_range:
+ size = np.random.randint(self.short_edge_length[0], self.short_edge_length[1] + 1)
+ else:
+ size = np.random.choice(self.short_edge_length)
+ if size == 0:
+ return NoOpTransform()
+
+ newh, neww = ResizeShortestEdge.get_output_shape(h, w, size, self.max_size)
+ return ResizeTransform(h, w, newh, neww, self.interp)
+
+ @staticmethod
+ def get_output_shape(
+ oldh: int, oldw: int, short_edge_length: int, max_size: int
+ ) -> Tuple[int, int]:
+ """
+ Compute the output size given input size and target short edge length.
+ """
+ h, w = oldh, oldw
+ size = short_edge_length * 1.0
+ scale = size / min(h, w)
+ if h < w:
+ newh, neww = size, scale * w
+ else:
+ newh, neww = scale * h, size
+ if max(newh, neww) > max_size:
+ scale = max_size * 1.0 / max(newh, neww)
+ newh = newh * scale
+ neww = neww * scale
+ neww = int(neww + 0.5)
+ newh = int(newh + 0.5)
+ return (newh, neww)
+
+
+class ResizeScale(Augmentation):
+ """
+ Takes target size as input and randomly scales the given target size between `min_scale`
+ and `max_scale`. It then scales the input image such that it fits inside the scaled target
+ box, keeping the aspect ratio constant.
+ This implements the resize part of the Google's 'resize_and_crop' data augmentation:
+ https://github.com/tensorflow/tpu/blob/master/models/official/detection/utils/input_utils.py#L127
+ """
+
+ def __init__(
+ self,
+ min_scale: float,
+ max_scale: float,
+ target_height: int,
+ target_width: int,
+ interp: int = Image.BILINEAR,
+ ):
+ """
+ Args:
+ min_scale: minimum image scale range.
+ max_scale: maximum image scale range.
+ target_height: target image height.
+ target_width: target image width.
+ interp: image interpolation method.
+ """
+ super().__init__()
+ self._init(locals())
+
+ def _get_resize(self, image: np.ndarray, scale: float) -> Transform:
+ input_size = image.shape[:2]
+
+ # Compute new target size given a scale.
+ target_size = (self.target_height, self.target_width)
+ target_scale_size = np.multiply(target_size, scale)
+
+ # Compute actual rescaling applied to input image and output size.
+ output_scale = np.minimum(
+ target_scale_size[0] / input_size[0], target_scale_size[1] / input_size[1]
+ )
+ output_size = np.round(np.multiply(input_size, output_scale)).astype(int)
+
+ return ResizeTransform(
+ input_size[0], input_size[1], output_size[0], output_size[1], self.interp
+ )
+
+ def get_transform(self, image: np.ndarray) -> Transform:
+ random_scale = np.random.uniform(self.min_scale, self.max_scale)
+ return self._get_resize(image, random_scale)
+
+
+class RandomRotation(Augmentation):
+ """
+ This method returns a copy of this image, rotated the given
+ number of degrees counter clockwise around the given center.
+ """
+
+ def __init__(self, angle, expand=True, center=None, sample_style="range", interp=None):
+ """
+ Args:
+ angle (list[float]): If ``sample_style=="range"``,
+ a [min, max] interval from which to sample the angle (in degrees).
+ If ``sample_style=="choice"``, a list of angles to sample from
+ expand (bool): choose if the image should be resized to fit the whole
+ rotated image (default), or simply cropped
+ center (list[[float, float]]): If ``sample_style=="range"``,
+ a [[minx, miny], [maxx, maxy]] relative interval from which to sample the center,
+ [0, 0] being the top left of the image and [1, 1] the bottom right.
+ If ``sample_style=="choice"``, a list of centers to sample from
+ Default: None, which means that the center of rotation is the center of the image
+ center has no effect if expand=True because it only affects shifting
+ """
+ super().__init__()
+ assert sample_style in ["range", "choice"], sample_style
+ self.is_range = sample_style == "range"
+ if isinstance(angle, (float, int)):
+ angle = (angle, angle)
+ if center is not None and isinstance(center[0], (float, int)):
+ center = (center, center)
+ self._init(locals())
+
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ center = None
+ if self.is_range:
+ angle = np.random.uniform(self.angle[0], self.angle[1])
+ if self.center is not None:
+ center = (
+ np.random.uniform(self.center[0][0], self.center[1][0]),
+ np.random.uniform(self.center[0][1], self.center[1][1]),
+ )
+ else:
+ angle = np.random.choice(self.angle)
+ if self.center is not None:
+ center = np.random.choice(self.center)
+
+ if center is not None:
+ center = (w * center[0], h * center[1]) # Convert to absolute coordinates
+
+ if angle % 360 == 0:
+ return NoOpTransform()
+
+ return RotationTransform(h, w, angle, expand=self.expand, center=center, interp=self.interp)
+
+
+class FixedSizeCrop(Augmentation):
+ """
+ If `crop_size` is smaller than the input image size, then it uses a random crop of
+ the crop size. If `crop_size` is larger than the input image size, then it pads
+ the right and the bottom of the image to the crop size if `pad` is True, otherwise
+ it returns the smaller image.
+ """
+
+ def __init__(
+ self,
+ crop_size: Tuple[int],
+ pad: bool = True,
+ pad_value: float = 128.0,
+ seg_pad_value: int = 255,
+ ):
+ """
+ Args:
+ crop_size: target image (height, width).
+ pad: if True, will pad images smaller than `crop_size` up to `crop_size`
+ pad_value: the padding value to the image.
+ seg_pad_value: the padding value to the segmentation mask.
+ """
+ super().__init__()
+ self._init(locals())
+
+ def _get_crop(self, image: np.ndarray) -> Transform:
+ # Compute the image scale and scaled size.
+ input_size = image.shape[:2]
+ output_size = self.crop_size
+
+ # Add random crop if the image is scaled up.
+ max_offset = np.subtract(input_size, output_size)
+ max_offset = np.maximum(max_offset, 0)
+ offset = np.multiply(max_offset, np.random.uniform(0.0, 1.0))
+ offset = np.round(offset).astype(int)
+ return CropTransform(
+ offset[1], offset[0], output_size[1], output_size[0], input_size[1], input_size[0]
+ )
+
+ def _get_pad(self, image: np.ndarray) -> Transform:
+ # Compute the image scale and scaled size.
+ input_size = image.shape[:2]
+ output_size = self.crop_size
+
+ # Add padding if the image is scaled down.
+ pad_size = np.subtract(output_size, input_size)
+ pad_size = np.maximum(pad_size, 0)
+ original_size = np.minimum(input_size, output_size)
+ return PadTransform(
+ 0,
+ 0,
+ pad_size[1],
+ pad_size[0],
+ original_size[1],
+ original_size[0],
+ self.pad_value,
+ self.seg_pad_value,
+ )
+
+ def get_transform(self, image: np.ndarray) -> TransformList:
+ transforms = [self._get_crop(image)]
+ if self.pad:
+ transforms.append(self._get_pad(image))
+ return TransformList(transforms)
+
+
+class RandomCrop(Augmentation):
+ """
+ Randomly crop a rectangle region out of an image.
+ """
+
+ def __init__(self, crop_type: str, crop_size):
+ """
+ Args:
+ crop_type (str): one of "relative_range", "relative", "absolute", "absolute_range".
+ crop_size (tuple[float, float]): two floats, explained below.
+
+ - "relative": crop a (H * crop_size[0], W * crop_size[1]) region from an input image of
+ size (H, W). crop size should be in (0, 1]
+ - "relative_range": uniformly sample two values from [crop_size[0], 1]
+ and [crop_size[1]], 1], and use them as in "relative" crop type.
+ - "absolute" crop a (crop_size[0], crop_size[1]) region from input image.
+ crop_size must be smaller than the input image size.
+ - "absolute_range", for an input of size (H, W), uniformly sample H_crop in
+ [crop_size[0], min(H, crop_size[1])] and W_crop in [crop_size[0], min(W, crop_size[1])].
+ Then crop a region (H_crop, W_crop).
+ """
+ # TODO style of relative_range and absolute_range are not consistent:
+ # one takes (h, w) but another takes (min, max)
+ super().__init__()
+ assert crop_type in ["relative_range", "relative", "absolute", "absolute_range"]
+ self._init(locals())
+
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ croph, cropw = self.get_crop_size((h, w))
+ assert h >= croph and w >= cropw, "Shape computation in {} has bugs.".format(self)
+ h0 = np.random.randint(h - croph + 1)
+ w0 = np.random.randint(w - cropw + 1)
+ return CropTransform(w0, h0, cropw, croph)
+
+ def get_crop_size(self, image_size):
+ """
+ Args:
+ image_size (tuple): height, width
+
+ Returns:
+ crop_size (tuple): height, width in absolute pixels
+ """
+ h, w = image_size
+ if self.crop_type == "relative":
+ ch, cw = self.crop_size
+ return int(h * ch + 0.5), int(w * cw + 0.5)
+ elif self.crop_type == "relative_range":
+ crop_size = np.asarray(self.crop_size, dtype=np.float32)
+ ch, cw = crop_size + np.random.rand(2) * (1 - crop_size)
+ return int(h * ch + 0.5), int(w * cw + 0.5)
+ elif self.crop_type == "absolute":
+ return (min(self.crop_size[0], h), min(self.crop_size[1], w))
+ elif self.crop_type == "absolute_range":
+ assert self.crop_size[0] <= self.crop_size[1]
+ ch = np.random.randint(min(h, self.crop_size[0]), min(h, self.crop_size[1]) + 1)
+ cw = np.random.randint(min(w, self.crop_size[0]), min(w, self.crop_size[1]) + 1)
+ return ch, cw
+ else:
+ raise NotImplementedError("Unknown crop type {}".format(self.crop_type))
+
+
+class RandomCrop_CategoryAreaConstraint(Augmentation):
+ """
+ Similar to :class:`RandomCrop`, but find a cropping window such that no single category
+ occupies a ratio of more than `single_category_max_area` in semantic segmentation ground
+ truth, which can cause unstability in training. The function attempts to find such a valid
+ cropping window for at most 10 times.
+ """
+
+ def __init__(
+ self,
+ crop_type: str,
+ crop_size,
+ single_category_max_area: float = 1.0,
+ ignored_category: int = None,
+ ):
+ """
+ Args:
+ crop_type, crop_size: same as in :class:`RandomCrop`
+ single_category_max_area: the maximum allowed area ratio of a
+ category. Set to 1.0 to disable
+ ignored_category: allow this category in the semantic segmentation
+ ground truth to exceed the area ratio. Usually set to the category
+ that's ignored in training.
+ """
+ self.crop_aug = RandomCrop(crop_type, crop_size)
+ self._init(locals())
+
+ def get_transform(self, image, sem_seg):
+ if self.single_category_max_area >= 1.0:
+ return self.crop_aug.get_transform(image)
+ else:
+ h, w = sem_seg.shape
+ for _ in range(10):
+ crop_size = self.crop_aug.get_crop_size((h, w))
+ y0 = np.random.randint(h - crop_size[0] + 1)
+ x0 = np.random.randint(w - crop_size[1] + 1)
+ sem_seg_temp = sem_seg[y0 : y0 + crop_size[0], x0 : x0 + crop_size[1]]
+ labels, cnt = np.unique(sem_seg_temp, return_counts=True)
+ if self.ignored_category is not None:
+ cnt = cnt[labels != self.ignored_category]
+ if len(cnt) > 1 and np.max(cnt) < np.sum(cnt) * self.single_category_max_area:
+ break
+ crop_tfm = CropTransform(x0, y0, crop_size[1], crop_size[0])
+ return crop_tfm
+
+
+class RandomExtent(Augmentation):
+ """
+ Outputs an image by cropping a random "subrect" of the source image.
+
+ The subrect can be parameterized to include pixels outside the source image,
+ in which case they will be set to zeros (i.e. black). The size of the output
+ image will vary with the size of the random subrect.
+ """
+
+ def __init__(self, scale_range, shift_range):
+ """
+ Args:
+ output_size (h, w): Dimensions of output image
+ scale_range (l, h): Range of input-to-output size scaling factor
+ shift_range (x, y): Range of shifts of the cropped subrect. The rect
+ is shifted by [w / 2 * Uniform(-x, x), h / 2 * Uniform(-y, y)],
+ where (w, h) is the (width, height) of the input image. Set each
+ component to zero to crop at the image's center.
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ img_h, img_w = image.shape[:2]
+
+ # Initialize src_rect to fit the input image.
+ src_rect = np.array([-0.5 * img_w, -0.5 * img_h, 0.5 * img_w, 0.5 * img_h])
+
+ # Apply a random scaling to the src_rect.
+ src_rect *= np.random.uniform(self.scale_range[0], self.scale_range[1])
+
+ # Apply a random shift to the coordinates origin.
+ src_rect[0::2] += self.shift_range[0] * img_w * (np.random.rand() - 0.5)
+ src_rect[1::2] += self.shift_range[1] * img_h * (np.random.rand() - 0.5)
+
+ # Map src_rect coordinates into image coordinates (center at corner).
+ src_rect[0::2] += 0.5 * img_w
+ src_rect[1::2] += 0.5 * img_h
+
+ return ExtentTransform(
+ src_rect=(src_rect[0], src_rect[1], src_rect[2], src_rect[3]),
+ output_size=(int(src_rect[3] - src_rect[1]), int(src_rect[2] - src_rect[0])),
+ )
+
+
+class RandomContrast(Augmentation):
+ """
+ Randomly transforms image contrast.
+
+ Contrast intensity is uniformly sampled in (intensity_min, intensity_max).
+ - intensity < 1 will reduce contrast
+ - intensity = 1 will preserve the input image
+ - intensity > 1 will increase contrast
+
+ See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
+ """
+
+ def __init__(self, intensity_min, intensity_max):
+ """
+ Args:
+ intensity_min (float): Minimum augmentation
+ intensity_max (float): Maximum augmentation
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ w = np.random.uniform(self.intensity_min, self.intensity_max)
+ return BlendTransform(src_image=image.mean(), src_weight=1 - w, dst_weight=w)
+
+
+class RandomBrightness(Augmentation):
+ """
+ Randomly transforms image brightness.
+
+ Brightness intensity is uniformly sampled in (intensity_min, intensity_max).
+ - intensity < 1 will reduce brightness
+ - intensity = 1 will preserve the input image
+ - intensity > 1 will increase brightness
+
+ See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
+ """
+
+ def __init__(self, intensity_min, intensity_max):
+ """
+ Args:
+ intensity_min (float): Minimum augmentation
+ intensity_max (float): Maximum augmentation
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ w = np.random.uniform(self.intensity_min, self.intensity_max)
+ return BlendTransform(src_image=0, src_weight=1 - w, dst_weight=w)
+
+
+class RandomSaturation(Augmentation):
+ """
+ Randomly transforms saturation of an RGB image.
+ Input images are assumed to have 'RGB' channel order.
+
+ Saturation intensity is uniformly sampled in (intensity_min, intensity_max).
+ - intensity < 1 will reduce saturation (make the image more grayscale)
+ - intensity = 1 will preserve the input image
+ - intensity > 1 will increase saturation
+
+ See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
+ """
+
+ def __init__(self, intensity_min, intensity_max):
+ """
+ Args:
+ intensity_min (float): Minimum augmentation (1 preserves input).
+ intensity_max (float): Maximum augmentation (1 preserves input).
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ assert image.shape[-1] == 3, "RandomSaturation only works on RGB images"
+ w = np.random.uniform(self.intensity_min, self.intensity_max)
+ grayscale = image.dot([0.299, 0.587, 0.114])[:, :, np.newaxis]
+ return BlendTransform(src_image=grayscale, src_weight=1 - w, dst_weight=w)
+
+
+class RandomLighting(Augmentation):
+ """
+ The "lighting" augmentation described in AlexNet, using fixed PCA over ImageNet.
+ Input images are assumed to have 'RGB' channel order.
+
+ The degree of color jittering is randomly sampled via a normal distribution,
+ with standard deviation given by the scale parameter.
+ """
+
+ def __init__(self, scale):
+ """
+ Args:
+ scale (float): Standard deviation of principal component weighting.
+ """
+ super().__init__()
+ self._init(locals())
+ self.eigen_vecs = np.array(
+ [[-0.5675, 0.7192, 0.4009], [-0.5808, -0.0045, -0.8140], [-0.5836, -0.6948, 0.4203]]
+ )
+ self.eigen_vals = np.array([0.2175, 0.0188, 0.0045])
+
+ def get_transform(self, image):
+ assert image.shape[-1] == 3, "RandomLighting only works on RGB images"
+ weights = np.random.normal(scale=self.scale, size=3)
+ return BlendTransform(
+ src_image=self.eigen_vecs.dot(weights * self.eigen_vals), src_weight=1.0, dst_weight=1.0
+ )
+
+
+class RandomResize(Augmentation):
+ """Randomly resize image to a target size in shape_list"""
+
+ def __init__(self, shape_list, interp=Image.BILINEAR):
+ """
+ Args:
+ shape_list: a list of shapes in (h, w)
+ interp: PIL interpolation method
+ """
+ self.shape_list = shape_list
+ self._init(locals())
+
+ def get_transform(self, image):
+ shape_idx = np.random.randint(low=0, high=len(self.shape_list))
+ h, w = self.shape_list[shape_idx]
+ return ResizeTransform(image.shape[0], image.shape[1], h, w, self.interp)
+
+
+class MinIoURandomCrop(Augmentation):
+ """Random crop the image & bboxes, the cropped patches have minimum IoU
+ requirement with original image & bboxes, the IoU threshold is randomly
+ selected from min_ious.
+
+ Args:
+ min_ious (tuple): minimum IoU threshold for all intersections with
+ bounding boxes
+ min_crop_size (float): minimum crop's size (i.e. h,w := a*h, a*w,
+ where a >= min_crop_size)
+ mode_trials: number of trials for sampling min_ious threshold
+ crop_trials: number of trials for sampling crop_size after cropping
+ """
+
+ def __init__(
+ self,
+ min_ious=(0.1, 0.3, 0.5, 0.7, 0.9),
+ min_crop_size=0.3,
+ mode_trials=1000,
+ crop_trials=50,
+ ):
+ self.min_ious = min_ious
+ self.sample_mode = (1, *min_ious, 0)
+ self.min_crop_size = min_crop_size
+ self.mode_trials = mode_trials
+ self.crop_trials = crop_trials
+
+ def get_transform(self, image, boxes):
+ """Call function to crop images and bounding boxes with minimum IoU
+ constraint.
+
+ Args:
+ boxes: ground truth boxes in (x1, y1, x2, y2) format
+ """
+ if boxes is None:
+ return NoOpTransform()
+ h, w, c = image.shape
+ for _ in range(self.mode_trials):
+ mode = random.choice(self.sample_mode)
+ self.mode = mode
+ if mode == 1:
+ return NoOpTransform()
+
+ min_iou = mode
+ for _ in range(self.crop_trials):
+ new_w = random.uniform(self.min_crop_size * w, w)
+ new_h = random.uniform(self.min_crop_size * h, h)
+
+ # h / w in [0.5, 2]
+ if new_h / new_w < 0.5 or new_h / new_w > 2:
+ continue
+
+ left = random.uniform(w - new_w)
+ top = random.uniform(h - new_h)
+
+ patch = np.array((int(left), int(top), int(left + new_w), int(top + new_h)))
+ # Line or point crop is not allowed
+ if patch[2] == patch[0] or patch[3] == patch[1]:
+ continue
+ overlaps = pairwise_iou(
+ Boxes(patch.reshape(-1, 4)), Boxes(boxes.reshape(-1, 4))
+ ).reshape(-1)
+ if len(overlaps) > 0 and overlaps.min() < min_iou:
+ continue
+
+ # center of boxes should inside the crop img
+ # only adjust boxes and instance masks when the gt is not empty
+ if len(overlaps) > 0:
+ # adjust boxes
+ def is_center_of_bboxes_in_patch(boxes, patch):
+ center = (boxes[:, :2] + boxes[:, 2:]) / 2
+ mask = (
+ (center[:, 0] > patch[0])
+ * (center[:, 1] > patch[1])
+ * (center[:, 0] < patch[2])
+ * (center[:, 1] < patch[3])
+ )
+ return mask
+
+ mask = is_center_of_bboxes_in_patch(boxes, patch)
+ if not mask.any():
+ continue
+ return CropTransform(int(left), int(top), int(new_w), int(new_h))
diff --git a/detectron2/detectron2/data/transforms/transform.py b/detectron2/detectron2/data/transforms/transform.py
new file mode 100755
index 0000000..de44b99
--- /dev/null
+++ b/detectron2/detectron2/data/transforms/transform.py
@@ -0,0 +1,351 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+See "Data Augmentation" tutorial for an overview of the system:
+https://detectron2.readthedocs.io/tutorials/augmentation.html
+"""
+
+import numpy as np
+import torch
+import torch.nn.functional as F
+from fvcore.transforms.transform import (
+ CropTransform,
+ HFlipTransform,
+ NoOpTransform,
+ Transform,
+ TransformList,
+)
+from PIL import Image
+
+try:
+ import cv2 # noqa
+except ImportError:
+ # OpenCV is an optional dependency at the moment
+ pass
+
+__all__ = [
+ "ExtentTransform",
+ "ResizeTransform",
+ "RotationTransform",
+ "ColorTransform",
+ "PILColorTransform",
+]
+
+
+class ExtentTransform(Transform):
+ """
+ Extracts a subregion from the source image and scales it to the output size.
+
+ The fill color is used to map pixels from the source rect that fall outside
+ the source image.
+
+ See: https://pillow.readthedocs.io/en/latest/PIL.html#PIL.ImageTransform.ExtentTransform
+ """
+
+ def __init__(self, src_rect, output_size, interp=Image.LINEAR, fill=0):
+ """
+ Args:
+ src_rect (x0, y0, x1, y1): src coordinates
+ output_size (h, w): dst image size
+ interp: PIL interpolation methods
+ fill: Fill color used when src_rect extends outside image
+ """
+ super().__init__()
+ self._set_attributes(locals())
+
+ def apply_image(self, img, interp=None):
+ h, w = self.output_size
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ pil_image = Image.fromarray(img[:, :, 0], mode="L")
+ else:
+ pil_image = Image.fromarray(img)
+ pil_image = pil_image.transform(
+ size=(w, h),
+ method=Image.EXTENT,
+ data=self.src_rect,
+ resample=interp if interp else self.interp,
+ fill=self.fill,
+ )
+ ret = np.asarray(pil_image)
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ ret = np.expand_dims(ret, -1)
+ return ret
+
+ def apply_coords(self, coords):
+ # Transform image center from source coordinates into output coordinates
+ # and then map the new origin to the corner of the output image.
+ h, w = self.output_size
+ x0, y0, x1, y1 = self.src_rect
+ new_coords = coords.astype(np.float32)
+ new_coords[:, 0] -= 0.5 * (x0 + x1)
+ new_coords[:, 1] -= 0.5 * (y0 + y1)
+ new_coords[:, 0] *= w / (x1 - x0)
+ new_coords[:, 1] *= h / (y1 - y0)
+ new_coords[:, 0] += 0.5 * w
+ new_coords[:, 1] += 0.5 * h
+ return new_coords
+
+ def apply_segmentation(self, segmentation):
+ segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
+ return segmentation
+
+
+class ResizeTransform(Transform):
+ """
+ Resize the image to a target size.
+ """
+
+ def __init__(self, h, w, new_h, new_w, interp=None):
+ """
+ Args:
+ h, w (int): original image size
+ new_h, new_w (int): new image size
+ interp: PIL interpolation methods, defaults to bilinear.
+ """
+ # TODO decide on PIL vs opencv
+ super().__init__()
+ if interp is None:
+ interp = Image.BILINEAR
+ self._set_attributes(locals())
+
+ def apply_image(self, img, interp=None):
+ assert img.shape[:2] == (self.h, self.w)
+ assert len(img.shape) <= 4
+ interp_method = interp if interp is not None else self.interp
+
+ if img.dtype == np.uint8:
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ pil_image = Image.fromarray(img[:, :, 0], mode="L")
+ else:
+ pil_image = Image.fromarray(img)
+ pil_image = pil_image.resize((self.new_w, self.new_h), interp_method)
+ ret = np.asarray(pil_image)
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ ret = np.expand_dims(ret, -1)
+ else:
+ # PIL only supports uint8
+ if any(x < 0 for x in img.strides):
+ img = np.ascontiguousarray(img)
+ img = torch.from_numpy(img)
+ shape = list(img.shape)
+ shape_4d = shape[:2] + [1] * (4 - len(shape)) + shape[2:]
+ img = img.view(shape_4d).permute(2, 3, 0, 1) # hw(c) -> nchw
+ _PIL_RESIZE_TO_INTERPOLATE_MODE = {
+ Image.NEAREST: "nearest",
+ Image.BILINEAR: "bilinear",
+ Image.BICUBIC: "bicubic",
+ }
+ mode = _PIL_RESIZE_TO_INTERPOLATE_MODE[interp_method]
+ align_corners = None if mode == "nearest" else False
+ img = F.interpolate(
+ img, (self.new_h, self.new_w), mode=mode, align_corners=align_corners
+ )
+ shape[:2] = (self.new_h, self.new_w)
+ ret = img.permute(2, 3, 0, 1).view(shape).numpy() # nchw -> hw(c)
+
+ return ret
+
+ def apply_coords(self, coords):
+ coords[:, 0] = coords[:, 0] * (self.new_w * 1.0 / self.w)
+ coords[:, 1] = coords[:, 1] * (self.new_h * 1.0 / self.h)
+ return coords
+
+ def apply_segmentation(self, segmentation):
+ segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
+ return segmentation
+
+ def inverse(self):
+ return ResizeTransform(self.new_h, self.new_w, self.h, self.w, self.interp)
+
+
+class RotationTransform(Transform):
+ """
+ This method returns a copy of this image, rotated the given
+ number of degrees counter clockwise around its center.
+ """
+
+ def __init__(self, h, w, angle, expand=True, center=None, interp=None):
+ """
+ Args:
+ h, w (int): original image size
+ angle (float): degrees for rotation
+ expand (bool): choose if the image should be resized to fit the whole
+ rotated image (default), or simply cropped
+ center (tuple (width, height)): coordinates of the rotation center
+ if left to None, the center will be fit to the center of each image
+ center has no effect if expand=True because it only affects shifting
+ interp: cv2 interpolation method, default cv2.INTER_LINEAR
+ """
+ super().__init__()
+ image_center = np.array((w / 2, h / 2))
+ if center is None:
+ center = image_center
+ if interp is None:
+ interp = cv2.INTER_LINEAR
+ abs_cos, abs_sin = (abs(np.cos(np.deg2rad(angle))), abs(np.sin(np.deg2rad(angle))))
+ if expand:
+ # find the new width and height bounds
+ bound_w, bound_h = np.rint(
+ [h * abs_sin + w * abs_cos, h * abs_cos + w * abs_sin]
+ ).astype(int)
+ else:
+ bound_w, bound_h = w, h
+
+ self._set_attributes(locals())
+ self.rm_coords = self.create_rotation_matrix()
+ # Needed because of this problem https://github.com/opencv/opencv/issues/11784
+ self.rm_image = self.create_rotation_matrix(offset=-0.5)
+
+ def apply_image(self, img, interp=None):
+ """
+ img should be a numpy array, formatted as Height * Width * Nchannels
+ """
+ if len(img) == 0 or self.angle % 360 == 0:
+ return img
+ assert img.shape[:2] == (self.h, self.w)
+ interp = interp if interp is not None else self.interp
+ return cv2.warpAffine(img, self.rm_image, (self.bound_w, self.bound_h), flags=interp)
+
+ def apply_coords(self, coords):
+ """
+ coords should be a N * 2 array-like, containing N couples of (x, y) points
+ """
+ coords = np.asarray(coords, dtype=float)
+ if len(coords) == 0 or self.angle % 360 == 0:
+ return coords
+ return cv2.transform(coords[:, np.newaxis, :], self.rm_coords)[:, 0, :]
+
+ def apply_segmentation(self, segmentation):
+ segmentation = self.apply_image(segmentation, interp=cv2.INTER_NEAREST)
+ return segmentation
+
+ def create_rotation_matrix(self, offset=0):
+ center = (self.center[0] + offset, self.center[1] + offset)
+ rm = cv2.getRotationMatrix2D(tuple(center), self.angle, 1)
+ if self.expand:
+ # Find the coordinates of the center of rotation in the new image
+ # The only point for which we know the future coordinates is the center of the image
+ rot_im_center = cv2.transform(self.image_center[None, None, :] + offset, rm)[0, 0, :]
+ new_center = np.array([self.bound_w / 2, self.bound_h / 2]) + offset - rot_im_center
+ # shift the rotation center to the new coordinates
+ rm[:, 2] += new_center
+ return rm
+
+ def inverse(self):
+ """
+ The inverse is to rotate it back with expand, and crop to get the original shape.
+ """
+ if not self.expand: # Not possible to inverse if a part of the image is lost
+ raise NotImplementedError()
+ rotation = RotationTransform(
+ self.bound_h, self.bound_w, -self.angle, True, None, self.interp
+ )
+ crop = CropTransform(
+ (rotation.bound_w - self.w) // 2, (rotation.bound_h - self.h) // 2, self.w, self.h
+ )
+ return TransformList([rotation, crop])
+
+
+class ColorTransform(Transform):
+ """
+ Generic wrapper for any photometric transforms.
+ These transformations should only affect the color space and
+ not the coordinate space of the image (e.g. annotation
+ coordinates such as bounding boxes should not be changed)
+ """
+
+ def __init__(self, op):
+ """
+ Args:
+ op (Callable): operation to be applied to the image,
+ which takes in an ndarray and returns an ndarray.
+ """
+ if not callable(op):
+ raise ValueError("op parameter should be callable")
+ super().__init__()
+ self._set_attributes(locals())
+
+ def apply_image(self, img):
+ return self.op(img)
+
+ def apply_coords(self, coords):
+ return coords
+
+ def inverse(self):
+ return NoOpTransform()
+
+ def apply_segmentation(self, segmentation):
+ return segmentation
+
+
+class PILColorTransform(ColorTransform):
+ """
+ Generic wrapper for PIL Photometric image transforms,
+ which affect the color space and not the coordinate
+ space of the image
+ """
+
+ def __init__(self, op):
+ """
+ Args:
+ op (Callable): operation to be applied to the image,
+ which takes in a PIL Image and returns a transformed
+ PIL Image.
+ For reference on possible operations see:
+ - https://pillow.readthedocs.io/en/stable/
+ """
+ if not callable(op):
+ raise ValueError("op parameter should be callable")
+ super().__init__(op)
+
+ def apply_image(self, img):
+ img = Image.fromarray(img)
+ return np.asarray(super().apply_image(img))
+
+
+def HFlip_rotated_box(transform, rotated_boxes):
+ """
+ Apply the horizontal flip transform on rotated boxes.
+
+ Args:
+ rotated_boxes (ndarray): Nx5 floating point array of
+ (x_center, y_center, width, height, angle_degrees) format
+ in absolute coordinates.
+ """
+ # Transform x_center
+ rotated_boxes[:, 0] = transform.width - rotated_boxes[:, 0]
+ # Transform angle
+ rotated_boxes[:, 4] = -rotated_boxes[:, 4]
+ return rotated_boxes
+
+
+def Resize_rotated_box(transform, rotated_boxes):
+ """
+ Apply the resizing transform on rotated boxes. For details of how these (approximation)
+ formulas are derived, please refer to :meth:`RotatedBoxes.scale`.
+
+ Args:
+ rotated_boxes (ndarray): Nx5 floating point array of
+ (x_center, y_center, width, height, angle_degrees) format
+ in absolute coordinates.
+ """
+ scale_factor_x = transform.new_w * 1.0 / transform.w
+ scale_factor_y = transform.new_h * 1.0 / transform.h
+ rotated_boxes[:, 0] *= scale_factor_x
+ rotated_boxes[:, 1] *= scale_factor_y
+ theta = rotated_boxes[:, 4] * np.pi / 180.0
+ c = np.cos(theta)
+ s = np.sin(theta)
+ rotated_boxes[:, 2] *= np.sqrt(np.square(scale_factor_x * c) + np.square(scale_factor_y * s))
+ rotated_boxes[:, 3] *= np.sqrt(np.square(scale_factor_x * s) + np.square(scale_factor_y * c))
+ rotated_boxes[:, 4] = np.arctan2(scale_factor_x * s, scale_factor_y * c) * 180 / np.pi
+
+ return rotated_boxes
+
+
+HFlipTransform.register_type("rotated_box", HFlip_rotated_box)
+ResizeTransform.register_type("rotated_box", Resize_rotated_box)
+
+# not necessary any more with latest fvcore
+NoOpTransform.register_type("rotated_box", lambda t, x: x)
diff --git a/detectron2/detectron2/engine/__init__.py b/detectron2/detectron2/engine/__init__.py
new file mode 100755
index 0000000..08a6157
--- /dev/null
+++ b/detectron2/detectron2/engine/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+from .launch import *
+from .train_loop import *
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
+
+
+# prefer to let hooks and defaults live in separate namespaces (therefore not in __all__)
+# but still make them available here
+from .hooks import *
+from .defaults import *
diff --git a/detectron2/detectron2/engine/defaults.py b/detectron2/detectron2/engine/defaults.py
new file mode 100755
index 0000000..5b95257
--- /dev/null
+++ b/detectron2/detectron2/engine/defaults.py
@@ -0,0 +1,715 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+This file contains components with some default boilerplate logic user may need
+in training / testing. They will not work for everyone, but many users may find them useful.
+
+The behavior of functions/classes in this file is subject to change,
+since they are meant to represent the "common default behavior" people need in their projects.
+"""
+
+import argparse
+import logging
+import os
+import sys
+import weakref
+from collections import OrderedDict
+from typing import Optional
+import torch
+from fvcore.nn.precise_bn import get_bn_modules
+from omegaconf import OmegaConf
+from torch.nn.parallel import DistributedDataParallel
+
+import detectron2.data.transforms as T
+from detectron2.checkpoint import DetectionCheckpointer
+from detectron2.config import CfgNode, LazyConfig
+from detectron2.data import (
+ MetadataCatalog,
+ build_detection_test_loader,
+ build_detection_train_loader,
+)
+from detectron2.evaluation import (
+ DatasetEvaluator,
+ inference_on_dataset,
+ print_csv_format,
+ verify_results,
+)
+from detectron2.modeling import build_model
+from detectron2.solver import build_lr_scheduler, build_optimizer
+from detectron2.utils import comm
+from detectron2.utils.collect_env import collect_env_info
+from detectron2.utils.env import seed_all_rng
+from detectron2.utils.events import CommonMetricPrinter, JSONWriter, TensorboardXWriter
+from detectron2.utils.file_io import PathManager
+from detectron2.utils.logger import setup_logger
+
+from . import hooks
+from .train_loop import AMPTrainer, SimpleTrainer, TrainerBase
+
+__all__ = [
+ "create_ddp_model",
+ "default_argument_parser",
+ "default_setup",
+ "default_writers",
+ "DefaultPredictor",
+ "DefaultTrainer",
+]
+
+
+def create_ddp_model(model, *, fp16_compression=False, **kwargs):
+ """
+ Create a DistributedDataParallel model if there are >1 processes.
+
+ Args:
+ model: a torch.nn.Module
+ fp16_compression: add fp16 compression hooks to the ddp object.
+ See more at https://pytorch.org/docs/stable/ddp_comm_hooks.html#torch.distributed.algorithms.ddp_comm_hooks.default_hooks.fp16_compress_hook
+ kwargs: other arguments of :module:`torch.nn.parallel.DistributedDataParallel`.
+ """ # noqa
+ if comm.get_world_size() == 1:
+ return model
+ if "device_ids" not in kwargs:
+ kwargs["device_ids"] = [comm.get_local_rank()]
+ ddp = DistributedDataParallel(model, **kwargs)
+ if fp16_compression:
+ from torch.distributed.algorithms.ddp_comm_hooks import default as comm_hooks
+
+ ddp.register_comm_hook(state=None, hook=comm_hooks.fp16_compress_hook)
+ return ddp
+
+
+def default_argument_parser(epilog=None):
+ """
+ Create a parser with some common arguments used by detectron2 users.
+
+ Args:
+ epilog (str): epilog passed to ArgumentParser describing the usage.
+
+ Returns:
+ argparse.ArgumentParser:
+ """
+ parser = argparse.ArgumentParser(
+ epilog=epilog
+ or f"""
+Examples:
+
+Run on single machine:
+ $ {sys.argv[0]} --num-gpus 8 --config-file cfg.yaml
+
+Change some config options:
+ $ {sys.argv[0]} --config-file cfg.yaml MODEL.WEIGHTS /path/to/weight.pth SOLVER.BASE_LR 0.001
+
+Run on multiple machines:
+ (machine0)$ {sys.argv[0]} --machine-rank 0 --num-machines 2 --dist-url [--other-flags]
+ (machine1)$ {sys.argv[0]} --machine-rank 1 --num-machines 2 --dist-url [--other-flags]
+""",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+ parser.add_argument("--config-file", default="", metavar="FILE", help="path to config file")
+ parser.add_argument(
+ "--resume",
+ action="store_true",
+ help="Whether to attempt to resume from the checkpoint directory. "
+ "See documentation of `DefaultTrainer.resume_or_load()` for what it means.",
+ )
+ parser.add_argument("--eval-only", action="store_true", help="perform evaluation only")
+ parser.add_argument("--num-gpus", type=int, default=1, help="number of gpus *per machine*")
+ parser.add_argument("--num-machines", type=int, default=1, help="total number of machines")
+ parser.add_argument(
+ "--machine-rank", type=int, default=0, help="the rank of this machine (unique per machine)"
+ )
+
+ # PyTorch still may leave orphan processes in multi-gpu training.
+ # Therefore we use a deterministic way to obtain port,
+ # so that users are aware of orphan processes by seeing the port occupied.
+ port = 2**15 + 2**14 + hash(os.getuid() if sys.platform != "win32" else 1) % 2**14
+ parser.add_argument(
+ "--dist-url",
+ default="tcp://127.0.0.1:{}".format(port),
+ help="initialization URL for pytorch distributed backend. See "
+ "https://pytorch.org/docs/stable/distributed.html for details.",
+ )
+ parser.add_argument(
+ "opts",
+ help="""
+Modify config options at the end of the command. For Yacs configs, use
+space-separated "PATH.KEY VALUE" pairs.
+For python-based LazyConfig, use "path.key=value".
+ """.strip(),
+ default=None,
+ nargs=argparse.REMAINDER,
+ )
+ return parser
+
+
+def _try_get_key(cfg, *keys, default=None):
+ """
+ Try select keys from cfg until the first key that exists. Otherwise return default.
+ """
+ if isinstance(cfg, CfgNode):
+ cfg = OmegaConf.create(cfg.dump())
+ for k in keys:
+ none = object()
+ p = OmegaConf.select(cfg, k, default=none)
+ if p is not none:
+ return p
+ return default
+
+
+def _highlight(code, filename):
+ try:
+ import pygments
+ except ImportError:
+ return code
+
+ from pygments.lexers import Python3Lexer, YamlLexer
+ from pygments.formatters import Terminal256Formatter
+
+ lexer = Python3Lexer() if filename.endswith(".py") else YamlLexer()
+ code = pygments.highlight(code, lexer, Terminal256Formatter(style="monokai"))
+ return code
+
+
+def default_setup(cfg, args):
+ """
+ Perform some basic common setups at the beginning of a job, including:
+
+ 1. Set up the detectron2 logger
+ 2. Log basic information about environment, cmdline arguments, and config
+ 3. Backup the config to the output directory
+
+ Args:
+ cfg (CfgNode or omegaconf.DictConfig): the full config to be used
+ args (argparse.NameSpace): the command line arguments to be logged
+ """
+ output_dir = _try_get_key(cfg, "OUTPUT_DIR", "output_dir", "train.output_dir")
+ if comm.is_main_process() and output_dir:
+ PathManager.mkdirs(output_dir)
+
+ rank = comm.get_rank()
+ setup_logger(output_dir, distributed_rank=rank, name="fvcore")
+ logger = setup_logger(output_dir, distributed_rank=rank)
+
+ logger.info("Rank of current process: {}. World size: {}".format(rank, comm.get_world_size()))
+ logger.info("Environment info:\n" + collect_env_info())
+
+ logger.info("Command line arguments: " + str(args))
+ if hasattr(args, "config_file") and args.config_file != "":
+ logger.info(
+ "Contents of args.config_file={}:\n{}".format(
+ args.config_file,
+ _highlight(PathManager.open(args.config_file, "r").read(), args.config_file),
+ )
+ )
+
+ if comm.is_main_process() and output_dir:
+ # Note: some of our scripts may expect the existence of
+ # config.yaml in output directory
+ path = os.path.join(output_dir, "config.yaml")
+ if isinstance(cfg, CfgNode):
+ logger.info("Running with full config:\n{}".format(_highlight(cfg.dump(), ".yaml")))
+ with PathManager.open(path, "w") as f:
+ f.write(cfg.dump())
+ else:
+ LazyConfig.save(cfg, path)
+ logger.info("Full config saved to {}".format(path))
+
+ # make sure each worker has a different, yet deterministic seed if specified
+ seed = _try_get_key(cfg, "SEED", "train.seed", default=-1)
+ seed_all_rng(None if seed < 0 else seed + rank)
+
+ # cudnn benchmark has large overhead. It shouldn't be used considering the small size of
+ # typical validation set.
+ if not (hasattr(args, "eval_only") and args.eval_only):
+ torch.backends.cudnn.benchmark = _try_get_key(
+ cfg, "CUDNN_BENCHMARK", "train.cudnn_benchmark", default=False
+ )
+
+
+def default_writers(output_dir: str, max_iter: Optional[int] = None):
+ """
+ Build a list of :class:`EventWriter` to be used.
+ It now consists of a :class:`CommonMetricPrinter`,
+ :class:`TensorboardXWriter` and :class:`JSONWriter`.
+
+ Args:
+ output_dir: directory to store JSON metrics and tensorboard events
+ max_iter: the total number of iterations
+
+ Returns:
+ list[EventWriter]: a list of :class:`EventWriter` objects.
+ """
+ PathManager.mkdirs(output_dir)
+ return [
+ # It may not always print what you want to see, since it prints "common" metrics only.
+ CommonMetricPrinter(max_iter),
+ JSONWriter(os.path.join(output_dir, "metrics.json")),
+ TensorboardXWriter(output_dir),
+ ]
+
+
+class DefaultPredictor:
+ """
+ Create a simple end-to-end predictor with the given config that runs on
+ single device for a single input image.
+
+ Compared to using the model directly, this class does the following additions:
+
+ 1. Load checkpoint from `cfg.MODEL.WEIGHTS`.
+ 2. Always take BGR image as the input and apply conversion defined by `cfg.INPUT.FORMAT`.
+ 3. Apply resizing defined by `cfg.INPUT.{MIN,MAX}_SIZE_TEST`.
+ 4. Take one input image and produce a single output, instead of a batch.
+
+ This is meant for simple demo purposes, so it does the above steps automatically.
+ This is not meant for benchmarks or running complicated inference logic.
+ If you'd like to do anything more complicated, please refer to its source code as
+ examples to build and use the model manually.
+
+ Attributes:
+ metadata (Metadata): the metadata of the underlying dataset, obtained from
+ cfg.DATASETS.TEST.
+
+ Examples:
+ ::
+ pred = DefaultPredictor(cfg)
+ inputs = cv2.imread("input.jpg")
+ outputs = pred(inputs)
+ """
+
+ def __init__(self, cfg):
+ self.cfg = cfg.clone() # cfg can be modified by model
+ self.model = build_model(self.cfg)
+ self.model.eval()
+ if len(cfg.DATASETS.TEST):
+ self.metadata = MetadataCatalog.get(cfg.DATASETS.TEST[0])
+
+ checkpointer = DetectionCheckpointer(self.model)
+ checkpointer.load(cfg.MODEL.WEIGHTS)
+
+ self.aug = T.ResizeShortestEdge(
+ [cfg.INPUT.MIN_SIZE_TEST, cfg.INPUT.MIN_SIZE_TEST], cfg.INPUT.MAX_SIZE_TEST
+ )
+
+ self.input_format = cfg.INPUT.FORMAT
+ assert self.input_format in ["RGB", "BGR"], self.input_format
+
+ def __call__(self, original_image):
+ """
+ Args:
+ original_image (np.ndarray): an image of shape (H, W, C) (in BGR order).
+
+ Returns:
+ predictions (dict):
+ the output of the model for one image only.
+ See :doc:`/tutorials/models` for details about the format.
+ """
+ with torch.no_grad(): # https://github.com/sphinx-doc/sphinx/issues/4258
+ # Apply pre-processing to image.
+ if self.input_format == "RGB":
+ # whether the model expects BGR inputs or RGB
+ original_image = original_image[:, :, ::-1]
+ height, width = original_image.shape[:2]
+ image = self.aug.get_transform(original_image).apply_image(original_image)
+ image = torch.as_tensor(image.astype("float32").transpose(2, 0, 1))
+
+ inputs = {"image": image, "height": height, "width": width}
+ predictions = self.model([inputs])[0]
+ return predictions
+
+
+class DefaultTrainer(TrainerBase):
+ """
+ A trainer with default training logic. It does the following:
+
+ 1. Create a :class:`SimpleTrainer` using model, optimizer, dataloader
+ defined by the given config. Create a LR scheduler defined by the config.
+ 2. Load the last checkpoint or `cfg.MODEL.WEIGHTS`, if exists, when
+ `resume_or_load` is called.
+ 3. Register a few common hooks defined by the config.
+
+ It is created to simplify the **standard model training workflow** and reduce code boilerplate
+ for users who only need the standard training workflow, with standard features.
+ It means this class makes *many assumptions* about your training logic that
+ may easily become invalid in a new research. In fact, any assumptions beyond those made in the
+ :class:`SimpleTrainer` are too much for research.
+
+ The code of this class has been annotated about restrictive assumptions it makes.
+ When they do not work for you, you're encouraged to:
+
+ 1. Overwrite methods of this class, OR:
+ 2. Use :class:`SimpleTrainer`, which only does minimal SGD training and
+ nothing else. You can then add your own hooks if needed. OR:
+ 3. Write your own training loop similar to `tools/plain_train_net.py`.
+
+ See the :doc:`/tutorials/training` tutorials for more details.
+
+ Note that the behavior of this class, like other functions/classes in
+ this file, is not stable, since it is meant to represent the "common default behavior".
+ It is only guaranteed to work well with the standard models and training workflow in detectron2.
+ To obtain more stable behavior, write your own training logic with other public APIs.
+
+ Examples:
+ ::
+ trainer = DefaultTrainer(cfg)
+ trainer.resume_or_load() # load last checkpoint or MODEL.WEIGHTS
+ trainer.train()
+
+ Attributes:
+ scheduler:
+ checkpointer (DetectionCheckpointer):
+ cfg (CfgNode):
+ """
+
+ def __init__(self, cfg):
+ """
+ Args:
+ cfg (CfgNode):
+ """
+ super().__init__()
+ logger = logging.getLogger("detectron2")
+ if not logger.isEnabledFor(logging.INFO): # setup_logger is not called for d2
+ setup_logger()
+ cfg = DefaultTrainer.auto_scale_workers(cfg, comm.get_world_size())
+
+ # Assume these objects must be constructed in this order.
+ model = self.build_model(cfg)
+ optimizer = self.build_optimizer(cfg, model)
+ data_loader = self.build_train_loader(cfg)
+
+ model = create_ddp_model(model, broadcast_buffers=False)
+ self._trainer = (AMPTrainer if cfg.SOLVER.AMP.ENABLED else SimpleTrainer)(
+ model, data_loader, optimizer
+ )
+
+ self.scheduler = self.build_lr_scheduler(cfg, optimizer)
+ self.checkpointer = DetectionCheckpointer(
+ # Assume you want to save checkpoints together with logs/statistics
+ model,
+ cfg.OUTPUT_DIR,
+ trainer=weakref.proxy(self),
+ )
+ self.start_iter = 0
+ self.max_iter = cfg.SOLVER.MAX_ITER
+ self.cfg = cfg
+
+ self.register_hooks(self.build_hooks())
+
+ def resume_or_load(self, resume=True):
+ """
+ If `resume==True` and `cfg.OUTPUT_DIR` contains the last checkpoint (defined by
+ a `last_checkpoint` file), resume from the file. Resuming means loading all
+ available states (eg. optimizer and scheduler) and update iteration counter
+ from the checkpoint. ``cfg.MODEL.WEIGHTS`` will not be used.
+
+ Otherwise, this is considered as an independent training. The method will load model
+ weights from the file `cfg.MODEL.WEIGHTS` (but will not load other states) and start
+ from iteration 0.
+
+ Args:
+ resume (bool): whether to do resume or not
+ """
+ self.checkpointer.resume_or_load(self.cfg.MODEL.WEIGHTS, resume=resume)
+ if resume and self.checkpointer.has_checkpoint():
+ # The checkpoint stores the training iteration that just finished, thus we start
+ # at the next iteration
+ self.start_iter = self.iter + 1
+
+ def build_hooks(self):
+ """
+ Build a list of default hooks, including timing, evaluation,
+ checkpointing, lr scheduling, precise BN, writing events.
+
+ Returns:
+ list[HookBase]:
+ """
+ cfg = self.cfg.clone()
+ cfg.defrost()
+ cfg.DATALOADER.NUM_WORKERS = 0 # save some memory and time for PreciseBN
+
+ ret = [
+ hooks.IterationTimer(),
+ hooks.LRScheduler(),
+ hooks.PreciseBN(
+ # Run at the same freq as (but before) evaluation.
+ cfg.TEST.EVAL_PERIOD,
+ self.model,
+ # Build a new data loader to not affect training
+ self.build_train_loader(cfg),
+ cfg.TEST.PRECISE_BN.NUM_ITER,
+ )
+ if cfg.TEST.PRECISE_BN.ENABLED and get_bn_modules(self.model)
+ else None,
+ ]
+
+ # Do PreciseBN before checkpointer, because it updates the model and need to
+ # be saved by checkpointer.
+ # This is not always the best: if checkpointing has a different frequency,
+ # some checkpoints may have more precise statistics than others.
+ if comm.is_main_process():
+ ret.append(hooks.PeriodicCheckpointer(self.checkpointer, cfg.SOLVER.CHECKPOINT_PERIOD))
+
+ def test_and_save_results():
+ self._last_eval_results = self.test(self.cfg, self.model)
+ return self._last_eval_results
+
+ # Do evaluation after checkpointer, because then if it fails,
+ # we can use the saved checkpoint to debug.
+ ret.append(hooks.EvalHook(cfg.TEST.EVAL_PERIOD, test_and_save_results))
+
+ if comm.is_main_process():
+ # Here the default print/log frequency of each writer is used.
+ # run writers in the end, so that evaluation metrics are written
+ ret.append(hooks.PeriodicWriter(self.build_writers(), period=20))
+ return ret
+
+ def build_writers(self):
+ """
+ Build a list of writers to be used using :func:`default_writers()`.
+ If you'd like a different list of writers, you can overwrite it in
+ your trainer.
+
+ Returns:
+ list[EventWriter]: a list of :class:`EventWriter` objects.
+ """
+ return default_writers(self.cfg.OUTPUT_DIR, self.max_iter)
+
+ def train(self):
+ """
+ Run training.
+
+ Returns:
+ OrderedDict of results, if evaluation is enabled. Otherwise None.
+ """
+ super().train(self.start_iter, self.max_iter)
+ if len(self.cfg.TEST.EXPECTED_RESULTS) and comm.is_main_process():
+ assert hasattr(
+ self, "_last_eval_results"
+ ), "No evaluation results obtained during training!"
+ verify_results(self.cfg, self._last_eval_results)
+ return self._last_eval_results
+
+ def run_step(self):
+ self._trainer.iter = self.iter
+ self._trainer.run_step()
+
+ def state_dict(self):
+ ret = super().state_dict()
+ ret["_trainer"] = self._trainer.state_dict()
+ return ret
+
+ def load_state_dict(self, state_dict):
+ super().load_state_dict(state_dict)
+ self._trainer.load_state_dict(state_dict["_trainer"])
+
+ @classmethod
+ def build_model(cls, cfg):
+ """
+ Returns:
+ torch.nn.Module:
+
+ It now calls :func:`detectron2.modeling.build_model`.
+ Overwrite it if you'd like a different model.
+ """
+ model = build_model(cfg)
+ logger = logging.getLogger(__name__)
+ logger.info("Model:\n{}".format(model))
+ return model
+
+ @classmethod
+ def build_optimizer(cls, cfg, model):
+ """
+ Returns:
+ torch.optim.Optimizer:
+
+ It now calls :func:`detectron2.solver.build_optimizer`.
+ Overwrite it if you'd like a different optimizer.
+ """
+ return build_optimizer(cfg, model)
+
+ @classmethod
+ def build_lr_scheduler(cls, cfg, optimizer):
+ """
+ It now calls :func:`detectron2.solver.build_lr_scheduler`.
+ Overwrite it if you'd like a different scheduler.
+ """
+ return build_lr_scheduler(cfg, optimizer)
+
+ @classmethod
+ def build_train_loader(cls, cfg):
+ """
+ Returns:
+ iterable
+
+ It now calls :func:`detectron2.data.build_detection_train_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ return build_detection_train_loader(cfg)
+
+ @classmethod
+ def build_test_loader(cls, cfg, dataset_name):
+ """
+ Returns:
+ iterable
+
+ It now calls :func:`detectron2.data.build_detection_test_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ return build_detection_test_loader(cfg, dataset_name)
+
+ @classmethod
+ def build_evaluator(cls, cfg, dataset_name):
+ """
+ Returns:
+ DatasetEvaluator or None
+
+ It is not implemented by default.
+ """
+ raise NotImplementedError(
+ """
+If you want DefaultTrainer to automatically run evaluation,
+please implement `build_evaluator()` in subclasses (see train_net.py for example).
+Alternatively, you can call evaluation functions yourself (see Colab balloon tutorial for example).
+"""
+ )
+
+ @classmethod
+ def test(cls, cfg, model, evaluators=None):
+ """
+ Evaluate the given model. The given model is expected to already contain
+ weights to evaluate.
+
+ Args:
+ cfg (CfgNode):
+ model (nn.Module):
+ evaluators (list[DatasetEvaluator] or None): if None, will call
+ :meth:`build_evaluator`. Otherwise, must have the same length as
+ ``cfg.DATASETS.TEST``.
+
+ Returns:
+ dict: a dict of result metrics
+ """
+ logger = logging.getLogger(__name__)
+ if isinstance(evaluators, DatasetEvaluator):
+ evaluators = [evaluators]
+ if evaluators is not None:
+ assert len(cfg.DATASETS.TEST) == len(evaluators), "{} != {}".format(
+ len(cfg.DATASETS.TEST), len(evaluators)
+ )
+
+ results = OrderedDict()
+ for idx, dataset_name in enumerate(cfg.DATASETS.TEST):
+ data_loader = cls.build_test_loader(cfg, dataset_name)
+ # When evaluators are passed in as arguments,
+ # implicitly assume that evaluators can be created before data_loader.
+ if evaluators is not None:
+ evaluator = evaluators[idx]
+ else:
+ try:
+ evaluator = cls.build_evaluator(cfg, dataset_name)
+ except NotImplementedError:
+ logger.warn(
+ "No evaluator found. Use `DefaultTrainer.test(evaluators=)`, "
+ "or implement its `build_evaluator` method."
+ )
+ results[dataset_name] = {}
+ continue
+ results_i = inference_on_dataset(model, data_loader, evaluator)
+ results[dataset_name] = results_i
+ if comm.is_main_process():
+ assert isinstance(
+ results_i, dict
+ ), "Evaluator must return a dict on the main process. Got {} instead.".format(
+ results_i
+ )
+ logger.info("Evaluation results for {} in csv format:".format(dataset_name))
+ print_csv_format(results_i)
+
+ if len(results) == 1:
+ results = list(results.values())[0]
+ return results
+
+ @staticmethod
+ def auto_scale_workers(cfg, num_workers: int):
+ """
+ When the config is defined for certain number of workers (according to
+ ``cfg.SOLVER.REFERENCE_WORLD_SIZE``) that's different from the number of
+ workers currently in use, returns a new cfg where the total batch size
+ is scaled so that the per-GPU batch size stays the same as the
+ original ``IMS_PER_BATCH // REFERENCE_WORLD_SIZE``.
+
+ Other config options are also scaled accordingly:
+ * training steps and warmup steps are scaled inverse proportionally.
+ * learning rate are scaled proportionally, following :paper:`ImageNet in 1h`.
+
+ For example, with the original config like the following:
+
+ .. code-block:: yaml
+
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.1
+ REFERENCE_WORLD_SIZE: 8
+ MAX_ITER: 5000
+ STEPS: (4000,)
+ CHECKPOINT_PERIOD: 1000
+
+ When this config is used on 16 GPUs instead of the reference number 8,
+ calling this method will return a new config with:
+
+ .. code-block:: yaml
+
+ IMS_PER_BATCH: 32
+ BASE_LR: 0.2
+ REFERENCE_WORLD_SIZE: 16
+ MAX_ITER: 2500
+ STEPS: (2000,)
+ CHECKPOINT_PERIOD: 500
+
+ Note that both the original config and this new config can be trained on 16 GPUs.
+ It's up to user whether to enable this feature (by setting ``REFERENCE_WORLD_SIZE``).
+
+ Returns:
+ CfgNode: a new config. Same as original if ``cfg.SOLVER.REFERENCE_WORLD_SIZE==0``.
+ """
+ old_world_size = cfg.SOLVER.REFERENCE_WORLD_SIZE
+ if old_world_size == 0 or old_world_size == num_workers:
+ return cfg
+ cfg = cfg.clone()
+ frozen = cfg.is_frozen()
+ cfg.defrost()
+
+ assert (
+ cfg.SOLVER.IMS_PER_BATCH % old_world_size == 0
+ ), "Invalid REFERENCE_WORLD_SIZE in config!"
+ scale = num_workers / old_world_size
+ bs = cfg.SOLVER.IMS_PER_BATCH = int(round(cfg.SOLVER.IMS_PER_BATCH * scale))
+ lr = cfg.SOLVER.BASE_LR = cfg.SOLVER.BASE_LR * scale
+ max_iter = cfg.SOLVER.MAX_ITER = int(round(cfg.SOLVER.MAX_ITER / scale))
+ warmup_iter = cfg.SOLVER.WARMUP_ITERS = int(round(cfg.SOLVER.WARMUP_ITERS / scale))
+ cfg.SOLVER.STEPS = tuple(int(round(s / scale)) for s in cfg.SOLVER.STEPS)
+ cfg.TEST.EVAL_PERIOD = int(round(cfg.TEST.EVAL_PERIOD / scale))
+ cfg.SOLVER.CHECKPOINT_PERIOD = int(round(cfg.SOLVER.CHECKPOINT_PERIOD / scale))
+ cfg.SOLVER.REFERENCE_WORLD_SIZE = num_workers # maintain invariant
+ logger = logging.getLogger(__name__)
+ logger.info(
+ f"Auto-scaling the config to batch_size={bs}, learning_rate={lr}, "
+ f"max_iter={max_iter}, warmup={warmup_iter}."
+ )
+
+ if frozen:
+ cfg.freeze()
+ return cfg
+
+
+# Access basic attributes from the underlying trainer
+for _attr in ["model", "data_loader", "optimizer"]:
+ setattr(
+ DefaultTrainer,
+ _attr,
+ property(
+ # getter
+ lambda self, x=_attr: getattr(self._trainer, x),
+ # setter
+ lambda self, value, x=_attr: setattr(self._trainer, x, value),
+ ),
+ )
diff --git a/detectron2/detectron2/engine/hooks.py b/detectron2/detectron2/engine/hooks.py
new file mode 100755
index 0000000..fc37af0
--- /dev/null
+++ b/detectron2/detectron2/engine/hooks.py
@@ -0,0 +1,690 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import datetime
+import itertools
+import logging
+import math
+import operator
+import os
+import tempfile
+import time
+import warnings
+from collections import Counter
+import torch
+from fvcore.common.checkpoint import Checkpointer
+from fvcore.common.checkpoint import PeriodicCheckpointer as _PeriodicCheckpointer
+from fvcore.common.param_scheduler import ParamScheduler
+from fvcore.common.timer import Timer
+from fvcore.nn.precise_bn import get_bn_modules, update_bn_stats
+
+import detectron2.utils.comm as comm
+from detectron2.evaluation.testing import flatten_results_dict
+from detectron2.solver import LRMultiplier
+from detectron2.solver import LRScheduler as _LRScheduler
+from detectron2.utils.events import EventStorage, EventWriter
+from detectron2.utils.file_io import PathManager
+
+from .train_loop import HookBase
+
+__all__ = [
+ "CallbackHook",
+ "IterationTimer",
+ "PeriodicWriter",
+ "PeriodicCheckpointer",
+ "BestCheckpointer",
+ "LRScheduler",
+ "AutogradProfiler",
+ "EvalHook",
+ "PreciseBN",
+ "TorchProfiler",
+ "TorchMemoryStats",
+]
+
+
+"""
+Implement some common hooks.
+"""
+
+
+class CallbackHook(HookBase):
+ """
+ Create a hook using callback functions provided by the user.
+ """
+
+ def __init__(self, *, before_train=None, after_train=None, before_step=None, after_step=None):
+ """
+ Each argument is a function that takes one argument: the trainer.
+ """
+ self._before_train = before_train
+ self._before_step = before_step
+ self._after_step = after_step
+ self._after_train = after_train
+
+ def before_train(self):
+ if self._before_train:
+ self._before_train(self.trainer)
+
+ def after_train(self):
+ if self._after_train:
+ self._after_train(self.trainer)
+ # The functions may be closures that hold reference to the trainer
+ # Therefore, delete them to avoid circular reference.
+ del self._before_train, self._after_train
+ del self._before_step, self._after_step
+
+ def before_step(self):
+ if self._before_step:
+ self._before_step(self.trainer)
+
+ def after_step(self):
+ if self._after_step:
+ self._after_step(self.trainer)
+
+
+class IterationTimer(HookBase):
+ """
+ Track the time spent for each iteration (each run_step call in the trainer).
+ Print a summary in the end of training.
+
+ This hook uses the time between the call to its :meth:`before_step`
+ and :meth:`after_step` methods.
+ Under the convention that :meth:`before_step` of all hooks should only
+ take negligible amount of time, the :class:`IterationTimer` hook should be
+ placed at the beginning of the list of hooks to obtain accurate timing.
+ """
+
+ def __init__(self, warmup_iter=3):
+ """
+ Args:
+ warmup_iter (int): the number of iterations at the beginning to exclude
+ from timing.
+ """
+ self._warmup_iter = warmup_iter
+ self._step_timer = Timer()
+ self._start_time = time.perf_counter()
+ self._total_timer = Timer()
+
+ def before_train(self):
+ self._start_time = time.perf_counter()
+ self._total_timer.reset()
+ self._total_timer.pause()
+
+ def after_train(self):
+ logger = logging.getLogger(__name__)
+ total_time = time.perf_counter() - self._start_time
+ total_time_minus_hooks = self._total_timer.seconds()
+ hook_time = total_time - total_time_minus_hooks
+
+ num_iter = self.trainer.storage.iter + 1 - self.trainer.start_iter - self._warmup_iter
+
+ if num_iter > 0 and total_time_minus_hooks > 0:
+ # Speed is meaningful only after warmup
+ # NOTE this format is parsed by grep in some scripts
+ logger.info(
+ "Overall training speed: {} iterations in {} ({:.4f} s / it)".format(
+ num_iter,
+ str(datetime.timedelta(seconds=int(total_time_minus_hooks))),
+ total_time_minus_hooks / num_iter,
+ )
+ )
+
+ logger.info(
+ "Total training time: {} ({} on hooks)".format(
+ str(datetime.timedelta(seconds=int(total_time))),
+ str(datetime.timedelta(seconds=int(hook_time))),
+ )
+ )
+
+ def before_step(self):
+ self._step_timer.reset()
+ self._total_timer.resume()
+
+ def after_step(self):
+ # +1 because we're in after_step, the current step is done
+ # but not yet counted
+ iter_done = self.trainer.storage.iter - self.trainer.start_iter + 1
+ if iter_done >= self._warmup_iter:
+ sec = self._step_timer.seconds()
+ self.trainer.storage.put_scalars(time=sec)
+ else:
+ self._start_time = time.perf_counter()
+ self._total_timer.reset()
+
+ self._total_timer.pause()
+
+
+class PeriodicWriter(HookBase):
+ """
+ Write events to EventStorage (by calling ``writer.write()``) periodically.
+
+ It is executed every ``period`` iterations and after the last iteration.
+ Note that ``period`` does not affect how data is smoothed by each writer.
+ """
+
+ def __init__(self, writers, period=20):
+ """
+ Args:
+ writers (list[EventWriter]): a list of EventWriter objects
+ period (int):
+ """
+ self._writers = writers
+ for w in writers:
+ assert isinstance(w, EventWriter), w
+ self._period = period
+
+ def after_step(self):
+ if (self.trainer.iter + 1) % self._period == 0 or (
+ self.trainer.iter == self.trainer.max_iter - 1
+ ):
+ for writer in self._writers:
+ writer.write()
+
+ def after_train(self):
+ for writer in self._writers:
+ # If any new data is found (e.g. produced by other after_train),
+ # write them before closing
+ writer.write()
+ writer.close()
+
+
+class PeriodicCheckpointer(_PeriodicCheckpointer, HookBase):
+ """
+ Same as :class:`detectron2.checkpoint.PeriodicCheckpointer`, but as a hook.
+
+ Note that when used as a hook,
+ it is unable to save additional data other than what's defined
+ by the given `checkpointer`.
+
+ It is executed every ``period`` iterations and after the last iteration.
+ """
+
+ def before_train(self):
+ self.max_iter = self.trainer.max_iter
+
+ def after_step(self):
+ # No way to use **kwargs
+ self.step(self.trainer.iter)
+
+
+class BestCheckpointer(HookBase):
+ """
+ Checkpoints best weights based off given metric.
+
+ This hook should be used in conjunction to and executed after the hook
+ that produces the metric, e.g. `EvalHook`.
+ """
+
+ def __init__(
+ self,
+ eval_period: int,
+ checkpointer: Checkpointer,
+ val_metric: str,
+ mode: str = "max",
+ file_prefix: str = "model_best",
+ ) -> None:
+ """
+ Args:
+ eval_period (int): the period `EvalHook` is set to run.
+ checkpointer: the checkpointer object used to save checkpoints.
+ val_metric (str): validation metric to track for best checkpoint, e.g. "bbox/AP50"
+ mode (str): one of {'max', 'min'}. controls whether the chosen val metric should be
+ maximized or minimized, e.g. for "bbox/AP50" it should be "max"
+ file_prefix (str): the prefix of checkpoint's filename, defaults to "model_best"
+ """
+ self._logger = logging.getLogger(__name__)
+ self._period = eval_period
+ self._val_metric = val_metric
+ assert mode in [
+ "max",
+ "min",
+ ], f'Mode "{mode}" to `BestCheckpointer` is unknown. It should be one of {"max", "min"}.'
+ if mode == "max":
+ self._compare = operator.gt
+ else:
+ self._compare = operator.lt
+ self._checkpointer = checkpointer
+ self._file_prefix = file_prefix
+ self.best_metric = None
+ self.best_iter = None
+
+ def _update_best(self, val, iteration):
+ if math.isnan(val) or math.isinf(val):
+ return False
+ self.best_metric = val
+ self.best_iter = iteration
+ return True
+
+ def _best_checking(self):
+ metric_tuple = self.trainer.storage.latest().get(self._val_metric)
+ if metric_tuple is None:
+ self._logger.warning(
+ f"Given val metric {self._val_metric} does not seem to be computed/stored."
+ "Will not be checkpointing based on it."
+ )
+ return
+ else:
+ latest_metric, metric_iter = metric_tuple
+
+ if self.best_metric is None:
+ if self._update_best(latest_metric, metric_iter):
+ additional_state = {"iteration": metric_iter}
+ self._checkpointer.save(f"{self._file_prefix}", **additional_state)
+ self._logger.info(
+ f"Saved first model at {self.best_metric:0.5f} @ {self.best_iter} steps"
+ )
+ elif self._compare(latest_metric, self.best_metric):
+ additional_state = {"iteration": metric_iter}
+ self._checkpointer.save(f"{self._file_prefix}", **additional_state)
+ self._logger.info(
+ f"Saved best model as latest eval score for {self._val_metric} is "
+ f"{latest_metric:0.5f}, better than last best score "
+ f"{self.best_metric:0.5f} @ iteration {self.best_iter}."
+ )
+ self._update_best(latest_metric, metric_iter)
+ else:
+ self._logger.info(
+ f"Not saving as latest eval score for {self._val_metric} is {latest_metric:0.5f}, "
+ f"not better than best score {self.best_metric:0.5f} @ iteration {self.best_iter}."
+ )
+
+ def after_step(self):
+ # same conditions as `EvalHook`
+ next_iter = self.trainer.iter + 1
+ if (
+ self._period > 0
+ and next_iter % self._period == 0
+ and next_iter != self.trainer.max_iter
+ ):
+ self._best_checking()
+
+ def after_train(self):
+ # same conditions as `EvalHook`
+ if self.trainer.iter + 1 >= self.trainer.max_iter:
+ self._best_checking()
+
+
+class LRScheduler(HookBase):
+ """
+ A hook which executes a torch builtin LR scheduler and summarizes the LR.
+ It is executed after every iteration.
+ """
+
+ def __init__(self, optimizer=None, scheduler=None):
+ """
+ Args:
+ optimizer (torch.optim.Optimizer):
+ scheduler (torch.optim.LRScheduler or fvcore.common.param_scheduler.ParamScheduler):
+ if a :class:`ParamScheduler` object, it defines the multiplier over the base LR
+ in the optimizer.
+
+ If any argument is not given, will try to obtain it from the trainer.
+ """
+ self._optimizer = optimizer
+ self._scheduler = scheduler
+
+ def before_train(self):
+ self._optimizer = self._optimizer or self.trainer.optimizer
+ if isinstance(self.scheduler, ParamScheduler):
+ self._scheduler = LRMultiplier(
+ self._optimizer,
+ self.scheduler,
+ self.trainer.max_iter,
+ last_iter=self.trainer.iter - 1,
+ )
+ self._best_param_group_id = LRScheduler.get_best_param_group_id(self._optimizer)
+
+ @staticmethod
+ def get_best_param_group_id(optimizer):
+ # NOTE: some heuristics on what LR to summarize
+ # summarize the param group with most parameters
+ largest_group = max(len(g["params"]) for g in optimizer.param_groups)
+
+ if largest_group == 1:
+ # If all groups have one parameter,
+ # then find the most common initial LR, and use it for summary
+ lr_count = Counter([g["lr"] for g in optimizer.param_groups])
+ lr = lr_count.most_common()[0][0]
+ for i, g in enumerate(optimizer.param_groups):
+ if g["lr"] == lr:
+ return i
+ else:
+ for i, g in enumerate(optimizer.param_groups):
+ if len(g["params"]) == largest_group:
+ return i
+
+ def after_step(self):
+ lr = self._optimizer.param_groups[self._best_param_group_id]["lr"]
+ self.trainer.storage.put_scalar("lr", lr, smoothing_hint=False)
+ self.scheduler.step()
+
+ @property
+ def scheduler(self):
+ return self._scheduler or self.trainer.scheduler
+
+ def state_dict(self):
+ if isinstance(self.scheduler, _LRScheduler):
+ return self.scheduler.state_dict()
+ return {}
+
+ def load_state_dict(self, state_dict):
+ if isinstance(self.scheduler, _LRScheduler):
+ logger = logging.getLogger(__name__)
+ logger.info("Loading scheduler from state_dict ...")
+ self.scheduler.load_state_dict(state_dict)
+
+
+class TorchProfiler(HookBase):
+ """
+ A hook which runs `torch.profiler.profile`.
+
+ Examples:
+ ::
+ hooks.TorchProfiler(
+ lambda trainer: 10 < trainer.iter < 20, self.cfg.OUTPUT_DIR
+ )
+
+ The above example will run the profiler for iteration 10~20 and dump
+ results to ``OUTPUT_DIR``. We did not profile the first few iterations
+ because they are typically slower than the rest.
+ The result files can be loaded in the ``chrome://tracing`` page in chrome browser,
+ and the tensorboard visualizations can be visualized using
+ ``tensorboard --logdir OUTPUT_DIR/log``
+ """
+
+ def __init__(self, enable_predicate, output_dir, *, activities=None, save_tensorboard=True):
+ """
+ Args:
+ enable_predicate (callable[trainer -> bool]): a function which takes a trainer,
+ and returns whether to enable the profiler.
+ It will be called once every step, and can be used to select which steps to profile.
+ output_dir (str): the output directory to dump tracing files.
+ activities (iterable): same as in `torch.profiler.profile`.
+ save_tensorboard (bool): whether to save tensorboard visualizations at (output_dir)/log/
+ """
+ self._enable_predicate = enable_predicate
+ self._activities = activities
+ self._output_dir = output_dir
+ self._save_tensorboard = save_tensorboard
+
+ def before_step(self):
+ if self._enable_predicate(self.trainer):
+ if self._save_tensorboard:
+ on_trace_ready = torch.profiler.tensorboard_trace_handler(
+ os.path.join(
+ self._output_dir,
+ "log",
+ "profiler-tensorboard-iter{}".format(self.trainer.iter),
+ ),
+ f"worker{comm.get_rank()}",
+ )
+ else:
+ on_trace_ready = None
+ self._profiler = torch.profiler.profile(
+ activities=self._activities,
+ on_trace_ready=on_trace_ready,
+ record_shapes=True,
+ profile_memory=True,
+ with_stack=True,
+ with_flops=True,
+ )
+ self._profiler.__enter__()
+ else:
+ self._profiler = None
+
+ def after_step(self):
+ if self._profiler is None:
+ return
+ self._profiler.__exit__(None, None, None)
+ if not self._save_tensorboard:
+ PathManager.mkdirs(self._output_dir)
+ out_file = os.path.join(
+ self._output_dir, "profiler-trace-iter{}.json".format(self.trainer.iter)
+ )
+ if "://" not in out_file:
+ self._profiler.export_chrome_trace(out_file)
+ else:
+ # Support non-posix filesystems
+ with tempfile.TemporaryDirectory(prefix="detectron2_profiler") as d:
+ tmp_file = os.path.join(d, "tmp.json")
+ self._profiler.export_chrome_trace(tmp_file)
+ with open(tmp_file) as f:
+ content = f.read()
+ with PathManager.open(out_file, "w") as f:
+ f.write(content)
+
+
+class AutogradProfiler(TorchProfiler):
+ """
+ A hook which runs `torch.autograd.profiler.profile`.
+
+ Examples:
+ ::
+ hooks.AutogradProfiler(
+ lambda trainer: 10 < trainer.iter < 20, self.cfg.OUTPUT_DIR
+ )
+
+ The above example will run the profiler for iteration 10~20 and dump
+ results to ``OUTPUT_DIR``. We did not profile the first few iterations
+ because they are typically slower than the rest.
+ The result files can be loaded in the ``chrome://tracing`` page in chrome browser.
+
+ Note:
+ When used together with NCCL on older version of GPUs,
+ autograd profiler may cause deadlock because it unnecessarily allocates
+ memory on every device it sees. The memory management calls, if
+ interleaved with NCCL calls, lead to deadlock on GPUs that do not
+ support ``cudaLaunchCooperativeKernelMultiDevice``.
+ """
+
+ def __init__(self, enable_predicate, output_dir, *, use_cuda=True):
+ """
+ Args:
+ enable_predicate (callable[trainer -> bool]): a function which takes a trainer,
+ and returns whether to enable the profiler.
+ It will be called once every step, and can be used to select which steps to profile.
+ output_dir (str): the output directory to dump tracing files.
+ use_cuda (bool): same as in `torch.autograd.profiler.profile`.
+ """
+ warnings.warn("AutogradProfiler has been deprecated in favor of TorchProfiler.")
+ self._enable_predicate = enable_predicate
+ self._use_cuda = use_cuda
+ self._output_dir = output_dir
+
+ def before_step(self):
+ if self._enable_predicate(self.trainer):
+ self._profiler = torch.autograd.profiler.profile(use_cuda=self._use_cuda)
+ self._profiler.__enter__()
+ else:
+ self._profiler = None
+
+
+class EvalHook(HookBase):
+ """
+ Run an evaluation function periodically, and at the end of training.
+
+ It is executed every ``eval_period`` iterations and after the last iteration.
+ """
+
+ def __init__(self, eval_period, eval_function, eval_after_train=True):
+ """
+ Args:
+ eval_period (int): the period to run `eval_function`. Set to 0 to
+ not evaluate periodically (but still evaluate after the last iteration
+ if `eval_after_train` is True).
+ eval_function (callable): a function which takes no arguments, and
+ returns a nested dict of evaluation metrics.
+ eval_after_train (bool): whether to evaluate after the last iteration
+
+ Note:
+ This hook must be enabled in all or none workers.
+ If you would like only certain workers to perform evaluation,
+ give other workers a no-op function (`eval_function=lambda: None`).
+ """
+ self._period = eval_period
+ self._func = eval_function
+ self._eval_after_train = eval_after_train
+
+ def _do_eval(self):
+ results = self._func()
+
+ if results:
+ assert isinstance(
+ results, dict
+ ), "Eval function must return a dict. Got {} instead.".format(results)
+
+ flattened_results = flatten_results_dict(results)
+ for k, v in flattened_results.items():
+ try:
+ v = float(v)
+ except Exception as e:
+ raise ValueError(
+ "[EvalHook] eval_function should return a nested dict of float. "
+ "Got '{}: {}' instead.".format(k, v)
+ ) from e
+ self.trainer.storage.put_scalars(**flattened_results, smoothing_hint=False)
+
+ # Evaluation may take different time among workers.
+ # A barrier make them start the next iteration together.
+ comm.synchronize()
+
+ def after_step(self):
+ next_iter = self.trainer.iter + 1
+ if self._period > 0 and next_iter % self._period == 0:
+ # do the last eval in after_train
+ if next_iter != self.trainer.max_iter:
+ self._do_eval()
+
+ def after_train(self):
+ # This condition is to prevent the eval from running after a failed training
+ if self._eval_after_train and self.trainer.iter + 1 >= self.trainer.max_iter:
+ self._do_eval()
+ # func is likely a closure that holds reference to the trainer
+ # therefore we clean it to avoid circular reference in the end
+ del self._func
+
+
+class PreciseBN(HookBase):
+ """
+ The standard implementation of BatchNorm uses EMA in inference, which is
+ sometimes suboptimal.
+ This class computes the true average of statistics rather than the moving average,
+ and put true averages to every BN layer in the given model.
+
+ It is executed every ``period`` iterations and after the last iteration.
+ """
+
+ def __init__(self, period, model, data_loader, num_iter):
+ """
+ Args:
+ period (int): the period this hook is run, or 0 to not run during training.
+ The hook will always run in the end of training.
+ model (nn.Module): a module whose all BN layers in training mode will be
+ updated by precise BN.
+ Note that user is responsible for ensuring the BN layers to be
+ updated are in training mode when this hook is triggered.
+ data_loader (iterable): it will produce data to be run by `model(data)`.
+ num_iter (int): number of iterations used to compute the precise
+ statistics.
+ """
+ self._logger = logging.getLogger(__name__)
+ if len(get_bn_modules(model)) == 0:
+ self._logger.info(
+ "PreciseBN is disabled because model does not contain BN layers in training mode."
+ )
+ self._disabled = True
+ return
+
+ self._model = model
+ self._data_loader = data_loader
+ self._num_iter = num_iter
+ self._period = period
+ self._disabled = False
+
+ self._data_iter = None
+
+ def after_step(self):
+ next_iter = self.trainer.iter + 1
+ is_final = next_iter == self.trainer.max_iter
+ if is_final or (self._period > 0 and next_iter % self._period == 0):
+ self.update_stats()
+
+ def update_stats(self):
+ """
+ Update the model with precise statistics. Users can manually call this method.
+ """
+ if self._disabled:
+ return
+
+ if self._data_iter is None:
+ self._data_iter = iter(self._data_loader)
+
+ def data_loader():
+ for num_iter in itertools.count(1):
+ if num_iter % 100 == 0:
+ self._logger.info(
+ "Running precise-BN ... {}/{} iterations.".format(num_iter, self._num_iter)
+ )
+ # This way we can reuse the same iterator
+ yield next(self._data_iter)
+
+ with EventStorage(): # capture events in a new storage to discard them
+ self._logger.info(
+ "Running precise-BN for {} iterations... ".format(self._num_iter)
+ + "Note that this could produce different statistics every time."
+ )
+ update_bn_stats(self._model, data_loader(), self._num_iter)
+
+
+class TorchMemoryStats(HookBase):
+ """
+ Writes pytorch's cuda memory statistics periodically.
+ """
+
+ def __init__(self, period=20, max_runs=10):
+ """
+ Args:
+ period (int): Output stats each 'period' iterations
+ max_runs (int): Stop the logging after 'max_runs'
+ """
+
+ self._logger = logging.getLogger(__name__)
+ self._period = period
+ self._max_runs = max_runs
+ self._runs = 0
+
+ def after_step(self):
+ if self._runs > self._max_runs:
+ return
+
+ if (self.trainer.iter + 1) % self._period == 0 or (
+ self.trainer.iter == self.trainer.max_iter - 1
+ ):
+ if torch.cuda.is_available():
+ max_reserved_mb = torch.cuda.max_memory_reserved() / 1024.0 / 1024.0
+ reserved_mb = torch.cuda.memory_reserved() / 1024.0 / 1024.0
+ max_allocated_mb = torch.cuda.max_memory_allocated() / 1024.0 / 1024.0
+ allocated_mb = torch.cuda.memory_allocated() / 1024.0 / 1024.0
+
+ self._logger.info(
+ (
+ " iter: {} "
+ " max_reserved_mem: {:.0f}MB "
+ " reserved_mem: {:.0f}MB "
+ " max_allocated_mem: {:.0f}MB "
+ " allocated_mem: {:.0f}MB "
+ ).format(
+ self.trainer.iter,
+ max_reserved_mb,
+ reserved_mb,
+ max_allocated_mb,
+ allocated_mb,
+ )
+ )
+
+ self._runs += 1
+ if self._runs == self._max_runs:
+ mem_summary = torch.cuda.memory_summary()
+ self._logger.info("\n" + mem_summary)
+
+ torch.cuda.reset_peak_memory_stats()
diff --git a/detectron2/detectron2/engine/launch.py b/detectron2/detectron2/engine/launch.py
new file mode 100755
index 0000000..7052c50
--- /dev/null
+++ b/detectron2/detectron2/engine/launch.py
@@ -0,0 +1,123 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+from datetime import timedelta
+import torch
+import torch.distributed as dist
+import torch.multiprocessing as mp
+
+from detectron2.utils import comm
+
+__all__ = ["DEFAULT_TIMEOUT", "launch"]
+
+DEFAULT_TIMEOUT = timedelta(minutes=30)
+
+
+def _find_free_port():
+ import socket
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ # Binding to port 0 will cause the OS to find an available port for us
+ sock.bind(("", 0))
+ port = sock.getsockname()[1]
+ sock.close()
+ # NOTE: there is still a chance the port could be taken by other processes.
+ return port
+
+
+def launch(
+ main_func,
+ # Should be num_processes_per_machine, but kept for compatibility.
+ num_gpus_per_machine,
+ num_machines=1,
+ machine_rank=0,
+ dist_url=None,
+ args=(),
+ timeout=DEFAULT_TIMEOUT,
+):
+ """
+ Launch multi-process or distributed training.
+ This function must be called on all machines involved in the training.
+ It will spawn child processes (defined by ``num_gpus_per_machine``) on each machine.
+
+ Args:
+ main_func: a function that will be called by `main_func(*args)`
+ num_gpus_per_machine (int): number of processes per machine. When
+ using GPUs, this should be the number of GPUs.
+ num_machines (int): the total number of machines
+ machine_rank (int): the rank of this machine
+ dist_url (str): url to connect to for distributed jobs, including protocol
+ e.g. "tcp://127.0.0.1:8686".
+ Can be set to "auto" to automatically select a free port on localhost
+ timeout (timedelta): timeout of the distributed workers
+ args (tuple): arguments passed to main_func
+ """
+ world_size = num_machines * num_gpus_per_machine
+ if world_size > 1:
+ # https://github.com/pytorch/pytorch/pull/14391
+ # TODO prctl in spawned processes
+
+ if dist_url == "auto":
+ assert num_machines == 1, "dist_url=auto not supported in multi-machine jobs."
+ port = _find_free_port()
+ dist_url = f"tcp://127.0.0.1:{port}"
+ if num_machines > 1 and dist_url.startswith("file://"):
+ logger = logging.getLogger(__name__)
+ logger.warning(
+ "file:// is not a reliable init_method in multi-machine jobs. Prefer tcp://"
+ )
+
+ mp.start_processes(
+ _distributed_worker,
+ nprocs=num_gpus_per_machine,
+ args=(
+ main_func,
+ world_size,
+ num_gpus_per_machine,
+ machine_rank,
+ dist_url,
+ args,
+ timeout,
+ ),
+ daemon=False,
+ )
+ else:
+ main_func(*args)
+
+
+def _distributed_worker(
+ local_rank,
+ main_func,
+ world_size,
+ num_gpus_per_machine,
+ machine_rank,
+ dist_url,
+ args,
+ timeout=DEFAULT_TIMEOUT,
+):
+ has_gpu = torch.cuda.is_available()
+ if has_gpu:
+ assert num_gpus_per_machine <= torch.cuda.device_count()
+ global_rank = machine_rank * num_gpus_per_machine + local_rank
+ try:
+ dist.init_process_group(
+ backend="NCCL" if has_gpu else "GLOO",
+ init_method=dist_url,
+ world_size=world_size,
+ rank=global_rank,
+ timeout=timeout,
+ )
+ except Exception as e:
+ logger = logging.getLogger(__name__)
+ logger.error("Process group URL: {}".format(dist_url))
+ raise e
+
+ # Setup the local process group.
+ comm.create_local_process_group(num_gpus_per_machine)
+ if has_gpu:
+ torch.cuda.set_device(local_rank)
+
+ # synchronize is needed here to prevent a possible timeout after calling init_process_group
+ # See: https://github.com/facebookresearch/maskrcnn-benchmark/issues/172
+ comm.synchronize()
+
+ main_func(*args)
diff --git a/detectron2/detectron2/engine/train_loop.py b/detectron2/detectron2/engine/train_loop.py
new file mode 100755
index 0000000..2f6b96d
--- /dev/null
+++ b/detectron2/detectron2/engine/train_loop.py
@@ -0,0 +1,528 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+import concurrent.futures
+import logging
+import numpy as np
+import time
+import weakref
+from typing import List, Mapping, Optional
+import torch
+from torch.nn.parallel import DataParallel, DistributedDataParallel
+
+import detectron2.utils.comm as comm
+from detectron2.utils.events import EventStorage, get_event_storage
+from detectron2.utils.logger import _log_api_usage
+
+__all__ = ["HookBase", "TrainerBase", "SimpleTrainer", "AMPTrainer"]
+
+
+class HookBase:
+ """
+ Base class for hooks that can be registered with :class:`TrainerBase`.
+
+ Each hook can implement 4 methods. The way they are called is demonstrated
+ in the following snippet:
+ ::
+ hook.before_train()
+ for iter in range(start_iter, max_iter):
+ hook.before_step()
+ trainer.run_step()
+ hook.after_step()
+ iter += 1
+ hook.after_train()
+
+ Notes:
+ 1. In the hook method, users can access ``self.trainer`` to access more
+ properties about the context (e.g., model, current iteration, or config
+ if using :class:`DefaultTrainer`).
+
+ 2. A hook that does something in :meth:`before_step` can often be
+ implemented equivalently in :meth:`after_step`.
+ If the hook takes non-trivial time, it is strongly recommended to
+ implement the hook in :meth:`after_step` instead of :meth:`before_step`.
+ The convention is that :meth:`before_step` should only take negligible time.
+
+ Following this convention will allow hooks that do care about the difference
+ between :meth:`before_step` and :meth:`after_step` (e.g., timer) to
+ function properly.
+
+ """
+
+ trainer: "TrainerBase" = None
+ """
+ A weak reference to the trainer object. Set by the trainer when the hook is registered.
+ """
+
+ def before_train(self):
+ """
+ Called before the first iteration.
+ """
+ pass
+
+ def after_train(self):
+ """
+ Called after the last iteration.
+ """
+ pass
+
+ def before_step(self):
+ """
+ Called before each iteration.
+ """
+ pass
+
+ def after_backward(self):
+ """
+ Called after the backward pass of each iteration.
+ """
+ pass
+
+ def after_step(self):
+ """
+ Called after each iteration.
+ """
+ pass
+
+ def state_dict(self):
+ """
+ Hooks are stateless by default, but can be made checkpointable by
+ implementing `state_dict` and `load_state_dict`.
+ """
+ return {}
+
+
+class TrainerBase:
+ """
+ Base class for iterative trainer with hooks.
+
+ The only assumption we made here is: the training runs in a loop.
+ A subclass can implement what the loop is.
+ We made no assumptions about the existence of dataloader, optimizer, model, etc.
+
+ Attributes:
+ iter(int): the current iteration.
+
+ start_iter(int): The iteration to start with.
+ By convention the minimum possible value is 0.
+
+ max_iter(int): The iteration to end training.
+
+ storage(EventStorage): An EventStorage that's opened during the course of training.
+ """
+
+ def __init__(self) -> None:
+ self._hooks: List[HookBase] = []
+ self.iter: int = 0
+ self.start_iter: int = 0
+ self.max_iter: int
+ self.storage: EventStorage
+ _log_api_usage("trainer." + self.__class__.__name__)
+
+ def register_hooks(self, hooks: List[Optional[HookBase]]) -> None:
+ """
+ Register hooks to the trainer. The hooks are executed in the order
+ they are registered.
+
+ Args:
+ hooks (list[Optional[HookBase]]): list of hooks
+ """
+ hooks = [h for h in hooks if h is not None]
+ for h in hooks:
+ assert isinstance(h, HookBase)
+ # To avoid circular reference, hooks and trainer cannot own each other.
+ # This normally does not matter, but will cause memory leak if the
+ # involved objects contain __del__:
+ # See http://engineering.hearsaysocial.com/2013/06/16/circular-references-in-python/
+ h.trainer = weakref.proxy(self)
+ self._hooks.extend(hooks)
+
+ def train(self, start_iter: int, max_iter: int):
+ """
+ Args:
+ start_iter, max_iter (int): See docs above
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Starting training from iteration {}".format(start_iter))
+
+ self.iter = self.start_iter = start_iter
+ self.max_iter = max_iter
+
+ with EventStorage(start_iter) as self.storage:
+ try:
+ self.before_train()
+ for self.iter in range(start_iter, max_iter):
+ self.before_step()
+ self.run_step()
+ self.after_step()
+ # self.iter == max_iter can be used by `after_train` to
+ # tell whether the training successfully finished or failed
+ # due to exceptions.
+ self.iter += 1
+ except Exception:
+ logger.exception("Exception during training:")
+ raise
+ finally:
+ self.after_train()
+
+ def before_train(self):
+ for h in self._hooks:
+ h.before_train()
+
+ def after_train(self):
+ self.storage.iter = self.iter
+ for h in self._hooks:
+ h.after_train()
+
+ def before_step(self):
+ # Maintain the invariant that storage.iter == trainer.iter
+ # for the entire execution of each step
+ self.storage.iter = self.iter
+
+ for h in self._hooks:
+ h.before_step()
+
+ def after_backward(self):
+ for h in self._hooks:
+ h.after_backward()
+
+ def after_step(self):
+ for h in self._hooks:
+ h.after_step()
+
+ def run_step(self):
+ raise NotImplementedError
+
+ def state_dict(self):
+ ret = {"iteration": self.iter}
+ hooks_state = {}
+ for h in self._hooks:
+ sd = h.state_dict()
+ if sd:
+ name = type(h).__qualname__
+ if name in hooks_state:
+ # TODO handle repetitive stateful hooks
+ continue
+ hooks_state[name] = sd
+ if hooks_state:
+ ret["hooks"] = hooks_state
+ return ret
+
+ def load_state_dict(self, state_dict):
+ logger = logging.getLogger(__name__)
+ self.iter = state_dict["iteration"]
+ for key, value in state_dict.get("hooks", {}).items():
+ for h in self._hooks:
+ try:
+ name = type(h).__qualname__
+ except AttributeError:
+ continue
+ if name == key:
+ h.load_state_dict(value)
+ break
+ else:
+ logger.warning(f"Cannot find the hook '{key}', its state_dict is ignored.")
+
+
+class SimpleTrainer(TrainerBase):
+ """
+ A simple trainer for the most common type of task:
+ single-cost single-optimizer single-data-source iterative optimization,
+ optionally using data-parallelism.
+ It assumes that every step, you:
+
+ 1. Compute the loss with a data from the data_loader.
+ 2. Compute the gradients with the above loss.
+ 3. Update the model with the optimizer.
+
+ All other tasks during training (checkpointing, logging, evaluation, LR schedule)
+ are maintained by hooks, which can be registered by :meth:`TrainerBase.register_hooks`.
+
+ If you want to do anything fancier than this,
+ either subclass TrainerBase and implement your own `run_step`,
+ or write your own training loop.
+ """
+
+ def __init__(
+ self,
+ model,
+ data_loader,
+ optimizer,
+ gather_metric_period=1,
+ zero_grad_before_forward=False,
+ async_write_metrics=False,
+ ):
+ """
+ Args:
+ model: a torch Module. Takes a data from data_loader and returns a
+ dict of losses.
+ data_loader: an iterable. Contains data to be used to call model.
+ optimizer: a torch optimizer.
+ gather_metric_period: an int. Every gather_metric_period iterations
+ the metrics are gathered from all the ranks to rank 0 and logged.
+ zero_grad_before_forward: whether to zero the gradients before the forward.
+ async_write_metrics: bool. If True, then write metrics asynchronously to improve
+ training speed
+ """
+ super().__init__()
+
+ """
+ We set the model to training mode in the trainer.
+ However it's valid to train a model that's in eval mode.
+ If you want your model (or a submodule of it) to behave
+ like evaluation during training, you can overwrite its train() method.
+ """
+ model.train()
+
+ self.model = model
+ self.data_loader = data_loader
+ # to access the data loader iterator, call `self._data_loader_iter`
+ self._data_loader_iter_obj = None
+ self.optimizer = optimizer
+ self.gather_metric_period = gather_metric_period
+ self.zero_grad_before_forward = zero_grad_before_forward
+ self.async_write_metrics = async_write_metrics
+ # create a thread pool that can execute non critical logic in run_step asynchronically
+ # use only 1 worker so tasks will be executred in order of submitting.
+ self.concurrent_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
+
+ def run_step(self):
+ """
+ Implement the standard training logic described above.
+ """
+ assert self.model.training, "[SimpleTrainer] model was changed to eval mode!"
+ start = time.perf_counter()
+ """
+ If you want to do something with the data, you can wrap the dataloader.
+ """
+ data = next(self._data_loader_iter)
+ data_time = time.perf_counter() - start
+
+ if self.zero_grad_before_forward:
+ """
+ If you need to accumulate gradients or do something similar, you can
+ wrap the optimizer with your custom `zero_grad()` method.
+ """
+ self.optimizer.zero_grad()
+
+ """
+ If you want to do something with the losses, you can wrap the model.
+ """
+ loss_dict = self.model(data)
+ if isinstance(loss_dict, torch.Tensor):
+ losses = loss_dict
+ loss_dict = {"total_loss": loss_dict}
+ else:
+ losses = sum(loss_dict.values())
+ if not self.zero_grad_before_forward:
+ """
+ If you need to accumulate gradients or do something similar, you can
+ wrap the optimizer with your custom `zero_grad()` method.
+ """
+ self.optimizer.zero_grad()
+ losses.backward()
+
+ self.after_backward()
+
+ if self.async_write_metrics:
+ # write metrics asynchronically
+ self.concurrent_executor.submit(
+ self._write_metrics, loss_dict, data_time, iter=self.iter
+ )
+ else:
+ self._write_metrics(loss_dict, data_time)
+
+ """
+ If you need gradient clipping/scaling or other processing, you can
+ wrap the optimizer with your custom `step()` method. But it is
+ suboptimal as explained in https://arxiv.org/abs/2006.15704 Sec 3.2.4
+ """
+ self.optimizer.step()
+
+ @property
+ def _data_loader_iter(self):
+ # only create the data loader iterator when it is used
+ if self._data_loader_iter_obj is None:
+ self._data_loader_iter_obj = iter(self.data_loader)
+ return self._data_loader_iter_obj
+
+ def reset_data_loader(self, data_loader_builder):
+ """
+ Delete and replace the current data loader with a new one, which will be created
+ by calling `data_loader_builder` (without argument).
+ """
+ del self.data_loader
+ data_loader = data_loader_builder()
+ self.data_loader = data_loader
+ self._data_loader_iter_obj = None
+
+ def _write_metrics(
+ self,
+ loss_dict: Mapping[str, torch.Tensor],
+ data_time: float,
+ prefix: str = "",
+ iter: Optional[int] = None,
+ ) -> None:
+ logger = logging.getLogger(__name__)
+
+ iter = self.iter if iter is None else iter
+ if (iter + 1) % self.gather_metric_period == 0:
+ try:
+ SimpleTrainer.write_metrics(loss_dict, data_time, iter, prefix)
+ except Exception:
+ logger.exception("Exception in writing metrics: ")
+ raise
+
+ @staticmethod
+ def write_metrics(
+ loss_dict: Mapping[str, torch.Tensor],
+ data_time: float,
+ cur_iter: int,
+ prefix: str = "",
+ ) -> None:
+ """
+ Args:
+ loss_dict (dict): dict of scalar losses
+ data_time (float): time taken by the dataloader iteration
+ prefix (str): prefix for logging keys
+ """
+ metrics_dict = {k: v.detach().cpu().item() for k, v in loss_dict.items()}
+ metrics_dict["data_time"] = data_time
+
+ # Gather metrics among all workers for logging
+ # This assumes we do DDP-style training, which is currently the only
+ # supported method in detectron2.
+ all_metrics_dict = comm.gather(metrics_dict)
+
+ if comm.is_main_process():
+ storage = get_event_storage()
+
+ # data_time among workers can have high variance. The actual latency
+ # caused by data_time is the maximum among workers.
+ data_time = np.max([x.pop("data_time") for x in all_metrics_dict])
+ storage.put_scalar("data_time", data_time, cur_iter=cur_iter)
+
+ # average the rest metrics
+ metrics_dict = {
+ k: np.mean([x[k] for x in all_metrics_dict]) for k in all_metrics_dict[0].keys()
+ }
+ total_losses_reduced = sum(metrics_dict.values())
+ if not np.isfinite(total_losses_reduced):
+ raise FloatingPointError(
+ f"Loss became infinite or NaN at iteration={cur_iter}!\n"
+ f"loss_dict = {metrics_dict}"
+ )
+
+ storage.put_scalar(
+ "{}total_loss".format(prefix), total_losses_reduced, cur_iter=cur_iter
+ )
+ if len(metrics_dict) > 1:
+ storage.put_scalars(cur_iter=cur_iter, **metrics_dict)
+
+ def state_dict(self):
+ ret = super().state_dict()
+ ret["optimizer"] = self.optimizer.state_dict()
+ return ret
+
+ def load_state_dict(self, state_dict):
+ super().load_state_dict(state_dict)
+ self.optimizer.load_state_dict(state_dict["optimizer"])
+
+ def after_train(self):
+ super().after_train()
+ self.concurrent_executor.shutdown(wait=True)
+
+
+class AMPTrainer(SimpleTrainer):
+ """
+ Like :class:`SimpleTrainer`, but uses PyTorch's native automatic mixed precision
+ in the training loop.
+ """
+
+ def __init__(
+ self,
+ model,
+ data_loader,
+ optimizer,
+ gather_metric_period=1,
+ zero_grad_before_forward=False,
+ grad_scaler=None,
+ precision: torch.dtype = torch.float16,
+ log_grad_scaler: bool = False,
+ async_write_metrics=False,
+ ):
+ """
+ Args:
+ model, data_loader, optimizer, gather_metric_period, zero_grad_before_forward,
+ async_write_metrics: same as in :class:`SimpleTrainer`.
+ grad_scaler: torch GradScaler to automatically scale gradients.
+ precision: torch.dtype as the target precision to cast to in computations
+ """
+ unsupported = "AMPTrainer does not support single-process multi-device training!"
+ if isinstance(model, DistributedDataParallel):
+ assert not (model.device_ids and len(model.device_ids) > 1), unsupported
+ assert not isinstance(model, DataParallel), unsupported
+
+ super().__init__(
+ model, data_loader, optimizer, gather_metric_period, zero_grad_before_forward
+ )
+
+ if grad_scaler is None:
+ from torch.cuda.amp import GradScaler
+
+ grad_scaler = GradScaler()
+ self.grad_scaler = grad_scaler
+ self.precision = precision
+ self.log_grad_scaler = log_grad_scaler
+
+ def run_step(self):
+ """
+ Implement the AMP training logic.
+ """
+ assert self.model.training, "[AMPTrainer] model was changed to eval mode!"
+ assert torch.cuda.is_available(), "[AMPTrainer] CUDA is required for AMP training!"
+ from torch.cuda.amp import autocast
+
+ start = time.perf_counter()
+ data = next(self._data_loader_iter)
+ data_time = time.perf_counter() - start
+
+ if self.zero_grad_before_forward:
+ self.optimizer.zero_grad()
+ with autocast(dtype=self.precision):
+ loss_dict = self.model(data)
+ if isinstance(loss_dict, torch.Tensor):
+ losses = loss_dict
+ loss_dict = {"total_loss": loss_dict}
+ else:
+ losses = sum(loss_dict.values())
+
+ if not self.zero_grad_before_forward:
+ self.optimizer.zero_grad()
+
+ self.grad_scaler.scale(losses).backward()
+
+ if self.log_grad_scaler:
+ storage = get_event_storage()
+ storage.put_scalar("[metric]grad_scaler", self.grad_scaler.get_scale())
+
+ self.after_backward()
+
+ if self.async_write_metrics:
+ # write metrics asynchronically
+ self.concurrent_executor.submit(
+ self._write_metrics, loss_dict, data_time, iter=self.iter
+ )
+ else:
+ self._write_metrics(loss_dict, data_time)
+
+ self.grad_scaler.step(self.optimizer)
+ self.grad_scaler.update()
+
+ def state_dict(self):
+ ret = super().state_dict()
+ ret["grad_scaler"] = self.grad_scaler.state_dict()
+ return ret
+
+ def load_state_dict(self, state_dict):
+ super().load_state_dict(state_dict)
+ self.grad_scaler.load_state_dict(state_dict["grad_scaler"])
diff --git a/detectron2/detectron2/evaluation/__init__.py b/detectron2/detectron2/evaluation/__init__.py
new file mode 100755
index 0000000..d96609e
--- /dev/null
+++ b/detectron2/detectron2/evaluation/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .cityscapes_evaluation import CityscapesInstanceEvaluator, CityscapesSemSegEvaluator
+from .coco_evaluation import COCOEvaluator
+from .rotated_coco_evaluation import RotatedCOCOEvaluator
+from .evaluator import DatasetEvaluator, DatasetEvaluators, inference_context, inference_on_dataset
+from .lvis_evaluation import LVISEvaluator
+from .panoptic_evaluation import COCOPanopticEvaluator
+from .pascal_voc_evaluation import PascalVOCDetectionEvaluator
+from .sem_seg_evaluation import SemSegEvaluator
+from .testing import print_csv_format, verify_results
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/detectron2/detectron2/evaluation/cityscapes_evaluation.py b/detectron2/detectron2/evaluation/cityscapes_evaluation.py
new file mode 100755
index 0000000..9cc7888
--- /dev/null
+++ b/detectron2/detectron2/evaluation/cityscapes_evaluation.py
@@ -0,0 +1,197 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import glob
+import logging
+import numpy as np
+import os
+import tempfile
+from collections import OrderedDict
+import torch
+from PIL import Image
+
+from detectron2.data import MetadataCatalog
+from detectron2.utils import comm
+from detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+
+class CityscapesEvaluator(DatasetEvaluator):
+ """
+ Base class for evaluation using cityscapes API.
+ """
+
+ def __init__(self, dataset_name):
+ """
+ Args:
+ dataset_name (str): the name of the dataset.
+ It must have the following metadata associated with it:
+ "thing_classes", "gt_dir".
+ """
+ self._metadata = MetadataCatalog.get(dataset_name)
+ self._cpu_device = torch.device("cpu")
+ self._logger = logging.getLogger(__name__)
+
+ def reset(self):
+ self._working_dir = tempfile.TemporaryDirectory(prefix="cityscapes_eval_")
+ self._temp_dir = self._working_dir.name
+ # All workers will write to the same results directory
+ # TODO this does not work in distributed training
+ assert (
+ comm.get_local_size() == comm.get_world_size()
+ ), "CityscapesEvaluator currently do not work with multiple machines."
+ self._temp_dir = comm.all_gather(self._temp_dir)[0]
+ if self._temp_dir != self._working_dir.name:
+ self._working_dir.cleanup()
+ self._logger.info(
+ "Writing cityscapes results to temporary directory {} ...".format(self._temp_dir)
+ )
+
+
+class CityscapesInstanceEvaluator(CityscapesEvaluator):
+ """
+ Evaluate instance segmentation results on cityscapes dataset using cityscapes API.
+
+ Note:
+ * It does not work in multi-machine distributed training.
+ * It contains a synchronization, therefore has to be used on all ranks.
+ * Only the main process runs evaluation.
+ """
+
+ def process(self, inputs, outputs):
+ from cityscapesscripts.helpers.labels import name2label
+
+ for input, output in zip(inputs, outputs):
+ file_name = input["file_name"]
+ basename = os.path.splitext(os.path.basename(file_name))[0]
+ pred_txt = os.path.join(self._temp_dir, basename + "_pred.txt")
+
+ if "instances" in output:
+ output = output["instances"].to(self._cpu_device)
+ num_instances = len(output)
+ with open(pred_txt, "w") as fout:
+ for i in range(num_instances):
+ pred_class = output.pred_classes[i]
+ classes = self._metadata.thing_classes[pred_class]
+ class_id = name2label[classes].id
+ score = output.scores[i]
+ mask = output.pred_masks[i].numpy().astype("uint8")
+ png_filename = os.path.join(
+ self._temp_dir, basename + "_{}_{}.png".format(i, classes)
+ )
+
+ Image.fromarray(mask * 255).save(png_filename)
+ fout.write(
+ "{} {} {}\n".format(os.path.basename(png_filename), class_id, score)
+ )
+ else:
+ # Cityscapes requires a prediction file for every ground truth image.
+ with open(pred_txt, "w") as fout:
+ pass
+
+ def evaluate(self):
+ """
+ Returns:
+ dict: has a key "segm", whose value is a dict of "AP" and "AP50".
+ """
+ comm.synchronize()
+ if comm.get_rank() > 0:
+ return
+ import cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling as cityscapes_eval
+
+ self._logger.info("Evaluating results under {} ...".format(self._temp_dir))
+
+ # set some global states in cityscapes evaluation API, before evaluating
+ cityscapes_eval.args.predictionPath = os.path.abspath(self._temp_dir)
+ cityscapes_eval.args.predictionWalk = None
+ cityscapes_eval.args.JSONOutput = False
+ cityscapes_eval.args.colorized = False
+ cityscapes_eval.args.gtInstancesFile = os.path.join(self._temp_dir, "gtInstances.json")
+
+ # These lines are adopted from
+ # https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/evalInstanceLevelSemanticLabeling.py # noqa
+ gt_dir = PathManager.get_local_path(self._metadata.gt_dir)
+ groundTruthImgList = glob.glob(os.path.join(gt_dir, "*", "*_gtFine_instanceIds.png"))
+ assert len(
+ groundTruthImgList
+ ), "Cannot find any ground truth images to use for evaluation. Searched for: {}".format(
+ cityscapes_eval.args.groundTruthSearch
+ )
+ predictionImgList = []
+ for gt in groundTruthImgList:
+ predictionImgList.append(cityscapes_eval.getPrediction(gt, cityscapes_eval.args))
+ results = cityscapes_eval.evaluateImgLists(
+ predictionImgList, groundTruthImgList, cityscapes_eval.args
+ )["averages"]
+
+ ret = OrderedDict()
+ ret["segm"] = {"AP": results["allAp"] * 100, "AP50": results["allAp50%"] * 100}
+ self._working_dir.cleanup()
+ return ret
+
+
+class CityscapesSemSegEvaluator(CityscapesEvaluator):
+ """
+ Evaluate semantic segmentation results on cityscapes dataset using cityscapes API.
+
+ Note:
+ * It does not work in multi-machine distributed training.
+ * It contains a synchronization, therefore has to be used on all ranks.
+ * Only the main process runs evaluation.
+ """
+
+ def process(self, inputs, outputs):
+ from cityscapesscripts.helpers.labels import trainId2label
+
+ for input, output in zip(inputs, outputs):
+ file_name = input["file_name"]
+ basename = os.path.splitext(os.path.basename(file_name))[0]
+ pred_filename = os.path.join(self._temp_dir, basename + "_pred.png")
+
+ output = output["sem_seg"].argmax(dim=0).to(self._cpu_device).numpy()
+ pred = 255 * np.ones(output.shape, dtype=np.uint8)
+ for train_id, label in trainId2label.items():
+ if label.ignoreInEval:
+ continue
+ pred[output == train_id] = label.id
+ Image.fromarray(pred).save(pred_filename)
+
+ def evaluate(self):
+ comm.synchronize()
+ if comm.get_rank() > 0:
+ return
+ # Load the Cityscapes eval script *after* setting the required env var,
+ # since the script reads CITYSCAPES_DATASET into global variables at load time.
+ import cityscapesscripts.evaluation.evalPixelLevelSemanticLabeling as cityscapes_eval
+
+ self._logger.info("Evaluating results under {} ...".format(self._temp_dir))
+
+ # set some global states in cityscapes evaluation API, before evaluating
+ cityscapes_eval.args.predictionPath = os.path.abspath(self._temp_dir)
+ cityscapes_eval.args.predictionWalk = None
+ cityscapes_eval.args.JSONOutput = False
+ cityscapes_eval.args.colorized = False
+
+ # These lines are adopted from
+ # https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/evalPixelLevelSemanticLabeling.py # noqa
+ gt_dir = PathManager.get_local_path(self._metadata.gt_dir)
+ groundTruthImgList = glob.glob(os.path.join(gt_dir, "*", "*_gtFine_labelIds.png"))
+ assert len(
+ groundTruthImgList
+ ), "Cannot find any ground truth images to use for evaluation. Searched for: {}".format(
+ cityscapes_eval.args.groundTruthSearch
+ )
+ predictionImgList = []
+ for gt in groundTruthImgList:
+ predictionImgList.append(cityscapes_eval.getPrediction(cityscapes_eval.args, gt))
+ results = cityscapes_eval.evaluateImgLists(
+ predictionImgList, groundTruthImgList, cityscapes_eval.args
+ )
+ ret = OrderedDict()
+ ret["sem_seg"] = {
+ "IoU": 100.0 * results["averageScoreClasses"],
+ "iIoU": 100.0 * results["averageScoreInstClasses"],
+ "IoU_sup": 100.0 * results["averageScoreCategories"],
+ "iIoU_sup": 100.0 * results["averageScoreInstCategories"],
+ }
+ self._working_dir.cleanup()
+ return ret
diff --git a/detectron2/detectron2/evaluation/coco_evaluation.py b/detectron2/detectron2/evaluation/coco_evaluation.py
new file mode 100755
index 0000000..fe8142c
--- /dev/null
+++ b/detectron2/detectron2/evaluation/coco_evaluation.py
@@ -0,0 +1,722 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import copy
+import io
+import itertools
+import json
+import logging
+import numpy as np
+import os
+import pickle
+from collections import OrderedDict
+import pycocotools.mask as mask_util
+import torch
+from pycocotools.coco import COCO
+from pycocotools.cocoeval import COCOeval
+from tabulate import tabulate
+
+import detectron2.utils.comm as comm
+from detectron2.config import CfgNode
+from detectron2.data import MetadataCatalog
+from detectron2.data.datasets.coco import convert_to_coco_json
+from detectron2.structures import Boxes, BoxMode, pairwise_iou
+from detectron2.utils.file_io import PathManager
+from detectron2.utils.logger import create_small_table
+
+from .evaluator import DatasetEvaluator
+
+try:
+ from detectron2.evaluation.fast_eval_api import COCOeval_opt
+except ImportError:
+ COCOeval_opt = COCOeval
+
+
+class COCOEvaluator(DatasetEvaluator):
+ """
+ Evaluate AR for object proposals, AP for instance detection/segmentation, AP
+ for keypoint detection outputs using COCO's metrics.
+ See http://cocodataset.org/#detection-eval and
+ http://cocodataset.org/#keypoints-eval to understand its metrics.
+ The metrics range from 0 to 100 (instead of 0 to 1), where a -1 or NaN means
+ the metric cannot be computed (e.g. due to no predictions made).
+
+ In addition to COCO, this evaluator is able to support any bounding box detection,
+ instance segmentation, or keypoint detection dataset.
+ """
+
+ def __init__(
+ self,
+ dataset_name,
+ tasks=None,
+ distributed=True,
+ output_dir=None,
+ *,
+ max_dets_per_image=None,
+ use_fast_impl=True,
+ kpt_oks_sigmas=(),
+ allow_cached_coco=True,
+ ):
+ """
+ Args:
+ dataset_name (str): name of the dataset to be evaluated.
+ It must have either the following corresponding metadata:
+
+ "json_file": the path to the COCO format annotation
+
+ Or it must be in detectron2's standard dataset format
+ so it can be converted to COCO format automatically.
+ tasks (tuple[str]): tasks that can be evaluated under the given
+ configuration. A task is one of "bbox", "segm", "keypoints".
+ By default, will infer this automatically from predictions.
+ distributed (True): if True, will collect results from all ranks and run evaluation
+ in the main process.
+ Otherwise, will only evaluate the results in the current process.
+ output_dir (str): optional, an output directory to dump all
+ results predicted on the dataset. The dump contains two files:
+
+ 1. "instances_predictions.pth" a file that can be loaded with `torch.load` and
+ contains all the results in the format they are produced by the model.
+ 2. "coco_instances_results.json" a json file in COCO's result format.
+ max_dets_per_image (int): limit on the maximum number of detections per image.
+ By default in COCO, this limit is to 100, but this can be customized
+ to be greater, as is needed in evaluation metrics AP fixed and AP pool
+ (see https://arxiv.org/pdf/2102.01066.pdf)
+ This doesn't affect keypoint evaluation.
+ use_fast_impl (bool): use a fast but **unofficial** implementation to compute AP.
+ Although the results should be very close to the official implementation in COCO
+ API, it is still recommended to compute results with the official API for use in
+ papers. The faster implementation also uses more RAM.
+ kpt_oks_sigmas (list[float]): The sigmas used to calculate keypoint OKS.
+ See http://cocodataset.org/#keypoints-eval
+ When empty, it will use the defaults in COCO.
+ Otherwise it should be the same length as ROI_KEYPOINT_HEAD.NUM_KEYPOINTS.
+ allow_cached_coco (bool): Whether to use cached coco json from previous validation
+ runs. You should set this to False if you need to use different validation data.
+ Defaults to True.
+ """
+ self._logger = logging.getLogger(__name__)
+ self._distributed = distributed
+ self._output_dir = output_dir
+
+ if use_fast_impl and (COCOeval_opt is COCOeval):
+ self._logger.info("Fast COCO eval is not built. Falling back to official COCO eval.")
+ use_fast_impl = False
+ self._use_fast_impl = use_fast_impl
+
+ # COCOeval requires the limit on the number of detections per image (maxDets) to be a list
+ # with at least 3 elements. The default maxDets in COCOeval is [1, 10, 100], in which the
+ # 3rd element (100) is used as the limit on the number of detections per image when
+ # evaluating AP. COCOEvaluator expects an integer for max_dets_per_image, so for COCOeval,
+ # we reformat max_dets_per_image into [1, 10, max_dets_per_image], based on the defaults.
+ if max_dets_per_image is None:
+ max_dets_per_image = [1, 10, 100]
+ else:
+ max_dets_per_image = [1, 10, max_dets_per_image]
+ self._max_dets_per_image = max_dets_per_image
+
+ if tasks is not None and isinstance(tasks, CfgNode):
+ kpt_oks_sigmas = (
+ tasks.TEST.KEYPOINT_OKS_SIGMAS if not kpt_oks_sigmas else kpt_oks_sigmas
+ )
+ self._logger.warn(
+ "COCO Evaluator instantiated using config, this is deprecated behavior."
+ " Please pass in explicit arguments instead."
+ )
+ self._tasks = None # Infering it from predictions should be better
+ else:
+ self._tasks = tasks
+
+ self._cpu_device = torch.device("cpu")
+
+ self._metadata = MetadataCatalog.get(dataset_name)
+ if not hasattr(self._metadata, "json_file"):
+ if output_dir is None:
+ raise ValueError(
+ "output_dir must be provided to COCOEvaluator "
+ "for datasets not in COCO format."
+ )
+ self._logger.info(f"Trying to convert '{dataset_name}' to COCO format ...")
+
+ cache_path = os.path.join(output_dir, f"{dataset_name}_coco_format.json")
+ self._metadata.json_file = cache_path
+ convert_to_coco_json(dataset_name, cache_path, allow_cached=allow_cached_coco)
+
+ json_file = PathManager.get_local_path(self._metadata.json_file)
+ with contextlib.redirect_stdout(io.StringIO()):
+ self._coco_api = COCO(json_file)
+
+ # Test set json files do not contain annotations (evaluation must be
+ # performed using the COCO evaluation server).
+ self._do_evaluation = "annotations" in self._coco_api.dataset
+ if self._do_evaluation:
+ self._kpt_oks_sigmas = kpt_oks_sigmas
+
+ def reset(self):
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
+ It is a list of dict. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name", "image_id".
+ outputs: the outputs of a COCO model. It is a list of dicts with key
+ "instances" that contains :class:`Instances`.
+ """
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+
+ if "instances" in output:
+ instances = output["instances"].to(self._cpu_device)
+ prediction["instances"] = instances_to_coco_json(instances, input["image_id"])
+ if "proposals" in output:
+ prediction["proposals"] = output["proposals"].to(self._cpu_device)
+ if len(prediction) > 1:
+ self._predictions.append(prediction)
+
+ def evaluate(self, img_ids=None):
+ """
+ Args:
+ img_ids: a list of image IDs to evaluate on. Default to None for the whole dataset
+ """
+ if self._distributed:
+ comm.synchronize()
+ predictions = comm.gather(self._predictions, dst=0)
+ predictions = list(itertools.chain(*predictions))
+
+ if not comm.is_main_process():
+ return {}
+ else:
+ predictions = self._predictions
+
+ if len(predictions) == 0:
+ self._logger.warning("[COCOEvaluator] Did not receive valid predictions.")
+ return {}
+
+ if self._output_dir:
+ PathManager.mkdirs(self._output_dir)
+ file_path = os.path.join(self._output_dir, "instances_predictions.pth")
+ with PathManager.open(file_path, "wb") as f:
+ torch.save(predictions, f)
+
+ self._results = OrderedDict()
+ if "proposals" in predictions[0]:
+ self._eval_box_proposals(predictions)
+ if "instances" in predictions[0]:
+ self._eval_predictions(predictions, img_ids=img_ids)
+ # Copy so the caller can do whatever with results
+ return copy.deepcopy(self._results)
+
+ def _tasks_from_predictions(self, predictions):
+ """
+ Get COCO API "tasks" (i.e. iou_type) from COCO-format predictions.
+ """
+ tasks = {"bbox"}
+ for pred in predictions:
+ if "segmentation" in pred:
+ tasks.add("segm")
+ if "keypoints" in pred:
+ tasks.add("keypoints")
+ return sorted(tasks)
+
+ def _eval_predictions(self, predictions, img_ids=None):
+ """
+ Evaluate predictions. Fill self._results with the metrics of the tasks.
+ """
+ self._logger.info("Preparing results for COCO format ...")
+ coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))
+ tasks = self._tasks or self._tasks_from_predictions(coco_results)
+
+ # unmap the category ids for COCO
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
+ dataset_id_to_contiguous_id = self._metadata.thing_dataset_id_to_contiguous_id
+ all_contiguous_ids = list(dataset_id_to_contiguous_id.values())
+ num_classes = len(all_contiguous_ids)
+ assert min(all_contiguous_ids) == 0 and max(all_contiguous_ids) == num_classes - 1
+
+ reverse_id_mapping = {v: k for k, v in dataset_id_to_contiguous_id.items()}
+ for result in coco_results:
+ category_id = result["category_id"]
+ assert category_id < num_classes, (
+ f"A prediction has class={category_id}, "
+ f"but the dataset only has {num_classes} classes and "
+ f"predicted class id should be in [0, {num_classes - 1}]."
+ )
+ result["category_id"] = reverse_id_mapping[category_id]
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "coco_instances_results.json")
+ self._logger.info("Saving results to {}".format(file_path))
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(coco_results))
+ f.flush()
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info(
+ "Evaluating predictions with {} COCO API...".format(
+ "unofficial" if self._use_fast_impl else "official"
+ )
+ )
+ for task in sorted(tasks):
+ assert task in {"bbox", "segm", "keypoints"}, f"Got unknown task: {task}!"
+ coco_eval = (
+ _evaluate_predictions_on_coco(
+ self._coco_api,
+ coco_results,
+ task,
+ kpt_oks_sigmas=self._kpt_oks_sigmas,
+ cocoeval_fn=COCOeval_opt if self._use_fast_impl else COCOeval,
+ img_ids=img_ids,
+ max_dets_per_image=self._max_dets_per_image,
+ )
+ if len(coco_results) > 0
+ else None # cocoapi does not handle empty results very well
+ )
+
+ res = self._derive_coco_results(
+ coco_eval, task, class_names=self._metadata.get("thing_classes")
+ )
+ self._results[task] = res
+
+ def _eval_box_proposals(self, predictions):
+ """
+ Evaluate the box proposals in predictions.
+ Fill self._results with the metrics for "box_proposals" task.
+ """
+ if self._output_dir:
+ # Saving generated box proposals to file.
+ # Predicted box_proposals are in XYXY_ABS mode.
+ bbox_mode = BoxMode.XYXY_ABS.value
+ ids, boxes, objectness_logits = [], [], []
+ for prediction in predictions:
+ ids.append(prediction["image_id"])
+ boxes.append(prediction["proposals"].proposal_boxes.tensor.numpy())
+ objectness_logits.append(prediction["proposals"].objectness_logits.numpy())
+
+ proposal_data = {
+ "boxes": boxes,
+ "objectness_logits": objectness_logits,
+ "ids": ids,
+ "bbox_mode": bbox_mode,
+ }
+ with PathManager.open(os.path.join(self._output_dir, "box_proposals.pkl"), "wb") as f:
+ pickle.dump(proposal_data, f)
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating bbox proposals ...")
+ res = {}
+ areas = {"all": "", "small": "s", "medium": "m", "large": "l"}
+ for limit in [100, 1000]:
+ for area, suffix in areas.items():
+ stats = _evaluate_box_proposals(predictions, self._coco_api, area=area, limit=limit)
+ key = "AR{}@{:d}".format(suffix, limit)
+ res[key] = float(stats["ar"].item() * 100)
+ self._logger.info("Proposal metrics: \n" + create_small_table(res))
+ self._results["box_proposals"] = res
+
+ def _derive_coco_results(self, coco_eval, iou_type, class_names=None):
+ """
+ Derive the desired score numbers from summarized COCOeval.
+
+ Args:
+ coco_eval (None or COCOEval): None represents no predictions from model.
+ iou_type (str):
+ class_names (None or list[str]): if provided, will use it to predict
+ per-category AP.
+
+ Returns:
+ a dict of {metric name: score}
+ """
+
+ metrics = {
+ "bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl"],
+ "segm": ["AP", "AP50", "AP75", "APs", "APm", "APl"],
+ "keypoints": ["AP", "AP50", "AP75", "APm", "APl"],
+ }[iou_type]
+
+ if coco_eval is None:
+ self._logger.warn("No predictions from the model!")
+ return {metric: float("nan") for metric in metrics}
+
+ # the standard metrics
+ results = {
+ metric: float(coco_eval.stats[idx] * 100 if coco_eval.stats[idx] >= 0 else "nan")
+ for idx, metric in enumerate(metrics)
+ }
+ self._logger.info(
+ "Evaluation results for {}: \n".format(iou_type) + create_small_table(results)
+ )
+ if not np.isfinite(sum(results.values())):
+ self._logger.info("Some metrics cannot be computed and is shown as NaN.")
+
+ if class_names is None or len(class_names) <= 1:
+ return results
+ # Compute per-category AP
+ # from https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L222-L252 # noqa
+ precisions = coco_eval.eval["precision"]
+ # precision has dims (iou, recall, cls, area range, max dets)
+ assert len(class_names) == precisions.shape[2]
+
+ results_per_category = []
+ for idx, name in enumerate(class_names):
+ # area range index 0: all area ranges
+ # max dets index -1: typically 100 per image
+ precision = precisions[:, :, idx, 0, -1]
+ precision = precision[precision > -1]
+ ap = np.mean(precision) if precision.size else float("nan")
+ results_per_category.append(("{}".format(name), float(ap * 100)))
+
+ # tabulate it
+ N_COLS = min(6, len(results_per_category) * 2)
+ results_flatten = list(itertools.chain(*results_per_category))
+ results_2d = itertools.zip_longest(*[results_flatten[i::N_COLS] for i in range(N_COLS)])
+ table = tabulate(
+ results_2d,
+ tablefmt="pipe",
+ floatfmt=".3f",
+ headers=["category", "AP"] * (N_COLS // 2),
+ numalign="left",
+ )
+ self._logger.info("Per-category {} AP: \n".format(iou_type) + table)
+
+ results.update({"AP-" + name: ap for name, ap in results_per_category})
+ return results
+
+
+def instances_to_coco_json(instances, img_id):
+ """
+ Dump an "Instances" object to a COCO-format json that's used for evaluation.
+
+ Args:
+ instances (Instances):
+ img_id (int): the image id
+
+ Returns:
+ list[dict]: list of json annotations in COCO format.
+ """
+ num_instance = len(instances)
+ if num_instance == 0:
+ return []
+
+ boxes = instances.pred_boxes.tensor.numpy()
+ boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
+ boxes = boxes.tolist()
+ scores = instances.scores.tolist()
+ classes = instances.pred_classes.tolist()
+
+ has_mask = instances.has("pred_masks")
+ if has_mask:
+ # use RLE to encode the masks, because they are too large and takes memory
+ # since this evaluator stores outputs of the entire dataset
+ rles = [
+ mask_util.encode(np.array(mask[:, :, None], order="F", dtype="uint8"))[0]
+ for mask in instances.pred_masks
+ ]
+ for rle in rles:
+ # "counts" is an array encoded by mask_util as a byte-stream. Python3's
+ # json writer which always produces strings cannot serialize a bytestream
+ # unless you decode it. Thankfully, utf-8 works out (which is also what
+ # the pycocotools/_mask.pyx does).
+ rle["counts"] = rle["counts"].decode("utf-8")
+
+ has_keypoints = instances.has("pred_keypoints")
+ if has_keypoints:
+ keypoints = instances.pred_keypoints
+
+ results = []
+ for k in range(num_instance):
+ result = {
+ "image_id": img_id,
+ "category_id": classes[k],
+ "bbox": boxes[k],
+ "score": scores[k],
+ }
+ if has_mask:
+ result["segmentation"] = rles[k]
+ if has_keypoints:
+ # In COCO annotations,
+ # keypoints coordinates are pixel indices.
+ # However our predictions are floating point coordinates.
+ # Therefore we subtract 0.5 to be consistent with the annotation format.
+ # This is the inverse of data loading logic in `datasets/coco.py`.
+ keypoints[k][:, :2] -= 0.5
+ result["keypoints"] = keypoints[k].flatten().tolist()
+ results.append(result)
+ return results
+
+
+# inspired from Detectron:
+# https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L255 # noqa
+def _evaluate_box_proposals(dataset_predictions, coco_api, thresholds=None, area="all", limit=None):
+ """
+ Evaluate detection proposal recall metrics. This function is a much
+ faster alternative to the official COCO API recall evaluation code. However,
+ it produces slightly different results.
+ """
+ # Record max overlap value for each gt box
+ # Return vector of overlap values
+ areas = {
+ "all": 0,
+ "small": 1,
+ "medium": 2,
+ "large": 3,
+ "96-128": 4,
+ "128-256": 5,
+ "256-512": 6,
+ "512-inf": 7,
+ }
+ area_ranges = [
+ [0**2, 1e5**2], # all
+ [0**2, 32**2], # small
+ [32**2, 96**2], # medium
+ [96**2, 1e5**2], # large
+ [96**2, 128**2], # 96-128
+ [128**2, 256**2], # 128-256
+ [256**2, 512**2], # 256-512
+ [512**2, 1e5**2],
+ ] # 512-inf
+ assert area in areas, "Unknown area range: {}".format(area)
+ area_range = area_ranges[areas[area]]
+ gt_overlaps = []
+ num_pos = 0
+
+ for prediction_dict in dataset_predictions:
+ predictions = prediction_dict["proposals"]
+
+ # sort predictions in descending order
+ # TODO maybe remove this and make it explicit in the documentation
+ inds = predictions.objectness_logits.sort(descending=True)[1]
+ predictions = predictions[inds]
+
+ ann_ids = coco_api.getAnnIds(imgIds=prediction_dict["image_id"])
+ anno = coco_api.loadAnns(ann_ids)
+ gt_boxes = [
+ BoxMode.convert(obj["bbox"], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS)
+ for obj in anno
+ if obj["iscrowd"] == 0
+ ]
+ gt_boxes = torch.as_tensor(gt_boxes).reshape(-1, 4) # guard against no boxes
+ gt_boxes = Boxes(gt_boxes)
+ gt_areas = torch.as_tensor([obj["area"] for obj in anno if obj["iscrowd"] == 0])
+
+ if len(gt_boxes) == 0 or len(predictions) == 0:
+ continue
+
+ valid_gt_inds = (gt_areas >= area_range[0]) & (gt_areas <= area_range[1])
+ gt_boxes = gt_boxes[valid_gt_inds]
+
+ num_pos += len(gt_boxes)
+
+ if len(gt_boxes) == 0:
+ continue
+
+ if limit is not None and len(predictions) > limit:
+ predictions = predictions[:limit]
+
+ overlaps = pairwise_iou(predictions.proposal_boxes, gt_boxes)
+
+ _gt_overlaps = torch.zeros(len(gt_boxes))
+ for j in range(min(len(predictions), len(gt_boxes))):
+ # find which proposal box maximally covers each gt box
+ # and get the iou amount of coverage for each gt box
+ max_overlaps, argmax_overlaps = overlaps.max(dim=0)
+
+ # find which gt box is 'best' covered (i.e. 'best' = most iou)
+ gt_ovr, gt_ind = max_overlaps.max(dim=0)
+ assert gt_ovr >= 0
+ # find the proposal box that covers the best covered gt box
+ box_ind = argmax_overlaps[gt_ind]
+ # record the iou coverage of this gt box
+ _gt_overlaps[j] = overlaps[box_ind, gt_ind]
+ assert _gt_overlaps[j] == gt_ovr
+ # mark the proposal box and the gt box as used
+ overlaps[box_ind, :] = -1
+ overlaps[:, gt_ind] = -1
+
+ # append recorded iou coverage level
+ gt_overlaps.append(_gt_overlaps)
+ gt_overlaps = (
+ torch.cat(gt_overlaps, dim=0) if len(gt_overlaps) else torch.zeros(0, dtype=torch.float32)
+ )
+ gt_overlaps, _ = torch.sort(gt_overlaps)
+
+ if thresholds is None:
+ step = 0.05
+ thresholds = torch.arange(0.5, 0.95 + 1e-5, step, dtype=torch.float32)
+ recalls = torch.zeros_like(thresholds)
+ # compute recall for each iou threshold
+ for i, t in enumerate(thresholds):
+ recalls[i] = (gt_overlaps >= t).float().sum() / float(num_pos)
+ # ar = 2 * np.trapz(recalls, thresholds)
+ ar = recalls.mean()
+ return {
+ "ar": ar,
+ "recalls": recalls,
+ "thresholds": thresholds,
+ "gt_overlaps": gt_overlaps,
+ "num_pos": num_pos,
+ }
+
+
+def _evaluate_predictions_on_coco(
+ coco_gt,
+ coco_results,
+ iou_type,
+ kpt_oks_sigmas=None,
+ cocoeval_fn=COCOeval_opt,
+ img_ids=None,
+ max_dets_per_image=None,
+):
+ """
+ Evaluate the coco results using COCOEval API.
+ """
+ assert len(coco_results) > 0
+
+ if iou_type == "segm":
+ coco_results = copy.deepcopy(coco_results)
+ # When evaluating mask AP, if the results contain bbox, cocoapi will
+ # use the box area as the area of the instance, instead of the mask area.
+ # This leads to a different definition of small/medium/large.
+ # We remove the bbox field to let mask AP use mask area.
+ for c in coco_results:
+ c.pop("bbox", None)
+
+ coco_dt = coco_gt.loadRes(coco_results)
+ coco_eval = cocoeval_fn(coco_gt, coco_dt, iou_type)
+ # For COCO, the default max_dets_per_image is [1, 10, 100].
+ if max_dets_per_image is None:
+ max_dets_per_image = [1, 10, 100] # Default from COCOEval
+ else:
+ assert (
+ len(max_dets_per_image) >= 3
+ ), "COCOeval requires maxDets (and max_dets_per_image) to have length at least 3"
+ # In the case that user supplies a custom input for max_dets_per_image,
+ # apply COCOevalMaxDets to evaluate AP with the custom input.
+ if max_dets_per_image[2] != 100:
+ coco_eval = COCOevalMaxDets(coco_gt, coco_dt, iou_type)
+ if iou_type != "keypoints":
+ coco_eval.params.maxDets = max_dets_per_image
+
+ if img_ids is not None:
+ coco_eval.params.imgIds = img_ids
+
+ if iou_type == "keypoints":
+ # Use the COCO default keypoint OKS sigmas unless overrides are specified
+ if kpt_oks_sigmas:
+ assert hasattr(coco_eval.params, "kpt_oks_sigmas"), "pycocotools is too old!"
+ coco_eval.params.kpt_oks_sigmas = np.array(kpt_oks_sigmas)
+ # COCOAPI requires every detection and every gt to have keypoints, so
+ # we just take the first entry from both
+ num_keypoints_dt = len(coco_results[0]["keypoints"]) // 3
+ num_keypoints_gt = len(next(iter(coco_gt.anns.values()))["keypoints"]) // 3
+ num_keypoints_oks = len(coco_eval.params.kpt_oks_sigmas)
+ assert num_keypoints_oks == num_keypoints_dt == num_keypoints_gt, (
+ f"[COCOEvaluator] Prediction contain {num_keypoints_dt} keypoints. "
+ f"Ground truth contains {num_keypoints_gt} keypoints. "
+ f"The length of cfg.TEST.KEYPOINT_OKS_SIGMAS is {num_keypoints_oks}. "
+ "They have to agree with each other. For meaning of OKS, please refer to "
+ "http://cocodataset.org/#keypoints-eval."
+ )
+
+ coco_eval.evaluate()
+ coco_eval.accumulate()
+ coco_eval.summarize()
+
+ return coco_eval
+
+
+class COCOevalMaxDets(COCOeval):
+ """
+ Modified version of COCOeval for evaluating AP with a custom
+ maxDets (by default for COCO, maxDets is 100)
+ """
+
+ def summarize(self):
+ """
+ Compute and display summary metrics for evaluation results given
+ a custom value for max_dets_per_image
+ """
+
+ def _summarize(ap=1, iouThr=None, areaRng="all", maxDets=100):
+ p = self.params
+ iStr = " {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}"
+ 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])
+ print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
+ return mean_s
+
+ def _summarizeDets():
+ stats = np.zeros((12,))
+ # Evaluate AP using the custom limit on maximum detections per image
+ stats[0] = _summarize(1, maxDets=self.params.maxDets[2])
+ stats[1] = _summarize(1, iouThr=0.5, maxDets=self.params.maxDets[2])
+ stats[2] = _summarize(1, iouThr=0.75, maxDets=self.params.maxDets[2])
+ stats[3] = _summarize(1, areaRng="small", maxDets=self.params.maxDets[2])
+ stats[4] = _summarize(1, areaRng="medium", maxDets=self.params.maxDets[2])
+ stats[5] = _summarize(1, areaRng="large", maxDets=self.params.maxDets[2])
+ stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
+ stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
+ stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
+ stats[9] = _summarize(0, areaRng="small", maxDets=self.params.maxDets[2])
+ stats[10] = _summarize(0, areaRng="medium", maxDets=self.params.maxDets[2])
+ stats[11] = _summarize(0, areaRng="large", maxDets=self.params.maxDets[2])
+ return stats
+
+ def _summarizeKps():
+ stats = np.zeros((10,))
+ stats[0] = _summarize(1, maxDets=20)
+ stats[1] = _summarize(1, maxDets=20, iouThr=0.5)
+ stats[2] = _summarize(1, maxDets=20, iouThr=0.75)
+ stats[3] = _summarize(1, maxDets=20, areaRng="medium")
+ stats[4] = _summarize(1, maxDets=20, areaRng="large")
+ stats[5] = _summarize(0, maxDets=20)
+ stats[6] = _summarize(0, maxDets=20, iouThr=0.5)
+ stats[7] = _summarize(0, maxDets=20, iouThr=0.75)
+ stats[8] = _summarize(0, maxDets=20, areaRng="medium")
+ stats[9] = _summarize(0, maxDets=20, areaRng="large")
+ return stats
+
+ if not self.eval:
+ raise Exception("Please run accumulate() first")
+ iouType = self.params.iouType
+ if iouType == "segm" or iouType == "bbox":
+ summarize = _summarizeDets
+ elif iouType == "keypoints":
+ summarize = _summarizeKps
+ self.stats = summarize()
+
+ def __str__(self):
+ self.summarize()
diff --git a/detectron2/detectron2/evaluation/evaluator.py b/detectron2/detectron2/evaluation/evaluator.py
new file mode 100755
index 0000000..baf9960
--- /dev/null
+++ b/detectron2/detectron2/evaluation/evaluator.py
@@ -0,0 +1,224 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import datetime
+import logging
+import time
+from collections import OrderedDict, abc
+from contextlib import ExitStack, contextmanager
+from typing import List, Union
+import torch
+from torch import nn
+
+from detectron2.utils.comm import get_world_size, is_main_process
+from detectron2.utils.logger import log_every_n_seconds
+
+
+class DatasetEvaluator:
+ """
+ Base class for a dataset evaluator.
+
+ The function :func:`inference_on_dataset` runs the model over
+ all samples in the dataset, and have a DatasetEvaluator to process the inputs/outputs.
+
+ This class will accumulate information of the inputs/outputs (by :meth:`process`),
+ and produce evaluation results in the end (by :meth:`evaluate`).
+ """
+
+ def reset(self):
+ """
+ Preparation for a new round of evaluation.
+ Should be called before starting a round of evaluation.
+ """
+ pass
+
+ def process(self, inputs, outputs):
+ """
+ Process the pair of inputs and outputs.
+ If they contain batches, the pairs can be consumed one-by-one using `zip`:
+
+ .. code-block:: python
+
+ for input_, output in zip(inputs, outputs):
+ # do evaluation on single input/output pair
+ ...
+
+ Args:
+ inputs (list): the inputs that's used to call the model.
+ outputs (list): the return value of `model(inputs)`
+ """
+ pass
+
+ def evaluate(self):
+ """
+ Evaluate/summarize the performance, after processing all input/output pairs.
+
+ Returns:
+ dict:
+ A new evaluator class can return a dict of arbitrary format
+ as long as the user can process the results.
+ In our train_net.py, we expect the following format:
+
+ * key: the name of the task (e.g., bbox)
+ * value: a dict of {metric name: score}, e.g.: {"AP50": 80}
+ """
+ pass
+
+
+class DatasetEvaluators(DatasetEvaluator):
+ """
+ Wrapper class to combine multiple :class:`DatasetEvaluator` instances.
+
+ This class dispatches every evaluation call to
+ all of its :class:`DatasetEvaluator`.
+ """
+
+ def __init__(self, evaluators):
+ """
+ Args:
+ evaluators (list): the evaluators to combine.
+ """
+ super().__init__()
+ self._evaluators = evaluators
+
+ def reset(self):
+ for evaluator in self._evaluators:
+ evaluator.reset()
+
+ def process(self, inputs, outputs):
+ for evaluator in self._evaluators:
+ evaluator.process(inputs, outputs)
+
+ def evaluate(self):
+ results = OrderedDict()
+ for evaluator in self._evaluators:
+ result = evaluator.evaluate()
+ if is_main_process() and result is not None:
+ for k, v in result.items():
+ assert (
+ k not in results
+ ), "Different evaluators produce results with the same key {}".format(k)
+ results[k] = v
+ return results
+
+
+def inference_on_dataset(
+ model, data_loader, evaluator: Union[DatasetEvaluator, List[DatasetEvaluator], None]
+):
+ """
+ Run model on the data_loader and evaluate the metrics with evaluator.
+ Also benchmark the inference speed of `model.__call__` accurately.
+ The model will be used in eval mode.
+
+ Args:
+ model (callable): a callable which takes an object from
+ `data_loader` and returns some outputs.
+
+ If it's an nn.Module, it will be temporarily set to `eval` mode.
+ If you wish to evaluate a model in `training` mode instead, you can
+ wrap the given model and override its behavior of `.eval()` and `.train()`.
+ data_loader: an iterable object with a length.
+ The elements it generates will be the inputs to the model.
+ evaluator: the evaluator(s) to run. Use `None` if you only want to benchmark,
+ but don't want to do any evaluation.
+
+ Returns:
+ The return value of `evaluator.evaluate()`
+ """
+ num_devices = get_world_size()
+ logger = logging.getLogger(__name__)
+ logger.info("Start inference on {} batches".format(len(data_loader)))
+
+ total = len(data_loader) # inference data loader must have a fixed length
+ if evaluator is None:
+ # create a no-op evaluator
+ evaluator = DatasetEvaluators([])
+ if isinstance(evaluator, abc.MutableSequence):
+ evaluator = DatasetEvaluators(evaluator)
+ evaluator.reset()
+
+ num_warmup = min(5, total - 1)
+ start_time = time.perf_counter()
+ total_data_time = 0
+ total_compute_time = 0
+ total_eval_time = 0
+ with ExitStack() as stack:
+ if isinstance(model, nn.Module):
+ stack.enter_context(inference_context(model))
+ stack.enter_context(torch.no_grad())
+
+ start_data_time = time.perf_counter()
+ for idx, inputs in enumerate(data_loader):
+ total_data_time += time.perf_counter() - start_data_time
+ if idx == num_warmup:
+ start_time = time.perf_counter()
+ total_data_time = 0
+ total_compute_time = 0
+ total_eval_time = 0
+
+ start_compute_time = time.perf_counter()
+ outputs = model(inputs)
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+ total_compute_time += time.perf_counter() - start_compute_time
+
+ start_eval_time = time.perf_counter()
+ evaluator.process(inputs, outputs)
+ total_eval_time += time.perf_counter() - start_eval_time
+
+ iters_after_start = idx + 1 - num_warmup * int(idx >= num_warmup)
+ data_seconds_per_iter = total_data_time / iters_after_start
+ compute_seconds_per_iter = total_compute_time / iters_after_start
+ eval_seconds_per_iter = total_eval_time / iters_after_start
+ total_seconds_per_iter = (time.perf_counter() - start_time) / iters_after_start
+ if idx >= num_warmup * 2 or compute_seconds_per_iter > 5:
+ eta = datetime.timedelta(seconds=int(total_seconds_per_iter * (total - idx - 1)))
+ log_every_n_seconds(
+ logging.INFO,
+ (
+ f"Inference done {idx + 1}/{total}. "
+ f"Dataloading: {data_seconds_per_iter:.4f} s/iter. "
+ f"Inference: {compute_seconds_per_iter:.4f} s/iter. "
+ f"Eval: {eval_seconds_per_iter:.4f} s/iter. "
+ f"Total: {total_seconds_per_iter:.4f} s/iter. "
+ f"ETA={eta}"
+ ),
+ n=5,
+ )
+ start_data_time = time.perf_counter()
+
+ # Measure the time only for this worker (before the synchronization barrier)
+ total_time = time.perf_counter() - start_time
+ total_time_str = str(datetime.timedelta(seconds=total_time))
+ # NOTE this format is parsed by grep
+ logger.info(
+ "Total inference time: {} ({:.6f} s / iter per device, on {} devices)".format(
+ total_time_str, total_time / (total - num_warmup), num_devices
+ )
+ )
+ total_compute_time_str = str(datetime.timedelta(seconds=int(total_compute_time)))
+ logger.info(
+ "Total inference pure compute time: {} ({:.6f} s / iter per device, on {} devices)".format(
+ total_compute_time_str, total_compute_time / (total - num_warmup), num_devices
+ )
+ )
+
+ results = evaluator.evaluate()
+ # An evaluator may return None when not in main process.
+ # Replace it by an empty dict instead to make it easier for downstream code to handle
+ if results is None:
+ results = {}
+ return results
+
+
+@contextmanager
+def inference_context(model):
+ """
+ A context where the model is temporarily changed to eval mode,
+ and restored to previous mode afterwards.
+
+ Args:
+ model: a torch Module
+ """
+ training_mode = model.training
+ model.eval()
+ yield
+ model.train(training_mode)
diff --git a/detectron2/detectron2/evaluation/fast_eval_api.py b/detectron2/detectron2/evaluation/fast_eval_api.py
new file mode 100755
index 0000000..2eb202b
--- /dev/null
+++ b/detectron2/detectron2/evaluation/fast_eval_api.py
@@ -0,0 +1,121 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import numpy as np
+import time
+from pycocotools.cocoeval import COCOeval
+
+from detectron2 import _C
+
+logger = logging.getLogger(__name__)
+
+
+class COCOeval_opt(COCOeval):
+ """
+ This is a slightly modified version of the original COCO API, where the functions evaluateImg()
+ and accumulate() are implemented in C++ to speedup evaluation
+ """
+
+ def evaluate(self):
+ """
+ Run per image evaluation on given images and store results in self.evalImgs_cpp, a
+ datastructure that isn't readable from Python but is used by a c++ implementation of
+ accumulate(). Unlike the original COCO PythonAPI, we don't populate the datastructure
+ self.evalImgs because this datastructure is a computational bottleneck.
+ :return: None
+ """
+ tic = time.time()
+
+ p = self.params
+ # add backward compatibility if useSegm is specified in params
+ if p.useSegm is not None:
+ p.iouType = "segm" if p.useSegm == 1 else "bbox"
+ logger.info("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() # bottleneck
+
+ # loop through images, area range, max detection number
+ catIds = p.catIds if p.useCats else [-1]
+
+ if p.iouType == "segm" or p.iouType == "bbox":
+ computeIoU = self.computeIoU
+ elif p.iouType == "keypoints":
+ computeIoU = self.computeOks
+ self.ious = {
+ (imgId, catId): computeIoU(imgId, catId) for imgId in p.imgIds for catId in catIds
+ } # bottleneck
+
+ maxDet = p.maxDets[-1]
+
+ # <<<< Beginning of code differences with original COCO API
+ def convert_instances_to_cpp(instances, is_det=False):
+ # Convert annotations for a list of instances in an image to a format that's fast
+ # to access in C++
+ instances_cpp = []
+ for instance in instances:
+ instance_cpp = _C.InstanceAnnotation(
+ int(instance["id"]),
+ instance["score"] if is_det else instance.get("score", 0.0),
+ instance["area"],
+ bool(instance.get("iscrowd", 0)),
+ bool(instance.get("ignore", 0)),
+ )
+ instances_cpp.append(instance_cpp)
+ return instances_cpp
+
+ # Convert GT annotations, detections, and IOUs to a format that's fast to access in C++
+ ground_truth_instances = [
+ [convert_instances_to_cpp(self._gts[imgId, catId]) for catId in p.catIds]
+ for imgId in p.imgIds
+ ]
+ detected_instances = [
+ [convert_instances_to_cpp(self._dts[imgId, catId], is_det=True) for catId in p.catIds]
+ for imgId in p.imgIds
+ ]
+ ious = [[self.ious[imgId, catId] for catId in catIds] for imgId in p.imgIds]
+
+ if not p.useCats:
+ # For each image, flatten per-category lists into a single list
+ ground_truth_instances = [[[o for c in i for o in c]] for i in ground_truth_instances]
+ detected_instances = [[[o for c in i for o in c]] for i in detected_instances]
+
+ # Call C++ implementation of self.evaluateImgs()
+ self._evalImgs_cpp = _C.COCOevalEvaluateImages(
+ p.areaRng, maxDet, p.iouThrs, ious, ground_truth_instances, detected_instances
+ )
+ self._evalImgs = None
+
+ self._paramsEval = copy.deepcopy(self.params)
+ toc = time.time()
+ logger.info("COCOeval_opt.evaluate() finished in {:0.2f} seconds.".format(toc - tic))
+ # >>>> End of code differences with original COCO API
+
+ def accumulate(self):
+ """
+ Accumulate per image evaluation results and store the result in self.eval. Does not
+ support changing parameter settings from those used by self.evaluate()
+ """
+ logger.info("Accumulating evaluation results...")
+ tic = time.time()
+ assert hasattr(
+ self, "_evalImgs_cpp"
+ ), "evaluate() must be called before accmulate() is called."
+
+ self.eval = _C.COCOevalAccumulate(self._paramsEval, self._evalImgs_cpp)
+
+ # recall is num_iou_thresholds X num_categories X num_area_ranges X num_max_detections
+ self.eval["recall"] = np.array(self.eval["recall"]).reshape(
+ self.eval["counts"][:1] + self.eval["counts"][2:]
+ )
+
+ # precision and scores are num_iou_thresholds X num_recall_thresholds X num_categories X
+ # num_area_ranges X num_max_detections
+ self.eval["precision"] = np.array(self.eval["precision"]).reshape(self.eval["counts"])
+ self.eval["scores"] = np.array(self.eval["scores"]).reshape(self.eval["counts"])
+ toc = time.time()
+ logger.info("COCOeval_opt.accumulate() finished in {:0.2f} seconds.".format(toc - tic))
diff --git a/detectron2/detectron2/evaluation/lvis_evaluation.py b/detectron2/detectron2/evaluation/lvis_evaluation.py
new file mode 100755
index 0000000..6cc854a
--- /dev/null
+++ b/detectron2/detectron2/evaluation/lvis_evaluation.py
@@ -0,0 +1,380 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import itertools
+import json
+import logging
+import os
+import pickle
+from collections import OrderedDict
+import torch
+
+import detectron2.utils.comm as comm
+from detectron2.config import CfgNode
+from detectron2.data import MetadataCatalog
+from detectron2.structures import Boxes, BoxMode, pairwise_iou
+from detectron2.utils.file_io import PathManager
+from detectron2.utils.logger import create_small_table
+
+from .coco_evaluation import instances_to_coco_json
+from .evaluator import DatasetEvaluator
+
+
+class LVISEvaluator(DatasetEvaluator):
+ """
+ Evaluate object proposal and instance detection/segmentation outputs using
+ LVIS's metrics and evaluation API.
+ """
+
+ def __init__(
+ self,
+ dataset_name,
+ tasks=None,
+ distributed=True,
+ output_dir=None,
+ *,
+ max_dets_per_image=None,
+ ):
+ """
+ Args:
+ dataset_name (str): name of the dataset to be evaluated.
+ It must have the following corresponding metadata:
+ "json_file": the path to the LVIS format annotation
+ tasks (tuple[str]): tasks that can be evaluated under the given
+ configuration. A task is one of "bbox", "segm".
+ By default, will infer this automatically from predictions.
+ distributed (True): if True, will collect results from all ranks for evaluation.
+ Otherwise, will evaluate the results in the current process.
+ output_dir (str): optional, an output directory to dump results.
+ max_dets_per_image (None or int): limit on maximum detections per image in evaluating AP
+ This limit, by default of the LVIS dataset, is 300.
+ """
+ from lvis import LVIS
+
+ self._logger = logging.getLogger(__name__)
+
+ if tasks is not None and isinstance(tasks, CfgNode):
+ self._logger.warn(
+ "COCO Evaluator instantiated using config, this is deprecated behavior."
+ " Please pass in explicit arguments instead."
+ )
+ self._tasks = None # Infering it from predictions should be better
+ else:
+ self._tasks = tasks
+
+ self._distributed = distributed
+ self._output_dir = output_dir
+ self._max_dets_per_image = max_dets_per_image
+
+ self._cpu_device = torch.device("cpu")
+
+ self._metadata = MetadataCatalog.get(dataset_name)
+ json_file = PathManager.get_local_path(self._metadata.json_file)
+ self._lvis_api = LVIS(json_file)
+ # Test set json files do not contain annotations (evaluation must be
+ # performed using the LVIS evaluation server).
+ self._do_evaluation = len(self._lvis_api.get_ann_ids()) > 0
+
+ def reset(self):
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a LVIS model (e.g., GeneralizedRCNN).
+ It is a list of dict. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name", "image_id".
+ outputs: the outputs of a LVIS model. It is a list of dicts with key
+ "instances" that contains :class:`Instances`.
+ """
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+
+ if "instances" in output:
+ instances = output["instances"].to(self._cpu_device)
+ prediction["instances"] = instances_to_coco_json(instances, input["image_id"])
+ if "proposals" in output:
+ prediction["proposals"] = output["proposals"].to(self._cpu_device)
+ self._predictions.append(prediction)
+
+ def evaluate(self):
+ if self._distributed:
+ comm.synchronize()
+ predictions = comm.gather(self._predictions, dst=0)
+ predictions = list(itertools.chain(*predictions))
+
+ if not comm.is_main_process():
+ return
+ else:
+ predictions = self._predictions
+
+ if len(predictions) == 0:
+ self._logger.warning("[LVISEvaluator] Did not receive valid predictions.")
+ return {}
+
+ if self._output_dir:
+ PathManager.mkdirs(self._output_dir)
+ file_path = os.path.join(self._output_dir, "instances_predictions.pth")
+ with PathManager.open(file_path, "wb") as f:
+ torch.save(predictions, f)
+
+ self._results = OrderedDict()
+ if "proposals" in predictions[0]:
+ self._eval_box_proposals(predictions)
+ if "instances" in predictions[0]:
+ self._eval_predictions(predictions)
+ # Copy so the caller can do whatever with results
+ return copy.deepcopy(self._results)
+
+ def _tasks_from_predictions(self, predictions):
+ for pred in predictions:
+ if "segmentation" in pred:
+ return ("bbox", "segm")
+ return ("bbox",)
+
+ def _eval_predictions(self, predictions):
+ """
+ Evaluate predictions. Fill self._results with the metrics of the tasks.
+
+ Args:
+ predictions (list[dict]): list of outputs from the model
+ """
+ self._logger.info("Preparing results in the LVIS format ...")
+ lvis_results = list(itertools.chain(*[x["instances"] for x in predictions]))
+ tasks = self._tasks or self._tasks_from_predictions(lvis_results)
+
+ # LVIS evaluator can be used to evaluate results for COCO dataset categories.
+ # In this case `_metadata` variable will have a field with COCO-specific category mapping.
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
+ reverse_id_mapping = {
+ v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items()
+ }
+ for result in lvis_results:
+ result["category_id"] = reverse_id_mapping[result["category_id"]]
+ else:
+ # unmap the category ids for LVIS (from 0-indexed to 1-indexed)
+ for result in lvis_results:
+ result["category_id"] += 1
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "lvis_instances_results.json")
+ self._logger.info("Saving results to {}".format(file_path))
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(lvis_results))
+ f.flush()
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating predictions ...")
+ for task in sorted(tasks):
+ res = _evaluate_predictions_on_lvis(
+ self._lvis_api,
+ lvis_results,
+ task,
+ max_dets_per_image=self._max_dets_per_image,
+ class_names=self._metadata.get("thing_classes"),
+ )
+ self._results[task] = res
+
+ def _eval_box_proposals(self, predictions):
+ """
+ Evaluate the box proposals in predictions.
+ Fill self._results with the metrics for "box_proposals" task.
+ """
+ if self._output_dir:
+ # Saving generated box proposals to file.
+ # Predicted box_proposals are in XYXY_ABS mode.
+ bbox_mode = BoxMode.XYXY_ABS.value
+ ids, boxes, objectness_logits = [], [], []
+ for prediction in predictions:
+ ids.append(prediction["image_id"])
+ boxes.append(prediction["proposals"].proposal_boxes.tensor.numpy())
+ objectness_logits.append(prediction["proposals"].objectness_logits.numpy())
+
+ proposal_data = {
+ "boxes": boxes,
+ "objectness_logits": objectness_logits,
+ "ids": ids,
+ "bbox_mode": bbox_mode,
+ }
+ with PathManager.open(os.path.join(self._output_dir, "box_proposals.pkl"), "wb") as f:
+ pickle.dump(proposal_data, f)
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating bbox proposals ...")
+ res = {}
+ areas = {"all": "", "small": "s", "medium": "m", "large": "l"}
+ for limit in [100, 1000]:
+ for area, suffix in areas.items():
+ stats = _evaluate_box_proposals(predictions, self._lvis_api, area=area, limit=limit)
+ key = "AR{}@{:d}".format(suffix, limit)
+ res[key] = float(stats["ar"].item() * 100)
+ self._logger.info("Proposal metrics: \n" + create_small_table(res))
+ self._results["box_proposals"] = res
+
+
+# inspired from Detectron:
+# https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L255 # noqa
+def _evaluate_box_proposals(dataset_predictions, lvis_api, thresholds=None, area="all", limit=None):
+ """
+ Evaluate detection proposal recall metrics. This function is a much
+ faster alternative to the official LVIS API recall evaluation code. However,
+ it produces slightly different results.
+ """
+ # Record max overlap value for each gt box
+ # Return vector of overlap values
+ areas = {
+ "all": 0,
+ "small": 1,
+ "medium": 2,
+ "large": 3,
+ "96-128": 4,
+ "128-256": 5,
+ "256-512": 6,
+ "512-inf": 7,
+ }
+ area_ranges = [
+ [0**2, 1e5**2], # all
+ [0**2, 32**2], # small
+ [32**2, 96**2], # medium
+ [96**2, 1e5**2], # large
+ [96**2, 128**2], # 96-128
+ [128**2, 256**2], # 128-256
+ [256**2, 512**2], # 256-512
+ [512**2, 1e5**2],
+ ] # 512-inf
+ assert area in areas, "Unknown area range: {}".format(area)
+ area_range = area_ranges[areas[area]]
+ gt_overlaps = []
+ num_pos = 0
+
+ for prediction_dict in dataset_predictions:
+ predictions = prediction_dict["proposals"]
+
+ # sort predictions in descending order
+ # TODO maybe remove this and make it explicit in the documentation
+ inds = predictions.objectness_logits.sort(descending=True)[1]
+ predictions = predictions[inds]
+
+ ann_ids = lvis_api.get_ann_ids(img_ids=[prediction_dict["image_id"]])
+ anno = lvis_api.load_anns(ann_ids)
+ gt_boxes = [
+ BoxMode.convert(obj["bbox"], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS) for obj in anno
+ ]
+ gt_boxes = torch.as_tensor(gt_boxes).reshape(-1, 4) # guard against no boxes
+ gt_boxes = Boxes(gt_boxes)
+ gt_areas = torch.as_tensor([obj["area"] for obj in anno])
+
+ if len(gt_boxes) == 0 or len(predictions) == 0:
+ continue
+
+ valid_gt_inds = (gt_areas >= area_range[0]) & (gt_areas <= area_range[1])
+ gt_boxes = gt_boxes[valid_gt_inds]
+
+ num_pos += len(gt_boxes)
+
+ if len(gt_boxes) == 0:
+ continue
+
+ if limit is not None and len(predictions) > limit:
+ predictions = predictions[:limit]
+
+ overlaps = pairwise_iou(predictions.proposal_boxes, gt_boxes)
+
+ _gt_overlaps = torch.zeros(len(gt_boxes))
+ for j in range(min(len(predictions), len(gt_boxes))):
+ # find which proposal box maximally covers each gt box
+ # and get the iou amount of coverage for each gt box
+ max_overlaps, argmax_overlaps = overlaps.max(dim=0)
+
+ # find which gt box is 'best' covered (i.e. 'best' = most iou)
+ gt_ovr, gt_ind = max_overlaps.max(dim=0)
+ assert gt_ovr >= 0
+ # find the proposal box that covers the best covered gt box
+ box_ind = argmax_overlaps[gt_ind]
+ # record the iou coverage of this gt box
+ _gt_overlaps[j] = overlaps[box_ind, gt_ind]
+ assert _gt_overlaps[j] == gt_ovr
+ # mark the proposal box and the gt box as used
+ overlaps[box_ind, :] = -1
+ overlaps[:, gt_ind] = -1
+
+ # append recorded iou coverage level
+ gt_overlaps.append(_gt_overlaps)
+ gt_overlaps = (
+ torch.cat(gt_overlaps, dim=0) if len(gt_overlaps) else torch.zeros(0, dtype=torch.float32)
+ )
+ gt_overlaps, _ = torch.sort(gt_overlaps)
+
+ if thresholds is None:
+ step = 0.05
+ thresholds = torch.arange(0.5, 0.95 + 1e-5, step, dtype=torch.float32)
+ recalls = torch.zeros_like(thresholds)
+ # compute recall for each iou threshold
+ for i, t in enumerate(thresholds):
+ recalls[i] = (gt_overlaps >= t).float().sum() / float(num_pos)
+ # ar = 2 * np.trapz(recalls, thresholds)
+ ar = recalls.mean()
+ return {
+ "ar": ar,
+ "recalls": recalls,
+ "thresholds": thresholds,
+ "gt_overlaps": gt_overlaps,
+ "num_pos": num_pos,
+ }
+
+
+def _evaluate_predictions_on_lvis(
+ lvis_gt, lvis_results, iou_type, max_dets_per_image=None, class_names=None
+):
+ """
+ Args:
+ iou_type (str):
+ max_dets_per_image (None or int): limit on maximum detections per image in evaluating AP
+ This limit, by default of the LVIS dataset, is 300.
+ class_names (None or list[str]): if provided, will use it to predict
+ per-category AP.
+
+ Returns:
+ a dict of {metric name: score}
+ """
+ metrics = {
+ "bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl", "APr", "APc", "APf"],
+ "segm": ["AP", "AP50", "AP75", "APs", "APm", "APl", "APr", "APc", "APf"],
+ }[iou_type]
+
+ logger = logging.getLogger(__name__)
+
+ if len(lvis_results) == 0: # TODO: check if needed
+ logger.warn("No predictions from the model!")
+ return {metric: float("nan") for metric in metrics}
+
+ if iou_type == "segm":
+ lvis_results = copy.deepcopy(lvis_results)
+ # When evaluating mask AP, if the results contain bbox, LVIS API will
+ # use the box area as the area of the instance, instead of the mask area.
+ # This leads to a different definition of small/medium/large.
+ # We remove the bbox field to let mask AP use mask area.
+ for c in lvis_results:
+ c.pop("bbox", None)
+
+ if max_dets_per_image is None:
+ max_dets_per_image = 300 # Default for LVIS dataset
+
+ from lvis import LVISEval, LVISResults
+
+ logger.info(f"Evaluating with max detections per image = {max_dets_per_image}")
+ lvis_results = LVISResults(lvis_gt, lvis_results, max_dets=max_dets_per_image)
+ lvis_eval = LVISEval(lvis_gt, lvis_results, iou_type)
+ lvis_eval.run()
+ lvis_eval.print_results()
+
+ # Pull the standard metrics from the LVIS results
+ results = lvis_eval.get_results()
+ results = {metric: float(results[metric] * 100) for metric in metrics}
+ logger.info("Evaluation results for {}: \n".format(iou_type) + create_small_table(results))
+ return results
diff --git a/detectron2/detectron2/evaluation/panoptic_evaluation.py b/detectron2/detectron2/evaluation/panoptic_evaluation.py
new file mode 100755
index 0000000..9fb3462
--- /dev/null
+++ b/detectron2/detectron2/evaluation/panoptic_evaluation.py
@@ -0,0 +1,199 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import io
+import itertools
+import json
+import logging
+import numpy as np
+import os
+import tempfile
+from collections import OrderedDict
+from typing import Optional
+from PIL import Image
+from tabulate import tabulate
+
+from detectron2.data import MetadataCatalog
+from detectron2.utils import comm
+from detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+logger = logging.getLogger(__name__)
+
+
+class COCOPanopticEvaluator(DatasetEvaluator):
+ """
+ Evaluate Panoptic Quality metrics on COCO using PanopticAPI.
+ It saves panoptic segmentation prediction in `output_dir`
+
+ It contains a synchronize call and has to be called from all workers.
+ """
+
+ def __init__(self, dataset_name: str, output_dir: Optional[str] = None):
+ """
+ Args:
+ dataset_name: name of the dataset
+ output_dir: output directory to save results for evaluation.
+ """
+ self._metadata = MetadataCatalog.get(dataset_name)
+ self._thing_contiguous_id_to_dataset_id = {
+ v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items()
+ }
+ self._stuff_contiguous_id_to_dataset_id = {
+ v: k for k, v in self._metadata.stuff_dataset_id_to_contiguous_id.items()
+ }
+
+ self._output_dir = output_dir
+ if self._output_dir is not None:
+ PathManager.mkdirs(self._output_dir)
+
+ def reset(self):
+ self._predictions = []
+
+ def _convert_category_id(self, segment_info):
+ isthing = segment_info.pop("isthing", None)
+ if isthing is None:
+ # the model produces panoptic category id directly. No more conversion needed
+ return segment_info
+ if isthing is True:
+ segment_info["category_id"] = self._thing_contiguous_id_to_dataset_id[
+ segment_info["category_id"]
+ ]
+ else:
+ segment_info["category_id"] = self._stuff_contiguous_id_to_dataset_id[
+ segment_info["category_id"]
+ ]
+ return segment_info
+
+ def process(self, inputs, outputs):
+ from panopticapi.utils import id2rgb
+
+ for input, output in zip(inputs, outputs):
+ panoptic_img, segments_info = output["panoptic_seg"]
+ panoptic_img = panoptic_img.cpu().numpy()
+ if segments_info is None:
+ # If "segments_info" is None, we assume "panoptic_img" is a
+ # H*W int32 image storing the panoptic_id in the format of
+ # category_id * label_divisor + instance_id. We reserve -1 for
+ # VOID label, and add 1 to panoptic_img since the official
+ # evaluation script uses 0 for VOID label.
+ label_divisor = self._metadata.label_divisor
+ segments_info = []
+ for panoptic_label in np.unique(panoptic_img):
+ if panoptic_label == -1:
+ # VOID region.
+ continue
+ pred_class = panoptic_label // label_divisor
+ isthing = (
+ pred_class in self._metadata.thing_dataset_id_to_contiguous_id.values()
+ )
+ segments_info.append(
+ {
+ "id": int(panoptic_label) + 1,
+ "category_id": int(pred_class),
+ "isthing": bool(isthing),
+ }
+ )
+ # Official evaluation script uses 0 for VOID label.
+ panoptic_img += 1
+
+ file_name = os.path.basename(input["file_name"])
+ file_name_png = os.path.splitext(file_name)[0] + ".png"
+ with io.BytesIO() as out:
+ Image.fromarray(id2rgb(panoptic_img)).save(out, format="PNG")
+ segments_info = [self._convert_category_id(x) for x in segments_info]
+ self._predictions.append(
+ {
+ "image_id": input["image_id"],
+ "file_name": file_name_png,
+ "png_string": out.getvalue(),
+ "segments_info": segments_info,
+ }
+ )
+
+ def evaluate(self):
+ comm.synchronize()
+
+ self._predictions = comm.gather(self._predictions)
+ self._predictions = list(itertools.chain(*self._predictions))
+ if not comm.is_main_process():
+ return
+
+ # PanopticApi requires local files
+ gt_json = PathManager.get_local_path(self._metadata.panoptic_json)
+ gt_folder = PathManager.get_local_path(self._metadata.panoptic_root)
+
+ with tempfile.TemporaryDirectory(prefix="panoptic_eval") as pred_dir:
+ logger.info("Writing all panoptic predictions to {} ...".format(pred_dir))
+ for p in self._predictions:
+ with open(os.path.join(pred_dir, p["file_name"]), "wb") as f:
+ f.write(p.pop("png_string"))
+
+ with open(gt_json, "r") as f:
+ json_data = json.load(f)
+ json_data["annotations"] = self._predictions
+
+ output_dir = self._output_dir or pred_dir
+ predictions_json = os.path.join(output_dir, "predictions.json")
+ with PathManager.open(predictions_json, "w") as f:
+ f.write(json.dumps(json_data))
+
+ from panopticapi.evaluation import pq_compute
+
+ with contextlib.redirect_stdout(io.StringIO()):
+ pq_res = pq_compute(
+ gt_json,
+ PathManager.get_local_path(predictions_json),
+ gt_folder=gt_folder,
+ pred_folder=pred_dir,
+ )
+
+ res = {}
+ res["PQ"] = 100 * pq_res["All"]["pq"]
+ res["SQ"] = 100 * pq_res["All"]["sq"]
+ res["RQ"] = 100 * pq_res["All"]["rq"]
+ res["PQ_th"] = 100 * pq_res["Things"]["pq"]
+ res["SQ_th"] = 100 * pq_res["Things"]["sq"]
+ res["RQ_th"] = 100 * pq_res["Things"]["rq"]
+ res["PQ_st"] = 100 * pq_res["Stuff"]["pq"]
+ res["SQ_st"] = 100 * pq_res["Stuff"]["sq"]
+ res["RQ_st"] = 100 * pq_res["Stuff"]["rq"]
+
+ results = OrderedDict({"panoptic_seg": res})
+ _print_panoptic_results(pq_res)
+
+ return results
+
+
+def _print_panoptic_results(pq_res):
+ headers = ["", "PQ", "SQ", "RQ", "#categories"]
+ data = []
+ for name in ["All", "Things", "Stuff"]:
+ row = [name] + [pq_res[name][k] * 100 for k in ["pq", "sq", "rq"]] + [pq_res[name]["n"]]
+ data.append(row)
+ table = tabulate(
+ data, headers=headers, tablefmt="pipe", floatfmt=".3f", stralign="center", numalign="center"
+ )
+ logger.info("Panoptic Evaluation Results:\n" + table)
+
+
+if __name__ == "__main__":
+ from detectron2.utils.logger import setup_logger
+
+ logger = setup_logger()
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--gt-json")
+ parser.add_argument("--gt-dir")
+ parser.add_argument("--pred-json")
+ parser.add_argument("--pred-dir")
+ args = parser.parse_args()
+
+ from panopticapi.evaluation import pq_compute
+
+ with contextlib.redirect_stdout(io.StringIO()):
+ pq_res = pq_compute(
+ args.gt_json, args.pred_json, gt_folder=args.gt_dir, pred_folder=args.pred_dir
+ )
+ _print_panoptic_results(pq_res)
diff --git a/detectron2/detectron2/evaluation/pascal_voc_evaluation.py b/detectron2/detectron2/evaluation/pascal_voc_evaluation.py
new file mode 100755
index 0000000..88bb42e
--- /dev/null
+++ b/detectron2/detectron2/evaluation/pascal_voc_evaluation.py
@@ -0,0 +1,300 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import logging
+import numpy as np
+import os
+import tempfile
+import xml.etree.ElementTree as ET
+from collections import OrderedDict, defaultdict
+from functools import lru_cache
+import torch
+
+from detectron2.data import MetadataCatalog
+from detectron2.utils import comm
+from detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+
+class PascalVOCDetectionEvaluator(DatasetEvaluator):
+ """
+ Evaluate Pascal VOC style AP for Pascal VOC dataset.
+ It contains a synchronization, therefore has to be called from all ranks.
+
+ Note that the concept of AP can be implemented in different ways and may not
+ produce identical results. This class mimics the implementation of the official
+ Pascal VOC Matlab API, and should produce similar but not identical results to the
+ official API.
+ """
+
+ def __init__(self, dataset_name):
+ """
+ Args:
+ dataset_name (str): name of the dataset, e.g., "voc_2007_test"
+ """
+ self._dataset_name = dataset_name
+ meta = MetadataCatalog.get(dataset_name)
+
+ # Too many tiny files, download all to local for speed.
+ annotation_dir_local = PathManager.get_local_path(
+ os.path.join(meta.dirname, "Annotations/")
+ )
+ self._anno_file_template = os.path.join(annotation_dir_local, "{}.xml")
+ self._image_set_path = os.path.join(meta.dirname, "ImageSets", "Main", meta.split + ".txt")
+ self._class_names = meta.thing_classes
+ assert meta.year in [2007, 2012], meta.year
+ self._is_2007 = meta.year == 2007
+ self._cpu_device = torch.device("cpu")
+ self._logger = logging.getLogger(__name__)
+
+ def reset(self):
+ self._predictions = defaultdict(list) # class name -> list of prediction strings
+
+ def process(self, inputs, outputs):
+ for input, output in zip(inputs, outputs):
+ image_id = input["image_id"]
+ instances = output["instances"].to(self._cpu_device)
+ boxes = instances.pred_boxes.tensor.numpy()
+ scores = instances.scores.tolist()
+ classes = instances.pred_classes.tolist()
+ for box, score, cls in zip(boxes, scores, classes):
+ xmin, ymin, xmax, ymax = box
+ # The inverse of data loading logic in `datasets/pascal_voc.py`
+ xmin += 1
+ ymin += 1
+ self._predictions[cls].append(
+ f"{image_id} {score:.3f} {xmin:.1f} {ymin:.1f} {xmax:.1f} {ymax:.1f}"
+ )
+
+ def evaluate(self):
+ """
+ Returns:
+ dict: has a key "segm", whose value is a dict of "AP", "AP50", and "AP75".
+ """
+ all_predictions = comm.gather(self._predictions, dst=0)
+ if not comm.is_main_process():
+ return
+ predictions = defaultdict(list)
+ for predictions_per_rank in all_predictions:
+ for clsid, lines in predictions_per_rank.items():
+ predictions[clsid].extend(lines)
+ del all_predictions
+
+ self._logger.info(
+ "Evaluating {} using {} metric. "
+ "Note that results do not use the official Matlab API.".format(
+ self._dataset_name, 2007 if self._is_2007 else 2012
+ )
+ )
+
+ with tempfile.TemporaryDirectory(prefix="pascal_voc_eval_") as dirname:
+ res_file_template = os.path.join(dirname, "{}.txt")
+
+ aps = defaultdict(list) # iou -> ap per class
+ for cls_id, cls_name in enumerate(self._class_names):
+ lines = predictions.get(cls_id, [""])
+
+ with open(res_file_template.format(cls_name), "w") as f:
+ f.write("\n".join(lines))
+
+ for thresh in range(50, 100, 5):
+ rec, prec, ap = voc_eval(
+ res_file_template,
+ self._anno_file_template,
+ self._image_set_path,
+ cls_name,
+ ovthresh=thresh / 100.0,
+ use_07_metric=self._is_2007,
+ )
+ aps[thresh].append(ap * 100)
+
+ ret = OrderedDict()
+ mAP = {iou: np.mean(x) for iou, x in aps.items()}
+ ret["bbox"] = {"AP": np.mean(list(mAP.values())), "AP50": mAP[50], "AP75": mAP[75]}
+ return ret
+
+
+##############################################################################
+#
+# Below code is modified from
+# https://github.com/rbgirshick/py-faster-rcnn/blob/master/lib/datasets/voc_eval.py
+# --------------------------------------------------------
+# Fast/er R-CNN
+# Licensed under The MIT License [see LICENSE for details]
+# Written by Bharath Hariharan
+# --------------------------------------------------------
+
+"""Python implementation of the PASCAL VOC devkit's AP evaluation code."""
+
+
+@lru_cache(maxsize=None)
+def parse_rec(filename):
+ """Parse a PASCAL VOC xml file."""
+ with PathManager.open(filename) as f:
+ tree = ET.parse(f)
+ objects = []
+ for obj in tree.findall("object"):
+ obj_struct = {}
+ obj_struct["name"] = obj.find("name").text
+ obj_struct["pose"] = obj.find("pose").text
+ obj_struct["truncated"] = int(obj.find("truncated").text)
+ obj_struct["difficult"] = int(obj.find("difficult").text)
+ bbox = obj.find("bndbox")
+ obj_struct["bbox"] = [
+ int(bbox.find("xmin").text),
+ int(bbox.find("ymin").text),
+ int(bbox.find("xmax").text),
+ int(bbox.find("ymax").text),
+ ]
+ objects.append(obj_struct)
+
+ return objects
+
+
+def voc_ap(rec, prec, use_07_metric=False):
+ """Compute VOC AP given precision and recall. If use_07_metric is true, uses
+ the VOC 07 11-point method (default:False).
+ """
+ if use_07_metric:
+ # 11 point metric
+ ap = 0.0
+ for t in np.arange(0.0, 1.1, 0.1):
+ if np.sum(rec >= t) == 0:
+ p = 0
+ else:
+ p = np.max(prec[rec >= t])
+ ap = ap + p / 11.0
+ else:
+ # correct AP calculation
+ # first append sentinel values at the end
+ mrec = np.concatenate(([0.0], rec, [1.0]))
+ mpre = np.concatenate(([0.0], prec, [0.0]))
+
+ # compute the precision envelope
+ for i in range(mpre.size - 1, 0, -1):
+ mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
+
+ # to calculate area under PR curve, look for points
+ # where X axis (recall) changes value
+ i = np.where(mrec[1:] != mrec[:-1])[0]
+
+ # and sum (\Delta recall) * prec
+ ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
+ return ap
+
+
+def voc_eval(detpath, annopath, imagesetfile, classname, ovthresh=0.5, use_07_metric=False):
+ """rec, prec, ap = voc_eval(detpath,
+ annopath,
+ imagesetfile,
+ classname,
+ [ovthresh],
+ [use_07_metric])
+
+ Top level function that does the PASCAL VOC evaluation.
+
+ detpath: Path to detections
+ detpath.format(classname) should produce the detection results file.
+ annopath: Path to annotations
+ annopath.format(imagename) should be the xml annotations file.
+ imagesetfile: Text file containing the list of images, one image per line.
+ classname: Category name (duh)
+ [ovthresh]: Overlap threshold (default = 0.5)
+ [use_07_metric]: Whether to use VOC07's 11 point AP computation
+ (default False)
+ """
+ # assumes detections are in detpath.format(classname)
+ # assumes annotations are in annopath.format(imagename)
+ # assumes imagesetfile is a text file with each line an image name
+
+ # first load gt
+ # read list of images
+ with PathManager.open(imagesetfile, "r") as f:
+ lines = f.readlines()
+ imagenames = [x.strip() for x in lines]
+
+ # load annots
+ recs = {}
+ for imagename in imagenames:
+ recs[imagename] = parse_rec(annopath.format(imagename))
+
+ # extract gt objects for this class
+ class_recs = {}
+ npos = 0
+ for imagename in imagenames:
+ R = [obj for obj in recs[imagename] if obj["name"] == classname]
+ bbox = np.array([x["bbox"] for x in R])
+ difficult = np.array([x["difficult"] for x in R]).astype(bool)
+ # difficult = np.array([False for x in R]).astype(bool) # treat all "difficult" as GT
+ det = [False] * len(R)
+ npos = npos + sum(~difficult)
+ class_recs[imagename] = {"bbox": bbox, "difficult": difficult, "det": det}
+
+ # read dets
+ detfile = detpath.format(classname)
+ with open(detfile, "r") as f:
+ lines = f.readlines()
+
+ splitlines = [x.strip().split(" ") for x in lines]
+ image_ids = [x[0] for x in splitlines]
+ confidence = np.array([float(x[1]) for x in splitlines])
+ BB = np.array([[float(z) for z in x[2:]] for x in splitlines]).reshape(-1, 4)
+
+ # sort by confidence
+ sorted_ind = np.argsort(-confidence)
+ BB = BB[sorted_ind, :]
+ image_ids = [image_ids[x] for x in sorted_ind]
+
+ # go down dets and mark TPs and FPs
+ nd = len(image_ids)
+ tp = np.zeros(nd)
+ fp = np.zeros(nd)
+ for d in range(nd):
+ R = class_recs[image_ids[d]]
+ bb = BB[d, :].astype(float)
+ ovmax = -np.inf
+ BBGT = R["bbox"].astype(float)
+
+ if BBGT.size > 0:
+ # compute overlaps
+ # intersection
+ ixmin = np.maximum(BBGT[:, 0], bb[0])
+ iymin = np.maximum(BBGT[:, 1], bb[1])
+ ixmax = np.minimum(BBGT[:, 2], bb[2])
+ iymax = np.minimum(BBGT[:, 3], bb[3])
+ iw = np.maximum(ixmax - ixmin + 1.0, 0.0)
+ ih = np.maximum(iymax - iymin + 1.0, 0.0)
+ inters = iw * ih
+
+ # union
+ uni = (
+ (bb[2] - bb[0] + 1.0) * (bb[3] - bb[1] + 1.0)
+ + (BBGT[:, 2] - BBGT[:, 0] + 1.0) * (BBGT[:, 3] - BBGT[:, 1] + 1.0)
+ - inters
+ )
+
+ overlaps = inters / uni
+ ovmax = np.max(overlaps)
+ jmax = np.argmax(overlaps)
+
+ if ovmax > ovthresh:
+ if not R["difficult"][jmax]:
+ if not R["det"][jmax]:
+ tp[d] = 1.0
+ R["det"][jmax] = 1
+ else:
+ fp[d] = 1.0
+ else:
+ fp[d] = 1.0
+
+ # compute precision recall
+ fp = np.cumsum(fp)
+ tp = np.cumsum(tp)
+ rec = tp / float(npos)
+ # avoid divide by zero in case the first detection matches a difficult
+ # ground truth
+ prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
+ ap = voc_ap(rec, prec, use_07_metric)
+
+ return rec, prec, ap
diff --git a/detectron2/detectron2/evaluation/rotated_coco_evaluation.py b/detectron2/detectron2/evaluation/rotated_coco_evaluation.py
new file mode 100755
index 0000000..ea6d1b3
--- /dev/null
+++ b/detectron2/detectron2/evaluation/rotated_coco_evaluation.py
@@ -0,0 +1,207 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import json
+import numpy as np
+import os
+import torch
+from pycocotools.cocoeval import COCOeval, maskUtils
+
+from detectron2.structures import BoxMode, RotatedBoxes, pairwise_iou_rotated
+from detectron2.utils.file_io import PathManager
+
+from .coco_evaluation import COCOEvaluator
+
+
+class RotatedCOCOeval(COCOeval):
+ @staticmethod
+ def is_rotated(box_list):
+ if type(box_list) == np.ndarray:
+ return box_list.shape[1] == 5
+ elif type(box_list) == list:
+ if box_list == []: # cannot decide the box_dim
+ return False
+ return np.all(
+ np.array(
+ [
+ (len(obj) == 5) and ((type(obj) == list) or (type(obj) == np.ndarray))
+ for obj in box_list
+ ]
+ )
+ )
+ return False
+
+ @staticmethod
+ def boxlist_to_tensor(boxlist, output_box_dim):
+ if type(boxlist) == np.ndarray:
+ box_tensor = torch.from_numpy(boxlist)
+ elif type(boxlist) == list:
+ if boxlist == []:
+ return torch.zeros((0, output_box_dim), dtype=torch.float32)
+ else:
+ box_tensor = torch.FloatTensor(boxlist)
+ else:
+ raise Exception("Unrecognized boxlist type")
+
+ input_box_dim = box_tensor.shape[1]
+ if input_box_dim != output_box_dim:
+ if input_box_dim == 4 and output_box_dim == 5:
+ box_tensor = BoxMode.convert(box_tensor, BoxMode.XYWH_ABS, BoxMode.XYWHA_ABS)
+ else:
+ raise Exception(
+ "Unable to convert from {}-dim box to {}-dim box".format(
+ input_box_dim, output_box_dim
+ )
+ )
+ return box_tensor
+
+ def compute_iou_dt_gt(self, dt, gt, is_crowd):
+ if self.is_rotated(dt) or self.is_rotated(gt):
+ # TODO: take is_crowd into consideration
+ assert all(c == 0 for c in is_crowd)
+ dt = RotatedBoxes(self.boxlist_to_tensor(dt, output_box_dim=5))
+ gt = RotatedBoxes(self.boxlist_to_tensor(gt, output_box_dim=5))
+ return pairwise_iou_rotated(dt, gt)
+ else:
+ # This is the same as the classical COCO evaluation
+ return maskUtils.iou(dt, gt, is_crowd)
+
+ 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 []
+ 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]]
+
+ assert p.iouType == "bbox", "unsupported iouType for iou computation"
+
+ g = [g["bbox"] for g in gt]
+ d = [d["bbox"] for d in dt]
+
+ # compute iou between each dt and gt region
+ iscrowd = [int(o["iscrowd"]) for o in gt]
+
+ # Note: this function is copied from cocoeval.py in cocoapi
+ # and the major difference is here.
+ ious = self.compute_iou_dt_gt(d, g, iscrowd)
+ return ious
+
+
+class RotatedCOCOEvaluator(COCOEvaluator):
+ """
+ Evaluate object proposal/instance detection outputs using COCO-like metrics and APIs,
+ with rotated boxes support.
+ Note: this uses IOU only and does not consider angle differences.
+ """
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
+ It is a list of dict. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name", "image_id".
+ outputs: the outputs of a COCO model. It is a list of dicts with key
+ "instances" that contains :class:`Instances`.
+ """
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+
+ if "instances" in output:
+ instances = output["instances"].to(self._cpu_device)
+
+ prediction["instances"] = self.instances_to_json(instances, input["image_id"])
+ if "proposals" in output:
+ prediction["proposals"] = output["proposals"].to(self._cpu_device)
+ self._predictions.append(prediction)
+
+ def instances_to_json(self, instances, img_id):
+ num_instance = len(instances)
+ if num_instance == 0:
+ return []
+
+ boxes = instances.pred_boxes.tensor.numpy()
+ if boxes.shape[1] == 4:
+ boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
+ boxes = boxes.tolist()
+ scores = instances.scores.tolist()
+ classes = instances.pred_classes.tolist()
+
+ results = []
+ for k in range(num_instance):
+ result = {
+ "image_id": img_id,
+ "category_id": classes[k],
+ "bbox": boxes[k],
+ "score": scores[k],
+ }
+
+ results.append(result)
+ return results
+
+ def _eval_predictions(self, predictions, img_ids=None): # img_ids: unused
+ """
+ Evaluate predictions on the given tasks.
+ Fill self._results with the metrics of the tasks.
+ """
+ self._logger.info("Preparing results for COCO format ...")
+ coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))
+
+ # unmap the category ids for COCO
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
+ reverse_id_mapping = {
+ v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items()
+ }
+ for result in coco_results:
+ result["category_id"] = reverse_id_mapping[result["category_id"]]
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "coco_instances_results.json")
+ self._logger.info("Saving results to {}".format(file_path))
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(coco_results))
+ f.flush()
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating predictions ...")
+
+ assert self._tasks is None or set(self._tasks) == {
+ "bbox"
+ }, "[RotatedCOCOEvaluator] Only bbox evaluation is supported"
+ coco_eval = (
+ self._evaluate_predictions_on_coco(self._coco_api, coco_results)
+ if len(coco_results) > 0
+ else None # cocoapi does not handle empty results very well
+ )
+
+ task = "bbox"
+ res = self._derive_coco_results(
+ coco_eval, task, class_names=self._metadata.get("thing_classes")
+ )
+ self._results[task] = res
+
+ def _evaluate_predictions_on_coco(self, coco_gt, coco_results):
+ """
+ Evaluate the coco results using COCOEval API.
+ """
+ assert len(coco_results) > 0
+
+ coco_dt = coco_gt.loadRes(coco_results)
+
+ # Only bbox is supported for now
+ coco_eval = RotatedCOCOeval(coco_gt, coco_dt, iouType="bbox")
+
+ coco_eval.evaluate()
+ coco_eval.accumulate()
+ coco_eval.summarize()
+
+ return coco_eval
diff --git a/detectron2/detectron2/evaluation/sem_seg_evaluation.py b/detectron2/detectron2/evaluation/sem_seg_evaluation.py
new file mode 100755
index 0000000..3735de6
--- /dev/null
+++ b/detectron2/detectron2/evaluation/sem_seg_evaluation.py
@@ -0,0 +1,265 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import json
+import logging
+import numpy as np
+import os
+from collections import OrderedDict
+from typing import Optional, Union
+import pycocotools.mask as mask_util
+import torch
+from PIL import Image
+
+from detectron2.data import DatasetCatalog, MetadataCatalog
+from detectron2.utils.comm import all_gather, is_main_process, synchronize
+from detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+_CV2_IMPORTED = True
+try:
+ import cv2 # noqa
+except ImportError:
+ # OpenCV is an optional dependency at the moment
+ _CV2_IMPORTED = False
+
+
+def load_image_into_numpy_array(
+ filename: str,
+ copy: bool = False,
+ dtype: Optional[Union[np.dtype, str]] = None,
+) -> np.ndarray:
+ with PathManager.open(filename, "rb") as f:
+ array = np.array(Image.open(f), copy=copy, dtype=dtype)
+ return array
+
+
+class SemSegEvaluator(DatasetEvaluator):
+ """
+ Evaluate semantic segmentation metrics.
+ """
+
+ def __init__(
+ self,
+ dataset_name,
+ distributed=True,
+ output_dir=None,
+ *,
+ sem_seg_loading_fn=load_image_into_numpy_array,
+ num_classes=None,
+ ignore_label=None,
+ ):
+ """
+ Args:
+ dataset_name (str): name of the dataset to be evaluated.
+ distributed (bool): if True, will collect results from all ranks for evaluation.
+ Otherwise, will evaluate the results in the current process.
+ output_dir (str): an output directory to dump results.
+ sem_seg_loading_fn: function to read sem seg file and load into numpy array.
+ Default provided, but projects can customize.
+ num_classes, ignore_label: deprecated argument
+ """
+ self._logger = logging.getLogger(__name__)
+ if num_classes is not None:
+ self._logger.warn(
+ "SemSegEvaluator(num_classes) is deprecated! It should be obtained from metadata."
+ )
+ if ignore_label is not None:
+ self._logger.warn(
+ "SemSegEvaluator(ignore_label) is deprecated! It should be obtained from metadata."
+ )
+ self._dataset_name = dataset_name
+ self._distributed = distributed
+ self._output_dir = output_dir
+
+ self._cpu_device = torch.device("cpu")
+
+ self.input_file_to_gt_file = {
+ dataset_record["file_name"]: dataset_record["sem_seg_file_name"]
+ for dataset_record in DatasetCatalog.get(dataset_name)
+ }
+
+ meta = MetadataCatalog.get(dataset_name)
+ # Dict that maps contiguous training ids to COCO category ids
+ try:
+ c2d = meta.stuff_dataset_id_to_contiguous_id
+ self._contiguous_id_to_dataset_id = {v: k for k, v in c2d.items()}
+ except AttributeError:
+ self._contiguous_id_to_dataset_id = None
+ self._class_names = meta.stuff_classes
+ self.sem_seg_loading_fn = sem_seg_loading_fn
+ self._num_classes = len(meta.stuff_classes)
+ if num_classes is not None:
+ assert self._num_classes == num_classes, f"{self._num_classes} != {num_classes}"
+ self._ignore_label = ignore_label if ignore_label is not None else meta.ignore_label
+
+ # This is because cv2.erode did not work for int datatype. Only works for uint8.
+ self._compute_boundary_iou = True
+ if not _CV2_IMPORTED:
+ self._compute_boundary_iou = False
+ self._logger.warn(
+ """Boundary IoU calculation requires OpenCV. B-IoU metrics are
+ not going to be computed because OpenCV is not available to import."""
+ )
+ if self._num_classes >= np.iinfo(np.uint8).max:
+ self._compute_boundary_iou = False
+ self._logger.warn(
+ f"""SemSegEvaluator(num_classes) is more than supported value for Boundary IoU calculation!
+ B-IoU metrics are not going to be computed. Max allowed value (exclusive)
+ for num_classes for calculating Boundary IoU is {np.iinfo(np.uint8).max}.
+ The number of classes of dataset {self._dataset_name} is {self._num_classes}"""
+ )
+
+ def reset(self):
+ self._conf_matrix = np.zeros((self._num_classes + 1, self._num_classes + 1), dtype=np.int64)
+ self._b_conf_matrix = np.zeros(
+ (self._num_classes + 1, self._num_classes + 1), dtype=np.int64
+ )
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a model.
+ It is a list of dicts. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name".
+ outputs: the outputs of a model. It is either list of semantic segmentation predictions
+ (Tensor [H, W]) or list of dicts with key "sem_seg" that contains semantic
+ segmentation prediction in the same format.
+ """
+ for input, output in zip(inputs, outputs):
+ output = output["sem_seg"].argmax(dim=0).to(self._cpu_device)
+ pred = np.array(output, dtype=np.int)
+ gt_filename = self.input_file_to_gt_file[input["file_name"]]
+ gt = self.sem_seg_loading_fn(gt_filename, dtype=np.int)
+
+ gt[gt == self._ignore_label] = self._num_classes
+
+ self._conf_matrix += np.bincount(
+ (self._num_classes + 1) * pred.reshape(-1) + gt.reshape(-1),
+ minlength=self._conf_matrix.size,
+ ).reshape(self._conf_matrix.shape)
+
+ if self._compute_boundary_iou:
+ b_gt = self._mask_to_boundary(gt.astype(np.uint8))
+ b_pred = self._mask_to_boundary(pred.astype(np.uint8))
+
+ self._b_conf_matrix += np.bincount(
+ (self._num_classes + 1) * b_pred.reshape(-1) + b_gt.reshape(-1),
+ minlength=self._conf_matrix.size,
+ ).reshape(self._conf_matrix.shape)
+
+ self._predictions.extend(self.encode_json_sem_seg(pred, input["file_name"]))
+
+ def evaluate(self):
+ """
+ Evaluates standard semantic segmentation metrics (http://cocodataset.org/#stuff-eval):
+
+ * Mean intersection-over-union averaged across classes (mIoU)
+ * Frequency Weighted IoU (fwIoU)
+ * Mean pixel accuracy averaged across classes (mACC)
+ * Pixel Accuracy (pACC)
+ """
+ if self._distributed:
+ synchronize()
+ conf_matrix_list = all_gather(self._conf_matrix)
+ b_conf_matrix_list = all_gather(self._b_conf_matrix)
+ self._predictions = all_gather(self._predictions)
+ self._predictions = list(itertools.chain(*self._predictions))
+ if not is_main_process():
+ return
+
+ self._conf_matrix = np.zeros_like(self._conf_matrix)
+ for conf_matrix in conf_matrix_list:
+ self._conf_matrix += conf_matrix
+
+ self._b_conf_matrix = np.zeros_like(self._b_conf_matrix)
+ for b_conf_matrix in b_conf_matrix_list:
+ self._b_conf_matrix += b_conf_matrix
+
+ if self._output_dir:
+ PathManager.mkdirs(self._output_dir)
+ file_path = os.path.join(self._output_dir, "sem_seg_predictions.json")
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(self._predictions))
+
+ acc = np.full(self._num_classes, np.nan, dtype=np.float)
+ iou = np.full(self._num_classes, np.nan, dtype=np.float)
+ tp = self._conf_matrix.diagonal()[:-1].astype(np.float)
+ pos_gt = np.sum(self._conf_matrix[:-1, :-1], axis=0).astype(np.float)
+ class_weights = pos_gt / np.sum(pos_gt)
+ pos_pred = np.sum(self._conf_matrix[:-1, :-1], axis=1).astype(np.float)
+ acc_valid = pos_gt > 0
+ acc[acc_valid] = tp[acc_valid] / pos_gt[acc_valid]
+ union = pos_gt + pos_pred - tp
+ iou_valid = np.logical_and(acc_valid, union > 0)
+ iou[iou_valid] = tp[iou_valid] / union[iou_valid]
+ macc = np.sum(acc[acc_valid]) / np.sum(acc_valid)
+ miou = np.sum(iou[iou_valid]) / np.sum(iou_valid)
+ fiou = np.sum(iou[iou_valid] * class_weights[iou_valid])
+ pacc = np.sum(tp) / np.sum(pos_gt)
+
+ if self._compute_boundary_iou:
+ b_iou = np.full(self._num_classes, np.nan, dtype=np.float)
+ b_tp = self._b_conf_matrix.diagonal()[:-1].astype(np.float)
+ b_pos_gt = np.sum(self._b_conf_matrix[:-1, :-1], axis=0).astype(np.float)
+ b_pos_pred = np.sum(self._b_conf_matrix[:-1, :-1], axis=1).astype(np.float)
+ b_union = b_pos_gt + b_pos_pred - b_tp
+ b_iou_valid = b_union > 0
+ b_iou[b_iou_valid] = b_tp[b_iou_valid] / b_union[b_iou_valid]
+
+ res = {}
+ res["mIoU"] = 100 * miou
+ res["fwIoU"] = 100 * fiou
+ for i, name in enumerate(self._class_names):
+ res[f"IoU-{name}"] = 100 * iou[i]
+ if self._compute_boundary_iou:
+ res[f"BoundaryIoU-{name}"] = 100 * b_iou[i]
+ res[f"min(IoU, B-Iou)-{name}"] = 100 * min(iou[i], b_iou[i])
+ res["mACC"] = 100 * macc
+ res["pACC"] = 100 * pacc
+ for i, name in enumerate(self._class_names):
+ res[f"ACC-{name}"] = 100 * acc[i]
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "sem_seg_evaluation.pth")
+ with PathManager.open(file_path, "wb") as f:
+ torch.save(res, f)
+ results = OrderedDict({"sem_seg": res})
+ self._logger.info(results)
+ return results
+
+ def encode_json_sem_seg(self, sem_seg, input_file_name):
+ """
+ Convert semantic segmentation to COCO stuff format with segments encoded as RLEs.
+ See http://cocodataset.org/#format-results
+ """
+ json_list = []
+ for label in np.unique(sem_seg):
+ if self._contiguous_id_to_dataset_id is not None:
+ assert (
+ label in self._contiguous_id_to_dataset_id
+ ), "Label {} is not in the metadata info for {}".format(label, self._dataset_name)
+ dataset_id = self._contiguous_id_to_dataset_id[label]
+ else:
+ dataset_id = int(label)
+ mask = (sem_seg == label).astype(np.uint8)
+ mask_rle = mask_util.encode(np.array(mask[:, :, None], order="F"))[0]
+ mask_rle["counts"] = mask_rle["counts"].decode("utf-8")
+ json_list.append(
+ {"file_name": input_file_name, "category_id": dataset_id, "segmentation": mask_rle}
+ )
+ return json_list
+
+ def _mask_to_boundary(self, mask: np.ndarray, dilation_ratio=0.02):
+ assert mask.ndim == 2, "mask_to_boundary expects a 2-dimensional image"
+ h, w = mask.shape
+ diag_len = np.sqrt(h**2 + w**2)
+ dilation = max(1, int(round(dilation_ratio * diag_len)))
+ kernel = np.ones((3, 3), dtype=np.uint8)
+
+ padded_mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0)
+ eroded_mask_with_padding = cv2.erode(padded_mask, kernel, iterations=dilation)
+ eroded_mask = eroded_mask_with_padding[1:-1, 1:-1]
+ boundary = mask - eroded_mask
+ return boundary
diff --git a/detectron2/detectron2/evaluation/testing.py b/detectron2/detectron2/evaluation/testing.py
new file mode 100755
index 0000000..9e5ae62
--- /dev/null
+++ b/detectron2/detectron2/evaluation/testing.py
@@ -0,0 +1,85 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import numpy as np
+import pprint
+import sys
+from collections.abc import Mapping
+
+
+def print_csv_format(results):
+ """
+ Print main metrics in a format similar to Detectron,
+ so that they are easy to copypaste into a spreadsheet.
+
+ Args:
+ results (OrderedDict[dict]): task_name -> {metric -> score}
+ unordered dict can also be printed, but in arbitrary order
+ """
+ assert isinstance(results, Mapping) or not len(results), results
+ logger = logging.getLogger(__name__)
+ for task, res in results.items():
+ if isinstance(res, Mapping):
+ # Don't print "AP-category" metrics since they are usually not tracked.
+ important_res = [(k, v) for k, v in res.items() if "-" not in k]
+ logger.info("copypaste: Task: {}".format(task))
+ logger.info("copypaste: " + ",".join([k[0] for k in important_res]))
+ logger.info("copypaste: " + ",".join(["{0:.4f}".format(k[1]) for k in important_res]))
+ else:
+ logger.info(f"copypaste: {task}={res}")
+
+
+def verify_results(cfg, results):
+ """
+ Args:
+ results (OrderedDict[dict]): task_name -> {metric -> score}
+
+ Returns:
+ bool: whether the verification succeeds or not
+ """
+ expected_results = cfg.TEST.EXPECTED_RESULTS
+ if not len(expected_results):
+ return True
+
+ ok = True
+ for task, metric, expected, tolerance in expected_results:
+ actual = results[task].get(metric, None)
+ if actual is None:
+ ok = False
+ continue
+ if not np.isfinite(actual):
+ ok = False
+ continue
+ diff = abs(actual - expected)
+ if diff > tolerance:
+ ok = False
+
+ logger = logging.getLogger(__name__)
+ if not ok:
+ logger.error("Result verification failed!")
+ logger.error("Expected Results: " + str(expected_results))
+ logger.error("Actual Results: " + pprint.pformat(results))
+
+ sys.exit(1)
+ else:
+ logger.info("Results verification passed.")
+ return ok
+
+
+def flatten_results_dict(results):
+ """
+ Expand a hierarchical dict of scalars into a flat dict of scalars.
+ If results[k1][k2][k3] = v, the returned dict will have the entry
+ {"k1/k2/k3": v}.
+
+ Args:
+ results (dict):
+ """
+ r = {}
+ for k, v in results.items():
+ if isinstance(v, Mapping):
+ v = flatten_results_dict(v)
+ for kk, vv in v.items():
+ r[k + "/" + kk] = vv
+ else:
+ r[k] = v
+ return r
diff --git a/detectron2/detectron2/export/README.md b/detectron2/detectron2/export/README.md
new file mode 100755
index 0000000..c86ff62
--- /dev/null
+++ b/detectron2/detectron2/export/README.md
@@ -0,0 +1,15 @@
+
+This directory contains code to prepare a detectron2 model for deployment.
+Currently it supports exporting a detectron2 model to TorchScript, ONNX, or (deprecated) Caffe2 format.
+
+Please see [documentation](https://detectron2.readthedocs.io/tutorials/deployment.html) for its usage.
+
+
+### Acknowledgements
+
+Thanks to Mobile Vision team at Facebook for developing the Caffe2 conversion tools.
+
+Thanks to Computing Platform Department - PAI team at Alibaba Group (@bddpqq, @chenbohua3) who
+help export Detectron2 models to TorchScript.
+
+Thanks to ONNX Converter team at Microsoft who help export Detectron2 models to ONNX.
diff --git a/detectron2/detectron2/export/__init__.py b/detectron2/detectron2/export/__init__.py
new file mode 100755
index 0000000..5a58758
--- /dev/null
+++ b/detectron2/detectron2/export/__init__.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import warnings
+
+from .flatten import TracingAdapter
+from .torchscript import dump_torchscript_IR, scripting_with_instances
+
+try:
+ from caffe2.proto import caffe2_pb2 as _tmp
+ from caffe2.python import core
+
+ # caffe2 is optional
+except ImportError:
+ pass
+else:
+ from .api import *
+
+
+# TODO: Update ONNX Opset version and run tests when a newer PyTorch is supported
+STABLE_ONNX_OPSET_VERSION = 11
+
+
+def add_export_config(cfg):
+ warnings.warn(
+ "add_export_config has been deprecated and behaves as no-op function.", DeprecationWarning
+ )
+ return cfg
+
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/detectron2/detectron2/export/api.py b/detectron2/detectron2/export/api.py
new file mode 100755
index 0000000..1a272fe
--- /dev/null
+++ b/detectron2/detectron2/export/api.py
@@ -0,0 +1,230 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import os
+import torch
+from caffe2.proto import caffe2_pb2
+from torch import nn
+
+from detectron2.config import CfgNode
+from detectron2.utils.file_io import PathManager
+
+from .caffe2_inference import ProtobufDetectionModel
+from .caffe2_modeling import META_ARCH_CAFFE2_EXPORT_TYPE_MAP, convert_batched_inputs_to_c2_format
+from .shared import get_pb_arg_vali, get_pb_arg_vals, save_graph
+
+__all__ = [
+ "Caffe2Model",
+ "Caffe2Tracer",
+]
+
+
+class Caffe2Tracer:
+ """
+ Make a detectron2 model traceable with Caffe2 operators.
+ This class creates a traceable version of a detectron2 model which:
+
+ 1. Rewrite parts of the model using ops in Caffe2. Note that some ops do
+ not have GPU implementation in Caffe2.
+ 2. Remove post-processing and only produce raw layer outputs
+
+ After making a traceable model, the class provide methods to export such a
+ model to different deployment formats.
+ Exported graph produced by this class take two input tensors:
+
+ 1. (1, C, H, W) float "data" which is an image (usually in [0, 255]).
+ (H, W) often has to be padded to multiple of 32 (depend on the model
+ architecture).
+ 2. 1x3 float "im_info", each row of which is (height, width, 1.0).
+ Height and width are true image shapes before padding.
+
+ The class currently only supports models using builtin meta architectures.
+ Batch inference is not supported, and contributions are welcome.
+ """
+
+ def __init__(self, cfg: CfgNode, model: nn.Module, inputs):
+ """
+ Args:
+ cfg (CfgNode): a detectron2 config used to construct caffe2-compatible model.
+ model (nn.Module): An original pytorch model. Must be among a few official models
+ in detectron2 that can be converted to become caffe2-compatible automatically.
+ Weights have to be already loaded to this model.
+ inputs: sample inputs that the given model takes for inference.
+ Will be used to trace the model. For most models, random inputs with
+ no detected objects will not work as they lead to wrong traces.
+ """
+ assert isinstance(cfg, CfgNode), cfg
+ assert isinstance(model, torch.nn.Module), type(model)
+
+ # TODO make it support custom models, by passing in c2 model directly
+ C2MetaArch = META_ARCH_CAFFE2_EXPORT_TYPE_MAP[cfg.MODEL.META_ARCHITECTURE]
+ self.traceable_model = C2MetaArch(cfg, copy.deepcopy(model))
+ self.inputs = inputs
+ self.traceable_inputs = self.traceable_model.get_caffe2_inputs(inputs)
+
+ def export_caffe2(self):
+ """
+ Export the model to Caffe2's protobuf format.
+ The returned object can be saved with its :meth:`.save_protobuf()` method.
+ The result can be loaded and executed using Caffe2 runtime.
+
+ Returns:
+ :class:`Caffe2Model`
+ """
+ from .caffe2_export import export_caffe2_detection_model
+
+ predict_net, init_net = export_caffe2_detection_model(
+ self.traceable_model, self.traceable_inputs
+ )
+ return Caffe2Model(predict_net, init_net)
+
+ def export_onnx(self):
+ """
+ Export the model to ONNX format.
+ Note that the exported model contains custom ops only available in caffe2, therefore it
+ cannot be directly executed by other runtime (such as onnxruntime or TensorRT).
+ Post-processing or transformation passes may be applied on the model to accommodate
+ different runtimes, but we currently do not provide support for them.
+
+ Returns:
+ onnx.ModelProto: an onnx model.
+ """
+ from .caffe2_export import export_onnx_model as export_onnx_model_impl
+
+ return export_onnx_model_impl(self.traceable_model, (self.traceable_inputs,))
+
+ def export_torchscript(self):
+ """
+ Export the model to a ``torch.jit.TracedModule`` by tracing.
+ The returned object can be saved to a file by ``.save()``.
+
+ Returns:
+ torch.jit.TracedModule: a torch TracedModule
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Tracing the model with torch.jit.trace ...")
+ with torch.no_grad():
+ return torch.jit.trace(self.traceable_model, (self.traceable_inputs,))
+
+
+class Caffe2Model(nn.Module):
+ """
+ A wrapper around the traced model in Caffe2's protobuf format.
+ The exported graph has different inputs/outputs from the original Pytorch
+ model, as explained in :class:`Caffe2Tracer`. This class wraps around the
+ exported graph to simulate the same interface as the original Pytorch model.
+ It also provides functions to save/load models in Caffe2's format.'
+
+ Examples:
+ ::
+ c2_model = Caffe2Tracer(cfg, torch_model, inputs).export_caffe2()
+ inputs = [{"image": img_tensor_CHW}]
+ outputs = c2_model(inputs)
+ orig_outputs = torch_model(inputs)
+ """
+
+ def __init__(self, predict_net, init_net):
+ super().__init__()
+ self.eval() # always in eval mode
+ self._predict_net = predict_net
+ self._init_net = init_net
+ self._predictor = None
+
+ __init__.__HIDE_SPHINX_DOC__ = True
+
+ @property
+ def predict_net(self):
+ """
+ caffe2.core.Net: the underlying caffe2 predict net
+ """
+ return self._predict_net
+
+ @property
+ def init_net(self):
+ """
+ caffe2.core.Net: the underlying caffe2 init net
+ """
+ return self._init_net
+
+ def save_protobuf(self, output_dir):
+ """
+ Save the model as caffe2's protobuf format.
+ It saves the following files:
+
+ * "model.pb": definition of the graph. Can be visualized with
+ tools like `netron `_.
+ * "model_init.pb": model parameters
+ * "model.pbtxt": human-readable definition of the graph. Not
+ needed for deployment.
+
+ Args:
+ output_dir (str): the output directory to save protobuf files.
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Saving model to {} ...".format(output_dir))
+ if not PathManager.exists(output_dir):
+ PathManager.mkdirs(output_dir)
+
+ with PathManager.open(os.path.join(output_dir, "model.pb"), "wb") as f:
+ f.write(self._predict_net.SerializeToString())
+ with PathManager.open(os.path.join(output_dir, "model.pbtxt"), "w") as f:
+ f.write(str(self._predict_net))
+ with PathManager.open(os.path.join(output_dir, "model_init.pb"), "wb") as f:
+ f.write(self._init_net.SerializeToString())
+
+ def save_graph(self, output_file, inputs=None):
+ """
+ Save the graph as SVG format.
+
+ Args:
+ output_file (str): a SVG file
+ inputs: optional inputs given to the model.
+ If given, the inputs will be used to run the graph to record
+ shape of every tensor. The shape information will be
+ saved together with the graph.
+ """
+ from .caffe2_export import run_and_save_graph
+
+ if inputs is None:
+ save_graph(self._predict_net, output_file, op_only=False)
+ else:
+ size_divisibility = get_pb_arg_vali(self._predict_net, "size_divisibility", 0)
+ device = get_pb_arg_vals(self._predict_net, "device", b"cpu").decode("ascii")
+ inputs = convert_batched_inputs_to_c2_format(inputs, size_divisibility, device)
+ inputs = [x.cpu().numpy() for x in inputs]
+ run_and_save_graph(self._predict_net, self._init_net, inputs, output_file)
+
+ @staticmethod
+ def load_protobuf(dir):
+ """
+ Args:
+ dir (str): a directory used to save Caffe2Model with
+ :meth:`save_protobuf`.
+ The files "model.pb" and "model_init.pb" are needed.
+
+ Returns:
+ Caffe2Model: the caffe2 model loaded from this directory.
+ """
+ predict_net = caffe2_pb2.NetDef()
+ with PathManager.open(os.path.join(dir, "model.pb"), "rb") as f:
+ predict_net.ParseFromString(f.read())
+
+ init_net = caffe2_pb2.NetDef()
+ with PathManager.open(os.path.join(dir, "model_init.pb"), "rb") as f:
+ init_net.ParseFromString(f.read())
+
+ return Caffe2Model(predict_net, init_net)
+
+ def __call__(self, inputs):
+ """
+ An interface that wraps around a Caffe2 model and mimics detectron2's models'
+ input/output format. See details about the format at :doc:`/tutorials/models`.
+ This is used to compare the outputs of caffe2 model with its original torch model.
+
+ Due to the extra conversion between Pytorch/Caffe2, this method is not meant for
+ benchmark. Because of the conversion, this method also has dependency
+ on detectron2 in order to convert to detectron2's output format.
+ """
+ if self._predictor is None:
+ self._predictor = ProtobufDetectionModel(self._predict_net, self._init_net)
+ return self._predictor(inputs)
diff --git a/detectron2/detectron2/export/c10.py b/detectron2/detectron2/export/c10.py
new file mode 100755
index 0000000..e9a3ee3
--- /dev/null
+++ b/detectron2/detectron2/export/c10.py
@@ -0,0 +1,571 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import math
+from typing import Dict
+import torch
+import torch.nn.functional as F
+
+from detectron2.layers import ShapeSpec, cat
+from detectron2.layers.roi_align_rotated import ROIAlignRotated
+from detectron2.modeling import poolers
+from detectron2.modeling.proposal_generator import rpn
+from detectron2.modeling.roi_heads.mask_head import mask_rcnn_inference
+from detectron2.structures import Boxes, ImageList, Instances, Keypoints, RotatedBoxes
+
+from .shared import alias, to_device
+
+
+"""
+This file contains caffe2-compatible implementation of several detectron2 components.
+"""
+
+
+class Caffe2Boxes(Boxes):
+ """
+ Representing a list of detectron2.structures.Boxes from minibatch, each box
+ is represented by a 5d vector (batch index + 4 coordinates), or a 6d vector
+ (batch index + 5 coordinates) for RotatedBoxes.
+ """
+
+ def __init__(self, tensor):
+ assert isinstance(tensor, torch.Tensor)
+ assert tensor.dim() == 2 and tensor.size(-1) in [4, 5, 6], tensor.size()
+ # TODO: make tensor immutable when dim is Nx5 for Boxes,
+ # and Nx6 for RotatedBoxes?
+ self.tensor = tensor
+
+
+# TODO clean up this class, maybe just extend Instances
+class InstancesList(object):
+ """
+ Tensor representation of a list of Instances object for a batch of images.
+
+ When dealing with a batch of images with Caffe2 ops, a list of bboxes
+ (instances) are usually represented by single Tensor with size
+ (sigma(Ni), 5) or (sigma(Ni), 4) plus a batch split Tensor. This class is
+ for providing common functions to convert between these two representations.
+ """
+
+ def __init__(self, im_info, indices, extra_fields=None):
+ # [N, 3] -> (H, W, Scale)
+ self.im_info = im_info
+ # [N,] -> indice of batch to which the instance belongs
+ self.indices = indices
+ # [N, ...]
+ self.batch_extra_fields = extra_fields or {}
+
+ self.image_size = self.im_info
+
+ def get_fields(self):
+ """like `get_fields` in the Instances object,
+ but return each field in tensor representations"""
+ ret = {}
+ for k, v in self.batch_extra_fields.items():
+ # if isinstance(v, torch.Tensor):
+ # tensor_rep = v
+ # elif isinstance(v, (Boxes, Keypoints)):
+ # tensor_rep = v.tensor
+ # else:
+ # raise ValueError("Can't find tensor representation for: {}".format())
+ ret[k] = v
+ return ret
+
+ def has(self, name):
+ return name in self.batch_extra_fields
+
+ def set(self, name, value):
+ # len(tensor) is a bad practice that generates ONNX constants during tracing.
+ # Although not a problem for the `assert` statement below, torch ONNX exporter
+ # still raises a misleading warning as it does not this call comes from `assert`
+ if isinstance(value, Boxes):
+ data_len = value.tensor.shape[0]
+ elif isinstance(value, torch.Tensor):
+ data_len = value.shape[0]
+ else:
+ data_len = len(value)
+ if len(self.batch_extra_fields):
+ assert (
+ len(self) == data_len
+ ), "Adding a field of length {} to a Instances of length {}".format(data_len, len(self))
+ self.batch_extra_fields[name] = value
+
+ def __getattr__(self, name):
+ if name not in self.batch_extra_fields:
+ raise AttributeError("Cannot find field '{}' in the given Instances!".format(name))
+ return self.batch_extra_fields[name]
+
+ def __len__(self):
+ return len(self.indices)
+
+ def flatten(self):
+ ret = []
+ for _, v in self.batch_extra_fields.items():
+ if isinstance(v, (Boxes, Keypoints)):
+ ret.append(v.tensor)
+ else:
+ ret.append(v)
+ return ret
+
+ @staticmethod
+ def to_d2_instances_list(instances_list):
+ """
+ Convert InstancesList to List[Instances]. The input `instances_list` can
+ also be a List[Instances], in this case this method is a non-op.
+ """
+ if not isinstance(instances_list, InstancesList):
+ assert all(isinstance(x, Instances) for x in instances_list)
+ return instances_list
+
+ ret = []
+ for i, info in enumerate(instances_list.im_info):
+ instances = Instances(torch.Size([int(info[0].item()), int(info[1].item())]))
+
+ ids = instances_list.indices == i
+ for k, v in instances_list.batch_extra_fields.items():
+ if isinstance(v, torch.Tensor):
+ instances.set(k, v[ids])
+ continue
+ elif isinstance(v, Boxes):
+ instances.set(k, v[ids, -4:])
+ continue
+
+ target_type, tensor_source = v
+ assert isinstance(tensor_source, torch.Tensor)
+ assert tensor_source.shape[0] == instances_list.indices.shape[0]
+ tensor_source = tensor_source[ids]
+
+ if issubclass(target_type, Boxes):
+ instances.set(k, Boxes(tensor_source[:, -4:]))
+ elif issubclass(target_type, Keypoints):
+ instances.set(k, Keypoints(tensor_source))
+ elif issubclass(target_type, torch.Tensor):
+ instances.set(k, tensor_source)
+ else:
+ raise ValueError("Can't handle targe type: {}".format(target_type))
+
+ ret.append(instances)
+ return ret
+
+
+class Caffe2Compatible(object):
+ """
+ A model can inherit this class to indicate that it can be traced and deployed with caffe2.
+ """
+
+ def _get_tensor_mode(self):
+ return self._tensor_mode
+
+ def _set_tensor_mode(self, v):
+ self._tensor_mode = v
+
+ tensor_mode = property(_get_tensor_mode, _set_tensor_mode)
+ """
+ If true, the model expects C2-style tensor only inputs/outputs format.
+ """
+
+
+class Caffe2RPN(Caffe2Compatible, rpn.RPN):
+ @classmethod
+ def from_config(cls, cfg, input_shape: Dict[str, ShapeSpec]):
+ ret = super(Caffe2Compatible, cls).from_config(cfg, input_shape)
+ assert tuple(cfg.MODEL.RPN.BBOX_REG_WEIGHTS) == (1.0, 1.0, 1.0, 1.0) or tuple(
+ cfg.MODEL.RPN.BBOX_REG_WEIGHTS
+ ) == (1.0, 1.0, 1.0, 1.0, 1.0)
+ return ret
+
+ def _generate_proposals(
+ self, images, objectness_logits_pred, anchor_deltas_pred, gt_instances=None
+ ):
+ assert isinstance(images, ImageList)
+ if self.tensor_mode:
+ im_info = images.image_sizes
+ else:
+ im_info = torch.tensor([[im_sz[0], im_sz[1], 1.0] for im_sz in images.image_sizes]).to(
+ images.tensor.device
+ )
+ assert isinstance(im_info, torch.Tensor)
+
+ rpn_rois_list = []
+ rpn_roi_probs_list = []
+ for scores, bbox_deltas, cell_anchors_tensor, feat_stride in zip(
+ objectness_logits_pred,
+ anchor_deltas_pred,
+ [b for (n, b) in self.anchor_generator.cell_anchors.named_buffers()],
+ self.anchor_generator.strides,
+ ):
+ scores = scores.detach()
+ bbox_deltas = bbox_deltas.detach()
+
+ rpn_rois, rpn_roi_probs = torch.ops._caffe2.GenerateProposals(
+ scores,
+ bbox_deltas,
+ im_info,
+ cell_anchors_tensor,
+ spatial_scale=1.0 / feat_stride,
+ pre_nms_topN=self.pre_nms_topk[self.training],
+ post_nms_topN=self.post_nms_topk[self.training],
+ nms_thresh=self.nms_thresh,
+ min_size=self.min_box_size,
+ # correct_transform_coords=True, # deprecated argument
+ angle_bound_on=True, # Default
+ angle_bound_lo=-180,
+ angle_bound_hi=180,
+ clip_angle_thresh=1.0, # Default
+ legacy_plus_one=False,
+ )
+ rpn_rois_list.append(rpn_rois)
+ rpn_roi_probs_list.append(rpn_roi_probs)
+
+ # For FPN in D2, in RPN all proposals from different levels are concated
+ # together, ranked and picked by top post_nms_topk. Then in ROIPooler
+ # it calculates level_assignments and calls the RoIAlign from
+ # the corresponding level.
+
+ if len(objectness_logits_pred) == 1:
+ rpn_rois = rpn_rois_list[0]
+ rpn_roi_probs = rpn_roi_probs_list[0]
+ else:
+ assert len(rpn_rois_list) == len(rpn_roi_probs_list)
+ rpn_post_nms_topN = self.post_nms_topk[self.training]
+
+ device = rpn_rois_list[0].device
+ input_list = [to_device(x, "cpu") for x in (rpn_rois_list + rpn_roi_probs_list)]
+
+ # TODO remove this after confirming rpn_max_level/rpn_min_level
+ # is not needed in CollectRpnProposals.
+ feature_strides = list(self.anchor_generator.strides)
+ rpn_min_level = int(math.log2(feature_strides[0]))
+ rpn_max_level = int(math.log2(feature_strides[-1]))
+ assert (rpn_max_level - rpn_min_level + 1) == len(
+ rpn_rois_list
+ ), "CollectRpnProposals requires continuous levels"
+
+ rpn_rois = torch.ops._caffe2.CollectRpnProposals(
+ input_list,
+ # NOTE: in current implementation, rpn_max_level and rpn_min_level
+ # are not needed, only the subtraction of two matters and it
+ # can be infer from the number of inputs. Keep them now for
+ # consistency.
+ rpn_max_level=2 + len(rpn_rois_list) - 1,
+ rpn_min_level=2,
+ rpn_post_nms_topN=rpn_post_nms_topN,
+ )
+ rpn_rois = to_device(rpn_rois, device)
+ rpn_roi_probs = []
+
+ proposals = self.c2_postprocess(im_info, rpn_rois, rpn_roi_probs, self.tensor_mode)
+ return proposals, {}
+
+ def forward(self, images, features, gt_instances=None):
+ assert not self.training
+ features = [features[f] for f in self.in_features]
+ objectness_logits_pred, anchor_deltas_pred = self.rpn_head(features)
+ return self._generate_proposals(
+ images,
+ objectness_logits_pred,
+ anchor_deltas_pred,
+ gt_instances,
+ )
+
+ @staticmethod
+ def c2_postprocess(im_info, rpn_rois, rpn_roi_probs, tensor_mode):
+ proposals = InstancesList(
+ im_info=im_info,
+ indices=rpn_rois[:, 0],
+ extra_fields={
+ "proposal_boxes": Caffe2Boxes(rpn_rois),
+ "objectness_logits": (torch.Tensor, rpn_roi_probs),
+ },
+ )
+ if not tensor_mode:
+ proposals = InstancesList.to_d2_instances_list(proposals)
+ else:
+ proposals = [proposals]
+ return proposals
+
+
+class Caffe2ROIPooler(Caffe2Compatible, poolers.ROIPooler):
+ @staticmethod
+ def c2_preprocess(box_lists):
+ assert all(isinstance(x, Boxes) for x in box_lists)
+ if all(isinstance(x, Caffe2Boxes) for x in box_lists):
+ # input is pure-tensor based
+ assert len(box_lists) == 1
+ pooler_fmt_boxes = box_lists[0].tensor
+ else:
+ pooler_fmt_boxes = poolers.convert_boxes_to_pooler_format(box_lists)
+ return pooler_fmt_boxes
+
+ def forward(self, x, box_lists):
+ assert not self.training
+
+ pooler_fmt_boxes = self.c2_preprocess(box_lists)
+ num_level_assignments = len(self.level_poolers)
+
+ if num_level_assignments == 1:
+ if isinstance(self.level_poolers[0], ROIAlignRotated):
+ c2_roi_align = torch.ops._caffe2.RoIAlignRotated
+ aligned = True
+ else:
+ c2_roi_align = torch.ops._caffe2.RoIAlign
+ aligned = self.level_poolers[0].aligned
+
+ x0 = x[0]
+ if x0.is_quantized:
+ x0 = x0.dequantize()
+
+ out = c2_roi_align(
+ x0,
+ pooler_fmt_boxes,
+ order="NCHW",
+ spatial_scale=float(self.level_poolers[0].spatial_scale),
+ pooled_h=int(self.output_size[0]),
+ pooled_w=int(self.output_size[1]),
+ sampling_ratio=int(self.level_poolers[0].sampling_ratio),
+ aligned=aligned,
+ )
+ return out
+
+ device = pooler_fmt_boxes.device
+ assert (
+ self.max_level - self.min_level + 1 == 4
+ ), "Currently DistributeFpnProposals only support 4 levels"
+ fpn_outputs = torch.ops._caffe2.DistributeFpnProposals(
+ to_device(pooler_fmt_boxes, "cpu"),
+ roi_canonical_scale=self.canonical_box_size,
+ roi_canonical_level=self.canonical_level,
+ roi_max_level=self.max_level,
+ roi_min_level=self.min_level,
+ legacy_plus_one=False,
+ )
+ fpn_outputs = [to_device(x, device) for x in fpn_outputs]
+
+ rois_fpn_list = fpn_outputs[:-1]
+ rois_idx_restore_int32 = fpn_outputs[-1]
+
+ roi_feat_fpn_list = []
+ for roi_fpn, x_level, pooler in zip(rois_fpn_list, x, self.level_poolers):
+ if isinstance(pooler, ROIAlignRotated):
+ c2_roi_align = torch.ops._caffe2.RoIAlignRotated
+ aligned = True
+ else:
+ c2_roi_align = torch.ops._caffe2.RoIAlign
+ aligned = bool(pooler.aligned)
+
+ if x_level.is_quantized:
+ x_level = x_level.dequantize()
+
+ roi_feat_fpn = c2_roi_align(
+ x_level,
+ roi_fpn,
+ order="NCHW",
+ spatial_scale=float(pooler.spatial_scale),
+ pooled_h=int(self.output_size[0]),
+ pooled_w=int(self.output_size[1]),
+ sampling_ratio=int(pooler.sampling_ratio),
+ aligned=aligned,
+ )
+ roi_feat_fpn_list.append(roi_feat_fpn)
+
+ roi_feat_shuffled = cat(roi_feat_fpn_list, dim=0)
+ assert roi_feat_shuffled.numel() > 0 and rois_idx_restore_int32.numel() > 0, (
+ "Caffe2 export requires tracing with a model checkpoint + input that can produce valid"
+ " detections. But no detections were obtained with the given checkpoint and input!"
+ )
+ roi_feat = torch.ops._caffe2.BatchPermutation(roi_feat_shuffled, rois_idx_restore_int32)
+ return roi_feat
+
+
+def caffe2_fast_rcnn_outputs_inference(tensor_mode, box_predictor, predictions, proposals):
+ """equivalent to FastRCNNOutputLayers.inference"""
+ num_classes = box_predictor.num_classes
+ score_thresh = box_predictor.test_score_thresh
+ nms_thresh = box_predictor.test_nms_thresh
+ topk_per_image = box_predictor.test_topk_per_image
+ is_rotated = len(box_predictor.box2box_transform.weights) == 5
+
+ if is_rotated:
+ box_dim = 5
+ assert box_predictor.box2box_transform.weights[4] == 1, (
+ "The weights for Rotated BBoxTransform in C2 have only 4 dimensions,"
+ + " thus enforcing the angle weight to be 1 for now"
+ )
+ box2box_transform_weights = box_predictor.box2box_transform.weights[:4]
+ else:
+ box_dim = 4
+ box2box_transform_weights = box_predictor.box2box_transform.weights
+
+ class_logits, box_regression = predictions
+ if num_classes + 1 == class_logits.shape[1]:
+ class_prob = F.softmax(class_logits, -1)
+ else:
+ assert num_classes == class_logits.shape[1]
+ class_prob = F.sigmoid(class_logits)
+ # BoxWithNMSLimit will infer num_classes from the shape of the class_prob
+ # So append a zero column as placeholder for the background class
+ class_prob = torch.cat((class_prob, torch.zeros(class_prob.shape[0], 1)), dim=1)
+
+ assert box_regression.shape[1] % box_dim == 0
+ cls_agnostic_bbox_reg = box_regression.shape[1] // box_dim == 1
+
+ input_tensor_mode = proposals[0].proposal_boxes.tensor.shape[1] == box_dim + 1
+
+ proposal_boxes = proposals[0].proposal_boxes
+ if isinstance(proposal_boxes, Caffe2Boxes):
+ rois = Caffe2Boxes.cat([p.proposal_boxes for p in proposals])
+ elif isinstance(proposal_boxes, RotatedBoxes):
+ rois = RotatedBoxes.cat([p.proposal_boxes for p in proposals])
+ elif isinstance(proposal_boxes, Boxes):
+ rois = Boxes.cat([p.proposal_boxes for p in proposals])
+ else:
+ raise NotImplementedError(
+ 'Expected proposals[0].proposal_boxes to be type "Boxes", '
+ f"instead got {type(proposal_boxes)}"
+ )
+
+ device, dtype = rois.tensor.device, rois.tensor.dtype
+ if input_tensor_mode:
+ im_info = proposals[0].image_size
+ rois = rois.tensor
+ else:
+ im_info = torch.tensor([[sz[0], sz[1], 1.0] for sz in [x.image_size for x in proposals]])
+ batch_ids = cat(
+ [
+ torch.full((b, 1), i, dtype=dtype, device=device)
+ for i, b in enumerate(len(p) for p in proposals)
+ ],
+ dim=0,
+ )
+ rois = torch.cat([batch_ids, rois.tensor], dim=1)
+
+ roi_pred_bbox, roi_batch_splits = torch.ops._caffe2.BBoxTransform(
+ to_device(rois, "cpu"),
+ to_device(box_regression, "cpu"),
+ to_device(im_info, "cpu"),
+ weights=box2box_transform_weights,
+ apply_scale=True,
+ rotated=is_rotated,
+ angle_bound_on=True,
+ angle_bound_lo=-180,
+ angle_bound_hi=180,
+ clip_angle_thresh=1.0,
+ legacy_plus_one=False,
+ )
+ roi_pred_bbox = to_device(roi_pred_bbox, device)
+ roi_batch_splits = to_device(roi_batch_splits, device)
+
+ nms_outputs = torch.ops._caffe2.BoxWithNMSLimit(
+ to_device(class_prob, "cpu"),
+ to_device(roi_pred_bbox, "cpu"),
+ to_device(roi_batch_splits, "cpu"),
+ score_thresh=float(score_thresh),
+ nms=float(nms_thresh),
+ detections_per_im=int(topk_per_image),
+ soft_nms_enabled=False,
+ soft_nms_method="linear",
+ soft_nms_sigma=0.5,
+ soft_nms_min_score_thres=0.001,
+ rotated=is_rotated,
+ cls_agnostic_bbox_reg=cls_agnostic_bbox_reg,
+ input_boxes_include_bg_cls=False,
+ output_classes_include_bg_cls=False,
+ legacy_plus_one=False,
+ )
+ roi_score_nms = to_device(nms_outputs[0], device)
+ roi_bbox_nms = to_device(nms_outputs[1], device)
+ roi_class_nms = to_device(nms_outputs[2], device)
+ roi_batch_splits_nms = to_device(nms_outputs[3], device)
+ roi_keeps_nms = to_device(nms_outputs[4], device)
+ roi_keeps_size_nms = to_device(nms_outputs[5], device)
+ if not tensor_mode:
+ roi_class_nms = roi_class_nms.to(torch.int64)
+
+ roi_batch_ids = cat(
+ [
+ torch.full((b, 1), i, dtype=dtype, device=device)
+ for i, b in enumerate(int(x.item()) for x in roi_batch_splits_nms)
+ ],
+ dim=0,
+ )
+
+ roi_class_nms = alias(roi_class_nms, "class_nms")
+ roi_score_nms = alias(roi_score_nms, "score_nms")
+ roi_bbox_nms = alias(roi_bbox_nms, "bbox_nms")
+ roi_batch_splits_nms = alias(roi_batch_splits_nms, "batch_splits_nms")
+ roi_keeps_nms = alias(roi_keeps_nms, "keeps_nms")
+ roi_keeps_size_nms = alias(roi_keeps_size_nms, "keeps_size_nms")
+
+ results = InstancesList(
+ im_info=im_info,
+ indices=roi_batch_ids[:, 0],
+ extra_fields={
+ "pred_boxes": Caffe2Boxes(roi_bbox_nms),
+ "scores": roi_score_nms,
+ "pred_classes": roi_class_nms,
+ },
+ )
+
+ if not tensor_mode:
+ results = InstancesList.to_d2_instances_list(results)
+ batch_splits = roi_batch_splits_nms.int().tolist()
+ kept_indices = list(roi_keeps_nms.to(torch.int64).split(batch_splits))
+ else:
+ results = [results]
+ kept_indices = [roi_keeps_nms]
+
+ return results, kept_indices
+
+
+class Caffe2FastRCNNOutputsInference:
+ def __init__(self, tensor_mode):
+ self.tensor_mode = tensor_mode # whether the output is caffe2 tensor mode
+
+ def __call__(self, box_predictor, predictions, proposals):
+ return caffe2_fast_rcnn_outputs_inference(
+ self.tensor_mode, box_predictor, predictions, proposals
+ )
+
+
+def caffe2_mask_rcnn_inference(pred_mask_logits, pred_instances):
+ """equivalent to mask_head.mask_rcnn_inference"""
+ if all(isinstance(x, InstancesList) for x in pred_instances):
+ assert len(pred_instances) == 1
+ mask_probs_pred = pred_mask_logits.sigmoid()
+ mask_probs_pred = alias(mask_probs_pred, "mask_fcn_probs")
+ pred_instances[0].set("pred_masks", mask_probs_pred)
+ else:
+ mask_rcnn_inference(pred_mask_logits, pred_instances)
+
+
+class Caffe2MaskRCNNInference:
+ def __call__(self, pred_mask_logits, pred_instances):
+ return caffe2_mask_rcnn_inference(pred_mask_logits, pred_instances)
+
+
+def caffe2_keypoint_rcnn_inference(use_heatmap_max_keypoint, pred_keypoint_logits, pred_instances):
+ # just return the keypoint heatmap for now,
+ # there will be option to call HeatmapMaxKeypointOp
+ output = alias(pred_keypoint_logits, "kps_score")
+ if all(isinstance(x, InstancesList) for x in pred_instances):
+ assert len(pred_instances) == 1
+ if use_heatmap_max_keypoint:
+ device = output.device
+ output = torch.ops._caffe2.HeatmapMaxKeypoint(
+ to_device(output, "cpu"),
+ pred_instances[0].pred_boxes.tensor,
+ should_output_softmax=True, # worth make it configerable?
+ )
+ output = to_device(output, device)
+ output = alias(output, "keypoints_out")
+ pred_instances[0].set("pred_keypoints", output)
+ return pred_keypoint_logits
+
+
+class Caffe2KeypointRCNNInference:
+ def __init__(self, use_heatmap_max_keypoint):
+ self.use_heatmap_max_keypoint = use_heatmap_max_keypoint
+
+ def __call__(self, pred_keypoint_logits, pred_instances):
+ return caffe2_keypoint_rcnn_inference(
+ self.use_heatmap_max_keypoint, pred_keypoint_logits, pred_instances
+ )
diff --git a/detectron2/detectron2/export/caffe2_export.py b/detectron2/detectron2/export/caffe2_export.py
new file mode 100755
index 0000000..d609c27
--- /dev/null
+++ b/detectron2/detectron2/export/caffe2_export.py
@@ -0,0 +1,203 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import copy
+import io
+import logging
+import numpy as np
+from typing import List
+import onnx
+import onnx.optimizer
+import torch
+from caffe2.proto import caffe2_pb2
+from caffe2.python import core
+from caffe2.python.onnx.backend import Caffe2Backend
+from tabulate import tabulate
+from termcolor import colored
+from torch.onnx import OperatorExportTypes
+
+from .shared import (
+ ScopedWS,
+ construct_init_net_from_params,
+ fuse_alias_placeholder,
+ fuse_copy_between_cpu_and_gpu,
+ get_params_from_init_net,
+ group_norm_replace_aten_with_caffe2,
+ infer_device_type,
+ remove_dead_end_ops,
+ remove_reshape_for_fc,
+ save_graph,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def export_onnx_model(model, inputs):
+ """
+ Trace and export a model to onnx format.
+
+ Args:
+ model (nn.Module):
+ inputs (tuple[args]): the model will be called by `model(*inputs)`
+
+ Returns:
+ an onnx model
+ """
+ assert isinstance(model, torch.nn.Module)
+
+ # make sure all modules are in eval mode, onnx may change the training state
+ # of the module if the states are not consistent
+ def _check_eval(module):
+ assert not module.training
+
+ model.apply(_check_eval)
+
+ # Export the model to ONNX
+ with torch.no_grad():
+ with io.BytesIO() as f:
+ torch.onnx.export(
+ model,
+ inputs,
+ f,
+ operator_export_type=OperatorExportTypes.ONNX_ATEN_FALLBACK,
+ # verbose=True, # NOTE: uncomment this for debugging
+ # export_params=True,
+ )
+ onnx_model = onnx.load_from_string(f.getvalue())
+
+ return onnx_model
+
+
+def _op_stats(net_def):
+ type_count = {}
+ for t in [op.type for op in net_def.op]:
+ type_count[t] = type_count.get(t, 0) + 1
+ type_count_list = sorted(type_count.items(), key=lambda kv: kv[0]) # alphabet
+ type_count_list = sorted(type_count_list, key=lambda kv: -kv[1]) # count
+ return "\n".join("{:>4}x {}".format(count, name) for name, count in type_count_list)
+
+
+def _assign_device_option(
+ predict_net: caffe2_pb2.NetDef, init_net: caffe2_pb2.NetDef, tensor_inputs: List[torch.Tensor]
+):
+ """
+ ONNX exported network doesn't have concept of device, assign necessary
+ device option for each op in order to make it runable on GPU runtime.
+ """
+
+ def _get_device_type(torch_tensor):
+ assert torch_tensor.device.type in ["cpu", "cuda"]
+ assert torch_tensor.device.index == 0
+ return torch_tensor.device.type
+
+ def _assign_op_device_option(net_proto, net_ssa, blob_device_types):
+ for op, ssa_i in zip(net_proto.op, net_ssa):
+ if op.type in ["CopyCPUToGPU", "CopyGPUToCPU"]:
+ op.device_option.CopyFrom(core.DeviceOption(caffe2_pb2.CUDA, 0))
+ else:
+ devices = [blob_device_types[b] for b in ssa_i[0] + ssa_i[1]]
+ assert all(d == devices[0] for d in devices)
+ if devices[0] == "cuda":
+ op.device_option.CopyFrom(core.DeviceOption(caffe2_pb2.CUDA, 0))
+
+ # update ops in predict_net
+ predict_net_input_device_types = {
+ (name, 0): _get_device_type(tensor)
+ for name, tensor in zip(predict_net.external_input, tensor_inputs)
+ }
+ predict_net_device_types = infer_device_type(
+ predict_net, known_status=predict_net_input_device_types, device_name_style="pytorch"
+ )
+ predict_net_ssa, _ = core.get_ssa(predict_net)
+ _assign_op_device_option(predict_net, predict_net_ssa, predict_net_device_types)
+
+ # update ops in init_net
+ init_net_ssa, versions = core.get_ssa(init_net)
+ init_net_output_device_types = {
+ (name, versions[name]): predict_net_device_types[(name, 0)]
+ for name in init_net.external_output
+ }
+ init_net_device_types = infer_device_type(
+ init_net, known_status=init_net_output_device_types, device_name_style="pytorch"
+ )
+ _assign_op_device_option(init_net, init_net_ssa, init_net_device_types)
+
+
+def export_caffe2_detection_model(model: torch.nn.Module, tensor_inputs: List[torch.Tensor]):
+ """
+ Export a caffe2-compatible Detectron2 model to caffe2 format via ONNX.
+
+ Arg:
+ model: a caffe2-compatible version of detectron2 model, defined in caffe2_modeling.py
+ tensor_inputs: a list of tensors that caffe2 model takes as input.
+ """
+ model = copy.deepcopy(model)
+ assert isinstance(model, torch.nn.Module)
+ assert hasattr(model, "encode_additional_info")
+
+ # Export via ONNX
+ logger.info(
+ "Exporting a {} model via ONNX ...".format(type(model).__name__)
+ + " Some warnings from ONNX are expected and are usually not to worry about."
+ )
+ onnx_model = export_onnx_model(model, (tensor_inputs,))
+ # Convert ONNX model to Caffe2 protobuf
+ init_net, predict_net = Caffe2Backend.onnx_graph_to_caffe2_net(onnx_model)
+ ops_table = [[op.type, op.input, op.output] for op in predict_net.op]
+ table = tabulate(ops_table, headers=["type", "input", "output"], tablefmt="pipe")
+ logger.info(
+ "ONNX export Done. Exported predict_net (before optimizations):\n" + colored(table, "cyan")
+ )
+
+ # Apply protobuf optimization
+ fuse_alias_placeholder(predict_net, init_net)
+ if any(t.device.type != "cpu" for t in tensor_inputs):
+ fuse_copy_between_cpu_and_gpu(predict_net)
+ remove_dead_end_ops(init_net)
+ _assign_device_option(predict_net, init_net, tensor_inputs)
+ params, device_options = get_params_from_init_net(init_net)
+ predict_net, params = remove_reshape_for_fc(predict_net, params)
+ init_net = construct_init_net_from_params(params, device_options)
+ group_norm_replace_aten_with_caffe2(predict_net)
+
+ # Record necessary information for running the pb model in Detectron2 system.
+ model.encode_additional_info(predict_net, init_net)
+
+ logger.info("Operators used in predict_net: \n{}".format(_op_stats(predict_net)))
+ logger.info("Operators used in init_net: \n{}".format(_op_stats(init_net)))
+
+ return predict_net, init_net
+
+
+def run_and_save_graph(predict_net, init_net, tensor_inputs, graph_save_path):
+ """
+ Run the caffe2 model on given inputs, recording the shape and draw the graph.
+
+ predict_net/init_net: caffe2 model.
+ tensor_inputs: a list of tensors that caffe2 model takes as input.
+ graph_save_path: path for saving graph of exported model.
+ """
+
+ logger.info("Saving graph of ONNX exported model to {} ...".format(graph_save_path))
+ save_graph(predict_net, graph_save_path, op_only=False)
+
+ # Run the exported Caffe2 net
+ logger.info("Running ONNX exported model ...")
+ with ScopedWS("__ws_tmp__", True) as ws:
+ ws.RunNetOnce(init_net)
+ initialized_blobs = set(ws.Blobs())
+ uninitialized = [inp for inp in predict_net.external_input if inp not in initialized_blobs]
+ for name, blob in zip(uninitialized, tensor_inputs):
+ ws.FeedBlob(name, blob)
+
+ try:
+ ws.RunNetOnce(predict_net)
+ except RuntimeError as e:
+ logger.warning("Encountered RuntimeError: \n{}".format(str(e)))
+
+ ws_blobs = {b: ws.FetchBlob(b) for b in ws.Blobs()}
+ blob_sizes = {b: ws_blobs[b].shape for b in ws_blobs if isinstance(ws_blobs[b], np.ndarray)}
+
+ logger.info("Saving graph with blob shapes to {} ...".format(graph_save_path))
+ save_graph(predict_net, graph_save_path, op_only=False, blob_sizes=blob_sizes)
+
+ return ws_blobs
diff --git a/detectron2/detectron2/export/caffe2_inference.py b/detectron2/detectron2/export/caffe2_inference.py
new file mode 100755
index 0000000..deb886c
--- /dev/null
+++ b/detectron2/detectron2/export/caffe2_inference.py
@@ -0,0 +1,161 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import logging
+import numpy as np
+from itertools import count
+import torch
+from caffe2.proto import caffe2_pb2
+from caffe2.python import core
+
+from .caffe2_modeling import META_ARCH_CAFFE2_EXPORT_TYPE_MAP, convert_batched_inputs_to_c2_format
+from .shared import ScopedWS, get_pb_arg_vali, get_pb_arg_vals, infer_device_type
+
+logger = logging.getLogger(__name__)
+
+
+# ===== ref: mobile-vision predictor's 'Caffe2Wrapper' class ======
+class ProtobufModel(torch.nn.Module):
+ """
+ Wrapper of a caffe2's protobuf model.
+ It works just like nn.Module, but running caffe2 under the hood.
+ Input/Output are tuple[tensor] that match the caffe2 net's external_input/output.
+ """
+
+ _ids = count(0)
+
+ def __init__(self, predict_net, init_net):
+ logger.info(f"Initializing ProtobufModel for: {predict_net.name} ...")
+ super().__init__()
+ assert isinstance(predict_net, caffe2_pb2.NetDef)
+ assert isinstance(init_net, caffe2_pb2.NetDef)
+ # create unique temporary workspace for each instance
+ self.ws_name = "__tmp_ProtobufModel_{}__".format(next(self._ids))
+ self.net = core.Net(predict_net)
+
+ logger.info("Running init_net once to fill the parameters ...")
+ with ScopedWS(self.ws_name, is_reset=True, is_cleanup=False) as ws:
+ ws.RunNetOnce(init_net)
+ uninitialized_external_input = []
+ for blob in self.net.Proto().external_input:
+ if blob not in ws.Blobs():
+ uninitialized_external_input.append(blob)
+ ws.CreateBlob(blob)
+ ws.CreateNet(self.net)
+
+ self._error_msgs = set()
+ self._input_blobs = uninitialized_external_input
+
+ def _infer_output_devices(self, inputs):
+ """
+ Returns:
+ list[str]: list of device for each external output
+ """
+
+ def _get_device_type(torch_tensor):
+ assert torch_tensor.device.type in ["cpu", "cuda"]
+ assert torch_tensor.device.index == 0
+ return torch_tensor.device.type
+
+ predict_net = self.net.Proto()
+ input_device_types = {
+ (name, 0): _get_device_type(tensor) for name, tensor in zip(self._input_blobs, inputs)
+ }
+ device_type_map = infer_device_type(
+ predict_net, known_status=input_device_types, device_name_style="pytorch"
+ )
+ ssa, versions = core.get_ssa(predict_net)
+ versioned_outputs = [(name, versions[name]) for name in predict_net.external_output]
+ output_devices = [device_type_map[outp] for outp in versioned_outputs]
+ return output_devices
+
+ def forward(self, inputs):
+ """
+ Args:
+ inputs (tuple[torch.Tensor])
+
+ Returns:
+ tuple[torch.Tensor]
+ """
+ assert len(inputs) == len(self._input_blobs), (
+ f"Length of inputs ({len(inputs)}) "
+ f"doesn't match the required input blobs: {self._input_blobs}"
+ )
+
+ with ScopedWS(self.ws_name, is_reset=False, is_cleanup=False) as ws:
+ for b, tensor in zip(self._input_blobs, inputs):
+ ws.FeedBlob(b, tensor)
+
+ try:
+ ws.RunNet(self.net.Proto().name)
+ except RuntimeError as e:
+ if not str(e) in self._error_msgs:
+ self._error_msgs.add(str(e))
+ logger.warning("Encountered new RuntimeError: \n{}".format(str(e)))
+ logger.warning("Catch the error and use partial results.")
+
+ c2_outputs = [ws.FetchBlob(b) for b in self.net.Proto().external_output]
+ # Remove outputs of current run, this is necessary in order to
+ # prevent fetching the result from previous run if the model fails
+ # in the middle.
+ for b in self.net.Proto().external_output:
+ # Needs to create uninitialized blob to make the net runable.
+ # This is "equivalent" to: ws.RemoveBlob(b) then ws.CreateBlob(b),
+ # but there'no such API.
+ ws.FeedBlob(b, f"{b}, a C++ native class of type nullptr (uninitialized).")
+
+ # Cast output to torch.Tensor on the desired device
+ output_devices = (
+ self._infer_output_devices(inputs)
+ if any(t.device.type != "cpu" for t in inputs)
+ else ["cpu" for _ in self.net.Proto().external_output]
+ )
+
+ outputs = []
+ for name, c2_output, device in zip(
+ self.net.Proto().external_output, c2_outputs, output_devices
+ ):
+ if not isinstance(c2_output, np.ndarray):
+ raise RuntimeError(
+ "Invalid output for blob {}, received: {}".format(name, c2_output)
+ )
+ outputs.append(torch.tensor(c2_output).to(device=device))
+ return tuple(outputs)
+
+
+class ProtobufDetectionModel(torch.nn.Module):
+ """
+ A class works just like a pytorch meta arch in terms of inference, but running
+ caffe2 model under the hood.
+ """
+
+ def __init__(self, predict_net, init_net, *, convert_outputs=None):
+ """
+ Args:
+ predict_net, init_net (core.Net): caffe2 nets
+ convert_outptus (callable): a function that converts caffe2
+ outputs to the same format of the original pytorch model.
+ By default, use the one defined in the caffe2 meta_arch.
+ """
+ super().__init__()
+ self.protobuf_model = ProtobufModel(predict_net, init_net)
+ self.size_divisibility = get_pb_arg_vali(predict_net, "size_divisibility", 0)
+ self.device = get_pb_arg_vals(predict_net, "device", b"cpu").decode("ascii")
+
+ if convert_outputs is None:
+ meta_arch = get_pb_arg_vals(predict_net, "meta_architecture", b"GeneralizedRCNN")
+ meta_arch = META_ARCH_CAFFE2_EXPORT_TYPE_MAP[meta_arch.decode("ascii")]
+ self._convert_outputs = meta_arch.get_outputs_converter(predict_net, init_net)
+ else:
+ self._convert_outputs = convert_outputs
+
+ def _convert_inputs(self, batched_inputs):
+ # currently all models convert inputs in the same way
+ return convert_batched_inputs_to_c2_format(
+ batched_inputs, self.size_divisibility, self.device
+ )
+
+ def forward(self, batched_inputs):
+ c2_inputs = self._convert_inputs(batched_inputs)
+ c2_results = self.protobuf_model(c2_inputs)
+ c2_results = dict(zip(self.protobuf_model.net.Proto().external_output, c2_results))
+ return self._convert_outputs(batched_inputs, c2_inputs, c2_results)
diff --git a/detectron2/detectron2/export/caffe2_modeling.py b/detectron2/detectron2/export/caffe2_modeling.py
new file mode 100755
index 0000000..3e675c4
--- /dev/null
+++ b/detectron2/detectron2/export/caffe2_modeling.py
@@ -0,0 +1,420 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import functools
+import io
+import struct
+import types
+import torch
+
+from detectron2.modeling import meta_arch
+from detectron2.modeling.box_regression import Box2BoxTransform
+from detectron2.modeling.roi_heads import keypoint_head
+from detectron2.structures import Boxes, ImageList, Instances, RotatedBoxes
+
+from .c10 import Caffe2Compatible
+from .caffe2_patch import ROIHeadsPatcher, patch_generalized_rcnn
+from .shared import (
+ alias,
+ check_set_pb_arg,
+ get_pb_arg_floats,
+ get_pb_arg_valf,
+ get_pb_arg_vali,
+ get_pb_arg_vals,
+ mock_torch_nn_functional_interpolate,
+)
+
+
+def assemble_rcnn_outputs_by_name(image_sizes, tensor_outputs, force_mask_on=False):
+ """
+ A function to assemble caffe2 model's outputs (i.e. Dict[str, Tensor])
+ to detectron2's format (i.e. list of Instances instance).
+ This only works when the model follows the Caffe2 detectron's naming convention.
+
+ Args:
+ image_sizes (List[List[int, int]]): [H, W] of every image.
+ tensor_outputs (Dict[str, Tensor]): external_output to its tensor.
+
+ force_mask_on (Bool): if true, the it make sure there'll be pred_masks even
+ if the mask is not found from tensor_outputs (usually due to model crash)
+ """
+
+ results = [Instances(image_size) for image_size in image_sizes]
+
+ batch_splits = tensor_outputs.get("batch_splits", None)
+ if batch_splits:
+ raise NotImplementedError()
+ assert len(image_sizes) == 1
+ result = results[0]
+
+ bbox_nms = tensor_outputs["bbox_nms"]
+ score_nms = tensor_outputs["score_nms"]
+ class_nms = tensor_outputs["class_nms"]
+ # Detection will always success because Conv support 0-batch
+ assert bbox_nms is not None
+ assert score_nms is not None
+ assert class_nms is not None
+ if bbox_nms.shape[1] == 5:
+ result.pred_boxes = RotatedBoxes(bbox_nms)
+ else:
+ result.pred_boxes = Boxes(bbox_nms)
+ result.scores = score_nms
+ result.pred_classes = class_nms.to(torch.int64)
+
+ mask_fcn_probs = tensor_outputs.get("mask_fcn_probs", None)
+ if mask_fcn_probs is not None:
+ # finish the mask pred
+ mask_probs_pred = mask_fcn_probs
+ num_masks = mask_probs_pred.shape[0]
+ class_pred = result.pred_classes
+ indices = torch.arange(num_masks, device=class_pred.device)
+ mask_probs_pred = mask_probs_pred[indices, class_pred][:, None]
+ result.pred_masks = mask_probs_pred
+ elif force_mask_on:
+ # NOTE: there's no way to know the height/width of mask here, it won't be
+ # used anyway when batch size is 0, so just set them to 0.
+ result.pred_masks = torch.zeros([0, 1, 0, 0], dtype=torch.uint8)
+
+ keypoints_out = tensor_outputs.get("keypoints_out", None)
+ kps_score = tensor_outputs.get("kps_score", None)
+ if keypoints_out is not None:
+ # keypoints_out: [N, 4, #kypoints], where 4 is in order of (x, y, score, prob)
+ keypoints_tensor = keypoints_out
+ # NOTE: it's possible that prob is not calculated if "should_output_softmax"
+ # is set to False in HeatmapMaxKeypoint, so just using raw score, seems
+ # it doesn't affect mAP. TODO: check more carefully.
+ keypoint_xyp = keypoints_tensor.transpose(1, 2)[:, :, [0, 1, 2]]
+ result.pred_keypoints = keypoint_xyp
+ elif kps_score is not None:
+ # keypoint heatmap to sparse data structure
+ pred_keypoint_logits = kps_score
+ keypoint_head.keypoint_rcnn_inference(pred_keypoint_logits, [result])
+
+ return results
+
+
+def _cast_to_f32(f64):
+ return struct.unpack("f", struct.pack("f", f64))[0]
+
+
+def set_caffe2_compatible_tensor_mode(model, enable=True):
+ def _fn(m):
+ if isinstance(m, Caffe2Compatible):
+ m.tensor_mode = enable
+
+ model.apply(_fn)
+
+
+def convert_batched_inputs_to_c2_format(batched_inputs, size_divisibility, device):
+ """
+ See get_caffe2_inputs() below.
+ """
+ assert all(isinstance(x, dict) for x in batched_inputs)
+ assert all(x["image"].dim() == 3 for x in batched_inputs)
+
+ images = [x["image"] for x in batched_inputs]
+ images = ImageList.from_tensors(images, size_divisibility)
+
+ im_info = []
+ for input_per_image, image_size in zip(batched_inputs, images.image_sizes):
+ target_height = input_per_image.get("height", image_size[0])
+ target_width = input_per_image.get("width", image_size[1]) # noqa
+ # NOTE: The scale inside im_info is kept as convention and for providing
+ # post-processing information if further processing is needed. For
+ # current Caffe2 model definitions that don't include post-processing inside
+ # the model, this number is not used.
+ # NOTE: There can be a slight difference between width and height
+ # scales, using a single number can results in numerical difference
+ # compared with D2's post-processing.
+ scale = target_height / image_size[0]
+ im_info.append([image_size[0], image_size[1], scale])
+ im_info = torch.Tensor(im_info)
+
+ return images.tensor.to(device), im_info.to(device)
+
+
+class Caffe2MetaArch(Caffe2Compatible, torch.nn.Module):
+ """
+ Base class for caffe2-compatible implementation of a meta architecture.
+ The forward is traceable and its traced graph can be converted to caffe2
+ graph through ONNX.
+ """
+
+ def __init__(self, cfg, torch_model, enable_tensor_mode=True):
+ """
+ Args:
+ cfg (CfgNode):
+ torch_model (nn.Module): the detectron2 model (meta_arch) to be
+ converted.
+ """
+ super().__init__()
+ self._wrapped_model = torch_model
+ self.eval()
+ set_caffe2_compatible_tensor_mode(self, enable_tensor_mode)
+
+ def get_caffe2_inputs(self, batched_inputs):
+ """
+ Convert pytorch-style structured inputs to caffe2-style inputs that
+ are tuples of tensors.
+
+ Args:
+ batched_inputs (list[dict]): inputs to a detectron2 model
+ in its standard format. Each dict has "image" (CHW tensor), and optionally
+ "height" and "width".
+
+ Returns:
+ tuple[Tensor]:
+ tuple of tensors that will be the inputs to the
+ :meth:`forward` method. For existing models, the first
+ is an NCHW tensor (padded and batched); the second is
+ a im_info Nx3 tensor, where the rows are
+ (height, width, unused legacy parameter)
+ """
+ return convert_batched_inputs_to_c2_format(
+ batched_inputs,
+ self._wrapped_model.backbone.size_divisibility,
+ self._wrapped_model.device,
+ )
+
+ def encode_additional_info(self, predict_net, init_net):
+ """
+ Save extra metadata that will be used by inference in the output protobuf.
+ """
+ pass
+
+ def forward(self, inputs):
+ """
+ Run the forward in caffe2-style. It has to use caffe2-compatible ops
+ and the method will be used for tracing.
+
+ Args:
+ inputs (tuple[Tensor]): inputs defined by :meth:`get_caffe2_input`.
+ They will be the inputs of the converted caffe2 graph.
+
+ Returns:
+ tuple[Tensor]: output tensors. They will be the outputs of the
+ converted caffe2 graph.
+ """
+ raise NotImplementedError
+
+ def _caffe2_preprocess_image(self, inputs):
+ """
+ Caffe2 implementation of preprocess_image, which is called inside each MetaArch's forward.
+ It normalizes the input images, and the final caffe2 graph assumes the
+ inputs have been batched already.
+ """
+ data, im_info = inputs
+ data = alias(data, "data")
+ im_info = alias(im_info, "im_info")
+ mean, std = self._wrapped_model.pixel_mean, self._wrapped_model.pixel_std
+ normalized_data = (data - mean) / std
+ normalized_data = alias(normalized_data, "normalized_data")
+
+ # Pack (data, im_info) into ImageList which is recognized by self.inference.
+ images = ImageList(tensor=normalized_data, image_sizes=im_info)
+ return images
+
+ @staticmethod
+ def get_outputs_converter(predict_net, init_net):
+ """
+ Creates a function that converts outputs of the caffe2 model to
+ detectron2's standard format.
+ The function uses information in `predict_net` and `init_net` that are
+ available at inferene time. Therefore the function logic can be used in inference.
+
+ The returned function has the following signature:
+
+ def convert(batched_inputs, c2_inputs, c2_results) -> detectron2_outputs
+
+ Where
+
+ * batched_inputs (list[dict]): the original input format of the meta arch
+ * c2_inputs (tuple[Tensor]): the caffe2 inputs.
+ * c2_results (dict[str, Tensor]): the caffe2 output format,
+ corresponding to the outputs of the :meth:`forward` function.
+ * detectron2_outputs: the original output format of the meta arch.
+
+ This function can be used to compare the outputs of the original meta arch and
+ the converted caffe2 graph.
+
+ Returns:
+ callable: a callable of the above signature.
+ """
+ raise NotImplementedError
+
+
+class Caffe2GeneralizedRCNN(Caffe2MetaArch):
+ def __init__(self, cfg, torch_model, enable_tensor_mode=True):
+ assert isinstance(torch_model, meta_arch.GeneralizedRCNN)
+ torch_model = patch_generalized_rcnn(torch_model)
+ super().__init__(cfg, torch_model, enable_tensor_mode)
+
+ try:
+ use_heatmap_max_keypoint = cfg.EXPORT_CAFFE2.USE_HEATMAP_MAX_KEYPOINT
+ except AttributeError:
+ use_heatmap_max_keypoint = False
+ self.roi_heads_patcher = ROIHeadsPatcher(
+ self._wrapped_model.roi_heads, use_heatmap_max_keypoint
+ )
+ if self.tensor_mode:
+ self.roi_heads_patcher.patch_roi_heads()
+
+ def encode_additional_info(self, predict_net, init_net):
+ size_divisibility = self._wrapped_model.backbone.size_divisibility
+ check_set_pb_arg(predict_net, "size_divisibility", "i", size_divisibility)
+ check_set_pb_arg(
+ predict_net, "device", "s", str.encode(str(self._wrapped_model.device), "ascii")
+ )
+ check_set_pb_arg(predict_net, "meta_architecture", "s", b"GeneralizedRCNN")
+
+ @mock_torch_nn_functional_interpolate()
+ def forward(self, inputs):
+ if not self.tensor_mode:
+ return self._wrapped_model.inference(inputs)
+ images = self._caffe2_preprocess_image(inputs)
+ features = self._wrapped_model.backbone(images.tensor)
+ proposals, _ = self._wrapped_model.proposal_generator(images, features)
+ detector_results, _ = self._wrapped_model.roi_heads(images, features, proposals)
+ return tuple(detector_results[0].flatten())
+
+ @staticmethod
+ def get_outputs_converter(predict_net, init_net):
+ def f(batched_inputs, c2_inputs, c2_results):
+ _, im_info = c2_inputs
+ image_sizes = [[int(im[0]), int(im[1])] for im in im_info]
+ results = assemble_rcnn_outputs_by_name(image_sizes, c2_results)
+ return meta_arch.GeneralizedRCNN._postprocess(results, batched_inputs, image_sizes)
+
+ return f
+
+
+class Caffe2RetinaNet(Caffe2MetaArch):
+ def __init__(self, cfg, torch_model):
+ assert isinstance(torch_model, meta_arch.RetinaNet)
+ super().__init__(cfg, torch_model)
+
+ @mock_torch_nn_functional_interpolate()
+ def forward(self, inputs):
+ assert self.tensor_mode
+ images = self._caffe2_preprocess_image(inputs)
+
+ # explicitly return the images sizes to avoid removing "im_info" by ONNX
+ # since it's not used in the forward path
+ return_tensors = [images.image_sizes]
+
+ features = self._wrapped_model.backbone(images.tensor)
+ features = [features[f] for f in self._wrapped_model.head_in_features]
+ for i, feature_i in enumerate(features):
+ features[i] = alias(feature_i, "feature_{}".format(i), is_backward=True)
+ return_tensors.append(features[i])
+
+ pred_logits, pred_anchor_deltas = self._wrapped_model.head(features)
+ for i, (box_cls_i, box_delta_i) in enumerate(zip(pred_logits, pred_anchor_deltas)):
+ return_tensors.append(alias(box_cls_i, "box_cls_{}".format(i)))
+ return_tensors.append(alias(box_delta_i, "box_delta_{}".format(i)))
+
+ return tuple(return_tensors)
+
+ def encode_additional_info(self, predict_net, init_net):
+ size_divisibility = self._wrapped_model.backbone.size_divisibility
+ check_set_pb_arg(predict_net, "size_divisibility", "i", size_divisibility)
+ check_set_pb_arg(
+ predict_net, "device", "s", str.encode(str(self._wrapped_model.device), "ascii")
+ )
+ check_set_pb_arg(predict_net, "meta_architecture", "s", b"RetinaNet")
+
+ # Inference parameters:
+ check_set_pb_arg(
+ predict_net, "score_threshold", "f", _cast_to_f32(self._wrapped_model.test_score_thresh)
+ )
+ check_set_pb_arg(
+ predict_net, "topk_candidates", "i", self._wrapped_model.test_topk_candidates
+ )
+ check_set_pb_arg(
+ predict_net, "nms_threshold", "f", _cast_to_f32(self._wrapped_model.test_nms_thresh)
+ )
+ check_set_pb_arg(
+ predict_net,
+ "max_detections_per_image",
+ "i",
+ self._wrapped_model.max_detections_per_image,
+ )
+
+ check_set_pb_arg(
+ predict_net,
+ "bbox_reg_weights",
+ "floats",
+ [_cast_to_f32(w) for w in self._wrapped_model.box2box_transform.weights],
+ )
+ self._encode_anchor_generator_cfg(predict_net)
+
+ def _encode_anchor_generator_cfg(self, predict_net):
+ # serialize anchor_generator for future use
+ serialized_anchor_generator = io.BytesIO()
+ torch.save(self._wrapped_model.anchor_generator, serialized_anchor_generator)
+ # Ideally we can put anchor generating inside the model, then we don't
+ # need to store this information.
+ bytes = serialized_anchor_generator.getvalue()
+ check_set_pb_arg(predict_net, "serialized_anchor_generator", "s", bytes)
+
+ @staticmethod
+ def get_outputs_converter(predict_net, init_net):
+ self = types.SimpleNamespace()
+ serialized_anchor_generator = io.BytesIO(
+ get_pb_arg_vals(predict_net, "serialized_anchor_generator", None)
+ )
+ self.anchor_generator = torch.load(serialized_anchor_generator)
+ bbox_reg_weights = get_pb_arg_floats(predict_net, "bbox_reg_weights", None)
+ self.box2box_transform = Box2BoxTransform(weights=tuple(bbox_reg_weights))
+ self.test_score_thresh = get_pb_arg_valf(predict_net, "score_threshold", None)
+ self.test_topk_candidates = get_pb_arg_vali(predict_net, "topk_candidates", None)
+ self.test_nms_thresh = get_pb_arg_valf(predict_net, "nms_threshold", None)
+ self.max_detections_per_image = get_pb_arg_vali(
+ predict_net, "max_detections_per_image", None
+ )
+
+ # hack to reuse inference code from RetinaNet
+ for meth in [
+ "forward_inference",
+ "inference_single_image",
+ "_transpose_dense_predictions",
+ "_decode_multi_level_predictions",
+ "_decode_per_level_predictions",
+ ]:
+ setattr(self, meth, functools.partial(getattr(meta_arch.RetinaNet, meth), self))
+
+ def f(batched_inputs, c2_inputs, c2_results):
+ _, im_info = c2_inputs
+ image_sizes = [[int(im[0]), int(im[1])] for im in im_info]
+ dummy_images = ImageList(
+ torch.randn(
+ (
+ len(im_info),
+ 3,
+ )
+ + tuple(image_sizes[0])
+ ),
+ image_sizes,
+ )
+
+ num_features = len([x for x in c2_results.keys() if x.startswith("box_cls_")])
+ pred_logits = [c2_results["box_cls_{}".format(i)] for i in range(num_features)]
+ pred_anchor_deltas = [c2_results["box_delta_{}".format(i)] for i in range(num_features)]
+
+ # For each feature level, feature should have the same batch size and
+ # spatial dimension as the box_cls and box_delta.
+ dummy_features = [x.clone()[:, 0:0, :, :] for x in pred_logits]
+ # self.num_classess can be inferred
+ self.num_classes = pred_logits[0].shape[1] // (pred_anchor_deltas[0].shape[1] // 4)
+
+ results = self.forward_inference(
+ dummy_images, dummy_features, [pred_logits, pred_anchor_deltas]
+ )
+ return meta_arch.GeneralizedRCNN._postprocess(results, batched_inputs, image_sizes)
+
+ return f
+
+
+META_ARCH_CAFFE2_EXPORT_TYPE_MAP = {
+ "GeneralizedRCNN": Caffe2GeneralizedRCNN,
+ "RetinaNet": Caffe2RetinaNet,
+}
diff --git a/detectron2/detectron2/export/caffe2_patch.py b/detectron2/detectron2/export/caffe2_patch.py
new file mode 100755
index 0000000..2da70ae
--- /dev/null
+++ b/detectron2/detectron2/export/caffe2_patch.py
@@ -0,0 +1,189 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import contextlib
+from unittest import mock
+import torch
+
+from detectron2.modeling import poolers
+from detectron2.modeling.proposal_generator import rpn
+from detectron2.modeling.roi_heads import keypoint_head, mask_head
+from detectron2.modeling.roi_heads.fast_rcnn import FastRCNNOutputLayers
+
+from .c10 import (
+ Caffe2Compatible,
+ Caffe2FastRCNNOutputsInference,
+ Caffe2KeypointRCNNInference,
+ Caffe2MaskRCNNInference,
+ Caffe2ROIPooler,
+ Caffe2RPN,
+ caffe2_fast_rcnn_outputs_inference,
+ caffe2_keypoint_rcnn_inference,
+ caffe2_mask_rcnn_inference,
+)
+
+
+class GenericMixin(object):
+ pass
+
+
+class Caffe2CompatibleConverter(object):
+ """
+ A GenericUpdater which implements the `create_from` interface, by modifying
+ module object and assign it with another class replaceCls.
+ """
+
+ def __init__(self, replaceCls):
+ self.replaceCls = replaceCls
+
+ def create_from(self, module):
+ # update module's class to the new class
+ assert isinstance(module, torch.nn.Module)
+ if issubclass(self.replaceCls, GenericMixin):
+ # replaceCls should act as mixin, create a new class on-the-fly
+ new_class = type(
+ "{}MixedWith{}".format(self.replaceCls.__name__, module.__class__.__name__),
+ (self.replaceCls, module.__class__),
+ {}, # {"new_method": lambda self: ...},
+ )
+ module.__class__ = new_class
+ else:
+ # replaceCls is complete class, this allow arbitrary class swap
+ module.__class__ = self.replaceCls
+
+ # initialize Caffe2Compatible
+ if isinstance(module, Caffe2Compatible):
+ module.tensor_mode = False
+
+ return module
+
+
+def patch(model, target, updater, *args, **kwargs):
+ """
+ recursively (post-order) update all modules with the target type and its
+ subclasses, make a initialization/composition/inheritance/... via the
+ updater.create_from.
+ """
+ for name, module in model.named_children():
+ model._modules[name] = patch(module, target, updater, *args, **kwargs)
+ if isinstance(model, target):
+ return updater.create_from(model, *args, **kwargs)
+ return model
+
+
+def patch_generalized_rcnn(model):
+ ccc = Caffe2CompatibleConverter
+ model = patch(model, rpn.RPN, ccc(Caffe2RPN))
+ model = patch(model, poolers.ROIPooler, ccc(Caffe2ROIPooler))
+
+ return model
+
+
+@contextlib.contextmanager
+def mock_fastrcnn_outputs_inference(
+ tensor_mode, check=True, box_predictor_type=FastRCNNOutputLayers
+):
+ with mock.patch.object(
+ box_predictor_type,
+ "inference",
+ autospec=True,
+ side_effect=Caffe2FastRCNNOutputsInference(tensor_mode),
+ ) as mocked_func:
+ yield
+ if check:
+ assert mocked_func.call_count > 0
+
+
+@contextlib.contextmanager
+def mock_mask_rcnn_inference(tensor_mode, patched_module, check=True):
+ with mock.patch(
+ "{}.mask_rcnn_inference".format(patched_module), side_effect=Caffe2MaskRCNNInference()
+ ) as mocked_func:
+ yield
+ if check:
+ assert mocked_func.call_count > 0
+
+
+@contextlib.contextmanager
+def mock_keypoint_rcnn_inference(tensor_mode, patched_module, use_heatmap_max_keypoint, check=True):
+ with mock.patch(
+ "{}.keypoint_rcnn_inference".format(patched_module),
+ side_effect=Caffe2KeypointRCNNInference(use_heatmap_max_keypoint),
+ ) as mocked_func:
+ yield
+ if check:
+ assert mocked_func.call_count > 0
+
+
+class ROIHeadsPatcher:
+ def __init__(self, heads, use_heatmap_max_keypoint):
+ self.heads = heads
+ self.use_heatmap_max_keypoint = use_heatmap_max_keypoint
+ self.previous_patched = {}
+
+ @contextlib.contextmanager
+ def mock_roi_heads(self, tensor_mode=True):
+ """
+ Patching several inference functions inside ROIHeads and its subclasses
+
+ Args:
+ tensor_mode (bool): whether the inputs/outputs are caffe2's tensor
+ format or not. Default to True.
+ """
+ # NOTE: this requries the `keypoint_rcnn_inference` and `mask_rcnn_inference`
+ # are called inside the same file as BaseXxxHead due to using mock.patch.
+ kpt_heads_mod = keypoint_head.BaseKeypointRCNNHead.__module__
+ mask_head_mod = mask_head.BaseMaskRCNNHead.__module__
+
+ mock_ctx_managers = [
+ mock_fastrcnn_outputs_inference(
+ tensor_mode=tensor_mode,
+ check=True,
+ box_predictor_type=type(self.heads.box_predictor),
+ )
+ ]
+ if getattr(self.heads, "keypoint_on", False):
+ mock_ctx_managers += [
+ mock_keypoint_rcnn_inference(
+ tensor_mode, kpt_heads_mod, self.use_heatmap_max_keypoint
+ )
+ ]
+ if getattr(self.heads, "mask_on", False):
+ mock_ctx_managers += [mock_mask_rcnn_inference(tensor_mode, mask_head_mod)]
+
+ with contextlib.ExitStack() as stack: # python 3.3+
+ for mgr in mock_ctx_managers:
+ stack.enter_context(mgr)
+ yield
+
+ def patch_roi_heads(self, tensor_mode=True):
+ self.previous_patched["box_predictor"] = self.heads.box_predictor.inference
+ self.previous_patched["keypoint_rcnn"] = keypoint_head.keypoint_rcnn_inference
+ self.previous_patched["mask_rcnn"] = mask_head.mask_rcnn_inference
+
+ def patched_fastrcnn_outputs_inference(predictions, proposal):
+ return caffe2_fast_rcnn_outputs_inference(
+ True, self.heads.box_predictor, predictions, proposal
+ )
+
+ self.heads.box_predictor.inference = patched_fastrcnn_outputs_inference
+
+ if getattr(self.heads, "keypoint_on", False):
+
+ def patched_keypoint_rcnn_inference(pred_keypoint_logits, pred_instances):
+ return caffe2_keypoint_rcnn_inference(
+ self.use_heatmap_max_keypoint, pred_keypoint_logits, pred_instances
+ )
+
+ keypoint_head.keypoint_rcnn_inference = patched_keypoint_rcnn_inference
+
+ if getattr(self.heads, "mask_on", False):
+
+ def patched_mask_rcnn_inference(pred_mask_logits, pred_instances):
+ return caffe2_mask_rcnn_inference(pred_mask_logits, pred_instances)
+
+ mask_head.mask_rcnn_inference = patched_mask_rcnn_inference
+
+ def unpatch_roi_heads(self):
+ self.heads.box_predictor.inference = self.previous_patched["box_predictor"]
+ keypoint_head.keypoint_rcnn_inference = self.previous_patched["keypoint_rcnn"]
+ mask_head.mask_rcnn_inference = self.previous_patched["mask_rcnn"]
diff --git a/detectron2/detectron2/export/flatten.py b/detectron2/detectron2/export/flatten.py
new file mode 100755
index 0000000..f5ba429
--- /dev/null
+++ b/detectron2/detectron2/export/flatten.py
@@ -0,0 +1,330 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import collections
+from dataclasses import dataclass
+from typing import Callable, List, Optional, Tuple
+import torch
+from torch import nn
+
+from detectron2.structures import Boxes, Instances, ROIMasks
+from detectron2.utils.registry import _convert_target_to_string, locate
+
+from .torchscript_patch import patch_builtin_len
+
+
+@dataclass
+class Schema:
+ """
+ A Schema defines how to flatten a possibly hierarchical object into tuple of
+ primitive objects, so it can be used as inputs/outputs of PyTorch's tracing.
+
+ PyTorch does not support tracing a function that produces rich output
+ structures (e.g. dict, Instances, Boxes). To trace such a function, we
+ flatten the rich object into tuple of tensors, and return this tuple of tensors
+ instead. Meanwhile, we also need to know how to "rebuild" the original object
+ from the flattened results, so we can evaluate the flattened results.
+ A Schema defines how to flatten an object, and while flattening it, it records
+ necessary schemas so that the object can be rebuilt using the flattened outputs.
+
+ The flattened object and the schema object is returned by ``.flatten`` classmethod.
+ Then the original object can be rebuilt with the ``__call__`` method of schema.
+
+ A Schema is a dataclass that can be serialized easily.
+ """
+
+ # inspired by FetchMapper in tensorflow/python/client/session.py
+
+ @classmethod
+ def flatten(cls, obj):
+ raise NotImplementedError
+
+ def __call__(self, values):
+ raise NotImplementedError
+
+ @staticmethod
+ def _concat(values):
+ ret = ()
+ sizes = []
+ for v in values:
+ assert isinstance(v, tuple), "Flattened results must be a tuple"
+ ret = ret + v
+ sizes.append(len(v))
+ return ret, sizes
+
+ @staticmethod
+ def _split(values, sizes):
+ if len(sizes):
+ expected_len = sum(sizes)
+ assert (
+ len(values) == expected_len
+ ), f"Values has length {len(values)} but expect length {expected_len}."
+ ret = []
+ for k in range(len(sizes)):
+ begin, end = sum(sizes[:k]), sum(sizes[: k + 1])
+ ret.append(values[begin:end])
+ return ret
+
+
+@dataclass
+class ListSchema(Schema):
+ schemas: List[Schema] # the schemas that define how to flatten each element in the list
+ sizes: List[int] # the flattened length of each element
+
+ def __call__(self, values):
+ values = self._split(values, self.sizes)
+ if len(values) != len(self.schemas):
+ raise ValueError(
+ f"Values has length {len(values)} but schemas " f"has length {len(self.schemas)}!"
+ )
+ values = [m(v) for m, v in zip(self.schemas, values)]
+ return list(values)
+
+ @classmethod
+ def flatten(cls, obj):
+ res = [flatten_to_tuple(k) for k in obj]
+ values, sizes = cls._concat([k[0] for k in res])
+ return values, cls([k[1] for k in res], sizes)
+
+
+@dataclass
+class TupleSchema(ListSchema):
+ def __call__(self, values):
+ return tuple(super().__call__(values))
+
+
+@dataclass
+class IdentitySchema(Schema):
+ def __call__(self, values):
+ return values[0]
+
+ @classmethod
+ def flatten(cls, obj):
+ return (obj,), cls()
+
+
+@dataclass
+class DictSchema(ListSchema):
+ keys: List[str]
+
+ def __call__(self, values):
+ values = super().__call__(values)
+ return dict(zip(self.keys, values))
+
+ @classmethod
+ def flatten(cls, obj):
+ for k in obj.keys():
+ if not isinstance(k, str):
+ raise KeyError("Only support flattening dictionaries if keys are str.")
+ keys = sorted(obj.keys())
+ values = [obj[k] for k in keys]
+ ret, schema = ListSchema.flatten(values)
+ return ret, cls(schema.schemas, schema.sizes, keys)
+
+
+@dataclass
+class InstancesSchema(DictSchema):
+ def __call__(self, values):
+ image_size, fields = values[-1], values[:-1]
+ fields = super().__call__(fields)
+ return Instances(image_size, **fields)
+
+ @classmethod
+ def flatten(cls, obj):
+ ret, schema = super().flatten(obj.get_fields())
+ size = obj.image_size
+ if not isinstance(size, torch.Tensor):
+ size = torch.tensor(size)
+ return ret + (size,), schema
+
+
+@dataclass
+class TensorWrapSchema(Schema):
+ """
+ For classes that are simple wrapper of tensors, e.g.
+ Boxes, RotatedBoxes, BitMasks
+ """
+
+ class_name: str
+
+ def __call__(self, values):
+ return locate(self.class_name)(values[0])
+
+ @classmethod
+ def flatten(cls, obj):
+ return (obj.tensor,), cls(_convert_target_to_string(type(obj)))
+
+
+# if more custom structures needed in the future, can allow
+# passing in extra schemas for custom types
+def flatten_to_tuple(obj):
+ """
+ Flatten an object so it can be used for PyTorch tracing.
+ Also returns how to rebuild the original object from the flattened outputs.
+
+ Returns:
+ res (tuple): the flattened results that can be used as tracing outputs
+ schema: an object with a ``__call__`` method such that ``schema(res) == obj``.
+ It is a pure dataclass that can be serialized.
+ """
+ schemas = [
+ ((str, bytes), IdentitySchema),
+ (list, ListSchema),
+ (tuple, TupleSchema),
+ (collections.abc.Mapping, DictSchema),
+ (Instances, InstancesSchema),
+ ((Boxes, ROIMasks), TensorWrapSchema),
+ ]
+ for klass, schema in schemas:
+ if isinstance(obj, klass):
+ F = schema
+ break
+ else:
+ F = IdentitySchema
+
+ return F.flatten(obj)
+
+
+class TracingAdapter(nn.Module):
+ """
+ A model may take rich input/output format (e.g. dict or custom classes),
+ but `torch.jit.trace` requires tuple of tensors as input/output.
+ This adapter flattens input/output format of a model so it becomes traceable.
+
+ It also records the necessary schema to rebuild model's inputs/outputs from flattened
+ inputs/outputs.
+
+ Example:
+ ::
+ outputs = model(inputs) # inputs/outputs may be rich structure
+ adapter = TracingAdapter(model, inputs)
+
+ # can now trace the model, with adapter.flattened_inputs, or another
+ # tuple of tensors with the same length and meaning
+ traced = torch.jit.trace(adapter, adapter.flattened_inputs)
+
+ # traced model can only produce flattened outputs (tuple of tensors)
+ flattened_outputs = traced(*adapter.flattened_inputs)
+ # adapter knows the schema to convert it back (new_outputs == outputs)
+ new_outputs = adapter.outputs_schema(flattened_outputs)
+ """
+
+ flattened_inputs: Tuple[torch.Tensor] = None
+ """
+ Flattened version of inputs given to this class's constructor.
+ """
+
+ inputs_schema: Schema = None
+ """
+ Schema of the inputs given to this class's constructor.
+ """
+
+ outputs_schema: Schema = None
+ """
+ Schema of the output produced by calling the given model with inputs.
+ """
+
+ def __init__(
+ self,
+ model: nn.Module,
+ inputs,
+ inference_func: Optional[Callable] = None,
+ allow_non_tensor: bool = False,
+ ):
+ """
+ Args:
+ model: an nn.Module
+ inputs: An input argument or a tuple of input arguments used to call model.
+ After flattening, it has to only consist of tensors.
+ inference_func: a callable that takes (model, *inputs), calls the
+ model with inputs, and return outputs. By default it
+ is ``lambda model, *inputs: model(*inputs)``. Can be override
+ if you need to call the model differently.
+ allow_non_tensor: allow inputs/outputs to contain non-tensor objects.
+ This option will filter out non-tensor objects to make the
+ model traceable, but ``inputs_schema``/``outputs_schema`` cannot be
+ used anymore because inputs/outputs cannot be rebuilt from pure tensors.
+ This is useful when you're only interested in the single trace of
+ execution (e.g. for flop count), but not interested in
+ generalizing the traced graph to new inputs.
+ """
+ super().__init__()
+ if isinstance(model, (nn.parallel.distributed.DistributedDataParallel, nn.DataParallel)):
+ model = model.module
+ self.model = model
+ if not isinstance(inputs, tuple):
+ inputs = (inputs,)
+ self.inputs = inputs
+ self.allow_non_tensor = allow_non_tensor
+
+ if inference_func is None:
+ inference_func = lambda model, *inputs: model(*inputs) # noqa
+ self.inference_func = inference_func
+
+ self.flattened_inputs, self.inputs_schema = flatten_to_tuple(inputs)
+
+ if all(isinstance(x, torch.Tensor) for x in self.flattened_inputs):
+ return
+ if self.allow_non_tensor:
+ self.flattened_inputs = tuple(
+ [x for x in self.flattened_inputs if isinstance(x, torch.Tensor)]
+ )
+ self.inputs_schema = None
+ else:
+ for input in self.flattened_inputs:
+ if not isinstance(input, torch.Tensor):
+ raise ValueError(
+ "Inputs for tracing must only contain tensors. "
+ f"Got a {type(input)} instead."
+ )
+
+ def forward(self, *args: torch.Tensor):
+ with torch.no_grad(), patch_builtin_len():
+ if self.inputs_schema is not None:
+ inputs_orig_format = self.inputs_schema(args)
+ else:
+ if len(args) != len(self.flattened_inputs) or any(
+ x is not y for x, y in zip(args, self.flattened_inputs)
+ ):
+ raise ValueError(
+ "TracingAdapter does not contain valid inputs_schema."
+ " So it cannot generalize to other inputs and must be"
+ " traced with `.flattened_inputs`."
+ )
+ inputs_orig_format = self.inputs
+
+ outputs = self.inference_func(self.model, *inputs_orig_format)
+ flattened_outputs, schema = flatten_to_tuple(outputs)
+
+ flattened_output_tensors = tuple(
+ [x for x in flattened_outputs if isinstance(x, torch.Tensor)]
+ )
+ if len(flattened_output_tensors) < len(flattened_outputs):
+ if self.allow_non_tensor:
+ flattened_outputs = flattened_output_tensors
+ self.outputs_schema = None
+ else:
+ raise ValueError(
+ "Model cannot be traced because some model outputs "
+ "cannot flatten to tensors."
+ )
+ else: # schema is valid
+ if self.outputs_schema is None:
+ self.outputs_schema = schema
+ else:
+ assert self.outputs_schema == schema, (
+ "Model should always return outputs with the same "
+ "structure so it can be traced!"
+ )
+ return flattened_outputs
+
+ def _create_wrapper(self, traced_model):
+ """
+ Return a function that has an input/output interface the same as the
+ original model, but it calls the given traced model under the hood.
+ """
+
+ def forward(*args):
+ flattened_inputs, _ = flatten_to_tuple(args)
+ flattened_outputs = traced_model(*flattened_inputs)
+ return self.outputs_schema(flattened_outputs)
+
+ return forward
diff --git a/detectron2/detectron2/export/shared.py b/detectron2/detectron2/export/shared.py
new file mode 100755
index 0000000..53ba933
--- /dev/null
+++ b/detectron2/detectron2/export/shared.py
@@ -0,0 +1,1039 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import collections
+import copy
+import functools
+import logging
+import numpy as np
+import os
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+from unittest import mock
+import caffe2.python.utils as putils
+import torch
+import torch.nn.functional as F
+from caffe2.proto import caffe2_pb2
+from caffe2.python import core, net_drawer, workspace
+from torch.nn.functional import interpolate as interp
+
+logger = logging.getLogger(__name__)
+
+
+# ==== torch/utils_toffee/cast.py =======================================
+
+
+def to_device(t, device_str):
+ """
+ This function is a replacement of .to(another_device) such that it allows the
+ casting to be traced properly by explicitly calling the underlying copy ops.
+ It also avoids introducing unncessary op when casting to the same device.
+ """
+ src = t.device
+ dst = torch.device(device_str)
+
+ if src == dst:
+ return t
+ elif src.type == "cuda" and dst.type == "cpu":
+ return torch.ops._caffe2.CopyGPUToCPU(t)
+ elif src.type == "cpu" and dst.type == "cuda":
+ return torch.ops._caffe2.CopyCPUToGPU(t)
+ else:
+ raise RuntimeError("Can't cast tensor from device {} to device {}".format(src, dst))
+
+
+# ==== torch/utils_toffee/interpolate.py =======================================
+
+
+# Note: borrowed from vision/detection/fair/detectron/detectron/modeling/detector.py
+def BilinearInterpolation(tensor_in, up_scale):
+ assert up_scale % 2 == 0, "Scale should be even"
+
+ def upsample_filt(size):
+ factor = (size + 1) // 2
+ if size % 2 == 1:
+ center = factor - 1
+ else:
+ center = factor - 0.5
+
+ og = np.ogrid[:size, :size]
+ return (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor)
+
+ kernel_size = int(up_scale) * 2
+ bil_filt = upsample_filt(kernel_size)
+
+ dim = int(tensor_in.shape[1])
+ kernel = np.zeros((dim, dim, kernel_size, kernel_size), dtype=np.float32)
+ kernel[range(dim), range(dim), :, :] = bil_filt
+
+ tensor_out = F.conv_transpose2d(
+ tensor_in,
+ weight=to_device(torch.Tensor(kernel), tensor_in.device),
+ bias=None,
+ stride=int(up_scale),
+ padding=int(up_scale / 2),
+ )
+
+ return tensor_out
+
+
+# NOTE: ONNX is incompatible with traced torch.nn.functional.interpolate if
+# using dynamic `scale_factor` rather than static `size`. (T43166860)
+# NOTE: Caffe2 Int8 conversion might not be able to quantize `size` properly.
+def onnx_compatibale_interpolate(
+ input, size=None, scale_factor=None, mode="nearest", align_corners=None
+):
+ # NOTE: The input dimensions are interpreted in the form:
+ # `mini-batch x channels x [optional depth] x [optional height] x width`.
+ if size is None and scale_factor is not None:
+ if input.dim() == 4:
+ if isinstance(scale_factor, (int, float)):
+ height_scale, width_scale = (scale_factor, scale_factor)
+ else:
+ assert isinstance(scale_factor, (tuple, list))
+ assert len(scale_factor) == 2
+ height_scale, width_scale = scale_factor
+
+ assert not align_corners, "No matching C2 op for align_corners == True"
+ if mode == "nearest":
+ return torch.ops._caffe2.ResizeNearest(
+ input, order="NCHW", width_scale=width_scale, height_scale=height_scale
+ )
+ elif mode == "bilinear":
+ logger.warning(
+ "Use F.conv_transpose2d for bilinear interpolate"
+ " because there's no such C2 op, this may cause significant"
+ " slowdown and the boundary pixels won't be as same as"
+ " using F.interpolate due to padding."
+ )
+ assert height_scale == width_scale
+ return BilinearInterpolation(input, up_scale=height_scale)
+ logger.warning("Output size is not static, it might cause ONNX conversion issue")
+
+ return interp(input, size, scale_factor, mode, align_corners)
+
+
+def mock_torch_nn_functional_interpolate():
+ def decorator(func):
+ @functools.wraps(func)
+ def _mock_torch_nn_functional_interpolate(*args, **kwargs):
+ if torch.onnx.is_in_onnx_export():
+ with mock.patch(
+ "torch.nn.functional.interpolate", side_effect=onnx_compatibale_interpolate
+ ):
+ return func(*args, **kwargs)
+ else:
+ return func(*args, **kwargs)
+
+ return _mock_torch_nn_functional_interpolate
+
+ return decorator
+
+
+# ==== torch/utils_caffe2/ws_utils.py ==========================================
+
+
+class ScopedWS(object):
+ def __init__(self, ws_name, is_reset, is_cleanup=False):
+ self.ws_name = ws_name
+ self.is_reset = is_reset
+ self.is_cleanup = is_cleanup
+ self.org_ws = ""
+
+ def __enter__(self):
+ self.org_ws = workspace.CurrentWorkspace()
+ if self.ws_name is not None:
+ workspace.SwitchWorkspace(self.ws_name, True)
+ if self.is_reset:
+ workspace.ResetWorkspace()
+
+ return workspace
+
+ def __exit__(self, *args):
+ if self.is_cleanup:
+ workspace.ResetWorkspace()
+ if self.ws_name is not None:
+ workspace.SwitchWorkspace(self.org_ws)
+
+
+def fetch_any_blob(name):
+ bb = None
+ try:
+ bb = workspace.FetchBlob(name)
+ except TypeError:
+ bb = workspace.FetchInt8Blob(name)
+ except Exception as e:
+ logger.error("Get blob {} error: {}".format(name, e))
+
+ return bb
+
+
+# ==== torch/utils_caffe2/protobuf.py ==========================================
+
+
+def get_pb_arg(pb, arg_name):
+ for x in pb.arg:
+ if x.name == arg_name:
+ return x
+ return None
+
+
+def get_pb_arg_valf(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return arg.f if arg is not None else default_val
+
+
+def get_pb_arg_floats(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return list(map(float, arg.floats)) if arg is not None else default_val
+
+
+def get_pb_arg_ints(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return list(map(int, arg.ints)) if arg is not None else default_val
+
+
+def get_pb_arg_vali(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return arg.i if arg is not None else default_val
+
+
+def get_pb_arg_vals(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return arg.s if arg is not None else default_val
+
+
+def get_pb_arg_valstrings(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return list(arg.strings) if arg is not None else default_val
+
+
+def check_set_pb_arg(pb, arg_name, arg_attr, arg_value, allow_override=False):
+ arg = get_pb_arg(pb, arg_name)
+ if arg is None:
+ arg = putils.MakeArgument(arg_name, arg_value)
+ assert hasattr(arg, arg_attr)
+ pb.arg.extend([arg])
+ if allow_override and getattr(arg, arg_attr) != arg_value:
+ logger.warning(
+ "Override argument {}: {} -> {}".format(arg_name, getattr(arg, arg_attr), arg_value)
+ )
+ setattr(arg, arg_attr, arg_value)
+ else:
+ assert arg is not None
+ assert getattr(arg, arg_attr) == arg_value, "Existing value {}, new value {}".format(
+ getattr(arg, arg_attr), arg_value
+ )
+
+
+def _create_const_fill_op_from_numpy(name, tensor, device_option=None):
+ assert type(tensor) == np.ndarray
+ kTypeNameMapper = {
+ np.dtype("float32"): "GivenTensorFill",
+ np.dtype("int32"): "GivenTensorIntFill",
+ np.dtype("int64"): "GivenTensorInt64Fill",
+ np.dtype("uint8"): "GivenTensorStringFill",
+ }
+
+ args_dict = {}
+ if tensor.dtype == np.dtype("uint8"):
+ args_dict.update({"values": [str(tensor.data)], "shape": [1]})
+ else:
+ args_dict.update({"values": tensor, "shape": tensor.shape})
+
+ if device_option is not None:
+ args_dict["device_option"] = device_option
+
+ return core.CreateOperator(kTypeNameMapper[tensor.dtype], [], [name], **args_dict)
+
+
+def _create_const_fill_op_from_c2_int8_tensor(name, int8_tensor):
+ assert type(int8_tensor) == workspace.Int8Tensor
+ kTypeNameMapper = {
+ np.dtype("int32"): "Int8GivenIntTensorFill",
+ np.dtype("uint8"): "Int8GivenTensorFill",
+ }
+
+ tensor = int8_tensor.data
+ assert tensor.dtype in [np.dtype("uint8"), np.dtype("int32")]
+ values = tensor.tobytes() if tensor.dtype == np.dtype("uint8") else tensor
+
+ return core.CreateOperator(
+ kTypeNameMapper[tensor.dtype],
+ [],
+ [name],
+ values=values,
+ shape=tensor.shape,
+ Y_scale=int8_tensor.scale,
+ Y_zero_point=int8_tensor.zero_point,
+ )
+
+
+def create_const_fill_op(
+ name: str,
+ blob: Union[np.ndarray, workspace.Int8Tensor],
+ device_option: Optional[caffe2_pb2.DeviceOption] = None,
+) -> caffe2_pb2.OperatorDef:
+ """
+ Given a blob object, return the Caffe2 operator that creates this blob
+ as constant. Currently support NumPy tensor and Caffe2 Int8Tensor.
+ """
+
+ tensor_type = type(blob)
+ assert tensor_type in [
+ np.ndarray,
+ workspace.Int8Tensor,
+ ], 'Error when creating const fill op for "{}", unsupported blob type: {}'.format(
+ name, type(blob)
+ )
+
+ if tensor_type == np.ndarray:
+ return _create_const_fill_op_from_numpy(name, blob, device_option)
+ elif tensor_type == workspace.Int8Tensor:
+ assert device_option is None
+ return _create_const_fill_op_from_c2_int8_tensor(name, blob)
+
+
+def construct_init_net_from_params(
+ params: Dict[str, Any], device_options: Optional[Dict[str, caffe2_pb2.DeviceOption]] = None
+) -> caffe2_pb2.NetDef:
+ """
+ Construct the init_net from params dictionary
+ """
+ init_net = caffe2_pb2.NetDef()
+ device_options = device_options or {}
+ for name, blob in params.items():
+ if isinstance(blob, str):
+ logger.warning(
+ (
+ "Blob {} with type {} is not supported in generating init net,"
+ " skipped.".format(name, type(blob))
+ )
+ )
+ continue
+ init_net.op.extend(
+ [create_const_fill_op(name, blob, device_option=device_options.get(name, None))]
+ )
+ init_net.external_output.append(name)
+ return init_net
+
+
+def get_producer_map(ssa):
+ """
+ Return dict from versioned blob to (i, j),
+ where i is index of producer op, j is the index of output of that op.
+ """
+ producer_map = {}
+ for i in range(len(ssa)):
+ outputs = ssa[i][1]
+ for j, outp in enumerate(outputs):
+ producer_map[outp] = (i, j)
+ return producer_map
+
+
+def get_consumer_map(ssa):
+ """
+ Return dict from versioned blob to list of (i, j),
+ where i is index of consumer op, j is the index of input of that op.
+ """
+ consumer_map = collections.defaultdict(list)
+ for i in range(len(ssa)):
+ inputs = ssa[i][0]
+ for j, inp in enumerate(inputs):
+ consumer_map[inp].append((i, j))
+ return consumer_map
+
+
+def get_params_from_init_net(
+ init_net: caffe2_pb2.NetDef,
+) -> [Dict[str, Any], Dict[str, caffe2_pb2.DeviceOption]]:
+ """
+ Take the output blobs from init_net by running it.
+ Outputs:
+ params: dict from blob name to numpy array
+ device_options: dict from blob name to the device option of its creating op
+ """
+ # NOTE: this assumes that the params is determined by producer op with the
+ # only exception be CopyGPUToCPU which is CUDA op but returns CPU tensor.
+ def _get_device_option(producer_op):
+ if producer_op.type == "CopyGPUToCPU":
+ return caffe2_pb2.DeviceOption()
+ else:
+ return producer_op.device_option
+
+ with ScopedWS("__get_params_from_init_net__", is_reset=True, is_cleanup=True) as ws:
+ ws.RunNetOnce(init_net)
+ params = {b: fetch_any_blob(b) for b in init_net.external_output}
+ ssa, versions = core.get_ssa(init_net)
+ producer_map = get_producer_map(ssa)
+ device_options = {
+ b: _get_device_option(init_net.op[producer_map[(b, versions[b])][0]])
+ for b in init_net.external_output
+ }
+ return params, device_options
+
+
+def _updater_raise(op, input_types, output_types):
+ raise RuntimeError(
+ "Failed to apply updater for op {} given input_types {} and"
+ " output_types {}".format(op, input_types, output_types)
+ )
+
+
+def _generic_status_identifier(
+ predict_net: caffe2_pb2.NetDef,
+ status_updater: Callable,
+ known_status: Dict[Tuple[str, int], Any],
+) -> Dict[Tuple[str, int], Any]:
+ """
+ Statically infer the status of each blob, the status can be such as device type
+ (CPU/GPU), layout (NCHW/NHWC), data type (float32/int8), etc. "Blob" here
+ is versioned blob (Tuple[str, int]) in the format compatible with ssa.
+ Inputs:
+ predict_net: the caffe2 network
+ status_updater: a callable, given an op and the status of its input/output,
+ it returns the updated status of input/output. `None` is used for
+ representing unknown status.
+ known_status: a dict containing known status, used as initialization.
+ Outputs:
+ A dict mapping from versioned blob to its status
+ """
+ ssa, versions = core.get_ssa(predict_net)
+ versioned_ext_input = [(b, 0) for b in predict_net.external_input]
+ versioned_ext_output = [(b, versions[b]) for b in predict_net.external_output]
+ all_versioned_blobs = set().union(*[set(x[0] + x[1]) for x in ssa])
+
+ allowed_vbs = all_versioned_blobs.union(versioned_ext_input).union(versioned_ext_output)
+ assert all(k in allowed_vbs for k in known_status)
+ assert all(v is not None for v in known_status.values())
+ _known_status = copy.deepcopy(known_status)
+
+ def _check_and_update(key, value):
+ assert value is not None
+ if key in _known_status:
+ if not _known_status[key] == value:
+ raise RuntimeError(
+ "Confilict status for {}, existing status {}, new status {}".format(
+ key, _known_status[key], value
+ )
+ )
+ _known_status[key] = value
+
+ def _update_i(op, ssa_i):
+ versioned_inputs = ssa_i[0]
+ versioned_outputs = ssa_i[1]
+
+ inputs_status = [_known_status.get(b, None) for b in versioned_inputs]
+ outputs_status = [_known_status.get(b, None) for b in versioned_outputs]
+
+ new_inputs_status, new_outputs_status = status_updater(op, inputs_status, outputs_status)
+
+ for versioned_blob, status in zip(
+ versioned_inputs + versioned_outputs, new_inputs_status + new_outputs_status
+ ):
+ if status is not None:
+ _check_and_update(versioned_blob, status)
+
+ for op, ssa_i in zip(predict_net.op, ssa):
+ _update_i(op, ssa_i)
+ for op, ssa_i in zip(reversed(predict_net.op), reversed(ssa)):
+ _update_i(op, ssa_i)
+
+ # NOTE: This strictly checks all the blob from predict_net must be assgined
+ # a known status. However sometimes it's impossible (eg. having deadend op),
+ # we may relax this constraint if
+ for k in all_versioned_blobs:
+ if k not in _known_status:
+ raise NotImplementedError(
+ "Can not infer the status for {}. Currently only support the case where"
+ " a single forward and backward pass can identify status for all blobs.".format(k)
+ )
+
+ return _known_status
+
+
+def infer_device_type(
+ predict_net: caffe2_pb2.NetDef,
+ known_status: Dict[Tuple[str, int], Any],
+ device_name_style: str = "caffe2",
+) -> Dict[Tuple[str, int], str]:
+ """Return the device type ("cpu" or "gpu"/"cuda") of each (versioned) blob"""
+
+ assert device_name_style in ["caffe2", "pytorch"]
+ _CPU_STR = "cpu"
+ _GPU_STR = "gpu" if device_name_style == "caffe2" else "cuda"
+
+ def _copy_cpu_to_gpu_updater(op, input_types, output_types):
+ if input_types[0] == _GPU_STR or output_types[0] == _CPU_STR:
+ _updater_raise(op, input_types, output_types)
+ return ([_CPU_STR], [_GPU_STR])
+
+ def _copy_gpu_to_cpu_updater(op, input_types, output_types):
+ if input_types[0] == _CPU_STR or output_types[0] == _GPU_STR:
+ _updater_raise(op, input_types, output_types)
+ return ([_GPU_STR], [_CPU_STR])
+
+ def _other_ops_updater(op, input_types, output_types):
+ non_none_types = [x for x in input_types + output_types if x is not None]
+ if len(non_none_types) > 0:
+ the_type = non_none_types[0]
+ if not all(x == the_type for x in non_none_types):
+ _updater_raise(op, input_types, output_types)
+ else:
+ the_type = None
+ return ([the_type for _ in op.input], [the_type for _ in op.output])
+
+ def _device_updater(op, *args, **kwargs):
+ return {
+ "CopyCPUToGPU": _copy_cpu_to_gpu_updater,
+ "CopyGPUToCPU": _copy_gpu_to_cpu_updater,
+ }.get(op.type, _other_ops_updater)(op, *args, **kwargs)
+
+ return _generic_status_identifier(predict_net, _device_updater, known_status)
+
+
+# ==== torch/utils_caffe2/vis.py ===============================================
+
+
+def _modify_blob_names(ops, blob_rename_f):
+ ret = []
+
+ def _replace_list(blob_list, replaced_list):
+ del blob_list[:]
+ blob_list.extend(replaced_list)
+
+ for x in ops:
+ cur = copy.deepcopy(x)
+ _replace_list(cur.input, list(map(blob_rename_f, cur.input)))
+ _replace_list(cur.output, list(map(blob_rename_f, cur.output)))
+ ret.append(cur)
+
+ return ret
+
+
+def _rename_blob(name, blob_sizes, blob_ranges):
+ def _list_to_str(bsize):
+ ret = ", ".join([str(x) for x in bsize])
+ ret = "[" + ret + "]"
+ return ret
+
+ ret = name
+ if blob_sizes is not None and name in blob_sizes:
+ ret += "\n" + _list_to_str(blob_sizes[name])
+ if blob_ranges is not None and name in blob_ranges:
+ ret += "\n" + _list_to_str(blob_ranges[name])
+
+ return ret
+
+
+# graph_name could not contain word 'graph'
+def save_graph(net, file_name, graph_name="net", op_only=True, blob_sizes=None, blob_ranges=None):
+ blob_rename_f = functools.partial(_rename_blob, blob_sizes=blob_sizes, blob_ranges=blob_ranges)
+ return save_graph_base(net, file_name, graph_name, op_only, blob_rename_f)
+
+
+def save_graph_base(net, file_name, graph_name="net", op_only=True, blob_rename_func=None):
+ graph = None
+ ops = net.op
+ if blob_rename_func is not None:
+ ops = _modify_blob_names(ops, blob_rename_func)
+ if not op_only:
+ graph = net_drawer.GetPydotGraph(ops, graph_name, rankdir="TB")
+ else:
+ graph = net_drawer.GetPydotGraphMinimal(
+ ops, graph_name, rankdir="TB", minimal_dependency=True
+ )
+
+ try:
+ par_dir = os.path.dirname(file_name)
+ if not os.path.exists(par_dir):
+ os.makedirs(par_dir)
+
+ format = os.path.splitext(os.path.basename(file_name))[-1]
+ if format == ".png":
+ graph.write_png(file_name)
+ elif format == ".pdf":
+ graph.write_pdf(file_name)
+ elif format == ".svg":
+ graph.write_svg(file_name)
+ else:
+ print("Incorrect format {}".format(format))
+ except Exception as e:
+ print("Error when writing graph to image {}".format(e))
+
+ return graph
+
+
+# ==== torch/utils_toffee/aten_to_caffe2.py ====================================
+
+
+def group_norm_replace_aten_with_caffe2(predict_net: caffe2_pb2.NetDef):
+ """
+ For ONNX exported model, GroupNorm will be represented as ATen op,
+ this can be a drop in replacement from ATen to GroupNorm
+ """
+ count = 0
+ for op in predict_net.op:
+ if op.type == "ATen":
+ op_name = get_pb_arg_vals(op, "operator", None) # return byte in py3
+ if op_name and op_name.decode() == "group_norm":
+ op.arg.remove(get_pb_arg(op, "operator"))
+
+ if get_pb_arg_vali(op, "cudnn_enabled", None):
+ op.arg.remove(get_pb_arg(op, "cudnn_enabled"))
+
+ num_groups = get_pb_arg_vali(op, "num_groups", None)
+ if num_groups is not None:
+ op.arg.remove(get_pb_arg(op, "num_groups"))
+ check_set_pb_arg(op, "group", "i", num_groups)
+
+ op.type = "GroupNorm"
+ count += 1
+ if count > 1:
+ logger.info("Replaced {} ATen operator to GroupNormOp".format(count))
+
+
+# ==== torch/utils_toffee/alias.py =============================================
+
+
+def alias(x, name, is_backward=False):
+ if not torch.onnx.is_in_onnx_export():
+ return x
+ assert isinstance(x, torch.Tensor)
+ return torch.ops._caffe2.AliasWithName(x, name, is_backward=is_backward)
+
+
+def fuse_alias_placeholder(predict_net, init_net):
+ """Remove AliasWithName placeholder and rename the input/output of it"""
+ # First we finish all the re-naming
+ for i, op in enumerate(predict_net.op):
+ if op.type == "AliasWithName":
+ assert len(op.input) == 1
+ assert len(op.output) == 1
+ name = get_pb_arg_vals(op, "name", None).decode()
+ is_backward = bool(get_pb_arg_vali(op, "is_backward", 0))
+ rename_op_input(predict_net, init_net, i, 0, name, from_producer=is_backward)
+ rename_op_output(predict_net, i, 0, name)
+
+ # Remove AliasWithName, should be very safe since it's a non-op
+ new_ops = []
+ for op in predict_net.op:
+ if op.type != "AliasWithName":
+ new_ops.append(op)
+ else:
+ # safety check
+ assert op.input == op.output
+ assert op.input[0] == op.arg[0].s.decode()
+ del predict_net.op[:]
+ predict_net.op.extend(new_ops)
+
+
+# ==== torch/utils_caffe2/graph_transform.py ===================================
+
+
+class IllegalGraphTransformError(ValueError):
+ """When a graph transform function call can't be executed."""
+
+
+def _rename_versioned_blob_in_proto(
+ proto: caffe2_pb2.NetDef,
+ old_name: str,
+ new_name: str,
+ version: int,
+ ssa: List[Tuple[List[Tuple[str, int]], List[Tuple[str, int]]]],
+ start_versions: Dict[str, int],
+ end_versions: Dict[str, int],
+):
+ """In given proto, rename all blobs with matched version"""
+ # Operater list
+ for op, i_th_ssa in zip(proto.op, ssa):
+ versioned_inputs, versioned_outputs = i_th_ssa
+ for i in range(len(op.input)):
+ if versioned_inputs[i] == (old_name, version):
+ op.input[i] = new_name
+ for i in range(len(op.output)):
+ if versioned_outputs[i] == (old_name, version):
+ op.output[i] = new_name
+ # external_input
+ if start_versions.get(old_name, 0) == version:
+ for i in range(len(proto.external_input)):
+ if proto.external_input[i] == old_name:
+ proto.external_input[i] = new_name
+ # external_output
+ if end_versions.get(old_name, 0) == version:
+ for i in range(len(proto.external_output)):
+ if proto.external_output[i] == old_name:
+ proto.external_output[i] = new_name
+
+
+def rename_op_input(
+ predict_net: caffe2_pb2.NetDef,
+ init_net: caffe2_pb2.NetDef,
+ op_id: int,
+ input_id: int,
+ new_name: str,
+ from_producer: bool = False,
+):
+ """
+ Rename the op_id-th operator in predict_net, change it's input_id-th input's
+ name to the new_name. It also does automatic re-route and change
+ external_input and init_net if necessary.
+ - It requires the input is only consumed by this op.
+ - This function modifies predict_net and init_net in-place.
+ - When from_producer is enable, this also updates other operators that consumes
+ the same input. Be cautious because may trigger unintended behavior.
+ """
+ assert isinstance(predict_net, caffe2_pb2.NetDef)
+ assert isinstance(init_net, caffe2_pb2.NetDef)
+
+ init_net_ssa, init_net_versions = core.get_ssa(init_net)
+ predict_net_ssa, predict_net_versions = core.get_ssa(
+ predict_net, copy.deepcopy(init_net_versions)
+ )
+
+ versioned_inputs, versioned_outputs = predict_net_ssa[op_id]
+ old_name, version = versioned_inputs[input_id]
+
+ if from_producer:
+ producer_map = get_producer_map(predict_net_ssa)
+ if not (old_name, version) in producer_map:
+ raise NotImplementedError(
+ "Can't find producer, the input {} is probably from"
+ " init_net, this is not supported yet.".format(old_name)
+ )
+ producer = producer_map[(old_name, version)]
+ rename_op_output(predict_net, producer[0], producer[1], new_name)
+ return
+
+ def contain_targets(op_ssa):
+ return (old_name, version) in op_ssa[0]
+
+ is_consumer = [contain_targets(op_ssa) for op_ssa in predict_net_ssa]
+ if sum(is_consumer) > 1:
+ raise IllegalGraphTransformError(
+ (
+ "Input '{}' of operator(#{}) are consumed by other ops, please use"
+ + " rename_op_output on the producer instead. Offending op: \n{}"
+ ).format(old_name, op_id, predict_net.op[op_id])
+ )
+
+ # update init_net
+ _rename_versioned_blob_in_proto(
+ init_net, old_name, new_name, version, init_net_ssa, {}, init_net_versions
+ )
+ # update predict_net
+ _rename_versioned_blob_in_proto(
+ predict_net,
+ old_name,
+ new_name,
+ version,
+ predict_net_ssa,
+ init_net_versions,
+ predict_net_versions,
+ )
+
+
+def rename_op_output(predict_net: caffe2_pb2.NetDef, op_id: int, output_id: int, new_name: str):
+ """
+ Rename the op_id-th operator in predict_net, change it's output_id-th input's
+ name to the new_name. It also does automatic re-route and change
+ external_output and if necessary.
+ - It allows multiple consumers of its output.
+ - This function modifies predict_net in-place, doesn't need init_net.
+ """
+ assert isinstance(predict_net, caffe2_pb2.NetDef)
+
+ ssa, blob_versions = core.get_ssa(predict_net)
+
+ versioned_inputs, versioned_outputs = ssa[op_id]
+ old_name, version = versioned_outputs[output_id]
+
+ # update predict_net
+ _rename_versioned_blob_in_proto(
+ predict_net, old_name, new_name, version, ssa, {}, blob_versions
+ )
+
+
+def get_sub_graph_external_input_output(
+ predict_net: caffe2_pb2.NetDef, sub_graph_op_indices: List[int]
+) -> Tuple[List[Tuple[str, int]], List[Tuple[str, int]]]:
+ """
+ Return the list of external input/output of sub-graph,
+ each element is tuple of the name and corresponding version in predict_net.
+
+ external input/output is defined the same way as caffe2 NetDef.
+ """
+ ssa, versions = core.get_ssa(predict_net)
+
+ all_inputs = []
+ all_outputs = []
+ for op_id in sub_graph_op_indices:
+ all_inputs += [inp for inp in ssa[op_id][0] if inp not in all_inputs]
+ all_outputs += list(ssa[op_id][1]) # ssa output won't repeat
+
+ # for versioned blobs, external inputs are just those blob in all_inputs
+ # but not in all_outputs
+ ext_inputs = [inp for inp in all_inputs if inp not in all_outputs]
+
+ # external outputs are essentially outputs of this subgraph that are used
+ # outside of this sub-graph (including predict_net.external_output)
+ all_other_inputs = sum(
+ (ssa[i][0] for i in range(len(ssa)) if i not in sub_graph_op_indices),
+ [(outp, versions[outp]) for outp in predict_net.external_output],
+ )
+ ext_outputs = [outp for outp in all_outputs if outp in set(all_other_inputs)]
+
+ return ext_inputs, ext_outputs
+
+
+class DiGraph:
+ """A DAG representation of caffe2 graph, each vertice is a versioned blob."""
+
+ def __init__(self):
+ self.vertices = set()
+ self.graph = collections.defaultdict(list)
+
+ def add_edge(self, u, v):
+ self.graph[u].append(v)
+ self.vertices.add(u)
+ self.vertices.add(v)
+
+ # grab from https://www.geeksforgeeks.org/find-paths-given-source-destination/
+ def get_all_paths(self, s, d):
+ visited = {k: False for k in self.vertices}
+ path = []
+ all_paths = []
+
+ def _get_all_paths_util(graph, u, d, visited, path):
+ visited[u] = True
+ path.append(u)
+ if u == d:
+ all_paths.append(copy.deepcopy(path))
+ else:
+ for i in graph[u]:
+ if not visited[i]:
+ _get_all_paths_util(graph, i, d, visited, path)
+ path.pop()
+ visited[u] = False
+
+ _get_all_paths_util(self.graph, s, d, visited, path)
+ return all_paths
+
+ @staticmethod
+ def from_ssa(ssa):
+ graph = DiGraph()
+ for op_id in range(len(ssa)):
+ for inp in ssa[op_id][0]:
+ for outp in ssa[op_id][1]:
+ graph.add_edge(inp, outp)
+ return graph
+
+
+def _get_dependency_chain(ssa, versioned_target, versioned_source):
+ """
+ Return the index list of relevant operator to produce target blob from source blob,
+ if there's no dependency, return empty list.
+ """
+
+ # finding all paths between nodes can be O(N!), thus we can only search
+ # in the subgraph using the op starting from the first consumer of source blob
+ # to the producer of the target blob.
+ consumer_map = get_consumer_map(ssa)
+ producer_map = get_producer_map(ssa)
+ start_op = min(x[0] for x in consumer_map[versioned_source]) - 15
+ end_op = (
+ producer_map[versioned_target][0] + 15 if versioned_target in producer_map else start_op
+ )
+ sub_graph_ssa = ssa[start_op : end_op + 1]
+ if len(sub_graph_ssa) > 30:
+ logger.warning(
+ "Subgraph bebetween {} and {} is large (from op#{} to op#{}), it"
+ " might take non-trival time to find all paths between them.".format(
+ versioned_source, versioned_target, start_op, end_op
+ )
+ )
+
+ dag = DiGraph.from_ssa(sub_graph_ssa)
+ paths = dag.get_all_paths(versioned_source, versioned_target) # include two ends
+ ops_in_paths = [[producer_map[blob][0] for blob in path[1:]] for path in paths]
+ return sorted(set().union(*[set(ops) for ops in ops_in_paths]))
+
+
+def identify_reshape_sub_graph(predict_net: caffe2_pb2.NetDef) -> List[List[int]]:
+ """
+ Idenfity the reshape sub-graph in a protobuf.
+ The reshape sub-graph is defined as matching the following pattern:
+
+ (input_blob) -> Op_1 -> ... -> Op_N -> (new_shape) -─┐
+ └-------------------------------------------> Reshape -> (output_blob)
+
+ Return:
+ List of sub-graphs, each sub-graph is represented as a list of indices
+ of the relavent ops, [Op_1, Op_2, ..., Op_N, Reshape]
+ """
+
+ ssa, _ = core.get_ssa(predict_net)
+
+ ret = []
+ for i, op in enumerate(predict_net.op):
+ if op.type == "Reshape":
+ assert len(op.input) == 2
+ input_ssa = ssa[i][0]
+ data_source = input_ssa[0]
+ shape_source = input_ssa[1]
+ op_indices = _get_dependency_chain(ssa, shape_source, data_source)
+ ret.append(op_indices + [i])
+ return ret
+
+
+def remove_reshape_for_fc(predict_net, params):
+ """
+ In PyTorch nn.Linear has to take 2D tensor, this often leads to reshape
+ a 4D tensor to 2D by calling .view(). However this (dynamic) reshaping
+ doesn't work well with ONNX and Int8 tools, and cause using extra
+ ops (eg. ExpandDims) that might not be available on mobile.
+ Luckily Caffe2 supports 4D tensor for FC, so we can remove those reshape
+ after exporting ONNX model.
+ """
+ from caffe2.python import core
+
+ # find all reshape sub-graph that can be removed, which is now all Reshape
+ # sub-graph whose output is only consumed by FC.
+ # TODO: to make it safer, we may need the actually value to better determine
+ # if a Reshape before FC is removable.
+ reshape_sub_graphs = identify_reshape_sub_graph(predict_net)
+ sub_graphs_to_remove = []
+ for reshape_sub_graph in reshape_sub_graphs:
+ reshape_op_id = reshape_sub_graph[-1]
+ assert predict_net.op[reshape_op_id].type == "Reshape"
+ ssa, _ = core.get_ssa(predict_net)
+ reshape_output = ssa[reshape_op_id][1][0]
+ consumers = [i for i in range(len(ssa)) if reshape_output in ssa[i][0]]
+ if all(predict_net.op[consumer].type == "FC" for consumer in consumers):
+ # safety check if the sub-graph is isolated, for this reshape sub-graph,
+ # it means it has one non-param external input and one external output.
+ ext_inputs, ext_outputs = get_sub_graph_external_input_output(
+ predict_net, reshape_sub_graph
+ )
+ non_params_ext_inputs = [inp for inp in ext_inputs if inp[1] != 0]
+ if len(non_params_ext_inputs) == 1 and len(ext_outputs) == 1:
+ sub_graphs_to_remove.append(reshape_sub_graph)
+
+ # perform removing subgraph by:
+ # 1: rename the Reshape's output to its input, then the graph can be
+ # seen as in-place itentify, meaning whose external input/output are the same.
+ # 2: simply remove those ops.
+ remove_op_ids = []
+ params_to_remove = []
+ for sub_graph in sub_graphs_to_remove:
+ logger.info(
+ "Remove Reshape sub-graph:\n{}".format(
+ "".join(["(#{:>4})\n{}".format(i, predict_net.op[i]) for i in sub_graph])
+ )
+ )
+ reshape_op_id = sub_graph[-1]
+ new_reshap_output = predict_net.op[reshape_op_id].input[0]
+ rename_op_output(predict_net, reshape_op_id, 0, new_reshap_output)
+ ext_inputs, ext_outputs = get_sub_graph_external_input_output(predict_net, sub_graph)
+ non_params_ext_inputs = [inp for inp in ext_inputs if inp[1] != 0]
+ params_ext_inputs = [inp for inp in ext_inputs if inp[1] == 0]
+ assert len(non_params_ext_inputs) == 1 and len(ext_outputs) == 1
+ assert ext_outputs[0][0] == non_params_ext_inputs[0][0]
+ assert ext_outputs[0][1] == non_params_ext_inputs[0][1] + 1
+ remove_op_ids.extend(sub_graph)
+ params_to_remove.extend(params_ext_inputs)
+
+ predict_net = copy.deepcopy(predict_net)
+ new_ops = [op for i, op in enumerate(predict_net.op) if i not in remove_op_ids]
+ del predict_net.op[:]
+ predict_net.op.extend(new_ops)
+ for versioned_params in params_to_remove:
+ name = versioned_params[0]
+ logger.info("Remove params: {} from init_net and predict_net.external_input".format(name))
+ del params[name]
+ predict_net.external_input.remove(name)
+
+ return predict_net, params
+
+
+def fuse_copy_between_cpu_and_gpu(predict_net: caffe2_pb2.NetDef):
+ """
+ In-place fuse extra copy ops between cpu/gpu for the following case:
+ a -CopyAToB-> b -CopyBToA> c1 -NextOp1-> d1
+ -CopyBToA> c2 -NextOp2-> d2
+ The fused network will look like:
+ a -NextOp1-> d1
+ -NextOp2-> d2
+ """
+
+ _COPY_OPS = ["CopyCPUToGPU", "CopyGPUToCPU"]
+
+ def _fuse_once(predict_net):
+ ssa, blob_versions = core.get_ssa(predict_net)
+ consumer_map = get_consumer_map(ssa)
+ versioned_external_output = [
+ (name, blob_versions[name]) for name in predict_net.external_output
+ ]
+
+ for op_id, op in enumerate(predict_net.op):
+ if op.type in _COPY_OPS:
+ fw_copy_versioned_output = ssa[op_id][1][0]
+ consumer_ids = [x[0] for x in consumer_map[fw_copy_versioned_output]]
+ reverse_op_type = _COPY_OPS[1 - _COPY_OPS.index(op.type)]
+
+ is_fusable = (
+ len(consumer_ids) > 0
+ and fw_copy_versioned_output not in versioned_external_output
+ and all(
+ predict_net.op[_op_id].type == reverse_op_type
+ and ssa[_op_id][1][0] not in versioned_external_output
+ for _op_id in consumer_ids
+ )
+ )
+
+ if is_fusable:
+ for rv_copy_op_id in consumer_ids:
+ # making each NextOp uses "a" directly and removing Copy ops
+ rs_copy_versioned_output = ssa[rv_copy_op_id][1][0]
+ next_op_id, inp_id = consumer_map[rs_copy_versioned_output][0]
+ predict_net.op[next_op_id].input[inp_id] = op.input[0]
+ # remove CopyOps
+ new_ops = [
+ op
+ for i, op in enumerate(predict_net.op)
+ if i != op_id and i not in consumer_ids
+ ]
+ del predict_net.op[:]
+ predict_net.op.extend(new_ops)
+ return True
+
+ return False
+
+ # _fuse_once returns False is nothing can be fused
+ while _fuse_once(predict_net):
+ pass
+
+
+def remove_dead_end_ops(net_def: caffe2_pb2.NetDef):
+ """remove ops if its output is not used or not in external_output"""
+ ssa, versions = core.get_ssa(net_def)
+ versioned_external_output = [(name, versions[name]) for name in net_def.external_output]
+ consumer_map = get_consumer_map(ssa)
+ removed_op_ids = set()
+
+ def _is_dead_end(versioned_blob):
+ return not (
+ versioned_blob in versioned_external_output
+ or (
+ len(consumer_map[versioned_blob]) > 0
+ and all(x[0] not in removed_op_ids for x in consumer_map[versioned_blob])
+ )
+ )
+
+ for i, ssa_i in reversed(list(enumerate(ssa))):
+ versioned_outputs = ssa_i[1]
+ if all(_is_dead_end(outp) for outp in versioned_outputs):
+ removed_op_ids.add(i)
+
+ # simply removing those deadend ops should have no effect to external_output
+ new_ops = [op for i, op in enumerate(net_def.op) if i not in removed_op_ids]
+ del net_def.op[:]
+ net_def.op.extend(new_ops)
diff --git a/detectron2/detectron2/export/torchscript.py b/detectron2/detectron2/export/torchscript.py
new file mode 100755
index 0000000..24fe59b
--- /dev/null
+++ b/detectron2/detectron2/export/torchscript.py
@@ -0,0 +1,132 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import os
+import torch
+
+from detectron2.utils.file_io import PathManager
+
+from .torchscript_patch import freeze_training_mode, patch_instances
+
+__all__ = ["scripting_with_instances", "dump_torchscript_IR"]
+
+
+def scripting_with_instances(model, fields):
+ """
+ Run :func:`torch.jit.script` on a model that uses the :class:`Instances` class. Since
+ attributes of :class:`Instances` are "dynamically" added in eager mode,it is difficult
+ for scripting to support it out of the box. This function is made to support scripting
+ a model that uses :class:`Instances`. It does the following:
+
+ 1. Create a scriptable ``new_Instances`` class which behaves similarly to ``Instances``,
+ but with all attributes been "static".
+ The attributes need to be statically declared in the ``fields`` argument.
+ 2. Register ``new_Instances``, and force scripting compiler to
+ use it when trying to compile ``Instances``.
+
+ After this function, the process will be reverted. User should be able to script another model
+ using different fields.
+
+ Example:
+ Assume that ``Instances`` in the model consist of two attributes named
+ ``proposal_boxes`` and ``objectness_logits`` with type :class:`Boxes` and
+ :class:`Tensor` respectively during inference. You can call this function like:
+ ::
+ fields = {"proposal_boxes": Boxes, "objectness_logits": torch.Tensor}
+ torchscipt_model = scripting_with_instances(model, fields)
+
+ Note:
+ It only support models in evaluation mode.
+
+ Args:
+ model (nn.Module): The input model to be exported by scripting.
+ fields (Dict[str, type]): Attribute names and corresponding type that
+ ``Instances`` will use in the model. Note that all attributes used in ``Instances``
+ need to be added, regardless of whether they are inputs/outputs of the model.
+ Data type not defined in detectron2 is not supported for now.
+
+ Returns:
+ torch.jit.ScriptModule: the model in torchscript format
+ """
+ assert (
+ not model.training
+ ), "Currently we only support exporting models in evaluation mode to torchscript"
+
+ with freeze_training_mode(model), patch_instances(fields):
+ scripted_model = torch.jit.script(model)
+ return scripted_model
+
+
+# alias for old name
+export_torchscript_with_instances = scripting_with_instances
+
+
+def dump_torchscript_IR(model, dir):
+ """
+ Dump IR of a TracedModule/ScriptModule/Function in various format (code, graph,
+ inlined graph). Useful for debugging.
+
+ Args:
+ model (TracedModule/ScriptModule/ScriptFUnction): traced or scripted module
+ dir (str): output directory to dump files.
+ """
+ dir = os.path.expanduser(dir)
+ PathManager.mkdirs(dir)
+
+ def _get_script_mod(mod):
+ if isinstance(mod, torch.jit.TracedModule):
+ return mod._actual_script_module
+ return mod
+
+ # Dump pretty-printed code: https://pytorch.org/docs/stable/jit.html#inspecting-code
+ with PathManager.open(os.path.join(dir, "model_ts_code.txt"), "w") as f:
+
+ def get_code(mod):
+ # Try a few ways to get code using private attributes.
+ try:
+ # This contains more information than just `mod.code`
+ return _get_script_mod(mod)._c.code
+ except AttributeError:
+ pass
+ try:
+ return mod.code
+ except AttributeError:
+ return None
+
+ def dump_code(prefix, mod):
+ code = get_code(mod)
+ name = prefix or "root model"
+ if code is None:
+ f.write(f"Could not found code for {name} (type={mod.original_name})\n")
+ f.write("\n")
+ else:
+ f.write(f"\nCode for {name}, type={mod.original_name}:\n")
+ f.write(code)
+ f.write("\n")
+ f.write("-" * 80)
+
+ for name, m in mod.named_children():
+ dump_code(prefix + "." + name, m)
+
+ if isinstance(model, torch.jit.ScriptFunction):
+ f.write(get_code(model))
+ else:
+ dump_code("", model)
+
+ def _get_graph(model):
+ try:
+ # Recursively dump IR of all modules
+ return _get_script_mod(model)._c.dump_to_str(True, False, False)
+ except AttributeError:
+ return model.graph.str()
+
+ with PathManager.open(os.path.join(dir, "model_ts_IR.txt"), "w") as f:
+ f.write(_get_graph(model))
+
+ # Dump IR of the entire graph (all submodules inlined)
+ with PathManager.open(os.path.join(dir, "model_ts_IR_inlined.txt"), "w") as f:
+ f.write(str(model.inlined_graph))
+
+ if not isinstance(model, torch.jit.ScriptFunction):
+ # Dump the model structure in pytorch style
+ with PathManager.open(os.path.join(dir, "model.txt"), "w") as f:
+ f.write(str(model))
diff --git a/detectron2/detectron2/export/torchscript_patch.py b/detectron2/detectron2/export/torchscript_patch.py
new file mode 100755
index 0000000..da9b324
--- /dev/null
+++ b/detectron2/detectron2/export/torchscript_patch.py
@@ -0,0 +1,406 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import os
+import sys
+import tempfile
+from contextlib import ExitStack, contextmanager
+from copy import deepcopy
+from unittest import mock
+import torch
+from torch import nn
+
+# need some explicit imports due to https://github.com/pytorch/pytorch/issues/38964
+import detectron2 # noqa F401
+from detectron2.structures import Boxes, Instances
+from detectron2.utils.env import _import_file
+
+_counter = 0
+
+
+def _clear_jit_cache():
+ from torch.jit._recursive import concrete_type_store
+ from torch.jit._state import _jit_caching_layer
+
+ concrete_type_store.type_store.clear() # for modules
+ _jit_caching_layer.clear() # for free functions
+
+
+def _add_instances_conversion_methods(newInstances):
+ """
+ Add from_instances methods to the scripted Instances class.
+ """
+ cls_name = newInstances.__name__
+
+ @torch.jit.unused
+ def from_instances(instances: Instances):
+ """
+ Create scripted Instances from original Instances
+ """
+ fields = instances.get_fields()
+ image_size = instances.image_size
+ ret = newInstances(image_size)
+ for name, val in fields.items():
+ assert hasattr(ret, f"_{name}"), f"No attribute named {name} in {cls_name}"
+ setattr(ret, name, deepcopy(val))
+ return ret
+
+ newInstances.from_instances = from_instances
+
+
+@contextmanager
+def patch_instances(fields):
+ """
+ A contextmanager, under which the Instances class in detectron2 is replaced
+ by a statically-typed scriptable class, defined by `fields`.
+ See more in `scripting_with_instances`.
+ """
+
+ with tempfile.TemporaryDirectory(prefix="detectron2") as dir, tempfile.NamedTemporaryFile(
+ mode="w", encoding="utf-8", suffix=".py", dir=dir, delete=False
+ ) as f:
+ try:
+ # Objects that use Instances should not reuse previously-compiled
+ # results in cache, because `Instances` could be a new class each time.
+ _clear_jit_cache()
+
+ cls_name, s = _gen_instance_module(fields)
+ f.write(s)
+ f.flush()
+ f.close()
+
+ module = _import(f.name)
+ new_instances = getattr(module, cls_name)
+ _ = torch.jit.script(new_instances)
+ # let torchscript think Instances was scripted already
+ Instances.__torch_script_class__ = True
+ # let torchscript find new_instances when looking for the jit type of Instances
+ Instances._jit_override_qualname = torch._jit_internal._qualified_name(new_instances)
+
+ _add_instances_conversion_methods(new_instances)
+ yield new_instances
+ finally:
+ try:
+ del Instances.__torch_script_class__
+ del Instances._jit_override_qualname
+ except AttributeError:
+ pass
+ sys.modules.pop(module.__name__)
+
+
+def _gen_instance_class(fields):
+ """
+ Args:
+ fields (dict[name: type])
+ """
+
+ class _FieldType:
+ def __init__(self, name, type_):
+ assert isinstance(name, str), f"Field name must be str, got {name}"
+ self.name = name
+ self.type_ = type_
+ self.annotation = f"{type_.__module__}.{type_.__name__}"
+
+ fields = [_FieldType(k, v) for k, v in fields.items()]
+
+ def indent(level, s):
+ return " " * 4 * level + s
+
+ lines = []
+
+ global _counter
+ _counter += 1
+
+ cls_name = "ScriptedInstances{}".format(_counter)
+
+ field_names = tuple(x.name for x in fields)
+ extra_args = ", ".join([f"{f.name}: Optional[{f.annotation}] = None" for f in fields])
+ lines.append(
+ f"""
+class {cls_name}:
+ def __init__(self, image_size: Tuple[int, int], {extra_args}):
+ self.image_size = image_size
+ self._field_names = {field_names}
+"""
+ )
+
+ for f in fields:
+ lines.append(
+ indent(2, f"self._{f.name} = torch.jit.annotate(Optional[{f.annotation}], {f.name})")
+ )
+
+ for f in fields:
+ lines.append(
+ f"""
+ @property
+ def {f.name}(self) -> {f.annotation}:
+ # has to use a local for type refinement
+ # https://pytorch.org/docs/stable/jit_language_reference.html#optional-type-refinement
+ t = self._{f.name}
+ assert t is not None, "{f.name} is None and cannot be accessed!"
+ return t
+
+ @{f.name}.setter
+ def {f.name}(self, value: {f.annotation}) -> None:
+ self._{f.name} = value
+"""
+ )
+
+ # support method `__len__`
+ lines.append(
+ """
+ def __len__(self) -> int:
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ return len(t)
+"""
+ )
+ lines.append(
+ """
+ raise NotImplementedError("Empty Instances does not support __len__!")
+"""
+ )
+
+ # support method `has`
+ lines.append(
+ """
+ def has(self, name: str) -> bool:
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ if name == "{f.name}":
+ return self._{f.name} is not None
+"""
+ )
+ lines.append(
+ """
+ return False
+"""
+ )
+
+ # support method `to`
+ none_args = ", None" * len(fields)
+ lines.append(
+ f"""
+ def to(self, device: torch.device) -> "{cls_name}":
+ ret = {cls_name}(self.image_size{none_args})
+"""
+ )
+ for f in fields:
+ if hasattr(f.type_, "to"):
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ ret._{f.name} = t.to(device)
+"""
+ )
+ else:
+ # For now, ignore fields that cannot be moved to devices.
+ # Maybe can support other tensor-like classes (e.g. __torch_function__)
+ pass
+ lines.append(
+ """
+ return ret
+"""
+ )
+
+ # support method `getitem`
+ none_args = ", None" * len(fields)
+ lines.append(
+ f"""
+ def __getitem__(self, item) -> "{cls_name}":
+ ret = {cls_name}(self.image_size{none_args})
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ ret._{f.name} = t[item]
+"""
+ )
+ lines.append(
+ """
+ return ret
+"""
+ )
+
+ # support method `cat`
+ # this version does not contain checks that all instances have same size and fields
+ none_args = ", None" * len(fields)
+ lines.append(
+ f"""
+ def cat(self, instances: List["{cls_name}"]) -> "{cls_name}":
+ ret = {cls_name}(self.image_size{none_args})
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ values: List[{f.annotation}] = [x.{f.name} for x in instances]
+ if torch.jit.isinstance(t, torch.Tensor):
+ ret._{f.name} = torch.cat(values, dim=0)
+ else:
+ ret._{f.name} = t.cat(values)
+"""
+ )
+ lines.append(
+ """
+ return ret"""
+ )
+
+ # support method `get_fields()`
+ lines.append(
+ """
+ def get_fields(self) -> Dict[str, Tensor]:
+ ret = {}
+ """
+ )
+ for f in fields:
+ if f.type_ == Boxes:
+ stmt = "t.tensor"
+ elif f.type_ == torch.Tensor:
+ stmt = "t"
+ else:
+ stmt = f'assert False, "unsupported type {str(f.type_)}"'
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ ret["{f.name}"] = {stmt}
+ """
+ )
+ lines.append(
+ """
+ return ret"""
+ )
+ return cls_name, os.linesep.join(lines)
+
+
+def _gen_instance_module(fields):
+ # TODO: find a more automatic way to enable import of other classes
+ s = """
+from copy import deepcopy
+import torch
+from torch import Tensor
+import typing
+from typing import *
+
+import detectron2
+from detectron2.structures import Boxes, Instances
+
+"""
+
+ cls_name, cls_def = _gen_instance_class(fields)
+ s += cls_def
+ return cls_name, s
+
+
+def _import(path):
+ return _import_file(
+ "{}{}".format(sys.modules[__name__].__name__, _counter), path, make_importable=True
+ )
+
+
+@contextmanager
+def patch_builtin_len(modules=()):
+ """
+ Patch the builtin len() function of a few detectron2 modules
+ to use __len__ instead, because __len__ does not convert values to
+ integers and therefore is friendly to tracing.
+
+ Args:
+ modules (list[stsr]): names of extra modules to patch len(), in
+ addition to those in detectron2.
+ """
+
+ def _new_len(obj):
+ return obj.__len__()
+
+ with ExitStack() as stack:
+ MODULES = [
+ "detectron2.modeling.roi_heads.fast_rcnn",
+ "detectron2.modeling.roi_heads.mask_head",
+ "detectron2.modeling.roi_heads.keypoint_head",
+ ] + list(modules)
+ ctxs = [stack.enter_context(mock.patch(mod + ".len")) for mod in MODULES]
+ for m in ctxs:
+ m.side_effect = _new_len
+ yield
+
+
+def patch_nonscriptable_classes():
+ """
+ Apply patches on a few nonscriptable detectron2 classes.
+ Should not have side-effects on eager usage.
+ """
+ # __prepare_scriptable__ can also be added to models for easier maintenance.
+ # But it complicates the clean model code.
+
+ from detectron2.modeling.backbone import ResNet, FPN
+
+ # Due to https://github.com/pytorch/pytorch/issues/36061,
+ # we change backbone to use ModuleList for scripting.
+ # (note: this changes param names in state_dict)
+
+ def prepare_resnet(self):
+ ret = deepcopy(self)
+ ret.stages = nn.ModuleList(ret.stages)
+ for k in self.stage_names:
+ delattr(ret, k)
+ return ret
+
+ ResNet.__prepare_scriptable__ = prepare_resnet
+
+ def prepare_fpn(self):
+ ret = deepcopy(self)
+ ret.lateral_convs = nn.ModuleList(ret.lateral_convs)
+ ret.output_convs = nn.ModuleList(ret.output_convs)
+ for name, _ in self.named_children():
+ if name.startswith("fpn_"):
+ delattr(ret, name)
+ return ret
+
+ FPN.__prepare_scriptable__ = prepare_fpn
+
+ # Annotate some attributes to be constants for the purpose of scripting,
+ # even though they are not constants in eager mode.
+ from detectron2.modeling.roi_heads import StandardROIHeads
+
+ if hasattr(StandardROIHeads, "__annotations__"):
+ # copy first to avoid editing annotations of base class
+ StandardROIHeads.__annotations__ = deepcopy(StandardROIHeads.__annotations__)
+ StandardROIHeads.__annotations__["mask_on"] = torch.jit.Final[bool]
+ StandardROIHeads.__annotations__["keypoint_on"] = torch.jit.Final[bool]
+
+
+# These patches are not supposed to have side-effects.
+patch_nonscriptable_classes()
+
+
+@contextmanager
+def freeze_training_mode(model):
+ """
+ A context manager that annotates the "training" attribute of every submodule
+ to constant, so that the training codepath in these modules can be
+ meta-compiled away. Upon exiting, the annotations are reverted.
+ """
+ classes = {type(x) for x in model.modules()}
+ # __constants__ is the old way to annotate constants and not compatible
+ # with __annotations__ .
+ classes = {x for x in classes if not hasattr(x, "__constants__")}
+ for cls in classes:
+ cls.__annotations__["training"] = torch.jit.Final[bool]
+ yield
+ for cls in classes:
+ cls.__annotations__["training"] = bool
diff --git a/detectron2/detectron2/layers/__init__.py b/detectron2/detectron2/layers/__init__.py
new file mode 100755
index 0000000..761a3d1
--- /dev/null
+++ b/detectron2/detectron2/layers/__init__.py
@@ -0,0 +1,26 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .batch_norm import FrozenBatchNorm2d, get_norm, NaiveSyncBatchNorm, CycleBatchNormList
+from .deform_conv import DeformConv, ModulatedDeformConv
+from .mask_ops import paste_masks_in_image
+from .nms import batched_nms, batched_nms_rotated, nms, nms_rotated
+from .roi_align import ROIAlign, roi_align
+from .roi_align_rotated import ROIAlignRotated, roi_align_rotated
+from .shape_spec import ShapeSpec
+from .wrappers import (
+ BatchNorm2d,
+ Conv2d,
+ ConvTranspose2d,
+ cat,
+ interpolate,
+ Linear,
+ nonzero_tuple,
+ cross_entropy,
+ empty_input_loss_func_wrapper,
+ shapes_to_tensor,
+ move_device_like,
+)
+from .blocks import CNNBlockBase, DepthwiseSeparableConv2d
+from .aspp import ASPP
+from .losses import ciou_loss, diou_loss
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/detectron2/detectron2/layers/aspp.py b/detectron2/detectron2/layers/aspp.py
new file mode 100755
index 0000000..14861aa
--- /dev/null
+++ b/detectron2/detectron2/layers/aspp.py
@@ -0,0 +1,144 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+from copy import deepcopy
+import fvcore.nn.weight_init as weight_init
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from .batch_norm import get_norm
+from .blocks import DepthwiseSeparableConv2d
+from .wrappers import Conv2d
+
+
+class ASPP(nn.Module):
+ """
+ Atrous Spatial Pyramid Pooling (ASPP).
+ """
+
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ dilations,
+ *,
+ norm,
+ activation,
+ pool_kernel_size=None,
+ dropout: float = 0.0,
+ use_depthwise_separable_conv=False,
+ ):
+ """
+ Args:
+ in_channels (int): number of input channels for ASPP.
+ out_channels (int): number of output channels.
+ dilations (list): a list of 3 dilations in ASPP.
+ norm (str or callable): normalization for all conv layers.
+ See :func:`layers.get_norm` for supported format. norm is
+ applied to all conv layers except the conv following
+ global average pooling.
+ activation (callable): activation function.
+ pool_kernel_size (tuple, list): the average pooling size (kh, kw)
+ for image pooling layer in ASPP. If set to None, it always
+ performs global average pooling. If not None, it must be
+ divisible by the shape of inputs in forward(). It is recommended
+ to use a fixed input feature size in training, and set this
+ option to match this size, so that it performs global average
+ pooling in training, and the size of the pooling window stays
+ consistent in inference.
+ dropout (float): apply dropout on the output of ASPP. It is used in
+ the official DeepLab implementation with a rate of 0.1:
+ https://github.com/tensorflow/models/blob/21b73d22f3ed05b650e85ac50849408dd36de32e/research/deeplab/model.py#L532 # noqa
+ use_depthwise_separable_conv (bool): use DepthwiseSeparableConv2d
+ for 3x3 convs in ASPP, proposed in :paper:`DeepLabV3+`.
+ """
+ super(ASPP, self).__init__()
+ assert len(dilations) == 3, "ASPP expects 3 dilations, got {}".format(len(dilations))
+ self.pool_kernel_size = pool_kernel_size
+ self.dropout = dropout
+ use_bias = norm == ""
+ self.convs = nn.ModuleList()
+ # conv 1x1
+ self.convs.append(
+ Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ bias=use_bias,
+ norm=get_norm(norm, out_channels),
+ activation=deepcopy(activation),
+ )
+ )
+ weight_init.c2_xavier_fill(self.convs[-1])
+ # atrous convs
+ for dilation in dilations:
+ if use_depthwise_separable_conv:
+ self.convs.append(
+ DepthwiseSeparableConv2d(
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ padding=dilation,
+ dilation=dilation,
+ norm1=norm,
+ activation1=deepcopy(activation),
+ norm2=norm,
+ activation2=deepcopy(activation),
+ )
+ )
+ else:
+ self.convs.append(
+ Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ padding=dilation,
+ dilation=dilation,
+ bias=use_bias,
+ norm=get_norm(norm, out_channels),
+ activation=deepcopy(activation),
+ )
+ )
+ weight_init.c2_xavier_fill(self.convs[-1])
+ # image pooling
+ # We do not add BatchNorm because the spatial resolution is 1x1,
+ # the original TF implementation has BatchNorm.
+ if pool_kernel_size is None:
+ image_pooling = nn.Sequential(
+ nn.AdaptiveAvgPool2d(1),
+ Conv2d(in_channels, out_channels, 1, bias=True, activation=deepcopy(activation)),
+ )
+ else:
+ image_pooling = nn.Sequential(
+ nn.AvgPool2d(kernel_size=pool_kernel_size, stride=1),
+ Conv2d(in_channels, out_channels, 1, bias=True, activation=deepcopy(activation)),
+ )
+ weight_init.c2_xavier_fill(image_pooling[1])
+ self.convs.append(image_pooling)
+
+ self.project = Conv2d(
+ 5 * out_channels,
+ out_channels,
+ kernel_size=1,
+ bias=use_bias,
+ norm=get_norm(norm, out_channels),
+ activation=deepcopy(activation),
+ )
+ weight_init.c2_xavier_fill(self.project)
+
+ def forward(self, x):
+ size = x.shape[-2:]
+ if self.pool_kernel_size is not None:
+ if size[0] % self.pool_kernel_size[0] or size[1] % self.pool_kernel_size[1]:
+ raise ValueError(
+ "`pool_kernel_size` must be divisible by the shape of inputs. "
+ "Input size: {} `pool_kernel_size`: {}".format(size, self.pool_kernel_size)
+ )
+ res = []
+ for conv in self.convs:
+ res.append(conv(x))
+ res[-1] = F.interpolate(res[-1], size=size, mode="bilinear", align_corners=False)
+ res = torch.cat(res, dim=1)
+ res = self.project(res)
+ res = F.dropout(res, self.dropout, training=self.training) if self.dropout > 0 else res
+ return res
diff --git a/detectron2/detectron2/layers/batch_norm.py b/detectron2/detectron2/layers/batch_norm.py
new file mode 100755
index 0000000..dd8a804
--- /dev/null
+++ b/detectron2/detectron2/layers/batch_norm.py
@@ -0,0 +1,300 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import torch
+import torch.distributed as dist
+from fvcore.nn.distributed import differentiable_all_reduce
+from torch import nn
+from torch.nn import functional as F
+
+from detectron2.utils import comm, env
+
+from .wrappers import BatchNorm2d
+
+
+class FrozenBatchNorm2d(nn.Module):
+ """
+ BatchNorm2d where the batch statistics and the affine parameters are fixed.
+
+ It contains non-trainable buffers called
+ "weight" and "bias", "running_mean", "running_var",
+ initialized to perform identity transformation.
+
+ The pre-trained backbone models from Caffe2 only contain "weight" and "bias",
+ which are computed from the original four parameters of BN.
+ The affine transform `x * weight + bias` will perform the equivalent
+ computation of `(x - running_mean) / sqrt(running_var) * weight + bias`.
+ When loading a backbone model from Caffe2, "running_mean" and "running_var"
+ will be left unchanged as identity transformation.
+
+ Other pre-trained backbone models may contain all 4 parameters.
+
+ The forward is implemented by `F.batch_norm(..., training=False)`.
+ """
+
+ _version = 3
+
+ def __init__(self, num_features, eps=1e-5):
+ super().__init__()
+ self.num_features = num_features
+ self.eps = eps
+ self.register_buffer("weight", torch.ones(num_features))
+ self.register_buffer("bias", torch.zeros(num_features))
+ self.register_buffer("running_mean", torch.zeros(num_features))
+ self.register_buffer("running_var", torch.ones(num_features) - eps)
+
+ def forward(self, x):
+ if x.requires_grad:
+ # When gradients are needed, F.batch_norm will use extra memory
+ # because its backward op computes gradients for weight/bias as well.
+ scale = self.weight * (self.running_var + self.eps).rsqrt()
+ bias = self.bias - self.running_mean * scale
+ scale = scale.reshape(1, -1, 1, 1)
+ bias = bias.reshape(1, -1, 1, 1)
+ out_dtype = x.dtype # may be half
+ return x * scale.to(out_dtype) + bias.to(out_dtype)
+ else:
+ # When gradients are not needed, F.batch_norm is a single fused op
+ # and provide more optimization opportunities.
+ return F.batch_norm(
+ x,
+ self.running_mean,
+ self.running_var,
+ self.weight,
+ self.bias,
+ training=False,
+ eps=self.eps,
+ )
+
+ def _load_from_state_dict(
+ self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
+ ):
+ version = local_metadata.get("version", None)
+
+ if version is None or version < 2:
+ # No running_mean/var in early versions
+ # This will silent the warnings
+ if prefix + "running_mean" not in state_dict:
+ state_dict[prefix + "running_mean"] = torch.zeros_like(self.running_mean)
+ if prefix + "running_var" not in state_dict:
+ state_dict[prefix + "running_var"] = torch.ones_like(self.running_var)
+
+ super()._load_from_state_dict(
+ state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
+ )
+
+ def __repr__(self):
+ return "FrozenBatchNorm2d(num_features={}, eps={})".format(self.num_features, self.eps)
+
+ @classmethod
+ def convert_frozen_batchnorm(cls, module):
+ """
+ Convert all BatchNorm/SyncBatchNorm in module into FrozenBatchNorm.
+
+ Args:
+ module (torch.nn.Module):
+
+ Returns:
+ If module is BatchNorm/SyncBatchNorm, returns a new module.
+ Otherwise, in-place convert module and return it.
+
+ Similar to convert_sync_batchnorm in
+ https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/batchnorm.py
+ """
+ bn_module = nn.modules.batchnorm
+ bn_module = (bn_module.BatchNorm2d, bn_module.SyncBatchNorm)
+ res = module
+ if isinstance(module, bn_module):
+ res = cls(module.num_features)
+ if module.affine:
+ res.weight.data = module.weight.data.clone().detach()
+ res.bias.data = module.bias.data.clone().detach()
+ res.running_mean.data = module.running_mean.data
+ res.running_var.data = module.running_var.data
+ res.eps = module.eps
+ else:
+ for name, child in module.named_children():
+ new_child = cls.convert_frozen_batchnorm(child)
+ if new_child is not child:
+ res.add_module(name, new_child)
+ return res
+
+
+def get_norm(norm, out_channels):
+ """
+ Args:
+ norm (str or callable): either one of BN, SyncBN, FrozenBN, GN;
+ or a callable that takes a channel number and returns
+ the normalization layer as a nn.Module.
+
+ Returns:
+ nn.Module or None: the normalization layer
+ """
+ if norm is None:
+ return None
+ if isinstance(norm, str):
+ if len(norm) == 0:
+ return None
+ norm = {
+ "BN": BatchNorm2d,
+ # Fixed in https://github.com/pytorch/pytorch/pull/36382
+ "SyncBN": NaiveSyncBatchNorm if env.TORCH_VERSION <= (1, 5) else nn.SyncBatchNorm,
+ "FrozenBN": FrozenBatchNorm2d,
+ "GN": lambda channels: nn.GroupNorm(32, channels),
+ # for debugging:
+ "nnSyncBN": nn.SyncBatchNorm,
+ "naiveSyncBN": NaiveSyncBatchNorm,
+ # expose stats_mode N as an option to caller, required for zero-len inputs
+ "naiveSyncBN_N": lambda channels: NaiveSyncBatchNorm(channels, stats_mode="N"),
+ "LN": lambda channels: LayerNorm(channels),
+ }[norm]
+ return norm(out_channels)
+
+
+class NaiveSyncBatchNorm(BatchNorm2d):
+ """
+ In PyTorch<=1.5, ``nn.SyncBatchNorm`` has incorrect gradient
+ when the batch size on each worker is different.
+ (e.g., when scale augmentation is used, or when it is applied to mask head).
+
+ This is a slower but correct alternative to `nn.SyncBatchNorm`.
+
+ Note:
+ There isn't a single definition of Sync BatchNorm.
+
+ When ``stats_mode==""``, this module computes overall statistics by using
+ statistics of each worker with equal weight. The result is true statistics
+ of all samples (as if they are all on one worker) only when all workers
+ have the same (N, H, W). This mode does not support inputs with zero batch size.
+
+ When ``stats_mode=="N"``, this module computes overall statistics by weighting
+ the statistics of each worker by their ``N``. The result is true statistics
+ of all samples (as if they are all on one worker) only when all workers
+ have the same (H, W). It is slower than ``stats_mode==""``.
+
+ Even though the result of this module may not be the true statistics of all samples,
+ it may still be reasonable because it might be preferrable to assign equal weights
+ to all workers, regardless of their (H, W) dimension, instead of putting larger weight
+ on larger images. From preliminary experiments, little difference is found between such
+ a simplified implementation and an accurate computation of overall mean & variance.
+ """
+
+ def __init__(self, *args, stats_mode="", **kwargs):
+ super().__init__(*args, **kwargs)
+ assert stats_mode in ["", "N"]
+ self._stats_mode = stats_mode
+
+ def forward(self, input):
+ if comm.get_world_size() == 1 or not self.training:
+ return super().forward(input)
+
+ B, C = input.shape[0], input.shape[1]
+
+ half_input = input.dtype == torch.float16
+ if half_input:
+ # fp16 does not have good enough numerics for the reduction here
+ input = input.float()
+ mean = torch.mean(input, dim=[0, 2, 3])
+ meansqr = torch.mean(input * input, dim=[0, 2, 3])
+
+ if self._stats_mode == "":
+ assert B > 0, 'SyncBatchNorm(stats_mode="") does not support zero batch size.'
+ vec = torch.cat([mean, meansqr], dim=0)
+ vec = differentiable_all_reduce(vec) * (1.0 / dist.get_world_size())
+ mean, meansqr = torch.split(vec, C)
+ momentum = self.momentum
+ else:
+ if B == 0:
+ vec = torch.zeros([2 * C + 1], device=mean.device, dtype=mean.dtype)
+ vec = vec + input.sum() # make sure there is gradient w.r.t input
+ else:
+ vec = torch.cat(
+ [mean, meansqr, torch.ones([1], device=mean.device, dtype=mean.dtype)], dim=0
+ )
+ vec = differentiable_all_reduce(vec * B)
+
+ total_batch = vec[-1].detach()
+ momentum = total_batch.clamp(max=1) * self.momentum # no update if total_batch is 0
+ mean, meansqr, _ = torch.split(vec / total_batch.clamp(min=1), C) # avoid div-by-zero
+
+ var = meansqr - mean * mean
+ invstd = torch.rsqrt(var + self.eps)
+ scale = self.weight * invstd
+ bias = self.bias - mean * scale
+ scale = scale.reshape(1, -1, 1, 1)
+ bias = bias.reshape(1, -1, 1, 1)
+
+ self.running_mean += momentum * (mean.detach() - self.running_mean)
+ self.running_var += momentum * (var.detach() - self.running_var)
+ ret = input * scale + bias
+ if half_input:
+ ret = ret.half()
+ return ret
+
+
+class CycleBatchNormList(nn.ModuleList):
+ """
+ Implement domain-specific BatchNorm by cycling.
+
+ When a BatchNorm layer is used for multiple input domains or input
+ features, it might need to maintain a separate test-time statistics
+ for each domain. See Sec 5.2 in :paper:`rethinking-batchnorm`.
+
+ This module implements it by using N separate BN layers
+ and it cycles through them every time a forward() is called.
+
+ NOTE: The caller of this module MUST guarantee to always call
+ this module by multiple of N times. Otherwise its test-time statistics
+ will be incorrect.
+ """
+
+ def __init__(self, length: int, bn_class=nn.BatchNorm2d, **kwargs):
+ """
+ Args:
+ length: number of BatchNorm layers to cycle.
+ bn_class: the BatchNorm class to use
+ kwargs: arguments of the BatchNorm class, such as num_features.
+ """
+ self._affine = kwargs.pop("affine", True)
+ super().__init__([bn_class(**kwargs, affine=False) for k in range(length)])
+ if self._affine:
+ # shared affine, domain-specific BN
+ channels = self[0].num_features
+ self.weight = nn.Parameter(torch.ones(channels))
+ self.bias = nn.Parameter(torch.zeros(channels))
+ self._pos = 0
+
+ def forward(self, x):
+ ret = self[self._pos](x)
+ self._pos = (self._pos + 1) % len(self)
+
+ if self._affine:
+ w = self.weight.reshape(1, -1, 1, 1)
+ b = self.bias.reshape(1, -1, 1, 1)
+ return ret * w + b
+ else:
+ return ret
+
+ def extra_repr(self):
+ return f"affine={self._affine}"
+
+
+class LayerNorm(nn.Module):
+ """
+ A LayerNorm variant, popularized by Transformers, that performs point-wise mean and
+ variance normalization over the channel dimension for inputs that have shape
+ (batch_size, channels, height, width).
+ https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa B950
+ """
+
+ def __init__(self, normalized_shape, eps=1e-6):
+ super().__init__()
+ self.weight = nn.Parameter(torch.ones(normalized_shape))
+ self.bias = nn.Parameter(torch.zeros(normalized_shape))
+ self.eps = eps
+ self.normalized_shape = (normalized_shape,)
+
+ def forward(self, x):
+ u = x.mean(1, keepdim=True)
+ s = (x - u).pow(2).mean(1, keepdim=True)
+ x = (x - u) / torch.sqrt(s + self.eps)
+ x = self.weight[:, None, None] * x + self.bias[:, None, None]
+ return x
diff --git a/detectron2/detectron2/layers/blocks.py b/detectron2/detectron2/layers/blocks.py
new file mode 100755
index 0000000..1995a4b
--- /dev/null
+++ b/detectron2/detectron2/layers/blocks.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import fvcore.nn.weight_init as weight_init
+from torch import nn
+
+from .batch_norm import FrozenBatchNorm2d, get_norm
+from .wrappers import Conv2d
+
+
+"""
+CNN building blocks.
+"""
+
+
+class CNNBlockBase(nn.Module):
+ """
+ A CNN block is assumed to have input channels, output channels and a stride.
+ The input and output of `forward()` method must be NCHW tensors.
+ The method can perform arbitrary computation but must match the given
+ channels and stride specification.
+
+ Attribute:
+ in_channels (int):
+ out_channels (int):
+ stride (int):
+ """
+
+ def __init__(self, in_channels, out_channels, stride):
+ """
+ The `__init__` method of any subclass should also contain these arguments.
+
+ Args:
+ in_channels (int):
+ out_channels (int):
+ stride (int):
+ """
+ super().__init__()
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.stride = stride
+
+ def freeze(self):
+ """
+ Make this block not trainable.
+ This method sets all parameters to `requires_grad=False`,
+ and convert all BatchNorm layers to FrozenBatchNorm
+
+ Returns:
+ the block itself
+ """
+ for p in self.parameters():
+ p.requires_grad = False
+ FrozenBatchNorm2d.convert_frozen_batchnorm(self)
+ return self
+
+
+class DepthwiseSeparableConv2d(nn.Module):
+ """
+ A kxk depthwise convolution + a 1x1 convolution.
+
+ In :paper:`xception`, norm & activation are applied on the second conv.
+ :paper:`mobilenet` uses norm & activation on both convs.
+ """
+
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ padding=1,
+ dilation=1,
+ *,
+ norm1=None,
+ activation1=None,
+ norm2=None,
+ activation2=None,
+ ):
+ """
+ Args:
+ norm1, norm2 (str or callable): normalization for the two conv layers.
+ activation1, activation2 (callable(Tensor) -> Tensor): activation
+ function for the two conv layers.
+ """
+ super().__init__()
+ self.depthwise = Conv2d(
+ in_channels,
+ in_channels,
+ kernel_size=kernel_size,
+ padding=padding,
+ dilation=dilation,
+ groups=in_channels,
+ bias=not norm1,
+ norm=get_norm(norm1, in_channels),
+ activation=activation1,
+ )
+ self.pointwise = Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ bias=not norm2,
+ norm=get_norm(norm2, out_channels),
+ activation=activation2,
+ )
+
+ # default initialization
+ weight_init.c2_msra_fill(self.depthwise)
+ weight_init.c2_msra_fill(self.pointwise)
+
+ def forward(self, x):
+ return self.pointwise(self.depthwise(x))
diff --git a/detectron2/detectron2/layers/csrc/README.md b/detectron2/detectron2/layers/csrc/README.md
new file mode 100755
index 0000000..778ed3d
--- /dev/null
+++ b/detectron2/detectron2/layers/csrc/README.md
@@ -0,0 +1,7 @@
+
+
+To add a new Op:
+
+1. Create a new directory
+2. Implement new ops there
+3. Delcare its Python interface in `vision.cpp`.
diff --git a/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated.h b/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated.h
new file mode 100755
index 0000000..03f4211
--- /dev/null
+++ b/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated.h
@@ -0,0 +1,115 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#pragma once
+#include
+
+namespace detectron2 {
+
+at::Tensor ROIAlignRotated_forward_cpu(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio);
+
+at::Tensor ROIAlignRotated_backward_cpu(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio);
+
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+at::Tensor ROIAlignRotated_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio);
+
+at::Tensor ROIAlignRotated_backward_cuda(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio);
+#endif
+
+// Interface for Python
+inline at::Tensor ROIAlignRotated_forward(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const double spatial_scale,
+ const int64_t pooled_height,
+ const int64_t pooled_width,
+ const int64_t sampling_ratio) {
+ if (input.is_cuda()) {
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+ return ROIAlignRotated_forward_cuda(
+ input,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ sampling_ratio);
+#else
+ AT_ERROR("Detectron2 is not compiled with GPU support!");
+#endif
+ }
+ return ROIAlignRotated_forward_cpu(
+ input, rois, spatial_scale, pooled_height, pooled_width, sampling_ratio);
+}
+
+inline at::Tensor ROIAlignRotated_backward(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const double spatial_scale,
+ const int64_t pooled_height,
+ const int64_t pooled_width,
+ const int64_t batch_size,
+ const int64_t channels,
+ const int64_t height,
+ const int64_t width,
+ const int64_t sampling_ratio) {
+ if (grad.is_cuda()) {
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+ return ROIAlignRotated_backward_cuda(
+ grad,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio);
+#else
+ AT_ERROR("Detectron2 is not compiled with GPU support!");
+#endif
+ }
+ return ROIAlignRotated_backward_cpu(
+ grad,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio);
+}
+
+} // namespace detectron2
diff --git a/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cpu.cpp b/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cpu.cpp
new file mode 100755
index 0000000..2a3d305
--- /dev/null
+++ b/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cpu.cpp
@@ -0,0 +1,522 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include
+#include "ROIAlignRotated.h"
+
+// Note: this implementation originates from the Caffe2 ROIAlignRotated Op
+// and PyTorch ROIAlign (non-rotated) Op implementations.
+// The key difference between this implementation and those ones is
+// we don't do "legacy offset" in this version, as there aren't many previous
+// works, if any, using the "legacy" ROIAlignRotated Op.
+// This would make the interface a bit cleaner.
+
+namespace detectron2 {
+
+namespace {
+template
+struct PreCalc {
+ int pos1;
+ int pos2;
+ int pos3;
+ int pos4;
+ T w1;
+ T w2;
+ T w3;
+ T w4;
+};
+
+template
+void pre_calc_for_bilinear_interpolate(
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int iy_upper,
+ const int ix_upper,
+ T roi_start_h,
+ T roi_start_w,
+ T bin_size_h,
+ T bin_size_w,
+ int roi_bin_grid_h,
+ int roi_bin_grid_w,
+ T roi_center_h,
+ T roi_center_w,
+ T cos_theta,
+ T sin_theta,
+ std::vector>& pre_calc) {
+ int pre_calc_index = 0;
+ for (int ph = 0; ph < pooled_height; ph++) {
+ for (int pw = 0; pw < pooled_width; pw++) {
+ for (int iy = 0; iy < iy_upper; iy++) {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < ix_upper; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ // In image space, (y, x) is the order for Right Handed System,
+ // and this is essentially multiplying the point by a rotation matrix
+ // to rotate it counterclockwise through angle theta.
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+ // deal with: inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ PreCalc pc;
+ pc.pos1 = 0;
+ pc.pos2 = 0;
+ pc.pos3 = 0;
+ pc.pos4 = 0;
+ pc.w1 = 0;
+ pc.w2 = 0;
+ pc.w3 = 0;
+ pc.w4 = 0;
+ pre_calc[pre_calc_index] = pc;
+ pre_calc_index += 1;
+ continue;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+ if (x < 0) {
+ x = 0;
+ }
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ // save weights and indices
+ PreCalc pc;
+ pc.pos1 = y_low * width + x_low;
+ pc.pos2 = y_low * width + x_high;
+ pc.pos3 = y_high * width + x_low;
+ pc.pos4 = y_high * width + x_high;
+ pc.w1 = w1;
+ pc.w2 = w2;
+ pc.w3 = w3;
+ pc.w4 = w4;
+ pre_calc[pre_calc_index] = pc;
+
+ pre_calc_index += 1;
+ }
+ }
+ }
+ }
+}
+
+template
+void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+
+ if (x < 0) {
+ x = 0;
+ }
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = input[y_low * width + x_low];
+ // T v2 = input[y_low * width + x_high];
+ // T v3 = input[y_high * width + x_low];
+ // T v4 = input[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+template
+inline void add(T* address, const T& val) {
+ *address += val;
+}
+
+} // namespace
+
+template
+void ROIAlignRotatedForward(
+ const int nthreads,
+ const T* input,
+ const T& spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* rois,
+ T* output) {
+ int n_rois = nthreads / channels / pooled_width / pooled_height;
+ // (n, c, ph, pw) is an element in the pooled output
+ // can be parallelized using omp
+ // #pragma omp parallel for num_threads(32)
+ for (int n = 0; n < n_rois; n++) {
+ int index_n = n * channels * pooled_width * pooled_height;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ AT_ASSERTM(
+ roi_width >= 0 && roi_height >= 0,
+ "ROIs in ROIAlignRotated do not have non-negative size!");
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // We do average (integral) pooling inside a bin
+ const T count = std::max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ // we want to precalculate indices and weights shared by all channels,
+ // this is the key point of optimization
+ std::vector> pre_calc(
+ roi_bin_grid_h * roi_bin_grid_w * pooled_width * pooled_height);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ pre_calc_for_bilinear_interpolate(
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ roi_bin_grid_h,
+ roi_bin_grid_w,
+ roi_start_h,
+ roi_start_w,
+ bin_size_h,
+ bin_size_w,
+ roi_bin_grid_h,
+ roi_bin_grid_w,
+ roi_center_h,
+ roi_center_w,
+ cos_theta,
+ sin_theta,
+ pre_calc);
+
+ for (int c = 0; c < channels; c++) {
+ int index_n_c = index_n + c * pooled_width * pooled_height;
+ const T* offset_input =
+ input + (roi_batch_ind * channels + c) * height * width;
+ int pre_calc_index = 0;
+
+ for (int ph = 0; ph < pooled_height; ph++) {
+ for (int pw = 0; pw < pooled_width; pw++) {
+ int index = index_n_c + ph * pooled_width + pw;
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) {
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ PreCalc pc = pre_calc[pre_calc_index];
+ output_val += pc.w1 * offset_input[pc.pos1] +
+ pc.w2 * offset_input[pc.pos2] +
+ pc.w3 * offset_input[pc.pos3] + pc.w4 * offset_input[pc.pos4];
+
+ pre_calc_index += 1;
+ }
+ }
+ output_val /= count;
+
+ output[index] = output_val;
+ } // for pw
+ } // for ph
+ } // for c
+ } // for n
+}
+
+template
+void ROIAlignRotatedBackward(
+ const int nthreads,
+ // may not be contiguous. should index using n_stride, etc
+ const T* grad_output,
+ const T& spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* grad_input,
+ const T* rois,
+ const int n_stride,
+ const int c_stride,
+ const int h_stride,
+ const int w_stride) {
+ for (int index = 0; index < nthreads; index++) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ AT_ASSERTM(
+ roi_width >= 0 && roi_height >= 0,
+ "ROIs in ROIAlignRotated do not have non-negative size!");
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_grad_input =
+ grad_input + ((roi_batch_ind * channels + c) * height * width);
+
+ int output_offset = n * n_stride + c * c_stride;
+ const T* offset_grad_output = grad_output + output_offset;
+ const T grad_output_this_bin =
+ offset_grad_output[ph * h_stride + pw * w_stride];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height, width, y, x, w1, w2, w3, w4, x_low, x_high, y_low, y_high);
+
+ T g1 = grad_output_this_bin * w1 / count;
+ T g2 = grad_output_this_bin * w2 / count;
+ T g3 = grad_output_this_bin * w3 / count;
+ T g4 = grad_output_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ // atomic add is not needed for now since it is single threaded
+ add(offset_grad_input + y_low * width + x_low, static_cast(g1));
+ add(offset_grad_input + y_low * width + x_high, static_cast(g2));
+ add(offset_grad_input + y_high * width + x_low, static_cast(g3));
+ add(offset_grad_input + y_high * width + x_high, static_cast(g4));
+ } // if
+ } // ix
+ } // iy
+ } // for
+} // ROIAlignRotatedBackward
+
+at::Tensor ROIAlignRotated_forward_cpu(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio) {
+ AT_ASSERTM(input.device().is_cpu(), "input must be a CPU tensor");
+ AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor");
+
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlign_forward_cpu";
+ at::checkAllSameType(c, {input_t, rois_t});
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ at::Tensor output = at::zeros(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+
+ if (output.numel() == 0) {
+ return output;
+ }
+
+ auto input_ = input.contiguous(), rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES_AND_HALF(
+ input.scalar_type(), "ROIAlignRotated_forward", [&] {
+ ROIAlignRotatedForward(
+ output_size,
+ input_.data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois_.data_ptr(),
+ output.data_ptr());
+ });
+ return output;
+}
+
+at::Tensor ROIAlignRotated_backward_cpu(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio) {
+ AT_ASSERTM(grad.device().is_cpu(), "grad must be a CPU tensor");
+ AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlignRotated_backward_cpu";
+ at::checkAllSameType(c, {grad_t, rois_t});
+
+ at::Tensor grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ // handle possibly empty gradients
+ if (grad.numel() == 0) {
+ return grad_input;
+ }
+
+ // get stride values to ensure indexing into gradients is correct.
+ int n_stride = grad.stride(0);
+ int c_stride = grad.stride(1);
+ int h_stride = grad.stride(2);
+ int w_stride = grad.stride(3);
+
+ auto rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES_AND_HALF(
+ grad.scalar_type(), "ROIAlignRotated_forward", [&] {
+ ROIAlignRotatedBackward(
+ grad.numel(),
+ grad.data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ grad_input.data_ptr(),
+ rois_.data_ptr(),
+ n_stride,
+ c_stride,
+ h_stride,
+ w_stride);
+ });
+ return grad_input;
+}
+
+} // namespace detectron2
diff --git a/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cuda.cu b/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cuda.cu
new file mode 100755
index 0000000..fca1865
--- /dev/null
+++ b/detectron2/detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cuda.cu
@@ -0,0 +1,443 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include
+#include
+#include
+#include
+
+// TODO make it in a common file
+#define CUDA_1D_KERNEL_LOOP(i, n) \
+ for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \
+ i += blockDim.x * gridDim.x)
+
+// Note: this implementation originates from the Caffe2 ROIAlignRotated Op
+// and PyTorch ROIAlign (non-rotated) Op implementations.
+// The key difference between this implementation and those ones is
+// we don't do "legacy offset" in this version, as there aren't many previous
+// works, if any, using the "legacy" ROIAlignRotated Op.
+// This would make the interface a bit cleaner.
+
+namespace detectron2 {
+
+namespace {
+
+template
+__device__ T bilinear_interpolate(
+ const T* input,
+ const int height,
+ const int width,
+ T y,
+ T x) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ return 0;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+
+ if (x < 0) {
+ x = 0;
+ }
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ // do bilinear interpolation
+ T v1 = input[y_low * width + x_low];
+ T v2 = input[y_low * width + x_high];
+ T v3 = input[y_high * width + x_low];
+ T v4 = input[y_high * width + x_high];
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ return val;
+}
+
+template
+__device__ void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+
+ if (x < 0) {
+ x = 0;
+ }
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = input[y_low * width + x_low];
+ // T v2 = input[y_low * width + x_high];
+ // T v3 = input[y_high * width + x_low];
+ // T v4 = input[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+} // namespace
+
+template
+__global__ void RoIAlignRotatedForward(
+ const int nthreads,
+ const T* input,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* rois,
+ T* top_data) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ const T* offset_input =
+ input + (roi_batch_ind * channels + c) * height * width;
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ // We do average (inte gral) pooling inside a bin
+ const T count = max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+
+ T val = bilinear_interpolate(offset_input, height, width, y, x);
+ output_val += val;
+ }
+ }
+ output_val /= count;
+
+ top_data[index] = output_val;
+ }
+}
+
+template
+__global__ void RoIAlignRotatedBackwardFeature(
+ const int nthreads,
+ const T* top_diff,
+ const int num_rois,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* bottom_diff,
+ const T* rois) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_bottom_diff =
+ bottom_diff + (roi_batch_ind * channels + c) * height * width;
+
+ int top_offset = (n * channels + c) * pooled_height * pooled_width;
+ const T* offset_top_diff = top_diff + top_offset;
+ const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height, width, y, x, w1, w2, w3, w4, x_low, x_high, y_low, y_high);
+
+ T g1 = top_diff_this_bin * w1 / count;
+ T g2 = top_diff_this_bin * w2 / count;
+ T g3 = top_diff_this_bin * w3 / count;
+ T g4 = top_diff_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_low, static_cast(g1));
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_high, static_cast(g2));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_low, static_cast(g3));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_high, static_cast(g4));
+ } // if
+ } // ix
+ } // iy
+ } // CUDA_1D_KERNEL_LOOP
+} // RoIAlignRotatedBackward
+
+at::Tensor ROIAlignRotated_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio) {
+ AT_ASSERTM(input.device().is_cuda(), "input must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlignRotated_forward_cuda";
+ at::checkAllSameGPU(c, {input_t, rois_t});
+ at::checkAllSameType(c, {input_t, rois_t});
+ at::cuda::CUDAGuard device_guard(input.device());
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ auto output = at::empty(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(output_size), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ if (output.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+ }
+
+ auto input_ = input.contiguous(), rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES(
+ input.scalar_type(), "ROIAlignRotated_forward", [&] {
+ RoIAlignRotatedForward<<>>(
+ output_size,
+ input_.data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois_.data_ptr(),
+ output.data_ptr());
+ });
+ cudaDeviceSynchronize();
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+}
+
+// TODO remove the dependency on input and use instead its sizes -> save memory
+at::Tensor ROIAlignRotated_backward_cuda(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio) {
+ AT_ASSERTM(grad.device().is_cuda(), "grad must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2};
+ at::CheckedFrom c = "ROIAlign_backward_cuda";
+ at::checkAllSameGPU(c, {grad_t, rois_t});
+ at::checkAllSameType(c, {grad_t, rois_t});
+ at::cuda::CUDAGuard device_guard(grad.device());
+
+ auto num_rois = rois.size(0);
+ auto grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast