[
  {
    "path": "README.md",
    "content": "### The trained models are temporarily unavailable, but you can train the code using reasonable computational resource.\n\n# [Location-Sensitive Visual Recognition with Cross-IOU Loss](https://arxiv.org/abs/2104.04899)\n\nby [Kaiwen Duan](https://scholar.google.com/citations?hl=zh-CN&user=TFHRaZUAAAAJ&scilu=&scisig=AMD79ooAAAAAXLv9_7ddy26i4c6z5n9agk05m97faUdN&gmla=AJsN-F78W-h98Pb2H78j6lTKbjdn0fklhe2X_8CCPqRU2fC4KJEIbllhD2c5F0irMR3zDiehKt_SH26N2MHI1HlUMw6qRba9HMbiP3vnQfJqD82FrMAPdlU&sciund=10706678259143520926&gmla=AJsN-F5cOpNUdnI6YrZ9joRa6JE2nP6wFKU1GKVkNIfCmmgjk431Lg2BYCS6wn5WWZxdnzBjLfaUwdUJtvPXo53vfoOQoTGP5fHh2X0cCssVtXm8BI4PaM3_oQvKYtCx7o1wivIt1l49sDK6AZPvHLMxxPbC4GbZ1Q&sciund=10445692451499027349), [Lingxi Xie](http://lingxixie.com/Home.html), [Honggang Qi](http://people.ucas.ac.cn/~hgqi), [Song Bai](http://songbai.site/), [Qingming Huang](https://scholar.google.com/citations?user=J1vMnRgAAAAJ&hl=zh-CN) and [Qi Tian](https://scholar.google.com/citations?user=61b6eYkAAAAJ&hl=zh-CN)\n\n**The code to train and evaluate the proposed LSNet is available here. For more technical details, please refer to our [arXiv paper](https://arxiv.org/abs/2104.04899).**\n\n<div align=center>\n<img src=https://github.com/Duankaiwen/LSNet/blob/main/code/resources/lsvr.png width = \"600\" height = \"250\" alt=\"\" align=center />\n  \n*The location-sensitive visual recognition tasks, including object detection, instance segmentation, and human pose estimation, can be formulated into localizing an anchor point (in red) and a set of landmarks (in green). Our work aims to offer a unified framework for these tasks.*\n</div>\n\n## Abstract\n\n  Object detection, instance segmentation, and pose estimation are popular visual recognition tasks which require localizing the object by internal or boundary landmarks. This paper summarizes these tasks as location-sensitive visual recognition and proposes a unified solution named location-sensitive network (LSNet). Based on a deep neural network as the backbone, LSNet predicts an anchor point and a set of landmarks which together define the shape of the target object. The key to optimizing the LSNet lies in the ability of fitting various scales, for which we design a novel loss function named cross-IOU loss that computes the cross-IOU of each anchor-landmark pair to approximate the global IOU between the prediction and groundtruth. The flexibly located and accurately predicted landmarks also enable LSNet to incorporate richer contextual information for visual recognition. Evaluated on the MSCOCO dataset, LSNet set the new state-of-the-art accuracy for anchor-free object detection (a 53.5% box AP) and instance segmentation (a 40.2% mask AP), and shows promising performance in detecting multi-scale human poses. \n\n**If you encounter any problems in using our code, please contact Kaiwen Duan: kaiwenduan@outlook.com**\n\n## Bbox AP(%) on COCO test-dev\n|Method          |  Backbone       | epoch | MS<sub>train<sub> |  AP  | AP<sub>50</sub> | AP<sub>75</sub> | AP<sub>S</sub> | AP<sub>M</sub> | AP<sub>L</sub> |\n| :------------- | :-------:       | :---: | :---------------: | :--: | :-------------: | :-------------: | :------------: | :------------: | :------------: | \n|                |   \n| *Anchor-based:*|   \n|Libra R-CNN     | X-101-64x4d     | 12  |      N            | 43.0 |     64.0        |       47.0      |      25.3      |      45.6      |      54.6      |\n| AB+FSAF*       | X-101-64x4d     | 18  |      Y            | 44.6 |     65.2        |       48.6      |      29.7      |      47.1      |      54.6      |\n| FreeAnchor*    | X-101-32x8d     | 24  |      Y            | 47.3 |     66.3        |       51.5      |      30.6      |      50.4      |      59.0      |\n| GFLV1*         | X-101-32x8d     | 24  |      Y            | 48.2 |     67.4        |       52.6      |      29.2      |      51.7      |      60.2      |\n| ATSS*          | X-101-64x4d-DCN | 24  |      Y            | 50.7 |     68.9        |       56.3      |      33.2      |      52.9      |      62.4      |\n| PAA*           | X-101-64x4d-DCN | 24  |      Y            | 51.4 |     69.7        |       57.0      |      34.0      |      53.8      |      64.0      |\n| GFLV2*         | R2-101-DCN      | 24  |      Y            | 53.3 |     70.9        |       59.2      |      35.7      |      56.1      |      65.6      |\n| YOLOv4-P7*     | CSP-P7          | 450 |      Y            | 56.0 |     73.3        |       61.2      |      38.9      |      60.0      |      68.6      |\n|                |   \n| *Anchor-free:* |\n| ExtremeNet*    | HG-104          | 200 |      Y            | 43.2 |     59.8        |       46.4      |      24.1       |     46.0      |      57.1      | \n| RepPointsV1*   | R-101-DCN       | 24  |      Y            | 46.5 |     67.4        |       50.9      |      30.3       |     49.7      |      57.1      |\n| SAPD           | X-101-64x4d-DCN | 24  |      Y            | 47.4 |     67.4        |       51.1      |      28.1       |     50.3      |      61.5      |\n| CornerNet*     | HG-104          | 200 |      Y            | 42.1 |     57.8        |       45.3      |      20.8       |     44.8      |      56.7      |\n| DETR           | R-101           | 500 |      Y            | 44.9 |     64.7        |       47.7      |      23.7       |     49.5      |      62.3      |\n| CenterNet*     | HG-104          | 190 |      Y            | 47.0 |     64.5        |       50.7      |      28.9       |     49.9      |      58.9      |\n| CPNDet*        | HG-104          | 100 |      Y            | 49.2 |     67.4        |       53.7      |      31.0       |     51.9      |      62.4      |\n| BorderDet*     | X-101-64x4d-DCN | 24  |      Y            | 50.3 |     68.9        |       55.2      |      32.8       |     52.8      |      62.3      |\n| FCOS-BiFPN     | X-101-32x8-DCN  | 24  |      Y            | 50.4 |     68.9        |       55.0      |      33.2       |     53.0      |      62.7      |\n| RepPointsV2*   | X-101-64x4d-DCN | 24  |      Y            | 52.1 |     70.1        |       57.5      |      34.5       |     54.6      |      63.6      |\n|                |\n| LSNet          | R-50            | 24  |      Y            | 44.8 |     64.1        |       48.8      |      26.6       |     47.7      |      55.7      |\n| LSNet          | X-101-64x4d     | 24  |      Y            | 48.2 |     67.6        |       52.6      |      29.6       |     51.3      |      60.5      |\n| LSNet          | X-101-64x4d-DCN | 24  |      Y            | 49.6 |     69.0        |       54.1      |      30.3       |     52.8      |      62.8      |\n| LSNet-CPV      | X-101-64x4d-DCN | 24  |      Y            | 50.4 |     69.4        |       54.5      |      31.0       |     53.3      |      64.0      |\n| LSNet-CPV      | R2-101-DCN      | 24  |      Y            | 51.1 |     70.3        |       55.2      |      31.2       |     54.3      |      65.0      |\n| LSNet-CPV*     | R2-101-DCN      | 24  |      Y            | 53.5 |     71.1        |       59.2      |      35.2       |     56.4      |      65.8      |\n\n*A comparison between LSNet and the sate-of-the-art methods in object detection on the MS-COCO test-dev set. LSNet surpasses all competitors in the anchor-free group. The abbreviations are: ‘R’ – ResNet, ‘X’ – ResNeXt, ‘HG’ – Hourglass network, ‘R2’ – Res2Net, ‘CPV’ – corner point verification, ‘MStrain’ – multi-scale training, * – multi-scale testing.*\n             \n## Segm AP(%) on COCO test-dev\n|Method            |  Backbone       | epoch |  AP  | AP<sub>50</sub> | AP<sub>75</sub> | AP<sub>S</sub> | AP<sub>M</sub> | AP<sub>L</sub> |\n| :-------------   | :-------:       | :---: | :--: | :-------------: | :-------------: | :------------: | :------------: | :------------: | \n|                  |   \n| *Pixel-based:*   |   \n| YOLACT           | R-101           | 48    | 31.2 |      50.6       |     32.8        |       12.1      |      33.3      |      47.1     |\n| TensorMask       | R-101           | 72    | 37.1 |      59.3       |     39.4        |       17.1      |      39.1      |      51.6     |\n| Mask R-CNN       | X-101-32x4d     | 12    | 37.1 |      60.0       |     39.4        |       16.9      |      39.9      |      53.5     |\n| HTC              | X-101-64x4d     | 20    | 41.2 |      63.9       |     44.7        |       22.8      |      43.9      |      54.6     |\n| DetectoRS*       | X-101-64x4d     | 40    | 48.5 |      72.0       |     53.3        |       31.6      |      50.9      |      61.5     |\n|                  |   \n| *Contour-based:* |\n| ExtremeNet       | HG-104          | 100   | 18.9 |      44.5       |      13.7       |        10.4     |      20.4      |      28.3     |\n| DeepSnake        | DLA-34          | 120   | 30.3 |       -         |       -         |         -       |       -        |        -      |\n| PolarMask        | X-101-64x4d-DCN | 24    | 36.2 |      59.4       |      37.7       |        17.8     |      37.7      |      51.5     |\n|                  |\n| LSNet            | X-101-64x4d-DCN | 30    | 37.6 |      64.0       |      38.3       |        22.1     |      39.9      |      49.1     |\n| LSNet            | R2-101-DCN      | 30    | 38.0 |      64.6       |      39.0       |        22.4     |      40.6      |      49.2     |\n| LSNet*           | X-101-64x4d-DCN | 30    | 39.7 |      65.5       |      41.3       |        25.5     |      41.3      |      50.4     |\n| LSNet*           | R2-101-DCN      | 30    | 40.2 |      66.2       |      42.1       |        25.8     |      42.2      |      51.0     |\n\n*Comparison of LSNet to the sate-of-the-art methods in instance segmentation task on the COCO test-dev set. Our LSNet achieves the state-of-the-art accuracy for contour-based instance segmentation. ‘R’ - ResNet, ‘X’ - ResNeXt, ‘HG’ - Hourglass, ‘R2’ - Res2Net, * -  multi-scale testing.*\n\n## Keypoints AP(%) on COCO test-dev\n|Method               |  Backbone       | epoch |  AP  | AP<sub>50</sub> | AP<sub>75</sub> | AP<sub>M</sub> | AP<sub>L</sub> |\n| :-------------      | :-------:       | :---: | :--: | :-------------: | :-------------: | :------------: | :------------: | \n|                     |   \n| *Heatmap-based:*    |   \n| CenterNet-jd        |  DLA-34         | 320   | 57.9 |      84.7       |      63.1       |       52.5     |      67.4      |\n| OpenPose            |  VGG-19         | -     | 61.8 |      84.9       |      67.5       |       58.0     |      70.4      |\n| Pose-AE             |  HG             | 300   | 62.8 |      84.6       |      69.2       |       57.5     |      70.6      |\n| CenterNet-jd        |  HG104          | 150   | 63.0 |      86.8       |      69.6       |       58.9     |      70.4      |\n| Mask R-CNN          |  R-50           | 28    | 63.1 |      87.3       |      68.7       |       57.8     |      71.4      |\n| PersonLab           |  R-152          | >1000 | 66.5 |      85.5       |      71.3       |       62.3     |      70.0      |\n| HRNet               |  HRNet-W32      | 210   | 74.9 |      92.5       |      82.8       |       71.3     |      80.9      |\n|                     |   \n| *Regression-based:* |\n| CenterNet-reg       |  DLA-34         | 320   | 51.7 |      81.4       |       55.2      |       44.6     |      63.0      |\n| CenterNet-reg       |  HG-104         | 150   | 55.0 |      83.5       |       59.7      |       49.4     |      64.0      |\n|                     |\n| LSNet w/ obj-box    |  X-101-64x4d-DCN| 60    | 55.7 |      81.3       |       61.0      |       52.9     |      60.5      |\n| LSNet w/ kps-box    |  X-101-64x4d-DCN| 20    | 59.0 |      83.6       |       65.2      |       53.3     |      67.9      |\n\n*Comparison of LSNet to the sate-of-the-art methods in pose estimation task on the COCO test-dev set. LSNet\npredict the keypoints by regression. ‘obj-box’ and ‘kps-box’ denote the object bounding boxes and the keypoint-boxes,\nrespectively. For LSNet w/ kps-box, we fine-tune the model from the LSNet w/ kps-box for another 20 epochs.*\n\n## Visualization\n\n<div align=center>\n<img src=https://github.com/Duankaiwen/LSNet/blob/main/code/resources/dect-segm-pose.png width = \"1000\" height = \"250\" alt=\"\" align=center />\n  \n*Some location-sensitive visual recognition results on the MS-COCO validation set.*\n</div>\n\n\n<div align=center>\n<img src=https://github.com/Duankaiwen/LSNet/blob/main/code/resources/pose1.png width = \"400\" height = \"350\" alt=\"\" align=center />\n  \n*We compared with the CenterNet to show that our LSNet w/ ‘obj-box’ tends to predict more human pose of small scales, which are not annotated on the dataset. Only pose results with scores higher than 0:3 are shown for both methods.*\n</div>\n\n<div align=center>\n<img src=https://github.com/Duankaiwen/LSNet/blob/main/code/resources/pose2.png width = \"500\" height = \"800\" alt=\"\" align=center />\n  \n*Left: LSNet uses the object bounding boxes to assign training samples. Right: LSNet uses the keypoint-boxes to\nassign training samples. Although LSNet with keypoint-boxes enjoys higher AP score, its ability of perceiving multi-scale\nhuman instances is weakened.*\n</div>\n\n## Preparation\nThe master branch works with PyTorch 1.5.0\n\nThe dataset directory should be like this:\n```plain\n├── data\n│   ├── coco\n│   │   ├── annotations\n│   │   ├── images\n            ├── train2017\n            ├── val2017\n            ├── test2017\n```\n\nGenerate extreme point annotation from segmentation:\n- ```cd code/tools```\n- ```python gen_coco_lsvr.py```\n- ```cd ..```\n\n## Installation\n\n##### 1. Installing cocoapi \n- ```cd cocoapi/pycocotools```\n- ```python setup.py develop```\n- ```cd ../..```\n\n##### 2. Installing mmcv \n- ```cd mmcv```\n- ```pip install -e.```\n- ```cd ..```\n\n##### 3. Installing mmdet \n- ```python setup.py develop```\n\n## Training and Evaluation\nOur LSNet is based on [mmdetection](https://github.com/open-mmlab/mmdetection). Please check [with existing dataset](https://github.com/open-mmlab/mmdetection/blob/master/docs/1_exist_data_model.md) for Training and Evaluation.\n\n\n\n\n\n"
  },
  {
    "path": "checkpoints/000.txt",
    "content": ""
  },
  {
    "path": "code/LICENSE",
    "content": "    MIT License\n\n    Copyright (c) Microsoft Corporation. All rights reserved.\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE\n"
  },
  {
    "path": "code/ThirdPartyNotices.txt",
    "content": "************************************************************************\n\nTHIRD-PARTY SOFTWARE NOTICES AND INFORMATION\n\nmmdetection (https://github.com/open-mmlab/mmdetection)\n\nCopyright 2018-2019 Open-MMLab. All rights reserved.\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2018-2019 Open-MMLab.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\nRepPointsV2 (https://github.com/Scalsol/RepPointsV2)\n\nMIT License\n\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE"
  },
  {
    "path": "code/cocoapi/.github/workflows/build.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: build\n\non: [push, pull_request]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [3.6, 3.7, 3.8]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v1\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install linting dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install flake8 isort==4.3.21 yapf\n    - name: Lint with flake8\n      run: flake8 --max-complexity 20 .\n    - name: Lint with isort\n      run: isort -rc --check-only --diff pycocotools/ lvis/\n    - name: Format with yapf\n      run: yapf -r -d pycocotools/ lvis/\n    - name: Install python dependencies\n      run: pip install numpy\n    - name: Build and install pycocotools\n      run: cd pycocotools && rm -rf *.eggs-info && pip install .\n    - name: Build and install lvis\n      run: cd lvis && rm -rf *.eggs-info && pip install .\n"
  },
  {
    "path": "code/cocoapi/.github/workflows/deploy.yml",
    "content": "name: deploy\n\non: push\n\njobs:\n  build-n-publish:\n    runs-on: ubuntu-latest\n    if: startsWith(github.event.ref, 'refs/tags')\n    steps:\n      - uses: actions/checkout@v2\n      - name: Set up Python 3.7\n        uses: actions/setup-python@v2\n        with:\n          python-version: 3.7\n      - name: Build mmpycocotools\n        run: |\n          pip install -r lvis/requirements.txt\n          cd pycocotools\n          python setup.py sdist\n      - name: Build mmvlis\n        run: |\n          pip install wheel\n          cd lvis\n          python setup.py sdist bdist_wheel\n      - name: Publish distribution to PyPI\n        run: |\n          pip install twine\n          twine upload pycocotools/dist/* -u __token__ -p ${{ secrets.pypi_password }}\n          twine upload lvis/dist/* -u __token__ -p ${{ secrets.pypi_password }}\n"
  },
  {
    "path": "code/cocoapi/.gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\nimages/\nannotations/\nresults/\nexternal/\n.vscode\n.idea\n.DS_Store\n\npycocotools/pycocotools/__init__.pyc\npycocotools/pycocotools/_mask.c\npycocotools/pycocotools/_mask.so\npycocotools/pycocotools/coco.pyc\npycocotools/pycocotools/cocoeval.pyc\npycocotools/pycocotools/mask.pyc\n"
  },
  {
    "path": "code/cocoapi/.isort.cfg",
    "content": "[settings]\nknown_third_party = cv2,matplotlib,numpy,setuptools\n"
  },
  {
    "path": "code/cocoapi/.pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://gitlab.com/pycqa/flake8.git\n    rev: 3.8.0\n    hooks:\n      - id: flake8\n  - repo: https://github.com/asottile/seed-isort-config\n    rev: v2.1.0\n    hooks:\n      - id: seed-isort-config\n  - repo: https://github.com/timothycrosley/isort\n    rev: 4.3.21\n    hooks:\n      - id: isort\n  - repo: https://github.com/pre-commit/mirrors-yapf\n    rev: v0.29.0\n    hooks:\n      - id: yapf\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v2.5.0\n    hooks:\n      - id: trailing-whitespace\n      - id: check-yaml\n      - id: end-of-file-fixer\n      - id: requirements-txt-fixer\n      - id: double-quote-string-fixer\n      - id: check-merge-conflict\n      - id: fix-encoding-pragma\n        args: [\"--remove\"]\n"
  },
  {
    "path": "code/cocoapi/MANIFEST.in",
    "content": "include pycocotools/pycocotools/*.pyx\ninclude lvis/requirements.txt\n"
  },
  {
    "path": "code/cocoapi/README.md",
    "content": "# OpenMMLab cocoapi\n\nIn this repo, we merged COCO and LVIS API into one repo.\n\nFor bug fixes and better compatability with OpenMMLab projects, we fork from original\nrepo, which receive few updates is likely to cause problems with some latest dependencies like numpy.\nWe remove some legacy codes and unify the api of COCO and LVIS since they share similar functions.\n\nNotes:\n\n* We add snack case aliases for functions of [COCO](pycocotools/coco.py).\n* The the package version requirement of `lvis-api` is relaxed.\n* The major version of `cocoapi` and `lvis-api` in this repo is offseted by 10.\n  Namely, `cocoapi@2.0.0->cocoapi@12.0.0`, `lvis-api@0.5.2->lvis-api@10.5.2`.\n\n## Installation\n\nCurrently, you could install by run\n\n```shell\n# Install cocoapi\npip install \"git+https://github.com/open-mmlab/cocoapi.git#subdirectory=pycocotools\"\n# Install lvis-api\npip install \"git+https://github.com/open-mmlab/cocoapi.git#subdirectory=lvis\"\n```\n\n## Reference\n\n* [cocoapi](https://github.com/cocodataset/cocoapi) of [COCO dataset](http://cocodataset.org/).\n* [lvis-api](https://github.com/lvis-dataset/lvis-api) of [LVIS dataset](http://lvisdataset.org).\n"
  },
  {
    "path": "code/cocoapi/license.txt",
    "content": "Copyright (c) 2014, Piotr Dollar and Tsung-Yi Lin\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those\nof the authors and should not be interpreted as representing official policies,\neither expressed or implied, of the FreeBSD Project.\n"
  },
  {
    "path": "code/cocoapi/lvis/lvis/__init__.py",
    "content": "from .eval import LVISEval\nfrom .lvis import LVIS\nfrom .results import LVISResults\nfrom .vis import LVISVis\n\n__all__ = ['LVIS', 'LVISResults', 'LVISEval', 'LVISVis']\n__version__ = '10.5.3'\n"
  },
  {
    "path": "code/cocoapi/lvis/lvis/colormap.py",
    "content": "\"\"\"An awesome colormap for really neat visualizations. Taken from detectron.\"\"\"\n\nimport numpy as np\n\n\ndef colormap(rgb=False):\n    color_list = np.array([\n        0.000,\n        0.447,\n        0.741,\n        0.850,\n        0.325,\n        0.098,\n        0.929,\n        0.694,\n        0.125,\n        0.494,\n        0.184,\n        0.556,\n        0.466,\n        0.674,\n        0.188,\n        0.301,\n        0.745,\n        0.933,\n        0.635,\n        0.078,\n        0.184,\n        0.300,\n        0.300,\n        0.300,\n        0.600,\n        0.600,\n        0.600,\n        1.000,\n        0.000,\n        0.000,\n        1.000,\n        0.500,\n        0.000,\n        0.749,\n        0.749,\n        0.000,\n        0.000,\n        1.000,\n        0.000,\n        0.000,\n        0.000,\n        1.000,\n        0.667,\n        0.000,\n        1.000,\n        0.333,\n        0.333,\n        0.000,\n        0.333,\n        0.667,\n        0.000,\n        0.333,\n        1.000,\n        0.000,\n        0.667,\n        0.333,\n        0.000,\n        0.667,\n        0.667,\n        0.000,\n        0.667,\n        1.000,\n        0.000,\n        1.000,\n        0.333,\n        0.000,\n        1.000,\n        0.667,\n        0.000,\n        1.000,\n        1.000,\n        0.000,\n        0.000,\n        0.333,\n        0.500,\n        0.000,\n        0.667,\n        0.500,\n        0.000,\n        1.000,\n        0.500,\n        0.333,\n        0.000,\n        0.500,\n        0.333,\n        0.333,\n        0.500,\n        0.333,\n        0.667,\n        0.500,\n        0.333,\n        1.000,\n        0.500,\n        0.667,\n        0.000,\n        0.500,\n        0.667,\n        0.333,\n        0.500,\n        0.667,\n        0.667,\n        0.500,\n        0.667,\n        1.000,\n        0.500,\n        1.000,\n        0.000,\n        0.500,\n        1.000,\n        0.333,\n        0.500,\n        1.000,\n        0.667,\n        0.500,\n        1.000,\n        1.000,\n        0.500,\n        0.000,\n        0.333,\n        1.000,\n        0.000,\n        0.667,\n        1.000,\n        0.000,\n        1.000,\n        1.000,\n        0.333,\n        0.000,\n        1.000,\n        0.333,\n        0.333,\n        1.000,\n        0.333,\n        0.667,\n        1.000,\n        0.333,\n        1.000,\n        1.000,\n        0.667,\n        0.000,\n        1.000,\n        0.667,\n        0.333,\n        1.000,\n        0.667,\n        0.667,\n        1.000,\n        0.667,\n        1.000,\n        1.000,\n        1.000,\n        0.000,\n        1.000,\n        1.000,\n        0.333,\n        1.000,\n        1.000,\n        0.667,\n        1.000,\n        0.167,\n        0.000,\n        0.000,\n        0.333,\n        0.000,\n        0.000,\n        0.500,\n        0.000,\n        0.000,\n        0.667,\n        0.000,\n        0.000,\n        0.833,\n        0.000,\n        0.000,\n        1.000,\n        0.000,\n        0.000,\n        0.000,\n        0.167,\n        0.000,\n        0.000,\n        0.333,\n        0.000,\n        0.000,\n        0.500,\n        0.000,\n        0.000,\n        0.667,\n        0.000,\n        0.000,\n        0.833,\n        0.000,\n        0.000,\n        1.000,\n        0.000,\n        0.000,\n        0.000,\n        0.167,\n        0.000,\n        0.000,\n        0.333,\n        0.000,\n        0.000,\n        0.500,\n        0.000,\n        0.000,\n        0.667,\n        0.000,\n        0.000,\n        0.833,\n        0.000,\n        0.000,\n        1.000,\n        0.000,\n        0.000,\n        0.000,\n        0.143,\n        0.143,\n        0.143,\n        0.286,\n        0.286,\n        0.286,\n        0.429,\n        0.429,\n        0.429,\n        0.571,\n        0.571,\n        0.571,\n        0.714,\n        0.714,\n        0.714,\n        0.857,\n        0.857,\n        0.857,\n        1.000,\n        1.000,\n        1.000,\n    ]).astype(np.float32)\n    color_list = color_list.reshape((-1, 3)) * 255\n    if not rgb:\n        color_list = color_list[:, ::-1]\n    return color_list\n"
  },
  {
    "path": "code/cocoapi/lvis/lvis/eval.py",
    "content": "import datetime\nimport logging\nfrom collections import OrderedDict, defaultdict\n\nimport numpy as np\n\nimport pycocotools.mask as mask_utils\n\nfrom .lvis import LVIS\nfrom .results import LVISResults\n\n\nclass LVISEval:\n    def __init__(self, lvis_gt, lvis_dt, iou_type='segm'):\n        \"\"\"Constructor for LVISEval.\n        Args:\n            lvis_gt (LVIS class instance, or str containing path of annotation\n                file)\n            lvis_dt (LVISResult class instance, or str containing path of\n                result file, or list of dict)\n            iou_type (str): segm or bbox evaluation\n        \"\"\"\n        self.logger = logging.getLogger(__name__)\n\n        if iou_type not in ['bbox', 'segm']:\n            raise ValueError('iou_type: {} is not supported.'.format(iou_type))\n\n        if isinstance(lvis_gt, LVIS):\n            self.lvis_gt = lvis_gt\n        elif isinstance(lvis_gt, str):\n            self.lvis_gt = LVIS(lvis_gt)\n        else:\n            raise TypeError('Unsupported type {} of lvis_gt.'.format(lvis_gt))\n\n        if isinstance(lvis_dt, LVISResults):\n            self.lvis_dt = lvis_dt\n        elif isinstance(lvis_dt, (str, list)):\n            self.lvis_dt = LVISResults(self.lvis_gt, lvis_dt)\n        else:\n            raise TypeError('Unsupported type {} of lvis_dt.'.format(lvis_dt))\n\n        # per-image per-category evaluation results\n        self.eval_imgs = defaultdict(list)\n        self.eval = {}  # accumulated evaluation results\n        self._gts = defaultdict(list)  # gt for evaluation\n        self._dts = defaultdict(list)  # dt for evaluation\n        self.params = Params(iou_type=iou_type)  # parameters\n        self.results = OrderedDict()\n        self.ious = {}  # ious between all gts and dts\n\n        self.params.img_ids = sorted(self.lvis_gt.get_img_ids())\n        self.params.cat_ids = sorted(self.lvis_gt.get_cat_ids())\n\n    def _to_mask(self, anns, lvis):\n        for ann in anns:\n            rle = lvis.ann_to_rle(ann)\n            ann['segmentation'] = rle\n\n    def _prepare(self):\n        \"\"\"Prepare self._gts and self._dts for evaluation based on params.\"\"\"\n\n        cat_ids = self.params.cat_ids if self.params.cat_ids else None\n\n        gts = self.lvis_gt.load_anns(\n            self.lvis_gt.get_ann_ids(img_ids=self.params.img_ids,\n                                     cat_ids=cat_ids))\n        dts = self.lvis_dt.load_anns(\n            self.lvis_dt.get_ann_ids(img_ids=self.params.img_ids,\n                                     cat_ids=cat_ids))\n        # convert ground truth to mask if iou_type == 'segm'\n        if self.params.iou_type == 'segm':\n            self._to_mask(gts, self.lvis_gt)\n            self._to_mask(dts, self.lvis_dt)\n\n        # set ignore flag\n        for gt in gts:\n            if 'ignore' not in gt:\n                gt['ignore'] = 0\n\n        for gt in gts:\n            self._gts[gt['image_id'], gt['category_id']].append(gt)\n\n        # For federated dataset evaluation we will filter out all dt for an\n        # image which belong to categories not present in gt and not present in\n        # the negative list for an image. In other words detector is not\n        # penalized for categories about which we don't have gt information\n        # about their presence or absence in an image.\n        img_data = self.lvis_gt.load_imgs(ids=self.params.img_ids)\n        # per image map of categories not present in image\n        img_nl = {d['id']: d['neg_category_ids'] for d in img_data}\n        # per image list of categories present in image\n        img_pl = defaultdict(set)\n        for ann in gts:\n            img_pl[ann['image_id']].add(ann['category_id'])\n        # per image map of categoires which have missing gt. For these\n        # categories we don't penalize the detector for flase positives.\n        self.img_nel = {\n            d['id']: d['not_exhaustive_category_ids']\n            for d in img_data\n        }\n\n        for dt in dts:\n            img_id, cat_id = dt['image_id'], dt['category_id']\n            if cat_id not in img_nl[img_id] and cat_id not in img_pl[img_id]:\n                continue\n            self._dts[img_id, cat_id].append(dt)\n\n        self.freq_groups = self._prepare_freq_group()\n\n    def _prepare_freq_group(self):\n        freq_groups = [[] for _ in self.params.img_count_lbl]\n        cat_data = self.lvis_gt.load_cats(self.params.cat_ids)\n        for idx, _cat_data in enumerate(cat_data):\n            frequency = _cat_data['frequency']\n            freq_groups[self.params.img_count_lbl.index(frequency)].append(idx)\n        return freq_groups\n\n    def evaluate(self):\n        \"\"\"\n        Run per image evaluation on given images and store results\n        (a list of dict) in self.eval_imgs.\n        \"\"\"\n        self.logger.info('Running per image evaluation.')\n        self.logger.info('Evaluate annotation type *{}*'.format(\n            self.params.iou_type))\n\n        self.params.img_ids = list(np.unique(self.params.img_ids))\n\n        if self.params.use_cats:\n            cat_ids = self.params.cat_ids\n        else:\n            cat_ids = [-1]\n\n        self._prepare()\n\n        self.ious = {(img_id, cat_id): self.compute_iou(img_id, cat_id)\n                     for img_id in self.params.img_ids for cat_id in cat_ids}\n\n        # loop through images, area range, max detection number\n        self.eval_imgs = [\n            self.evaluate_img(img_id, cat_id, area_rng) for cat_id in cat_ids\n            for area_rng in self.params.area_rng\n            for img_id in self.params.img_ids\n        ]\n\n    def _get_gt_dt(self, img_id, cat_id):\n        \"\"\"Create gt, dt which are list of anns/dets. If use_cats is true\n        only anns/dets corresponding to tuple (img_id, cat_id) will be\n        used. Else, all anns/dets in image are used and cat_id is not used.\n        \"\"\"\n        if self.params.use_cats:\n            gt = self._gts[img_id, cat_id]\n            dt = self._dts[img_id, cat_id]\n        else:\n            gt = [\n                _ann for _cat_id in self.params.cat_ids\n                for _ann in self._gts[img_id, cat_id]\n            ]\n            dt = [\n                _ann for _cat_id in self.params.cat_ids\n                for _ann in self._dts[img_id, cat_id]\n            ]\n        return gt, dt\n\n    def compute_iou(self, img_id, cat_id):\n        gt, dt = self._get_gt_dt(img_id, cat_id)\n\n        if len(gt) == 0 and len(dt) == 0:\n            return []\n\n        # Sort detections in decreasing order of score.\n        idx = np.argsort([-d['score'] for d in dt], kind='mergesort')\n        dt = [dt[i] for i in idx]\n\n        iscrowd = [int(False)] * len(gt)\n\n        if self.params.iou_type == 'segm':\n            ann_type = 'segmentation'\n        elif self.params.iou_type == 'bbox':\n            ann_type = 'bbox'\n        else:\n            raise ValueError('Unknown iou_type for iou computation.')\n        gt = [g[ann_type] for g in gt]\n        dt = [d[ann_type] for d in dt]\n\n        # compute iou between each dt and gt region\n        # will return array of shape len(dt), len(gt)\n        ious = mask_utils.iou(dt, gt, iscrowd)\n        return ious\n\n    def evaluate_img(self, img_id, cat_id, area_rng):\n        \"\"\"Perform evaluation for single category and image.\"\"\"\n        gt, dt = self._get_gt_dt(img_id, cat_id)\n\n        if len(gt) == 0 and len(dt) == 0:\n            return None\n\n        # Add another filed _ignore to only consider anns based on area range.\n        for g in gt:\n            if g['ignore'] or (g['area'] < area_rng[0]\n                               or g['area'] > area_rng[1]):\n                g['_ignore'] = 1\n            else:\n                g['_ignore'] = 0\n\n        # Sort gt ignore last\n        gt_idx = np.argsort([g['_ignore'] for g in gt], kind='mergesort')\n        gt = [gt[i] for i in gt_idx]\n\n        # Sort dt highest score first\n        dt_idx = np.argsort([-d['score'] for d in dt], kind='mergesort')\n        dt = [dt[i] for i in dt_idx]\n\n        # load computed ious\n        ious = (self.ious[img_id, cat_id][:, gt_idx]\n                if len(self.ious[img_id, cat_id]) > 0 else self.ious[img_id,\n                                                                     cat_id])\n\n        num_thrs = len(self.params.iou_thrs)\n        num_gt = len(gt)\n        num_dt = len(dt)\n\n        # Array to store the \"id\" of the matched dt/gt\n        gt_m = np.zeros((num_thrs, num_gt))\n        dt_m = np.zeros((num_thrs, num_dt))\n\n        gt_ig = np.array([g['_ignore'] for g in gt])\n        dt_ig = np.zeros((num_thrs, num_dt))\n\n        for iou_thr_idx, iou_thr in enumerate(self.params.iou_thrs):\n            if len(ious) == 0:\n                break\n\n            for dt_idx, _dt in enumerate(dt):\n                iou = min([iou_thr, 1 - 1e-10])\n                # information about best match so far (m=-1 -> unmatched)\n                # store the gt_idx which matched for _dt\n                m = -1\n                for gt_idx, _ in enumerate(gt):\n                    # if this gt already matched continue\n                    if gt_m[iou_thr_idx, gt_idx] > 0:\n                        continue\n                    # if _dt matched to reg gt, and on ignore gt, stop\n                    if m > -1 and gt_ig[m] == 0 and gt_ig[gt_idx] == 1:\n                        break\n                    # continue to next gt unless better match made\n                    if ious[dt_idx, gt_idx] < iou:\n                        continue\n                    # if match successful and best so far, store appropriately\n                    iou = ious[dt_idx, gt_idx]\n                    m = gt_idx\n\n                # No match found for _dt, go to next _dt\n                if m == -1:\n                    continue\n\n                # if gt to ignore for some reason update dt_ig.\n                # Should not be used in evaluation.\n                dt_ig[iou_thr_idx, dt_idx] = gt_ig[m]\n                # _dt match found, update gt_m, and dt_m with \"id\"\n                dt_m[iou_thr_idx, dt_idx] = gt[m]['id']\n                gt_m[iou_thr_idx, m] = _dt['id']\n\n        # For LVIS we will ignore any unmatched detection if that category was\n        # not exhaustively annotated in gt.\n        dt_ig_mask = [\n            d['area'] < area_rng[0] or d['area'] > area_rng[1]\n            or d['category_id'] in self.img_nel[d['image_id']] for d in dt\n        ]\n        dt_ig_mask = np.array(dt_ig_mask).reshape((1, num_dt))  # 1 X num_dt\n        dt_ig_mask = np.repeat(dt_ig_mask, num_thrs, 0)  # num_thrs X num_dt\n        # Based on dt_ig_mask ignore any unmatched detection by updating dt_ig\n        dt_ig = np.logical_or(dt_ig, np.logical_and(dt_m == 0, dt_ig_mask))\n        # store results for given image and category\n        return {\n            'image_id': img_id,\n            'category_id': cat_id,\n            'area_rng': area_rng,\n            'dt_ids': [d['id'] for d in dt],\n            'gt_ids': [g['id'] for g in gt],\n            'dt_matches': dt_m,\n            'gt_matches': gt_m,\n            'dt_scores': [d['score'] for d in dt],\n            'gt_ignore': gt_ig,\n            'dt_ignore': dt_ig,\n        }\n\n    def accumulate(self):\n        \"\"\"Accumulate per image evaluation results and store the result in\n        self.eval.\n        \"\"\"\n        self.logger.info('Accumulating evaluation results.')\n\n        if not self.eval_imgs:\n            self.logger.warn('Please run evaluate first.')\n\n        if self.params.use_cats:\n            cat_ids = self.params.cat_ids\n        else:\n            cat_ids = [-1]\n\n        num_thrs = len(self.params.iou_thrs)\n        num_recalls = len(self.params.rec_thrs)\n        num_cats = len(cat_ids)\n        num_area_rngs = len(self.params.area_rng)\n        num_imgs = len(self.params.img_ids)\n\n        # -1 for absent categories\n        precision = -np.ones((num_thrs, num_recalls, num_cats, num_area_rngs))\n        recall = -np.ones((num_thrs, num_cats, num_area_rngs))\n\n        # Initialize dt_pointers\n        dt_pointers = {}\n        for cat_idx in range(num_cats):\n            dt_pointers[cat_idx] = {}\n            for area_idx in range(num_area_rngs):\n                dt_pointers[cat_idx][area_idx] = {}\n\n        # Per category evaluation\n        for cat_idx in range(num_cats):\n            Nk = cat_idx * num_area_rngs * num_imgs\n            for area_idx in range(num_area_rngs):\n                Na = area_idx * num_imgs\n                E = [\n                    self.eval_imgs[Nk + Na + img_idx]\n                    for img_idx in range(num_imgs)\n                ]\n                # Remove elements which are None\n                E = [e for e in E if e is not None]\n                if len(E) == 0:\n                    continue\n\n                # Append all scores: shape (N,)\n                dt_scores = np.concatenate([e['dt_scores'] for e in E], axis=0)\n                dt_ids = np.concatenate([e['dt_ids'] for e in E], axis=0)\n\n                dt_idx = np.argsort(-dt_scores, kind='mergesort')\n                dt_scores = dt_scores[dt_idx]\n                dt_ids = dt_ids[dt_idx]\n\n                dt_m = np.concatenate([e['dt_matches'] for e in E],\n                                      axis=1)[:, dt_idx]\n                dt_ig = np.concatenate([e['dt_ignore'] for e in E],\n                                       axis=1)[:, dt_idx]\n\n                gt_ig = np.concatenate([e['gt_ignore'] for e in E])\n                # num gt anns to consider\n                num_gt = np.count_nonzero(gt_ig == 0)\n\n                if num_gt == 0:\n                    continue\n\n                tps = np.logical_and(dt_m, np.logical_not(dt_ig))\n                fps = np.logical_and(np.logical_not(dt_m),\n                                     np.logical_not(dt_ig))\n\n                tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)\n                fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)\n\n                dt_pointers[cat_idx][area_idx] = {\n                    'dt_ids': dt_ids,\n                    'tps': tps,\n                    'fps': fps,\n                }\n\n                for iou_thr_idx, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):\n                    tp = np.array(tp)\n                    fp = np.array(fp)\n                    num_tp = len(tp)\n                    rc = tp / num_gt\n                    if num_tp:\n                        recall[iou_thr_idx, cat_idx, area_idx] = rc[-1]\n                    else:\n                        recall[iou_thr_idx, cat_idx, area_idx] = 0\n\n                    # np.spacing(1) ~= eps\n                    pr = tp / (fp + tp + np.spacing(1))\n                    pr = pr.tolist()\n\n                    # Replace each precision value with the maximum precision\n                    # value to the right of that recall level. This ensures\n                    # that the  calculated AP value will be less suspectable\n                    # to small variations in the ranking.\n                    for i in range(num_tp - 1, 0, -1):\n                        if pr[i] > pr[i - 1]:\n                            pr[i - 1] = pr[i]\n\n                    rec_thrs_insert_idx = np.searchsorted(rc,\n                                                          self.params.rec_thrs,\n                                                          side='left')\n\n                    pr_at_recall = [0.0] * num_recalls\n\n                    try:\n                        for _idx, pr_idx in enumerate(rec_thrs_insert_idx):\n                            pr_at_recall[_idx] = pr[pr_idx]\n                    except:  # noqa: E722\n                        pass\n                    precision[iou_thr_idx, :, cat_idx,\n                              area_idx] = np.array(pr_at_recall)\n\n        self.eval = {\n            'params': self.params,\n            'counts': [num_thrs, num_recalls, num_cats, num_area_rngs],\n            'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),\n            'precision': precision,\n            'recall': recall,\n            'dt_pointers': dt_pointers,\n        }\n\n    def _summarize(self,\n                   summary_type,\n                   iou_thr=None,\n                   area_rng='all',\n                   freq_group_idx=None):\n        aidx = [\n            idx for idx, _area_rng in enumerate(self.params.area_rng_lbl)\n            if _area_rng == area_rng\n        ]\n\n        if summary_type == 'ap':\n            s = self.eval['precision']\n            if iou_thr is not None:\n                tidx = np.where(iou_thr == self.params.iou_thrs)[0]\n                s = s[tidx]\n            if freq_group_idx is not None:\n                s = s[:, :, self.freq_groups[freq_group_idx], aidx]\n            else:\n                s = s[:, :, :, aidx]\n        else:\n            s = self.eval['recall']\n            if iou_thr is not None:\n                tidx = np.where(iou_thr == self.params.iou_thrs)[0]\n                s = s[tidx]\n            s = s[:, :, aidx]\n\n        if len(s[s > -1]) == 0:\n            mean_s = -1\n        else:\n            mean_s = np.mean(s[s > -1])\n        return mean_s\n\n    def summarize(self):\n        \"\"\"Compute and display summary metrics for evaluation results.\"\"\"\n        if not self.eval:\n            raise RuntimeError('Please run accumulate() first.')\n\n        max_dets = self.params.max_dets\n\n        self.results['AP'] = self._summarize('ap')\n        self.results['AP50'] = self._summarize('ap', iou_thr=0.50)\n        self.results['AP75'] = self._summarize('ap', iou_thr=0.75)\n        self.results['APs'] = self._summarize('ap', area_rng='small')\n        self.results['APm'] = self._summarize('ap', area_rng='medium')\n        self.results['APl'] = self._summarize('ap', area_rng='large')\n        self.results['APr'] = self._summarize('ap', freq_group_idx=0)\n        self.results['APc'] = self._summarize('ap', freq_group_idx=1)\n        self.results['APf'] = self._summarize('ap', freq_group_idx=2)\n\n        key = 'AR@{}'.format(max_dets)\n        self.results[key] = self._summarize('ar')\n\n        for area_rng in ['small', 'medium', 'large']:\n            key = 'AR{}@{}'.format(area_rng[0], max_dets)\n            self.results[key] = self._summarize('ar', area_rng=area_rng)\n\n    def run(self):\n        \"\"\"Wrapper function which calculates the results.\"\"\"\n        self.evaluate()\n        self.accumulate()\n        self.summarize()\n\n    def print_results(self):\n        template = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} catIds={:>3s}] = {:0.3f}'  # noqa: E501\n\n        for key, value in self.results.items():\n            max_dets = self.params.max_dets\n            if 'AP' in key:\n                title = 'Average Precision'\n                _type = '(AP)'\n            else:\n                title = 'Average Recall'\n                _type = '(AR)'\n\n            if len(key) > 2 and key[2].isdigit():\n                iou_thr = (float(key[2:]) / 100)\n                iou = '{:0.2f}'.format(iou_thr)\n            else:\n                iou = '{:0.2f}:{:0.2f}'.format(self.params.iou_thrs[0],\n                                               self.params.iou_thrs[-1])\n\n            if len(key) > 2 and key[2] in ['r', 'c', 'f']:\n                cat_group_name = key[2]\n            else:\n                cat_group_name = 'all'\n\n            if len(key) > 2 and key[2] in ['s', 'm', 'l']:\n                area_rng = key[2]\n            else:\n                area_rng = 'all'\n\n            print(\n                template.format(title, _type, iou, area_rng, max_dets,\n                                cat_group_name, value))\n\n    def get_results(self):\n        if not self.results:\n            self.logger.warn('results is empty. Call run().')\n        return self.results\n\n\nclass Params:\n    def __init__(self, iou_type):\n        \"\"\"Params for LVIS evaluation API.\"\"\"\n        self.img_ids = []\n        self.cat_ids = []\n        # np.arange causes trouble.  the data point on arange is slightly\n        # larger than the true value\n        self.iou_thrs = np.linspace(0.5,\n                                    0.95,\n                                    int(np.round((0.95 - 0.5) / 0.05)) + 1,\n                                    endpoint=True)\n        self.rec_thrs = np.linspace(0.0,\n                                    1.00,\n                                    int(np.round((1.00 - 0.0) / 0.01)) + 1,\n                                    endpoint=True)\n        self.max_dets = 300\n        self.area_rng = [\n            [0**2, 1e5**2],\n            [0**2, 32**2],\n            [32**2, 96**2],\n            [96**2, 1e5**2],\n        ]\n        self.area_rng_lbl = ['all', 'small', 'medium', 'large']\n        self.use_cats = 1\n        # We bin categories in three bins based how many images of the training\n        # set the category is present in.\n        # r: Rare    :  < 10\n        # c: Common  : >= 10 and < 100\n        # f: Frequent: >= 100\n        self.img_count_lbl = ['r', 'c', 'f']\n        self.iou_type = iou_type\n"
  },
  {
    "path": "code/cocoapi/lvis/lvis/lvis.py",
    "content": "\"\"\"\nAPI for accessing LVIS Dataset: https://lvisdataset.org.\n\nLVIS API is a Python API that assists in loading, parsing and visualizing\nthe annotations in LVIS. In addition to this API, please download\nimages and annotations from the LVIS website.\n\"\"\"\n\nimport json\nimport logging\nimport os\nfrom collections import defaultdict\nfrom urllib.request import urlretrieve\n\nimport pycocotools.mask as mask_utils\n\n\nclass LVIS:\n    def __init__(self, annotation_path):\n        \"\"\"Class for reading and visualizing annotations.\n        Args:\n            annotation_path (str): location of annotation file\n        \"\"\"\n        self.logger = logging.getLogger(__name__)\n        self.logger.info('Loading annotations.')\n\n        self.dataset = self._load_json(annotation_path)\n\n        assert (type(self.dataset) == dict\n                ), 'Annotation file format {} not supported.'.format(\n                    type(self.dataset))\n        self._create_index()\n\n    def _load_json(self, path):\n        with open(path, 'r') as f:\n            return json.load(f)\n\n    def _create_index(self):\n        self.logger.info('Creating index.')\n\n        self.img_ann_map = defaultdict(list)\n        self.cat_img_map = defaultdict(list)\n\n        self.anns = {}\n        self.cats = {}\n        self.imgs = {}\n\n        for ann in self.dataset['annotations']:\n            self.img_ann_map[ann['image_id']].append(ann)\n            self.anns[ann['id']] = ann\n\n        for img in self.dataset['images']:\n            self.imgs[img['id']] = img\n\n        for cat in self.dataset['categories']:\n            self.cats[cat['id']] = cat\n\n        for ann in self.dataset['annotations']:\n            self.cat_img_map[ann['category_id']].append(ann['image_id'])\n\n        self.logger.info('Index created.')\n\n    def get_ann_ids(self, img_ids=None, cat_ids=None, area_rng=None):\n        \"\"\"Get ann ids that satisfy given filter conditions.\n\n        Args:\n            img_ids (int array): get anns for given imgs\n            cat_ids (int array): get anns for given cats\n            area_rng (float array): get anns for a given area range.\n                e.g [0, inf]\n\n        Returns:\n            ids (int array): integer array of ann ids\n        \"\"\"\n        anns = []\n        if img_ids is not None:\n            for img_id in img_ids:\n                anns.extend(self.img_ann_map[img_id])\n        else:\n            anns = self.dataset['annotations']\n\n        # return early if no more filtering required\n        if cat_ids is None and area_rng is None:\n            return [_ann['id'] for _ann in anns]\n\n        cat_ids = set(cat_ids)\n\n        if area_rng is None:\n            area_rng = [0, float('inf')]\n\n        ann_ids = [\n            _ann['id'] for _ann in anns if _ann['category_id'] in cat_ids\n            and _ann['area'] > area_rng[0] and _ann['area'] < area_rng[1]\n        ]\n        return ann_ids\n\n    def get_cat_ids(self):\n        \"\"\"Get all category ids.\n\n        Returns:\n            ids (int array): integer array of category ids\n        \"\"\"\n        return list(self.cats.keys())\n\n    def get_img_ids(self):\n        \"\"\"Get all img ids.\n\n        Returns:\n            ids (int array): integer array of image ids\n        \"\"\"\n        return list(self.imgs.keys())\n\n    def _load_helper(self, _dict, ids):\n        if ids is None:\n            return list(_dict.values())\n        else:\n            return [_dict[id] for id in ids]\n\n    def load_anns(self, ids=None):\n        \"\"\"Load anns with the specified ids. If ids=None load all anns.\n\n        Args:\n            ids (int array): integer array of annotation ids\n\n        Returns:\n            anns (dict array) : loaded annotation objects\n        \"\"\"\n        return self._load_helper(self.anns, ids)\n\n    def load_cats(self, ids):\n        \"\"\"Load categories with the specified ids. If ids=None load all\n        categories.\n\n        Args:\n            ids (int array): integer array of category ids\n\n        Returns:\n            cats (dict array) : loaded category dicts\n        \"\"\"\n        return self._load_helper(self.cats, ids)\n\n    def load_imgs(self, ids):\n        \"\"\"Load categories with the specified ids. If ids=None load all images.\n\n        Args:\n            ids (int array): integer array of image ids\n\n        Returns:\n            imgs (dict array) : loaded image dicts\n        \"\"\"\n        return self._load_helper(self.imgs, ids)\n\n    def download(self, save_dir, img_ids=None):\n        \"\"\"Download images from mscoco.org server.\n        Args:\n            save_dir (str): dir to save downloaded images\n            img_ids (int array): img ids of images to download\n        \"\"\"\n        imgs = self.load_imgs(img_ids)\n\n        if not os.path.exists(save_dir):\n            os.makedirs(save_dir)\n\n        for img in imgs:\n            file_name = os.path.join(save_dir, img['coco_url'].split('/')[-1])\n            if not os.path.exists(file_name):\n                urlretrieve(img['coco_url'], file_name)\n\n    def ann_to_rle(self, ann):\n        \"\"\"Convert annotation which can be polygons, uncompressed RLE to RLE.\n        Args:\n            ann (dict) : annotation object\n\n        Returns:\n            ann (rle)\n        \"\"\"\n        img_data = self.imgs[ann['image_id']]\n        h, w = img_data['height'], img_data['width']\n        segm = ann['segmentation']\n        if isinstance(segm, list):\n            # polygon -- a single object might consist of multiple parts\n            # we merge all parts into one mask rle code\n            rles = mask_utils.frPyObjects(segm, h, w)\n            rle = mask_utils.merge(rles)\n        elif isinstance(segm['counts'], list):\n            # uncompressed RLE\n            rle = mask_utils.frPyObjects(segm, h, w)\n        else:\n            # rle\n            rle = ann['segmentation']\n        return rle\n\n    def ann_to_mask(self, ann):\n        \"\"\"Convert annotation which can be polygons, uncompressed RLE, or RLE\n        to binary mask.\n        Args:\n            ann (dict) : annotation object\n\n        Returns:\n            binary mask (numpy 2D array)\n        \"\"\"\n        rle = self.ann_to_rle(ann)\n        return mask_utils.decode(rle)\n"
  },
  {
    "path": "code/cocoapi/lvis/lvis/results.py",
    "content": "import logging\nfrom collections import defaultdict\nfrom copy import deepcopy\n\nimport pycocotools.mask as mask_utils\n\nfrom .lvis import LVIS\n\n\nclass LVISResults(LVIS):\n    def __init__(self, lvis_gt, results, max_dets=300):\n        \"\"\"Constructor for LVIS results.\n        Args:\n            lvis_gt (LVIS class instance, or str containing path of\n            annotation file)\n            results (str containing path of result file or a list of dicts)\n            max_dets (int):  max number of detections per image. The official\n            value of max_dets for LVIS is 300.\n        \"\"\"\n        if isinstance(lvis_gt, LVIS):\n            self.dataset = deepcopy(lvis_gt.dataset)\n        elif isinstance(lvis_gt, str):\n            self.dataset = self._load_json(lvis_gt)\n        else:\n            raise TypeError('Unsupported type {} of lvis_gt.'.format(lvis_gt))\n\n        self.logger = logging.getLogger(__name__)\n        self.logger.info('Loading and preparing results.')\n\n        if isinstance(results, str):\n            result_anns = self._load_json(results)\n        else:\n            # this path way is provided to avoid saving and loading result\n            # during training.\n            self.logger.warn(\n                'Assuming user provided the results in correct format.')\n            result_anns = results\n\n        assert isinstance(result_anns, list), 'results is not a list.'\n\n        if max_dets >= 0:\n            result_anns = self.limit_dets_per_image(result_anns, max_dets)\n\n        if 'bbox' in result_anns[0]:\n            for id, ann in enumerate(result_anns):\n                x1, y1, w, h = ann['bbox']\n                x2 = x1 + w\n                y2 = y1 + h\n\n                if 'segmentation' not in ann:\n                    ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]]\n\n                ann['area'] = w * h\n                ann['id'] = id + 1\n\n        elif 'segmentation' in result_anns[0]:\n            for id, ann in enumerate(result_anns):\n                # Only support compressed RLE format as segmentation results\n                ann['area'] = mask_utils.area(ann['segmentation'])\n\n                if 'bbox' not in ann:\n                    ann['bbox'] = mask_utils.toBbox(ann['segmentation'])\n\n                ann['id'] = id + 1\n\n        self.dataset['annotations'] = result_anns\n        self._create_index()\n\n        img_ids_in_result = [ann['image_id'] for ann in result_anns]\n\n        assert set(img_ids_in_result) == (\n            set(img_ids_in_result) & set(self.get_img_ids())\n        ), 'Results do not correspond to current LVIS set.'\n\n    def limit_dets_per_image(self, anns, max_dets):\n        img_ann = defaultdict(list)\n        for ann in anns:\n            img_ann[ann['image_id']].append(ann)\n\n        for img_id, _anns in img_ann.items():\n            if len(_anns) <= max_dets:\n                continue\n            _anns = sorted(_anns, key=lambda ann: ann['score'], reverse=True)\n            img_ann[img_id] = _anns[:max_dets]\n\n        return [ann for anns in img_ann.values() for ann in anns]\n\n    def get_top_results(self, img_id, score_thrs):\n        ann_ids = self.get_ann_ids(img_ids=[img_id])\n        anns = self.load_anns(ann_ids)\n        return list(filter(lambda ann: ann['score'] > score_thrs, anns))\n"
  },
  {
    "path": "code/cocoapi/lvis/lvis/vis.py",
    "content": "import logging\nimport os\n\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import Polygon\n\nfrom lvis.colormap import colormap\nfrom lvis.lvis import LVIS\nfrom lvis.results import LVISResults\n\n\nclass LVISVis:\n    def __init__(self, lvis_gt, lvis_dt=None, img_dir=None, dpi=75):\n        \"\"\"Constructor for LVISVis.\n\n        Args:\n            lvis_gt (LVIS class instance, or str containing path of annotation\n                file)\n            lvis_dt (LVISResult class instance, or str containing path of\n                result file, or list of dict)\n            img_dir (str): path of folder containing all images. If None, the\n                image to be displayed will be downloaded to the current working\n                dir.\n            dpi (int): dpi for figure size setup\n        \"\"\"\n        self.logger = logging.getLogger(__name__)\n\n        if isinstance(lvis_gt, LVIS):\n            self.lvis_gt = lvis_gt\n        elif isinstance(lvis_gt, str):\n            self.lvis_gt = LVIS(lvis_gt)\n        else:\n            raise TypeError('Unsupported type {} of lvis_gt.'.format(lvis_gt))\n\n        if lvis_dt is not None:\n            if isinstance(lvis_dt, LVISResults):\n                self.lvis_dt = lvis_dt\n            elif isinstance(lvis_dt, (str, list)):\n                self.lvis_dt = LVISResults(self.lvis_gt, lvis_dt)\n            else:\n                raise TypeError(\n                    'Unsupported type {} of lvis_dt.'.format(lvis_dt))\n        else:\n            self.lvis_dt = None\n        self.dpi = dpi\n        self.img_dir = img_dir if img_dir else '.'\n        if self.img_dir == '.':\n            self.logger.warn(\n                'img_dir not specified. Images will be downloaded.')\n\n    def coco_segm_to_poly(self, _list):\n        x = _list[0::2]\n        y = _list[1::2]\n        points = np.asarray([x, y])\n        return np.transpose(points)\n\n    def get_synset(self, idx):\n        synset = self.lvis_gt.load_cats(ids=[idx])[0]['synset']\n        text = synset.split('.')\n        text = '{}.{}'.format(text[0], int(text[-1]))\n        return text\n\n    def setup_figure(self, img, title='', dpi=75):\n        fig = plt.figure(frameon=False)\n        fig.set_size_inches(img.shape[1] / dpi, img.shape[0] / dpi)\n        ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0])\n        ax.set_title(title)\n        ax.axis('off')\n        fig.add_axes(ax)\n        ax.imshow(img)\n        return fig, ax\n\n    def vis_bbox(self, ax, bbox, box_alpha=0.5, edgecolor='g', linestyle='--'):\n        # bbox should be of the form x, y, w, h\n        ax.add_patch(\n            plt.Rectangle(\n                (bbox[0], bbox[1]),\n                bbox[2],\n                bbox[3],\n                fill=False,\n                edgecolor=edgecolor,\n                linewidth=2.5,\n                alpha=box_alpha,\n                linestyle=linestyle,\n            ))\n\n    def vis_text(self, ax, bbox, text, color='w'):\n        ax.text(\n            bbox[0],\n            bbox[1] - 2,\n            text,\n            fontsize=15,\n            family='serif',\n            bbox=dict(facecolor='none', alpha=0.4, pad=0, edgecolor='none'),\n            color=color,\n            zorder=10,\n        )\n\n    def vis_mask(self, ax, segm, color):\n        # segm is numpy array of shape Nx2\n        polygon = Polygon(segm,\n                          fill=True,\n                          facecolor=color,\n                          edgecolor=color,\n                          linewidth=3,\n                          alpha=0.5)\n        ax.add_patch(polygon)\n\n    def get_color(self, idx):\n        color_list = colormap(rgb=True) / 255\n        return color_list[idx % len(color_list), 0:3]\n\n    def load_img(self, img_id):\n        img = self.lvis_gt.load_imgs([img_id])[0]\n        img_path = os.path.join(self.img_dir, img['coco_url'].split('/')[-1])\n        if not os.path.exists(img_path):\n            self.lvis_gt.download(self.img_dir, img_ids=[img_id])\n        img = cv2.imread(img_path)\n        b, g, r = cv2.split(img)\n        return cv2.merge([r, g, b])\n\n    def vis_img(self,\n                img_id,\n                show_boxes=False,\n                show_segms=True,\n                show_classes=False,\n                cat_ids_to_show=None):\n        ann_ids = self.lvis_gt.get_ann_ids(img_ids=[img_id])\n        anns = self.lvis_gt.load_anns(ids=ann_ids)\n        boxes, segms, classes = [], [], []\n        for ann in anns:\n            boxes.append(ann['bbox'])\n            segms.append(ann['segmentation'])\n            classes.append(ann['category_id'])\n\n        if len(boxes) == 0:\n            self.logger.warn('No gt anno found for img_id: {}'.format(img_id))\n            return\n\n        boxes = np.asarray(boxes)\n        areas = boxes[:, 2] * boxes[:, 3]\n        sorted_inds = np.argsort(-areas)\n\n        fig, ax = self.setup_figure(self.load_img(img_id))\n\n        for idx in sorted_inds:\n            if cat_ids_to_show is not None and classes[\n                    idx] not in cat_ids_to_show:\n                continue\n            color = self.get_color(idx)\n            if show_boxes:\n                self.vis_bbox(ax, boxes[idx], edgecolor=color)\n            if show_classes:\n                text = self.get_synset(classes[idx])\n                self.vis_text(ax, boxes[idx], text)\n            if show_segms:\n                for segm in segms[idx]:\n                    self.vis_mask(ax, self.coco_segm_to_poly(segm), color)\n\n    def vis_result(self,\n                   img_id,\n                   show_boxes=False,\n                   show_segms=True,\n                   show_classes=False,\n                   cat_ids_to_show=None,\n                   score_thrs=0.0,\n                   show_scores=True):\n        assert self.lvis_dt is not None, 'lvis_dt was not specified.'\n        anns = self.lvis_dt.get_top_results(img_id, score_thrs)\n        boxes, segms, classes, scores = [], [], [], []\n        for ann in anns:\n            boxes.append(ann['bbox'])\n            segms.append(ann['segmentation'])\n            classes.append(ann['category_id'])\n            scores.append(ann['score'])\n\n        if len(boxes) == 0:\n            self.logger.warn('No gt anno found for img_id: {}'.format(img_id))\n            return\n\n        boxes = np.asarray(boxes)\n        areas = boxes[:, 2] * boxes[:, 3]\n        sorted_inds = np.argsort(-areas)\n\n        fig, ax = self.setup_figure(self.load_img(img_id))\n\n        for idx in sorted_inds:\n            if cat_ids_to_show is not None and classes[\n                    idx] not in cat_ids_to_show:\n                continue\n            color = self.get_color(idx)\n            if show_boxes:\n                self.vis_bbox(ax, boxes[idx], edgecolor=color)\n            if show_classes:\n                text = self.get_synset(classes[idx])\n                if show_scores:\n                    text = '{}: {:.2f}'.format(text, scores[idx])\n                self.vis_text(ax, boxes[idx], text)\n            if show_segms:\n                for segm in segms[idx]:\n                    self.vis_mask(ax, self.coco_segm_to_poly(segm), color)\n"
  },
  {
    "path": "code/cocoapi/lvis/requirements.txt",
    "content": "cycler>=0.10.0\nCython>=0.29.12\nkiwisolver>=1.1.0\nmatplotlib>=3.1.1\nnumpy>=1.18.2\nopencv-python>=4.1.0.25\npyparsing>=2.4.0\npython-dateutil>=2.8.0\nsix>=1.12.0\n"
  },
  {
    "path": "code/cocoapi/lvis/setup.py",
    "content": "\"\"\"LVIS (pronounced ‘el-vis’): is a new dataset for Large Vocabulary Instance\nSegmentation. We collect over 2 million high-quality instance segmentation\nmasks for over 1200 entry-level object categories in 164k images. LVIS API\nenables reading and interacting with annotation files, visualizing annotations,\nand evaluating results.\n\n\"\"\"\nimport os.path\nimport sys\n\nimport setuptools\n\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lvis'))\n\ndir_path = os.path.dirname(os.path.realpath(__file__))\nwith open(os.path.join(dir_path, 'requirements.txt')) as f:\n    reqs = f.read()\n\nDISTNAME = 'mmlvis'\nDESCRIPTION = 'Python API for LVIS dataset.'\nAUTHOR = 'Agrim Gupta'\nREQUIREMENTS = (reqs.strip().split('\\n'), )\nDOCLINES = (__doc__ or '')\n\nif __name__ == '__main__':\n    setuptools.setup(name=DISTNAME,\n                     install_requires=REQUIREMENTS,\n                     packages=setuptools.find_packages(),\n                     version='10.5.3',\n                     description=DESCRIPTION,\n                     long_description=DOCLINES,\n                     long_description_content_type='text/markdown',\n                     author=AUTHOR)\n"
  },
  {
    "path": "code/cocoapi/pycocotools/MANIFEST.in",
    "content": "include common/*.cpp common/*.h common/*.c\ninclude pycocotools/_mask.pyx\n"
  },
  {
    "path": "code/cocoapi/pycocotools/Makefile",
    "content": "all:\n    # install pycocotools locally\n\tpython setup.py build_ext --inplace\n\trm -rf build\n\ninstall:\n\t# install pycocotools to the Python site-packages\n\tpython setup.py build_ext install\n\trm -rf build\n"
  },
  {
    "path": "code/cocoapi/pycocotools/common/gason.cpp",
    "content": "// https://github.com/vivkin/gason - pulled January 10, 2016\n#include \"gason.h\"\n#include <stdlib.h>\n\n#define JSON_ZONE_SIZE 4096\n#define JSON_STACK_SIZE 32\n\nconst char *jsonStrError(int err) {\n    switch (err) {\n#define XX(no, str) \\\n    case JSON_##no: \\\n        return str;\n        JSON_ERRNO_MAP(XX)\n#undef XX\n    default:\n        return \"unknown\";\n    }\n}\n\nvoid *JsonAllocator::allocate(size_t size) {\n    size = (size + 7) & ~7;\n\n    if (head && head->used + size <= JSON_ZONE_SIZE) {\n        char *p = (char *)head + head->used;\n        head->used += size;\n        return p;\n    }\n\n    size_t allocSize = sizeof(Zone) + size;\n    Zone *zone = (Zone *)malloc(allocSize <= JSON_ZONE_SIZE ? JSON_ZONE_SIZE : allocSize);\n    if (zone == nullptr)\n        return nullptr;\n    zone->used = allocSize;\n    if (allocSize <= JSON_ZONE_SIZE || head == nullptr) {\n        zone->next = head;\n        head = zone;\n    } else {\n        zone->next = head->next;\n        head->next = zone;\n    }\n    return (char *)zone + sizeof(Zone);\n}\n\nvoid JsonAllocator::deallocate() {\n    while (head) {\n        Zone *next = head->next;\n        free(head);\n        head = next;\n    }\n}\n\nstatic inline bool isspace(char c) {\n    return c == ' ' || (c >= '\\t' && c <= '\\r');\n}\n\nstatic inline bool isdelim(char c) {\n    return c == ',' || c == ':' || c == ']' || c == '}' || isspace(c) || !c;\n}\n\nstatic inline bool isdigit(char c) {\n    return c >= '0' && c <= '9';\n}\n\nstatic inline bool isxdigit(char c) {\n    return (c >= '0' && c <= '9') || ((c & ~' ') >= 'A' && (c & ~' ') <= 'F');\n}\n\nstatic inline int char2int(char c) {\n    if (c <= '9')\n        return c - '0';\n    return (c & ~' ') - 'A' + 10;\n}\n\nstatic double string2double(char *s, char **endptr) {\n    char ch = *s;\n    if (ch == '-')\n        ++s;\n\n    double result = 0;\n    while (isdigit(*s))\n        result = (result * 10) + (*s++ - '0');\n\n    if (*s == '.') {\n        ++s;\n\n        double fraction = 1;\n        while (isdigit(*s)) {\n            fraction *= 0.1;\n            result += (*s++ - '0') * fraction;\n        }\n    }\n\n    if (*s == 'e' || *s == 'E') {\n        ++s;\n\n        double base = 10;\n        if (*s == '+')\n            ++s;\n        else if (*s == '-') {\n            ++s;\n            base = 0.1;\n        }\n\n        unsigned int exponent = 0;\n        while (isdigit(*s))\n            exponent = (exponent * 10) + (*s++ - '0');\n\n        double power = 1;\n        for (; exponent; exponent >>= 1, base *= base)\n            if (exponent & 1)\n                power *= base;\n\n        result *= power;\n    }\n\n    *endptr = s;\n    return ch == '-' ? -result : result;\n}\n\nstatic inline JsonNode *insertAfter(JsonNode *tail, JsonNode *node) {\n    if (!tail)\n        return node->next = node;\n    node->next = tail->next;\n    tail->next = node;\n    return node;\n}\n\nstatic inline JsonValue listToValue(JsonTag tag, JsonNode *tail) {\n    if (tail) {\n        auto head = tail->next;\n        tail->next = nullptr;\n        return JsonValue(tag, head);\n    }\n    return JsonValue(tag, nullptr);\n}\n\nint jsonParse(char *s, char **endptr, JsonValue *value, JsonAllocator &allocator) {\n    JsonNode *tails[JSON_STACK_SIZE];\n    JsonTag tags[JSON_STACK_SIZE];\n    char *keys[JSON_STACK_SIZE];\n    JsonValue o;\n    int pos = -1;\n    bool separator = true;\n    JsonNode *node;\n    *endptr = s;\n\n    while (*s) {\n        while (isspace(*s)) {\n            ++s;\n            if (!*s) break;\n        }\n        *endptr = s++;\n        switch (**endptr) {\n        case '-':\n            if (!isdigit(*s) && *s != '.') {\n                *endptr = s;\n                return JSON_BAD_NUMBER;\n            }\n        case '0':\n        case '1':\n        case '2':\n        case '3':\n        case '4':\n        case '5':\n        case '6':\n        case '7':\n        case '8':\n        case '9':\n            o = JsonValue(string2double(*endptr, &s));\n            if (!isdelim(*s)) {\n                *endptr = s;\n                return JSON_BAD_NUMBER;\n            }\n            break;\n        case '\"':\n            o = JsonValue(JSON_STRING, s);\n            for (char *it = s; *s; ++it, ++s) {\n                int c = *it = *s;\n                if (c == '\\\\') {\n                    c = *++s;\n                    switch (c) {\n                    case '\\\\':\n                    case '\"':\n                    case '/':\n                        *it = c;\n                        break;\n                    case 'b':\n                        *it = '\\b';\n                        break;\n                    case 'f':\n                        *it = '\\f';\n                        break;\n                    case 'n':\n                        *it = '\\n';\n                        break;\n                    case 'r':\n                        *it = '\\r';\n                        break;\n                    case 't':\n                        *it = '\\t';\n                        break;\n                    case 'u':\n                        c = 0;\n                        for (int i = 0; i < 4; ++i) {\n                            if (isxdigit(*++s)) {\n                                c = c * 16 + char2int(*s);\n                            } else {\n                                *endptr = s;\n                                return JSON_BAD_STRING;\n                            }\n                        }\n                        if (c < 0x80) {\n                            *it = c;\n                        } else if (c < 0x800) {\n                            *it++ = 0xC0 | (c >> 6);\n                            *it = 0x80 | (c & 0x3F);\n                        } else {\n                            *it++ = 0xE0 | (c >> 12);\n                            *it++ = 0x80 | ((c >> 6) & 0x3F);\n                            *it = 0x80 | (c & 0x3F);\n                        }\n                        break;\n                    default:\n                        *endptr = s;\n                        return JSON_BAD_STRING;\n                    }\n                } else if ((unsigned int)c < ' ' || c == '\\x7F') {\n                    *endptr = s;\n                    return JSON_BAD_STRING;\n                } else if (c == '\"') {\n                    *it = 0;\n                    ++s;\n                    break;\n                }\n            }\n            if (!isdelim(*s)) {\n                *endptr = s;\n                return JSON_BAD_STRING;\n            }\n            break;\n        case 't':\n            if (!(s[0] == 'r' && s[1] == 'u' && s[2] == 'e' && isdelim(s[3])))\n                return JSON_BAD_IDENTIFIER;\n            o = JsonValue(JSON_TRUE);\n            s += 3;\n            break;\n        case 'f':\n            if (!(s[0] == 'a' && s[1] == 'l' && s[2] == 's' && s[3] == 'e' && isdelim(s[4])))\n                return JSON_BAD_IDENTIFIER;\n            o = JsonValue(JSON_FALSE);\n            s += 4;\n            break;\n        case 'n':\n            if (!(s[0] == 'u' && s[1] == 'l' && s[2] == 'l' && isdelim(s[3])))\n                return JSON_BAD_IDENTIFIER;\n            o = JsonValue(JSON_NULL);\n            s += 3;\n            break;\n        case ']':\n            if (pos == -1)\n                return JSON_STACK_UNDERFLOW;\n            if (tags[pos] != JSON_ARRAY)\n                return JSON_MISMATCH_BRACKET;\n            o = listToValue(JSON_ARRAY, tails[pos--]);\n            break;\n        case '}':\n            if (pos == -1)\n                return JSON_STACK_UNDERFLOW;\n            if (tags[pos] != JSON_OBJECT)\n                return JSON_MISMATCH_BRACKET;\n            if (keys[pos] != nullptr)\n                return JSON_UNEXPECTED_CHARACTER;\n            o = listToValue(JSON_OBJECT, tails[pos--]);\n            break;\n        case '[':\n            if (++pos == JSON_STACK_SIZE)\n                return JSON_STACK_OVERFLOW;\n            tails[pos] = nullptr;\n            tags[pos] = JSON_ARRAY;\n            keys[pos] = nullptr;\n            separator = true;\n            continue;\n        case '{':\n            if (++pos == JSON_STACK_SIZE)\n                return JSON_STACK_OVERFLOW;\n            tails[pos] = nullptr;\n            tags[pos] = JSON_OBJECT;\n            keys[pos] = nullptr;\n            separator = true;\n            continue;\n        case ':':\n            if (separator || keys[pos] == nullptr)\n                return JSON_UNEXPECTED_CHARACTER;\n            separator = true;\n            continue;\n        case ',':\n            if (separator || keys[pos] != nullptr)\n                return JSON_UNEXPECTED_CHARACTER;\n            separator = true;\n            continue;\n        case '\\0':\n            continue;\n        default:\n            return JSON_UNEXPECTED_CHARACTER;\n        }\n\n        separator = false;\n\n        if (pos == -1) {\n            *endptr = s;\n            *value = o;\n            return JSON_OK;\n        }\n\n        if (tags[pos] == JSON_OBJECT) {\n            if (!keys[pos]) {\n                if (o.getTag() != JSON_STRING)\n                    return JSON_UNQUOTED_KEY;\n                keys[pos] = o.toString();\n                continue;\n            }\n            if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode))) == nullptr)\n                return JSON_ALLOCATION_FAILURE;\n            tails[pos] = insertAfter(tails[pos], node);\n            tails[pos]->key = keys[pos];\n            keys[pos] = nullptr;\n        } else {\n            if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode) - sizeof(char *))) == nullptr)\n                return JSON_ALLOCATION_FAILURE;\n            tails[pos] = insertAfter(tails[pos], node);\n        }\n        tails[pos]->value = o;\n    }\n    return JSON_BREAKING_BAD;\n}\n"
  },
  {
    "path": "code/cocoapi/pycocotools/common/gason.h",
    "content": "// https://github.com/vivkin/gason - pulled January 10, 2016\n#pragma once\n\n#include <stdint.h>\n#include <stddef.h>\n#include <assert.h>\n\nenum JsonTag {\n    JSON_NUMBER = 0,\n    JSON_STRING,\n    JSON_ARRAY,\n    JSON_OBJECT,\n    JSON_TRUE,\n    JSON_FALSE,\n    JSON_NULL = 0xF\n};\n\nstruct JsonNode;\n\n#define JSON_VALUE_PAYLOAD_MASK 0x00007FFFFFFFFFFFULL\n#define JSON_VALUE_NAN_MASK 0x7FF8000000000000ULL\n#define JSON_VALUE_TAG_MASK 0xF\n#define JSON_VALUE_TAG_SHIFT 47\n\nunion JsonValue {\n    uint64_t ival;\n    double fval;\n\n    JsonValue(double x)\n        : fval(x) {\n    }\n    JsonValue(JsonTag tag = JSON_NULL, void *payload = nullptr) {\n        assert((uintptr_t)payload <= JSON_VALUE_PAYLOAD_MASK);\n        ival = JSON_VALUE_NAN_MASK | ((uint64_t)tag << JSON_VALUE_TAG_SHIFT) | (uintptr_t)payload;\n    }\n    bool isDouble() const {\n        return (int64_t)ival <= (int64_t)JSON_VALUE_NAN_MASK;\n    }\n    JsonTag getTag() const {\n        return isDouble() ? JSON_NUMBER : JsonTag((ival >> JSON_VALUE_TAG_SHIFT) & JSON_VALUE_TAG_MASK);\n    }\n    uint64_t getPayload() const {\n        assert(!isDouble());\n        return ival & JSON_VALUE_PAYLOAD_MASK;\n    }\n    double toNumber() const {\n        assert(getTag() == JSON_NUMBER);\n        return fval;\n    }\n    char *toString() const {\n        assert(getTag() == JSON_STRING);\n        return (char *)getPayload();\n    }\n    JsonNode *toNode() const {\n        assert(getTag() == JSON_ARRAY || getTag() == JSON_OBJECT);\n        return (JsonNode *)getPayload();\n    }\n};\n\nstruct JsonNode {\n    JsonValue value;\n    JsonNode *next;\n    char *key;\n};\n\nstruct JsonIterator {\n    JsonNode *p;\n\n    void operator++() {\n        p = p->next;\n    }\n    bool operator!=(const JsonIterator &x) const {\n        return p != x.p;\n    }\n    JsonNode *operator*() const {\n        return p;\n    }\n    JsonNode *operator->() const {\n        return p;\n    }\n};\n\ninline JsonIterator begin(JsonValue o) {\n    return JsonIterator{o.toNode()};\n}\ninline JsonIterator end(JsonValue) {\n    return JsonIterator{nullptr};\n}\n\n#define JSON_ERRNO_MAP(XX)                           \\\n    XX(OK, \"ok\")                                     \\\n    XX(BAD_NUMBER, \"bad number\")                     \\\n    XX(BAD_STRING, \"bad string\")                     \\\n    XX(BAD_IDENTIFIER, \"bad identifier\")             \\\n    XX(STACK_OVERFLOW, \"stack overflow\")             \\\n    XX(STACK_UNDERFLOW, \"stack underflow\")           \\\n    XX(MISMATCH_BRACKET, \"mismatch bracket\")         \\\n    XX(UNEXPECTED_CHARACTER, \"unexpected character\") \\\n    XX(UNQUOTED_KEY, \"unquoted key\")                 \\\n    XX(BREAKING_BAD, \"breaking bad\")                 \\\n    XX(ALLOCATION_FAILURE, \"allocation failure\")\n\nenum JsonErrno {\n#define XX(no, str) JSON_##no,\n    JSON_ERRNO_MAP(XX)\n#undef XX\n};\n\nconst char *jsonStrError(int err);\n\nclass JsonAllocator {\n    struct Zone {\n        Zone *next;\n        size_t used;\n    } *head = nullptr;\n\npublic:\n    JsonAllocator() = default;\n    JsonAllocator(const JsonAllocator &) = delete;\n    JsonAllocator &operator=(const JsonAllocator &) = delete;\n    JsonAllocator(JsonAllocator &&x) : head(x.head) {\n        x.head = nullptr;\n    }\n    JsonAllocator &operator=(JsonAllocator &&x) {\n        head = x.head;\n        x.head = nullptr;\n        return *this;\n    }\n    ~JsonAllocator() {\n        deallocate();\n    }\n    void *allocate(size_t size);\n    void deallocate();\n};\n\nint jsonParse(char *str, char **endptr, JsonValue *value, JsonAllocator &allocator);\n"
  },
  {
    "path": "code/cocoapi/pycocotools/common/maskApi.c",
    "content": "/**************************************************************************\n* Microsoft COCO Toolbox.      version 2.0\n* Data, paper, and tutorials available at:  http://mscoco.org/\n* Code written by Piotr Dollar and Tsung-Yi Lin, 2015.\n* Licensed under the Simplified BSD License [see coco/license.txt]\n**************************************************************************/\n#include \"maskApi.h\"\n#include <math.h>\n#include <stdlib.h>\n\nuint umin( uint a, uint b ) { return (a<b) ? a : b; }\nuint umax( uint a, uint b ) { return (a>b) ? a : b; }\n\nvoid rleInit( RLE *R, siz h, siz w, siz m, uint *cnts ) {\n  R->h=h; R->w=w; R->m=m; R->cnts=(m==0)?0:malloc(sizeof(uint)*m);\n  siz j; if(cnts) for(j=0; j<m; j++) R->cnts[j]=cnts[j];\n}\n\nvoid rleFree( RLE *R ) {\n  free(R->cnts); R->cnts=0;\n}\n\nvoid rlesInit( RLE **R, siz n ) {\n  siz i; *R = (RLE*) malloc(sizeof(RLE)*n);\n  for(i=0; i<n; i++) rleInit((*R)+i,0,0,0,0);\n}\n\nvoid rlesFree( RLE **R, siz n ) {\n  siz i; for(i=0; i<n; i++) rleFree((*R)+i); free(*R); *R=0;\n}\n\nvoid rleEncode( RLE *R, const byte *M, siz h, siz w, siz n ) {\n  siz i, j, k, a=w*h; uint c, *cnts; byte p;\n  cnts = malloc(sizeof(uint)*(a+1));\n  for(i=0; i<n; i++) {\n    const byte *T=M+a*i; k=0; p=0; c=0;\n    for(j=0; j<a; j++) { if(T[j]!=p) { cnts[k++]=c; c=0; p=T[j]; } c++; }\n    cnts[k++]=c; rleInit(R+i,h,w,k,cnts);\n  }\n  free(cnts);\n}\n\nvoid rleDecode( const RLE *R, byte *M, siz n ) {\n  siz i, j, k; for( i=0; i<n; i++ ) {\n    byte v=0; for( j=0; j<R[i].m; j++ ) {\n      for( k=0; k<R[i].cnts[j]; k++ ) *(M++)=v; v=!v; }}\n}\n\nvoid rleMerge( const RLE *R, RLE *M, siz n, int intersect ) {\n  uint *cnts, c, ca, cb, cc, ct; int v, va, vb, vp;\n  siz i, a, b, h=R[0].h, w=R[0].w, m=R[0].m; RLE A, B;\n  if(n==0) { rleInit(M,0,0,0,0); return; }\n  if(n==1) { rleInit(M,h,w,m,R[0].cnts); return; }\n  cnts = malloc(sizeof(uint)*(h*w+1));\n  for( a=0; a<m; a++ ) cnts[a]=R[0].cnts[a];\n  for( i=1; i<n; i++ ) {\n    B=R[i]; if(B.h!=h||B.w!=w) { h=w=m=0; break; }\n    rleInit(&A,h,w,m,cnts); ca=A.cnts[0]; cb=B.cnts[0];\n    v=va=vb=0; m=0; a=b=1; cc=0; ct=1;\n    while( ct>0 ) {\n      c=umin(ca,cb); cc+=c; ct=0;\n      ca-=c; if(!ca && a<A.m) { ca=A.cnts[a++]; va=!va; } ct+=ca;\n      cb-=c; if(!cb && b<B.m) { cb=B.cnts[b++]; vb=!vb; } ct+=cb;\n      vp=v; if(intersect) v=va&&vb; else v=va||vb;\n      if( v!=vp||ct==0 ) { cnts[m++]=cc; cc=0; }\n    }\n    rleFree(&A);\n  }\n  rleInit(M,h,w,m,cnts); free(cnts);\n}\n\nvoid rleArea( const RLE *R, siz n, uint *a ) {\n  siz i, j; for( i=0; i<n; i++ ) {\n    a[i]=0; for( j=1; j<R[i].m; j+=2 ) a[i]+=R[i].cnts[j]; }\n}\n\nvoid rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ) {\n  siz g, d; BB db, gb; int crowd;\n  db=malloc(sizeof(double)*m*4); rleToBbox(dt,db,m);\n  gb=malloc(sizeof(double)*n*4); rleToBbox(gt,gb,n);\n  bbIou(db,gb,m,n,iscrowd,o); free(db); free(gb);\n  for( g=0; g<n; g++ ) for( d=0; d<m; d++ ) if(o[g*m+d]>0) {\n    crowd=iscrowd!=NULL && iscrowd[g];\n    if(dt[d].h!=gt[g].h || dt[d].w!=gt[g].w) { o[g*m+d]=-1; continue; }\n    siz ka, kb, a, b; uint c, ca, cb, ct, i, u; int va, vb;\n    ca=dt[d].cnts[0]; ka=dt[d].m; va=vb=0;\n    cb=gt[g].cnts[0]; kb=gt[g].m; a=b=1; i=u=0; ct=1;\n    while( ct>0 ) {\n      c=umin(ca,cb); if(va||vb) { u+=c; if(va&&vb) i+=c; } ct=0;\n      ca-=c; if(!ca && a<ka) { ca=dt[d].cnts[a++]; va=!va; } ct+=ca;\n      cb-=c; if(!cb && b<kb) { cb=gt[g].cnts[b++]; vb=!vb; } ct+=cb;\n    }\n    if(i==0) u=1; else if(crowd) rleArea(dt+d,1,&u);\n    o[g*m+d] = (double)i/(double)u;\n  }\n}\n\nvoid rleNms( RLE *dt, siz n, uint *keep, double thr ) {\n  siz i, j; double u;\n  for( i=0; i<n; i++ ) keep[i]=1;\n  for( i=0; i<n; i++ ) if(keep[i]) {\n    for( j=i+1; j<n; j++ ) if(keep[j]) {\n      rleIou(dt+i,dt+j,1,1,0,&u);\n      if(u>thr) keep[j]=0;\n    }\n  }\n}\n\nvoid bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ) {\n  double h, w, i, u, ga, da; siz g, d; int crowd;\n  for( g=0; g<n; g++ ) {\n    BB G=gt+g*4; ga=G[2]*G[3]; crowd=iscrowd!=NULL && iscrowd[g];\n    for( d=0; d<m; d++ ) {\n      BB D=dt+d*4; da=D[2]*D[3]; o[g*m+d]=0;\n      w=fmin(D[2]+D[0],G[2]+G[0])-fmax(D[0],G[0]); if(w<=0) continue;\n      h=fmin(D[3]+D[1],G[3]+G[1])-fmax(D[1],G[1]); if(h<=0) continue;\n      i=w*h; u = crowd ? da : da+ga-i; o[g*m+d]=i/u;\n    }\n  }\n}\n\nvoid bbNms( BB dt, siz n, uint *keep, double thr ) {\n  siz i, j; double u;\n  for( i=0; i<n; i++ ) keep[i]=1;\n  for( i=0; i<n; i++ ) if(keep[i]) {\n    for( j=i+1; j<n; j++ ) if(keep[j]) {\n      bbIou(dt+i*4,dt+j*4,1,1,0,&u);\n      if(u>thr) keep[j]=0;\n    }\n  }\n}\n\nvoid rleToBbox( const RLE *R, BB bb, siz n ) {\n  siz i; for( i=0; i<n; i++ ) {\n    uint h, w, x, y, xs, ys, xe, ye, xp, cc, t; siz j, m;\n    h=(uint)R[i].h; w=(uint)R[i].w; m=R[i].m;\n    m=((siz)(m/2))*2; xs=w; ys=h; xe=ye=0; cc=0;\n    if(m==0) { bb[4*i+0]=bb[4*i+1]=bb[4*i+2]=bb[4*i+3]=0; continue; }\n    for( j=0; j<m; j++ ) {\n      cc+=R[i].cnts[j]; t=cc-j%2; y=t%h; x=(t-y)/h;\n      if(j%2==0) xp=x; else if(xp<x) { ys=0; ye=h-1; }\n      xs=umin(xs,x); xe=umax(xe,x); ys=umin(ys,y); ye=umax(ye,y);\n    }\n    bb[4*i+0]=xs; bb[4*i+2]=xe-xs+1;\n    bb[4*i+1]=ys; bb[4*i+3]=ye-ys+1;\n  }\n}\n\nvoid rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n ) {\n  siz i; for( i=0; i<n; i++ ) {\n    double xs=bb[4*i+0], xe=xs+bb[4*i+2];\n    double ys=bb[4*i+1], ye=ys+bb[4*i+3];\n    double xy[8] = {xs,ys,xs,ye,xe,ye,xe,ys};\n    rleFrPoly( R+i, xy, 4, h, w );\n  }\n}\n\nint uintCompare(const void *a, const void *b) {\n  uint c=*((uint*)a), d=*((uint*)b); return c>d?1:c<d?-1:0;\n}\n\nvoid rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w ) {\n  /* upsample and get discrete points densely along entire boundary */\n  siz j, m=0; double scale=5; int *x, *y, *u, *v; uint *a, *b;\n  x=malloc(sizeof(int)*(k+1)); y=malloc(sizeof(int)*(k+1));\n  for(j=0; j<k; j++) x[j]=(int)(scale*xy[j*2+0]+.5); x[k]=x[0];\n  for(j=0; j<k; j++) y[j]=(int)(scale*xy[j*2+1]+.5); y[k]=y[0];\n  for(j=0; j<k; j++) m+=umax(abs(x[j]-x[j+1]),abs(y[j]-y[j+1]))+1;\n  u=malloc(sizeof(int)*m); v=malloc(sizeof(int)*m); m=0;\n  for( j=0; j<k; j++ ) {\n    int xs=x[j], xe=x[j+1], ys=y[j], ye=y[j+1], dx, dy, t, d;\n    int flip; double s; dx=abs(xe-xs); dy=abs(ys-ye);\n    flip = (dx>=dy && xs>xe) || (dx<dy && ys>ye);\n    if(flip) { t=xs; xs=xe; xe=t; t=ys; ys=ye; ye=t; }\n    s = dx>=dy ? (double)(ye-ys)/dx : (double)(xe-xs)/dy;\n    if(dx>=dy) for( d=0; d<=dx; d++ ) {\n      t=flip?dx-d:d; u[m]=t+xs; v[m]=(int)(ys+s*t+.5); m++;\n    } else for( d=0; d<=dy; d++ ) {\n      t=flip?dy-d:d; v[m]=t+ys; u[m]=(int)(xs+s*t+.5); m++;\n    }\n  }\n  /* get points along y-boundary and downsample */\n  free(x); free(y); k=m; m=0; double xd, yd;\n  x=malloc(sizeof(int)*k); y=malloc(sizeof(int)*k);\n  for( j=1; j<k; j++ ) if(u[j]!=u[j-1]) {\n    xd=(double)(u[j]<u[j-1]?u[j]:u[j]-1); xd=(xd+.5)/scale-.5;\n    if( floor(xd)!=xd || xd<0 || xd>w-1 ) continue;\n    yd=(double)(v[j]<v[j-1]?v[j]:v[j-1]); yd=(yd+.5)/scale-.5;\n    if(yd<0) yd=0; else if(yd>h) yd=h; yd=ceil(yd);\n    x[m]=(int) xd; y[m]=(int) yd; m++;\n  }\n  /* compute rle encoding given y-boundary points */\n  k=m; a=malloc(sizeof(uint)*(k+1));\n  for( j=0; j<k; j++ ) a[j]=(uint)(x[j]*(int)(h)+y[j]);\n  a[k++]=(uint)(h*w); free(u); free(v); free(x); free(y);\n  qsort(a,k,sizeof(uint),uintCompare); uint p=0;\n  for( j=0; j<k; j++ ) { uint t=a[j]; a[j]-=p; p=t; }\n  b=malloc(sizeof(uint)*k); j=m=0; b[m++]=a[j++];\n  while(j<k) if(a[j]>0) b[m++]=a[j++]; else {\n    j++; if(j<k) b[m-1]+=a[j++]; }\n  rleInit(R,h,w,m,b); free(a); free(b);\n}\n\nchar* rleToString( const RLE *R ) {\n  /* Similar to LEB128 but using 6 bits/char and ascii chars 48-111. */\n  siz i, m=R->m, p=0; long x; int more;\n  char *s=malloc(sizeof(char)*m*6);\n  for( i=0; i<m; i++ ) {\n    x=(long) R->cnts[i]; if(i>2) x-=(long) R->cnts[i-2]; more=1;\n    while( more ) {\n      char c=x & 0x1f; x >>= 5; more=(c & 0x10) ? x!=-1 : x!=0;\n      if(more) c |= 0x20; c+=48; s[p++]=c;\n    }\n  }\n  s[p]=0; return s;\n}\n\nvoid rleFrString( RLE *R, char *s, siz h, siz w ) {\n  siz m=0, p=0, k; long x; int more; uint *cnts;\n  while( s[m] ) m++; cnts=malloc(sizeof(uint)*m); m=0;\n  while( s[p] ) {\n    x=0; k=0; more=1;\n    while( more ) {\n      char c=s[p]-48; x |= (c & 0x1f) << 5*k;\n      more = c & 0x20; p++; k++;\n      if(!more && (c & 0x10)) x |= -1 << 5*k;\n    }\n    if(m>2) x+=(long) cnts[m-2]; cnts[m++]=(uint) x;\n  }\n  rleInit(R,h,w,m,cnts); free(cnts);\n}\n"
  },
  {
    "path": "code/cocoapi/pycocotools/common/maskApi.h",
    "content": "/**************************************************************************\n* Microsoft COCO Toolbox.      version 2.0\n* Data, paper, and tutorials available at:  http://mscoco.org/\n* Code written by Piotr Dollar and Tsung-Yi Lin, 2015.\n* Licensed under the Simplified BSD License [see coco/license.txt]\n**************************************************************************/\n#pragma once\n\ntypedef unsigned int uint;\ntypedef unsigned long siz;\ntypedef unsigned char byte;\ntypedef double* BB;\ntypedef struct { siz h, w, m; uint *cnts; } RLE;\n\n/* Initialize/destroy RLE. */\nvoid rleInit( RLE *R, siz h, siz w, siz m, uint *cnts );\nvoid rleFree( RLE *R );\n\n/* Initialize/destroy RLE array. */\nvoid rlesInit( RLE **R, siz n );\nvoid rlesFree( RLE **R, siz n );\n\n/* Encode binary masks using RLE. */\nvoid rleEncode( RLE *R, const byte *mask, siz h, siz w, siz n );\n\n/* Decode binary masks encoded via RLE. */\nvoid rleDecode( const RLE *R, byte *mask, siz n );\n\n/* Compute union or intersection of encoded masks. */\nvoid rleMerge( const RLE *R, RLE *M, siz n, int intersect );\n\n/* Compute area of encoded masks. */\nvoid rleArea( const RLE *R, siz n, uint *a );\n\n/* Compute intersection over union between masks. */\nvoid rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o );\n\n/* Compute non-maximum suppression between bounding masks */\nvoid rleNms( RLE *dt, siz n, uint *keep, double thr );\n\n/* Compute intersection over union between bounding boxes. */\nvoid bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o );\n\n/* Compute non-maximum suppression between bounding boxes */\nvoid bbNms( BB dt, siz n, uint *keep, double thr );\n\n/* Get bounding boxes surrounding encoded masks. */\nvoid rleToBbox( const RLE *R, BB bb, siz n );\n\n/* Convert bounding boxes to encoded masks. */\nvoid rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n );\n\n/* Convert polygon to encoded mask. */\nvoid rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w );\n\n/* Get compressed string representation of encoded mask. */\nchar* rleToString( const RLE *R );\n\n/* Convert from compressed string representation of encoded mask. */\nvoid rleFrString( RLE *R, char *s, siz h, siz w );\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocoDemo.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"from pycocotools.coco import COCO\\n\",\n    \"import numpy as np\\n\",\n    \"import skimage.io as io\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import pylab\\n\",\n    \"pylab.rcParams['figure.figsize'] = (8.0, 10.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"dataDir='..'\\n\",\n    \"dataType='val2017'\\n\",\n    \"annFile='{}/annotations/instances_{}.json'.format(dataDir,dataType)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"loading annotations into memory...\\n\",\n      \"Done (t=0.81s)\\n\",\n      \"creating index...\\n\",\n      \"index created!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# initialize COCO api for instance annotations\\n\",\n    \"coco=COCO(annFile)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"COCO categories: \\n\",\n      \"person bicycle car motorcycle airplane bus train truck boat traffic light fire hydrant stop sign parking meter bench bird cat dog horse sheep cow elephant bear zebra giraffe backpack umbrella handbag tie suitcase frisbee skis snowboard sports ball kite baseball bat baseball glove skateboard surfboard tennis racket bottle wine glass cup fork knife spoon bowl banana apple sandwich orange broccoli carrot hot dog pizza donut cake chair couch potted plant bed dining table toilet tv laptop mouse remote keyboard cell phone microwave oven toaster sink refrigerator book clock vase scissors teddy bear hair drier toothbrush\\n\",\n      \"\\n\",\n      \"COCO supercategories: \\n\",\n      \"outdoor food indoor appliance sports person animal vehicle furniture accessory electronic kitchen\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# display COCO categories and supercategories\\n\",\n    \"cats = coco.loadCats(coco.getCatIds())\\n\",\n    \"nms=[cat['name'] for cat in cats]\\n\",\n    \"print('COCO categories: \\\\n{}\\\\n'.format(' '.join(nms)))\\n\",\n    \"\\n\",\n    \"nms = set([cat['supercategory'] for cat in cats])\\n\",\n    \"print('COCO supercategories: \\\\n{}'.format(' '.join(nms)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# get all images containing given categories, select one at random\\n\",\n    \"catIds = coco.getCatIds(catNms=['person','dog','skateboard']);\\n\",\n    \"imgIds = coco.getImgIds(catIds=catIds );\\n\",\n    \"imgIds = coco.getImgIds(imgIds = [324158])\\n\",\n    \"img = coco.loadImgs(imgIds[np.random.randint(0,len(imgIds))])[0]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\nYgVJUgjDiCAICYIQQ9dRFHj2/DmDQbr5q1QuUq2WCaMALaOl42WSTEbNUC6VGI5G1GpVOp0zHGeK\\nZRlEUbqgY+bM8D0P3/Vp1OvMbJtKtcLB3j6lYplSqYLnpQtnfNdlNpkws20Arl67hu956TrbTIYw\\nSAviTueCRrNGp3PB2toGkCDJCXGU0Gg0vlGcSsj0e32aKw2iKKTX6yOj0esN+PLBQ1ZWWmxsbvLe\\n996lVq9zdnHG6ekpGUXm5PiEyPdwZjNM00DXFJI4YTIeY+gmjuekGr6WwfccRqMpjheBrBMnUGs0\\n0A2T0XBIuVRgOhmTtUwMI2Wy9vb2cF2PbM5i0O+TzVpUKwWkOKR/2cHQFMbDAadnp0RRgKpqrK6u\\n4vs+L1684Dvf/y+//QB+uXv/fd9zmU0nZC0TKZEIPI/dFy/QLYtms4mi6QzHMzTd5OjsFDWTYefo\\nmFprg989eEKcqHS7A955511KlTr/509+Tm8w5tr2VbKGxoPP7/PLX/6c884FtVqTWq1Cs9Gg1++j\\nz28sp6en/PPPf8a1q9d47733uHp1m16vj+8FbGxsUiqVCUJnnl5UZWVlJZ33G/bJZ03e/M4bXLm6\\nRXuljZnNkSuWGc5czGyO0XjI1E7XM8ooqPOubDweIsvKIuFsPB5SKZfIqAqqqlCulNJOSUn1VLFA\\nwjBMHMclk8mQz+cXI0nCVS10XGFEWx4NWx7PE/Sq6GKX54DFrO6yWU0YkAQoL8eICso9iRNc11t0\\nbALcVFUsQnEWICC6U9F9is5xGQhFZ75M4wvAWdabxRdeAJJIShPmNmABoAJYROLZ8i/xWMFyiHMp\\n9FvBQohzJf4rzvuy7r485iZibcUomSgUAs/Hssx0J/JSp5/L5RZygnhuYTAT70Ofa5ji/C/HxS67\\n34UeLzwMwhEvstfFeRRFx/K0wfLYnDDi5XK5b1wvgjURoB1FEbV5Fr94LhGqAyzOU7/fp1qt4swc\\ncvkcg2EfSIjCgO3NLV5/5VUeP3rEy3fvcP/+fTY2NrBtm43NTfKFAjdu3GAynlGvN/D9AFlWmIwm\\nrKys0m61WVtfSw1Z+/vcuv0S48mE7773HpedDhdn51x0Oly/fp1mq8Xu7i4vvXQT00yzDNrrqxiW\\nwWg8IZ8r4HohejbPdDajVCrj+T6e75EzLeI4wjJ0HN+j1WrT7VximSa/+OUvsYwsDx8+xtAs7NkY\\n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\\nSUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x110703450>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# load and display image\\n\",\n    \"# I = io.imread('%s/images/%s/%s'%(dataDir,dataType,img['file_name']))\\n\",\n    \"# use url to load image\\n\",\n    \"I = io.imread(img['coco_url'])\\n\",\n    \"plt.axis('off')\\n\",\n    \"plt.imshow(I)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11f12e9d0>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# load and display instance annotations\\n\",\n    \"plt.imshow(I); plt.axis('off')\\n\",\n    \"annIds = coco.getAnnIds(imgIds=img['id'], catIds=catIds, iscrowd=None)\\n\",\n    \"anns = coco.loadAnns(annIds)\\n\",\n    \"coco.showAnns(anns)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"loading annotations into memory...\\n\",\n      \"Done (t=0.58s)\\n\",\n      \"creating index...\\n\",\n      \"index created!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# initialize COCO api for person keypoints annotations\\n\",\n    \"annFile = '{}/annotations/person_keypoints_{}.json'.format(dataDir,dataType)\\n\",\n    \"coco_kps=COCO(annFile)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\nAtGYWJb1BT27aBYsy1pL2QzDWIcm2Hb6+QW+TxTFSAmomkKUSFh2ht7NhH/zp/8Wz48YTWZYdoaN\\nzR1evjikUMojSwmbzTpvHOxyZ3+XQj5P1sqQMU22d2t0u0POLtvESYxlZ7CzWRxngm3lkBUVTdeR\\nFSV159J0ZEVFVWVAXr1PMbP5hN/49W+QzWSI/SWzyZg4CdE1jYXjY5oWSSzhByEZO7dahcyRZWUV\\nDhOsPt+IOE798sMwYLlcoCgq0/EI1dDxQ598NsMnv/gFzUqNWrWccgeyGn7sMJj0OXhwQHOrhRO4\\naJaOaRhrj4VWa4N6rUo+m6XVqFMu5RkP+0hEhK5D++Q1i/kYXZMIQxeJhDCSePvtd7m8vMQ2DTJ2\\nkV63m0K87pLZbMpgMOD+/VQWNJ4ueOONu7ieR6lU5Wtf+zqWmeX7P/wR+WKV8bBHp9OhVCqzubmF\\nbZvYpsFkMsK0MkiqzvPnz5hMRuSyWXRVodfvUW/UmYxnhFFEuVxh/+BO6sXtuuRzOeazKXEUEMcB\\nmpqmYBVzNoViHmSJTz5+wuPHb+F5Ls1mk7PTU1RNR5Fler0evutSLOSxbQvbMsnnMsxnM3KZDPlc\\njoxtk82kqY8KMs+fPcXUNQrFPJPJlCiOqTdSA5LBeIppWkhaGsSzv3dAHMboehojHIYxru993kiH\\nIaqmYtsZ4jhZS7/CMEztnSWJTL7AZDrl8rpNq9UiSRIq1QoL1yGKEpbLBa7rIEupEdBsNqVSrBBF\\nMZVKjevrG0qlMrpuoCgqlmUyn82QZYVOp4uuqeiqhqzA5kaTJInI5ixuOm3yhQzdmxs816FSLnN5\\ncUG5VMP3fCzTImNnyGVzXJyfYxoW1Wo9zdMYjQjDAN/1cJbLlT10klq15nI06un/s0wLe5WkJ0hy\\nuq5z8M7fgQn8f/6f/odvtza2ODk+R1HSQ+/87CJlEmYsNra2yGayWJksqqZTqTeIgfnMBUnCC3wq\\n5RJIoGsaUZi68cirWLdSqZSGKEQxbz56RK1WxbZ1NNXg1avXHB4dsZjPefbsU2q1Mnt7G+ga/OJn\\nP2Jns8nF+QmX7XPuP7zHqDdi2O/TqtUY3XRoVGpoaLQvL5m7M37/H/w+e/t7tFoN3nx0Hz8MyJXy\\nVGt1Wq0NWs0N/uW//F/54//jf2d/b4vz42M0zeD8/CKdWj2Pr37lq3z9/ffxPJfr6zaNZp3RZMz1\\n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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11ffa3a50>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# load and display keypoints annotations\\n\",\n    \"plt.imshow(I); plt.axis('off')\\n\",\n    \"ax = plt.gca()\\n\",\n    \"annIds = coco_kps.getAnnIds(imgIds=img['id'], catIds=catIds, iscrowd=None)\\n\",\n    \"anns = coco_kps.loadAnns(annIds)\\n\",\n    \"coco_kps.showAnns(anns)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"loading annotations into memory...\\n\",\n      \"Done (t=0.13s)\\n\",\n      \"creating index...\\n\",\n      \"index created!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# initialize COCO api for caption annotations\\n\",\n    \"annFile = '{}/annotations/captions_{}.json'.format(dataDir,dataType)\\n\",\n    \"coco_caps=COCO(annFile)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"A man is skate boarding down a path and a dog is running by his side.\\n\",\n      \"A man on a skateboard with a dog outside. \\n\",\n      \"A person riding a skate board with a dog following beside.\\n\",\n      \"This man is riding a skateboard behind a dog.\\n\",\n      \"A man walking his dog on a quiet country road.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nYgVJUgjDiCAICYIQQ9dRFHj2/DmDQbr5q1QuUq2WCaMALaOl42WSTEbNUC6VGI5G1GpVOp0zHGeK\\nZRlEUbqgY+bM8D0P3/Vp1OvMbJtKtcLB3j6lYplSqYLnpQtnfNdlNpkws20Arl67hu956TrbTIYw\\nSAviTueCRrNGp3PB2toGkCDJCXGU0Gg0vlGcSsj0e32aKw2iKKTX6yOj0esN+PLBQ1ZWWmxsbvLe\\n996lVq9zdnHG6ekpGUXm5PiEyPdwZjNM00DXFJI4YTIeY+gmjuekGr6WwfccRqMpjheBrBMnUGs0\\n0A2T0XBIuVRgOhmTtUwMI2Wy9vb2cF2PbM5i0O+TzVpUKwWkOKR/2cHQFMbDAadnp0RRgKpqrK6u\\n4vs+L1684Dvf/y+//QB+uXv/fd9zmU0nZC0TKZEIPI/dFy/QLYtms4mi6QzHMzTd5OjsFDWTYefo\\nmFprg989eEKcqHS7A955511KlTr/509+Tm8w5tr2VbKGxoPP7/PLX/6c884FtVqTWq1Cs9Gg1++j\\nz28sp6en/PPPf8a1q9d47733uHp1m16vj+8FbGxsUiqVCUJnnl5UZWVlJZ33G/bJZ03e/M4bXLm6\\nRXuljZnNkSuWGc5czGyO0XjI1E7XM8ooqPOubDweIsvKIuFsPB5SKZfIqAqqqlCulNJOSUn1VLFA\\nwjBMHMclk8mQz+cXI0nCVS10XGFEWx4NWx7PE/Sq6GKX54DFrO6yWU0YkAQoL8eICso9iRNc11t0\\nbALcVFUsQnEWICC6U9F9is5xGQhFZ75M4wvAWdabxRdeAJJIShPmNmABoAJYROLZ8i/xWMFyiHMp\\n9FvBQohzJf4rzvuy7r485iZibcUomSgUAs/Hssx0J/JSp5/L5RZygnhuYTAT70Ofa5ji/C/HxS67\\n34UeLzwMwhEvstfFeRRFx/K0wfLYnDDi5XK5b1wvgjURoB1FEbV5Fr94LhGqAyzOU7/fp1qt4swc\\ncvkcg2EfSIjCgO3NLV5/5VUeP3rEy3fvcP/+fTY2NrBtm43NTfKFAjdu3GAynlGvN/D9AFlWmIwm\\nrKys0m61WVtfSw1Z+/vcuv0S48mE7773HpedDhdn51x0Oly/fp1mq8Xu7i4vvXQT00yzDNrrqxiW\\nwWg8IZ8r4HohejbPdDajVCrj+T6e75EzLeI4wjJ0HN+j1WrT7VximSa/+OUvsYwsDx8+xtAs7NkY\\n2x5RrhQZDgcUSiWMrEWYxORyBcqVCsVSiWqlwng8XoSthFFIvdnEtn063S4bq2sYmkkul6dULrKz\\ns0M+n6fZbHJ4eIAfpBMWN25s02w2CMMARZJI5jLWaDREUy1s2+Hw8IBatUochpi6zo0b13DnI7pT\\n22YwGFDIFchkNGqVKp2LLkEYYOo6vf6AQrmMHwSMBqmPqZDPYc9zOUbj8eL+4boucZQwsydUaxVs\\n206XvYwnyHI6373SXuXs7ALXTV/fnxv1DNMgjgM++eQT8rkSpWKdv/0//g6AG9ev82d//kNyhSxq\\nRiMKIgb9Lu1Wi4dfPaJ7cU6jUSeOAk6PjriytUXg+eRzecI4TNcRRyGO4+N4IZZVZDydIUkJg8GA\\nWqWCqqo8+OLzuct+hmZm+fijj7Asi52dHX7729/y8p07nJ2dokgJBwd7GHq6x/zJkyfomk6j2WA0\\nni7uH9VqlSt3/xNwoT/6+J/et+0ZiiJTr1UZjQZ89eALLN1gdW0V3dDpDUbkyxVaKxucdrrkSiV0\\nI8vB0RlRAkkUUchqmIZOlChs377LenuFG1tXsCyN7e0NrLzBW2+9yQ++/wNeeeVlOucdVtor2La9\\nWME3Gg54++13uHPnDk+fPuXy8pJr164DEkdHx6yttblx4wa9Xi9dWFIs8OXnX5AkMZPZGE03+NnP\\nf4FmZRk7LlImw3g6YTyZks3nkUiH/+2Zg55RkVQZXTFwPIc4Tm+ehq6ldLOUMJtvgVo2qum6Ti6X\\nWwCEuEEbhrFwRi/rkiKuU5jPBCUrnMcCjIRpSriMkySZbzGTFgCxHOginkNQ6EkSYVlZHDuNSAQW\\nC0MALCuVAX4fYD3Xx7TMhSPddd0F6C+bt4ThTlC/whi2rNmLblgUFcLstcwaLGvvAlCWw2dM08S2\\n7YWeLUaqBMUuQF10+6KIEcY38fOiU19mQIAF46FIMlEYEYfzhS+ZDBIQhSHK/DMQBjohWwgnvHhf\\ny+9NdPu+7y9CdSCdSHBdd5Eut5wMJ+b/c7n081o2LYrrRujmy/KD0MfFNSRG0QRLIAoJUUCJ60vk\\nFIzH49SEOS+sBr0BsiITxSGGZnBxfs5KrY6UxPiOi6Z/7cbXdZ0wjjg8OiRJEp49e4rrOWQ0lRvX\\nr3N0fIREQpzE3Lt3jy+//JJCocDDx49oNJvp9WkaqHK6D/r27ducnp3jeS57+zucnZ1SKOZZWVtB\\nURUkRUZRM9y8eZuZbaOrGeSMypWrW3iOTb1cxnNdqvU6judgWvOktiAFCc/1+e/+2/8eTcmQxAG5\\ngsnBwQ5r6xsoms5gOGI6nbG5sbG4rpS5AVEwTNPpBG8uwZRLZSqVMrm8xWjUZ2bPKJcrtFotepeX\\nBEF6fJcXHZI4ptVsMe6PePTwEaPxGGfm0m6tUK5UcWyb2WzK+to6v/n1r1lpr/Dk8TOiIEEzLSZT\\nGyubp9Vu0z2/5OjoiEajzng8Yjwc4bk+tVaLarWKlESUi0Vcz+f6tRt4rk+vd7n4HrdaLXb3dpGU\\nNEEyzfpIHfC93iXjyYR6rTGfwNFozZkU1/GYzoYoioSm6WRUgyePX/D48XP+8i//HaVSnkazSqfb\\npXs5xDQtWq069mRMsVCkXC5ABDnTpHNxDkk0Z2FsKqUS9tSmUl9lPLHx55vVSoUct2/dSr1RksTF\\n+QWmZWAYGW699BIPvnzE5sYmlUqZy8tLZlOHP/zj7zGejFBVncvugFyhSLFQYua6vPLqaxhmjtF4\\nPB+V01hZWaG+de/bD+Af//yv3h/0eqy0W0xmQ1zH5eTwgMuLczJKBiSJR8+eEqEy9QLy5RqRJPMv\\nv/5XXn/9NV69e5vvvHWPdrPK+toG7/7BHxHLKu16Hc+esbbSRjMyXL91lXa7zcn+HkfHR8hymv/c\\nbjQxTINXXnmFP/3TH/HBB//CX//1XxPHMW+99SYnJ8dEYcKjRw95++23ePr0KaViBc93MTIanu+w\\nstoml88zs22e7eyQK1aIAMf38HwX08oRR6AoGcbjKRlVQ81kKFZLZHWL0XhEsVigUimTxDHuPMdc\\nMzRc28V13YVzWwCgGMtZBgfh2haGLEmSFoYnAXICzIHFf8X8uGEYCyAS/yZ0TAGkonsTrymoc1VV\\nSBKQJBlJ+trIJbqw6Wy80H+BdEe4rM7Hx6IFGAon+PK4kjBWife2nEommAFx/Kmu5iy6TtHRz2az\\nxZdHUM0imU1V1W8Ajzh/olgQzy/em2AkBKiK4xRRqEJvXh7B0gwDc04fR0FI4KXLHQzDIPBTU1Wc\\npF2waRjIytcb0pZd9uJ8/H4UrKCSxWcojH6iuxaF1GQy+YYeLhZaCM1QFAziWMT1I3R8UQQtB9Z4\\nnke1Wl2AtSgElgsfUSguZ7orisLp6SnNZjOVkcplXMdhtdlkc22NWqWCpRtMpmOGwyG7u7vpWGEQ\\n0Gy1GA6HyDJUKmXCMODk9ATPdcgoCo1GnadPX/DixQ4rK23yxQI7uy+oVSs8f/KUt7/zDu2VFV68\\n2GX76lXW19dQFCkFtPUVgtBj6kzQdQ3TsKjVGiiqhqGp+KHPcNhn1O1iaBpRlNDt9zALRVZX19Az\\nGY6OjsjlCxweHPO3f/O3fPXgIbV6hc8efMxLL90im80xGk4ACdPKkcRpdkK9Xufk9JjpeMyVK1e4\\nvLzE0HTCIGWvxuMhuplBzkh4gY1jz2i1m4xHY7JZi/sff0wchSRRTLPe4PnT57iux0e//YibN29h\\nz2astle57HdxXJv9/X0+/fQ+lUqVaqXB8dEJ2WwBP4yx8nkKxTLj4RB7OsMyTCRZ5rJzgTOzsR0X\\nVJUoCbFnU2bjNE53OBzS7XaZzibcunWLWq3GaDTivHOGYRj0B33W1tcp5AuEoU+hkEdWVCrlKrKs\\n0G6vEAQ+Mztd1OIFDmEUsrqyxmzqEwSwvrbF6moLNSNzObjENC0UxaBareE4EyaTAY1qgygMSKKQ\\nQi5PLpd+Z4yMznQyxdR1dnYOKbfXGE3S0BrHnnJ2eoQcSaiywsHhIVnL5JXX75LLZ5nZNvs7hwuZ\\ny/d96rUGK6ttTNPg6rWXiJKYw+NTGs02M8dFVjPEsUShmKdQKCw8KBsvvfvtB/BPfvo370sZhcHI\\n5sXjY3wvYu+kx8tv/RH3v3zOycUQ20k4Pj7nyZNnkMDqxhZXNrco5kuEUcx4NCNXqGDm8uzv71HO\\nZmnUCmxvr/Pzf/4V7eoVPvnXB3z++QNOO6ecX57w3nt/TBglFEpV6o02//RPP+Z39z/j8cMntNst\\nGo0m4/GEjz76iPPzU977g/fo9fpMJw6VWoVbd7cYTHpkdIuziz6Fag3NyKLoFpphMXM8ojCERCKO\\nQJYkPM+FJML33TSzN1dgPB5RrVQI/RBVltE1HS2jo6oaiqSQxBKNejPVY/2AJE4YjcaEYZrqJivy\\nYt5bAIsAGWDRFYlqXpqnvRmmief7RHE8T4tKgwsEYAiQFF2ueF5BDy9r0+lrpZ16QoJuaIShT0JM\\nQroWVmjsAjQymUxqXJozDAI0hcYrukkBrJC6sAWd7Mwdycuz0mKDGLCQE/r9/oKa1zQtzUmeA4ss\\ny/8vdzvEREm6Zz2jGURxjKyoJEhEcepmXV69KssyMekuL0mWkWSZMIpAktK/IwX+7DyBLQgCFFUh\\nkUDVMkxmU7L5XLoEw/OQZIloTm8LN7hgOgQLI4xigh1ZnhMXs/HLBjfx7/B1oIygwAV4Cs+DSDUz\\nTXOheQtQF+defFYCkAUNL15LFIP+fGZaeAZEQWkYBufn53PZaEyxVEDVdUbTKY16g+O9A25tb6Oq\\nClbOZOPKVkozF/JkDB3dMNAyBqZpYRpZut0+b7z+Fjs7e4zGU1ora+imxcXFJddv3CRfKDIcjPjf\\n/+pvaDZXuPvqazx49JD9w0M2Njb41a9+wdHREbdu3eCV115lOOhx/eYtohgUOYOmGQy63XRTXxjS\\n6w1oVKv4gU+pnOOyf4brTNEkhYysklFkxqMhF5cd8jkLmYhGrYSiSvzJ93/A0fEpn3/xgO/94R/S\\naLYoFkqcXZyRxAmXFxeAQhBG7OzupO7pUpnheEBGkynkDaYzm9APsPQsjWaDk5NTRqN0Ocu1azfJ\\n5ssMRjZeFPLRx5/y4KvH/Nmf/+dcv3mDvf1dXr33KsPBgPFoTKlcYn11g6yVI5vNUq/X+d2nv+Pa\\njWuQSFTLVcbjMa2VFrIiU61U6Q8mlMsVxq5NpdZMExkzCgQek/El/c4FekbGlxJeffNNJFnm8HCf\\nnJHDNBQqhRzlQokgiMgYOo3WBtqcdchmDSCm3++RUdOobFlSCIMY1/UYj0dIcsjNl67w6OFXrK9t\\n4vkuaytr5AyNyaiPoWSYjMdIUUCpZOG6U6buFMcNiBKJ4WzK1HUwC/l0NNBzuXntCi+ePaV7cU6z\\nVmcym1Iq5SnkLSazMe3WOoVCmc8ffEkxX+DWrVv8+te/oVgugSSj6BYff/aARFHoDkZIqs76xjY7\\ne4dECZTKBQrz6z2dKY9ZvfGdbz+A/83/+r+8f3h8yedfvODsfMz5+QX75ydcu/UyA8fm40/vI6vp\\nTeTevXtsra0zGwz53ne/hyRJjAZDJpMppVKZbDaHPXMolcrM7ClxAk+fPiUIA166cxvD0mm06lQq\\nVYbjGZ999gUx8Nn9T/nyq4eQxGRz2bm7M+T4+Ji7d++mVKuU0LnsECcxlVoVM2dwcHjEaDLj7iuv\\nIUkKUSIxHk+x5yNQsqwQRSH+fIa1VCotOtByqUQYhpRKpUXXEs/1Y01LXdUpZezNO0uHJEmfU5KY\\np895GIbOaDRa7JNe3jIFLP5NdNBidjodGTOQpPSmHs11b9H5DgaDBYBYlkW/3190TuK3ADIx2ytc\\nzQLwRQcqOlhBNRcKhW8kiNVqtQWwFItFXNel1+st3ONpFvzXqWmiGBFFhaB2BWAIrVtQ9ctLRMTj\\nlgsdAYapaczH9XyiSPgH0pWMAJaVJfC9xbatxfPPj1+cg2W3tboUniM6WuGqF0xAEASLIKB0UYez\\ncISLET1x3AKcHcdZnHtRbInPZ3kuXdDUgoERhZ0wnYkxNVHAiM9BAHmSJPOtY+lnt/z+xaif0MiX\\npR3xHsW5FxJGpVJhNBotOvl0rnxMgpSeRz/k5PCAt994ndlsyqDb5fD4iHw+v9DVXdelVEwjVMvl\\nMoeHhziOQ61WI5fL8eTJEzqdDrqhU2/U+d3937G2vsbK6grdXhfXc9ENg3fffZef/vgnjMcjXnvt\\nVaazCXEU0rm8JI4TBqMBYRhysLufjgQCVt5EVWR8zyWfN6k3a/ieQ7VaI5/LoaoZppMR1VoFVVZI\\n4oiXbtzgr/63v+LuK3fxfR/Xsbl95y5bW9uEcZxmN8QRnuPwwQcfsLl1hZlj47kOSRxRLVWIvIC8\\nmWV3d5e8lcN3fSajMflilovzcyqVCrbt4jgBzdY6/w93b9YkSZ5d9/18D/eI8Ni3jFyrcqnqrq2r\\nq6enezDAzGAEwKSBCBJ40ANBo8xEQY960AfoZ0l80BspAyQSlCCa0UhAFDDAYDB7z0xPL7XvS+5b\\n7LuHx+IeevD4e0VR+gKNNKuXzMqMCPeI/73n3HPOdV1Y2yixtLSMqulEoxZmxGDn8jZTf8LLV7uU\\nl8uUy8vIchCve3JyQsQyMWIWznDIUqkcvA+sCO1Wi5PjY9KpNKoWrCx++WqPre0dtre2cHpdfN9D\\nlWaMhyN0TePKOzdoNOo0GnWYzbBjNr1Ocz7aM5FkDc+fMfV8atVzbNum2WwwmUwQO9YHg2AOr+vB\\nHvSLFy8Qj9koioqiqIzHI3KFbMAijsY0m018zydiRLDjCbxpwDBNpz4PHjxEMwzW1taoVqvkcrn5\\nLnYHyzJZX11BVVXevnJ5PlbzKZVKdPs9TDOKoqq8evmKYaNNPBbDGQyYAUvra8xUFdOOkU1lkWcS\\ny8UysWiMT3/xCeXSEhE9QqfbotPp0Gw2GQ6HbP198IH/8b/4nz56uS/xNyYAACAASURBVH/I6XmD\\n1fUNOm6FW1/9Cg+ePeUrH36NeDxOLG6zs7XNf/at3ySXyVKrVrHMGIYeIWIYvPPOTZKpJLKksLy6\\nwquXL4jGovzlX/4lmiYTiajM8OgPB1zcvESr3eflq32ePH1Ks9Uik80y6PUpl5cxrQhHx8eUlkp8\\n4ze+wdHRUSjMidsWE29KMpNCVlVUw8JxRiyvrNMfDGm22kSsKJ1OH3c0BglUNThYxXpIXddZLi0x\\nGAxIJpNvzJyF4Gc69fA8H0mSQ/Qo5p8B6tQJ7Fkmo5EbRnIuzh6F+lnEqIaRneMJ02nQzQJMJtOg\\nYVgQWsHr0JXFeEwgLBqO44RFSMzXRREEQppdpH2JAiD+njjgRXEXTYVQx4utVkIZL66TKDxiJruo\\nWF9kDhbHC0Lkp2naG5GsvV4vnMWLRSyuO8KfQSwWqLuDpsAF5tSw64TXQ3i7jTltLtgA4HVzMC+g\\nQHgNxQhERLUupqeJ94BokHq9Htlsln6/HxZCoUJf3Mu9GJYCzLdKaW/MoBeR/KIqXiDrRZGgaZrh\\nYhxxbZrNJsIL32w2w0ZIMDEiX3tRryHsbULM57pBZKbIKVBVFdcdkEimaTSbWIaFoaicHx+yslRk\\nb+8VHoTLVMTzPzk5RZaDRRXFYjFskO/fv8/jx4/5/d//fYqlPHfu3sbzp8iSwje+8Q3u3r1LoVDg\\n2rVrHB4dgSTx7W99i2jU4uz8mHq9jucF9zSTyTJ0R6ytriJJEuXyMqsbq1jRCLV6lel0TK/bZjR0\\nSaUy1BsN8GbM/CnZbIaR6/Kzn/yEe3fucHl7m9/5zu8yGY/IZnOBTTQRNPSmFaVerXB+fsbKygqZ\\nbA5n6LC+tkoumyGVSDJ0HFx3iDTzuXhhk0jEIpfPM/VnTCZT2q02qq4wnU54tfuCpZUysuoxGLo8\\ne/aM3/7t38HzpmQzaaq1CqOxy+n5Ob1+H1lRefH8BXrEYOxNSeeyzHwfO5nAHTpkMhl+9cmnpBIp\\nTk/OSGcynJ4cs7q+imGa4Pl0200m4xF2LIY0f095QCqT4eTkBDyfZMzG88YUCnlqtTrRmM3U9/D8\\nGYrMnNnz581IIISz7TiOMww/E71eD0UJrLKmGWHoOnhTD20+mjk9PaXb7fPd7/413/jmt3i1u8tw\\nNOLJk2dEozHanTbr6+soikI2lwtWM0+mTEbuPFFuPNdqmIzGQzRNJZvOUm+26fX7lEol4lGLmSRh\\nRC0arSZ6xGDoDFlbXkWezXCHfdq1KtGIjqErxKImqqKwVC6Fn2PP89i68feggCet2Eer61ukMwVU\\nTeOf/rN/zNbOFXY2r/Pizj02llZYLpSYjsacnZzyxe3b2IkEjUoF1xmABKcnx1QrFTRDo9lqMnZd\\nev0eN995h5XlEr/+a+/juD0qlQbPnuzyV9/9PmrEIJVO886N6zQbDTKpNAcHRyQTca5dvcqjhw/I\\n5XKhYV9RFIrlJFuXtjg6OcGfmbTbLr6vcXbWoNVtM3AnTHzwCChYQ9fxvGk497NtOxDN2XaItEQR\\nEl2nCLoQqm54TXtGo9EQSQbBLBq6rr2hErdtO2w4kslkqPCeTCb0e4P5IhA1tCgFtPaUmf96vaQ4\\nWAWVDQFiE0VQFHHXdd+wni0WYOHpFhS4+AA2m80Q6YnoWbFbWqDORZuTM7ekiCIDhHPbRVW20AgI\\nml4Uc0EdC5Quio24xhAU6TdQrjfDmwsHVVWl3+8xHs+3bfmB9S6dTodiru58ji7sYqKJURQFb474\\nxRxYNHHiQ7y4L30xoUz8XND88Xg8vBbiHrxuOtzQA78Y1LOo/hbXJJlMMhgMQhQtGh7xPMTjLyrL\\n+/1+uAdeoHPBWogGqVarBb7dhYjcxQZSsB+CESkWi4EOQlEwIgqSrCBLOlbE5Gh/D9mfsrxSxtA1\\nsoUCz58/R9M0qtUqqqpiWdGQqo9EIkSjUR4+fMitW7e4desW+/v7NBstolaMiGHOg2zib8THvv32\\n25yfndHrdnj27Clvv32ZbrfLwBlgRaOsrq3hOoGeIp1McV6psHe4R7vdIWpFKRaWKBVW8D0JCYV6\\nrU4imUCd73IYDB3ef/8DJFkhm85wWjkNFsy4I/KFAp99/hlmxKRcKvKD73+PZ0+ecOXtK+gRk1gs\\nQb/XYToZ4yszsrkss5nHytoKk5mHpMgcnp4wHg/x/TExyyCfThGPGty6eY1es0lpbYlirsDFjU2S\\ncZvJeMTeq5eoCmTSGYauQ61So5gvkcvlmE58ZjKkUkm2dnaYTEdoikar2aHT6rF5cQvTshiPXVbX\\nlzk9OSSXyzDs93DdIfVGhb3Dw2D82O9TXCnT7LQp5PM4jsPZ4QnZbIrxZMxo4uH7IKsKDx89Zntr\\nk/6gSzQWZTqZkEwm6Xa7nJ6eYllRXr16RSqV4vz8HNOK4PlT2p0Oa+trtFttMpkMz54948qVK2iq\\nzm/8xjepVOqcnVVwnCmqqqHpGl/72lep1ao8e/acTCaDZVl0mg18z+P09JhUOokzGuJNJzQaDQAa\\njSbGHIG/ePGS7Z0t9o8PUDWdXDGPrulMx1P2d1/Rb9TwRw6FVAxNmTLzhlhRA1lTaDbbJJNJnj9/\\njizLXH7v21/+Av69v/g/P/KnE/KZAq16l3anzeHBHkvFIrZt8+TpE5qNJsdHR8StKFHDpNtqY0Ut\\nUqkUTr9PNpdjY2ODVrvFeDTi/OycnUs7gQpZkhk6A9qtLqoeQZIULm5uIssyN2+9y6e/+oR6rYZp\\nRDg9OSaTTvDZZ5/yB3/wB/zyk08oFArk83lmM4lKo8JoPCWZzFCrd9B1k4HjBkhbkzEiJgN3hM8M\\nWVFIRKMkU4kQmYriMZ0XTlkORF+i4Eaj0VCEJJCGmDGKwxQI0d5o5DKb+eHBLWhU8f+EwEoInXx/\\nxmQ6DUVmovAqiow3P/RFARLCKXH4C+HUbB4yIuha4eddLOwB9TUIGwJBHws0J/zngqJfpNwF0hNj\\nACEcE4euruvhtRTIUiC9Xq8XUulCHS1Qp6B/RREdjUYhba2qakj/KoqCZUXpz8VbwTXx8bwpsVic\\n8WgYhsQIJGzNmYjFoBXRnI3nTRgEjcdigyWum/gZECrYRRFcFMuJYitU9Yt72sX1WKS5hXpc3DPx\\n/HRdD2N1hX5C6A/E3Fw0XIsiOlH8BPsgBG7j8ZhMJhM+Z/HeSKVSDAaDsLESO9RlWaYxX7QSNKMe\\nSCqargVaECCiykjSjAubF+j2emQymZDu1zSNoRPsAxdaAfG6VldXGY1GpFIp6vUGnU6Hq1evAYFw\\nstVq0et1saIRXu2+pNNuc3J0TCaT5vj4GN/3yGZz+LMZUy9ovkTTXa3VSCRtzEgwejINi0a9QTwa\\nw/e8ufhqFIwJeoHwzhk4uMMh2XSGXDZLr9djbX2No6MjDF3F86c4gx5vv/0Wqqpg6Dq+pDDxJjDz\\nuXhhA3yP4cChUa0Sj8XZe7lHoVAI7F1GnLPjc6IRG103qFebxGM2Dx/cxxm5NGpN9vcPOT894+T4\\niF6/w/HJMTvbO8RiCey5WjoACDGits3QDVIO/emUer1BNBLn5ctXZHN5fvXpp7RbDS5c3OD07Ijx\\nnMnz/SmyKvPxx79EMwyePn1CNGFjJ2yYzYhFo8gepHNBCJYzHJFKZRhNxsx8QJoy6AWK+Hq9HmYF\\niE2M4/GYUqnEz3/+c4rFIq1Wi3qjST6fp1FvMJ1OaLdb1Go1ZnO9xI9+/ENq9TqGYbK6vko0Ftg1\\no9EYve6AWr3G4eEh2ly7IssKZsRiNByRSqWpVmtIMsTsBJOJTyKZZG9vj3y5yPalHY5PTxk5QzRV\\no1Gps/9ql067Tz6bRVYUhs6QRrvFk+cvqdabbGxs8Pnnn3PhwgV+67d+Cym+/OUv4Pc//dlH56dn\\n4MtsXrzMJ7/8jEHP4dXzVzx69piIbmDoBpVKhfLSEjOR2OUGCCGZTDGdTMnkckwnU9bW1smXCkiy\\nzHf/+q8xIhH29g4xrRjNZgvN0NF0jZOTU0zL4tnTJ2xtbtHvdvnaB1+l2+vg+z6d+b7nq9evcXR8\\nytNnT9GjCTLZIvtHR1jRONVmHVVXmEl+sAbU95khz4ukhB21UCTwmYWWJm88maPc0ZwS1bGs11Q0\\nvPZrCxp4sRAGB+sM3xce59feb3FYS5KEOxxhmpHw8J2Mg8NoPJmgaUFRAglZnqeFya/z1EejEfF4\\nPKTGBU29mAQn/gkL0iKVLURuwtokPNTCUiKU2iKdS2wVEzNVgZYFihS+adHECPS3iL4FohSPvVjA\\nhMpa2LqE2EogSmHfCVBwMI82Ita8kQoakVQqheMEK21FLrhoRrw5IyKYBhE4AzBa2LUt2AExQhAF\\nWsy5hYocgr3ZYpYtCrSwxImceFGYxWhlUZwmy3I44hDXVOwgf62BeD3uaLfbYSMo5o/ifon3pGAw\\nZrNZWJjF31nUXYj3rFhlKzzf4n0qmrxOpxM8xnjC1J/hez6KLPH4wT2ajRpLxYBy9Pxgx7awwwXv\\nmwDNN5tNHj58SDabxXVdTk5OaLVaSJLE3t4ev/d7v4dpmnz++ef82tc/pFgqgDTj6OgoeG+ZFnY8\\n2HDYbDV4+9o10qkkSBK1eg1fgpgVpdFsBo2jMkNRZGQFMuk0R4dHrJRXOD87YffVC5KpBOfn59Sq\\nVdKpNKZhcLR/yHiuKRhPJnx+5zY7l7aRpRmNegV3EDA3/synXq8z8SXKy8tk0il0TaXT7qIqGsgS\\njVaLTrdPt9Oj1e5g6FHsWIKjoxNSdopGo8XnX3xBoVzEcUesr23QaXcYOgNa7QYPHz/k177+dTx/\\nxrPnz9jc2mIwcDg/q1JcKpEr5KlUK7iuS7vdZG1ljfv3HxKL2WQyacrlMkOnz8Dp0+t12Lywyf17\\nD/C94PP++Z27vHPtHTY2NjivnLG+voHT71OvNnAHDnYyhqapJJIp2u02njclkUjRaTWp1WqhpmMy\\nmaBqBpKkcO/uHayISXmpjCxJpFNpDMOiWqlgRWPkshn29/dZXl6m3W6ztbPFyekxtVqFt65e5eTo\\njOWVMnv7r6g36ly+dIlWq41h6CwvL2NETIyIScyKsbu7h+f5rK4GmeitboejoyNGUy9oDqdjdg/3\\nidkJYlaU4WBILpPlRz/8EWYkSjSVQo9ESGVSDIcDLl1+i1QmjSKpwQZIWabT7sBsRm7j74GN7IuP\\nv/cRkkunW2N39wWZbIZkIsrMn1Is5LEsE0mCdCpNzI5z++4drl6/xngkxF0uBweHyIrK3Tv3GE8m\\nNLttTs5Oefr8WRBDOBozcFw+/sXH3Hr/PcauizfzuXvnNtevX2fsDNF1FU1VababFOfChdF4jKRo\\neL5PfzAkmSnR6w1QdZ27Dx6QL+aDdCNDxR2OiMVtJlMfVQsOy0zKDuIJpdc2L28yndPNr1HXdBoU\\n1UgkmJcKZCOQXHD4S3heMK9eLHRIhKEcAjkPh0NUJYh9FN5lTQuaATNqhapuWX49i1bnxUFQ0cE+\\n3dfWMoHOBCIXHzJB2wshk6B5RTEVOeS+74eUsxBSiQIqbE9ASDVnMpng8A6v0esgl2q1Gha1RYGb\\nyOAG3hCzLSL3RfGceOzBYBDO5oNNTsb8us6YzKMgBR09maN3sXtalmX0eXMlNp4Jans2m2HPZ/kC\\nmQsqWzRCr8chemhdWwxuWVxOIoqnKNiCWRHhEOJxRqNRGEYjmJl+vx8K1UTB/f+zBIprLRoxob9Q\\nFCXcIiaaKnF/BfqNxWLhdRSzb9FMCKbBtm06nU54WBuGgTPos7RUxh0F8cBRw8CbjLi0s0N/0Auv\\nq2hiVVWl0Why7do1SqUSqqqGdjThXR8Oh8RjMeq1CkdHh8x8j6PDfQaDLjeuX8X3fT54/32ePXtK\\nu9Wk1Wrxm7/17cCG6Ax48PAhqmFw7fo18tlcaJPTIwr1epW4FaNeb2DOk7pMUyeXTTEZu+zv7dFu\\ntYhFo/gTj5PjIwrZLKPJBN0yUVU58MJPRzDzMQ2DSq1Ko9VAQmFl7QJRK4oiwenxMbpl0R30cUZj\\nDCtKdzAgm82TzeXp9SpYpobvTej3+xRLSyiaRnF5ZR7a0mU28zBMg4gZ4eLmRdrdNt1+j1y+gKoZ\\neDOfa9euU6nXqNXrzGRQkEilEiQTKUbulG63x2Dg0Ot1qNdqzKQZO1sXMfUo62sbRGMxZkDSThCL\\nxihks2xub8NMwoyYKJKMHU8Qt+fskm7Q7fawoia1Wh1FkbFtO7zHM4RuY8ra8jIiXVF8foVltVKt\\nEItG6S6ExshKEHvdH/bwvBnJVA5vOqbRrDGZjIPNjTOJTqdLvpAjYkbQjYAFvHfnPjdvvssnn/yK\\ngeuwsrKK5/lUqlXS6TSe7/OXf/4fWSmUiEZMti5s8sXtL7j41mV6I5eVtRXSGRtVV5Bkn/FkyNh1\\nA/eK53Pv3j0ajTp7e3t89du//+Uv4I/v/OwjaSazt3fIZDKhVCxQLBXIFjKsra6wv7+HJAX5tvVG\\nHVVTAQkzalFtNjivVSiWigycIZqucXRyDJLM5tYmrV6HRDrNwHHRDZOvfvABjWYdSQErGiWbzZHP\\nBik9ibhFvXpKMhVnqbyEGUtwYWsHSTUYjiboEZP+eMRwPGbqBfM0XTOQJZnxaE5Re1NGwwGJmEU6\\nEWfijuh2e5gRnfFojCprGIbOZDLCtCKMxiMg2NRlmhFkKSjSiiIHytQ54pzNPPyZh6ZrwAxZkXGc\\nIbqh/X/QjziYFVnDm/qMx1MkZAwjgu97DB0nQN2yjMSMfq9LfH7wiuhTUTxEzrewBAnqXCBDcfCL\\noivm9BDMlcWMU6BHIUQTvyvGA8L+JObswjssaHXhSxe0tSjEIkdcUOwQ0KT9fh/LshgMBuFrEQxC\\nvV4Pi7woTOK1Bih8vspUkfG9IKkpalmoiszM98LHEa9dzLklIGIYr+lCScIZDNB1FVmWmE58NP21\\nrkEg+PQ8dUso+UUTItgDEcAivkSx7HQ6r9mW+RhikZYXtCMQxAXncqElTFDZIYMwL7QAlUolDLQR\\nYjMgZDzEfY7H42GDINgYca0F7QyEjYC4vqIJE/8/cGbM8HyPZNzm7PCQdMJmMh5jWBFGkymj0Zhu\\nu4szcMgm0+TzBTKZDO1ui9PzMy7tbPPsxXNKK0ts7uxQKBZp1hsYqka33aWQy5HPZojGtLnKvMrE\\ndZCZ8WrvJcfnx6SyKVx3hB2Pk8qlufHODfK5HJWzcyRJ4vDwiJE7pXJeZbW8gqHrTFyHk+N9ut06\\n6YzNg/v3uLy1zXQ8JqpbvLXzNo1aIxgNzKYk7SQX396k73T55c9+SkRVMTSVsT9k9eIWpfIyXcdh\\nfWmFfqfL2dkZfXeI446IRG2mM5lMsYSmBaFVkYiGaVjs7R9gRCIUS3lu3/6cycyn1ulx+OIVmqLw\\n//zVf2T70jY9Z0C2WOSLO3f54KsfoukGnX4PMxolErNodTqkUxlOjk8pZjP4kwnHhwc06kEAytCd\\nsLJ6kZHnoasq6WSKCQqSZmJFg6Kla3pwnvgz+u029+/cpZgvkEynkCSVVqdLq9nE9zxOjs7A88nn\\n0gwGLtl0nqE7xhlPiSXSWDEbDRV/MsIyTeLRGINen6VSiV6/R66QRVEkZkzJZFNUqhWsqEk+V6Tf\\nGzDouOSLRbqdHppuIKka+VKJRqOJZZlsXbhIrV5F1RUqlRrxeIKVlQ1ajS53v7jH+oWL7B4esLy2\\nzq133mEytxB++N4HxE2LdrtNxx1Qr9cpZjMYtkkhG2M0dsnmMsiKzsgNGqux65CMJ/jxD3/CeaVG\\nMV/mK9/+vS9/Ab/7ix985LpuQH3nikynPnu7u/ODyuDhw0eUSiXq9TrNZpOV5VXq9XoghtF0avU6\\nQ8fBjBh87cMP6HTapLJpnjx9ylKxzPLSKnYsycrSMh988BX0iEatfsbIHeF7E54/f8pk7LK9s8na\\n+irOMPCtHp9XMKMxzs7OGU/HTCZTGq0WmUzgi4xGo29YkYRwKJfLhfPiiG7Mi8UssE1IMpPJGNOM\\n4HlB0RKzYUmS6Pf6c4r3daZ0PB4sNJHkALUK5XYymUDX9VD5uziH1nUdTdXfCGwJ6NbgkPZ8L0Sh\\nYqYq5pQQFCch6hLrIxdT2gQKE0ir1+uRSCTwPI9utxvSxyJ4RiA2QXULRLyYeCZmuKJAtdvt19a3\\nBeQv5vCi6C4qnMXSDdu2iUQiWJZFPB4Pn7/QIIgiLIpcfL43ezHVTqjdA5W0+8aIQ6Bb0ZwIJLpY\\nwMR8dzQK7vcMQkZBxJV6nhcWS+FNF6MCUaBFYyXGFWJGLSjtRU2CQOCyLIc74sVzFqyOGDsIZmNx\\nk1ir1QoT/cRmtsWYWxH0IlgLTdPCQi2ocdEoiXuqqirdbhd4bRdcZEA0TaM3GKBrKuPRmNHQYdDr\\nITGj2WpiJxKsrq7iDgLXg8+M58+fUy6XqFQrPHzwgGazyeHBAbadYGP9Aq9evWI0HPHxzz9muVym\\n0+kiyzLlcpn9vWNOj8+5eGGbF89fcXRyzB/+4T8hl83PPfpqKDwdDAakUilu375NPp/n/PyM0WgY\\nhMi0muxsb7FSXmF1dZWDvUOmUz+geDNZEqkMjuvyau8VM8VDUgDJ5+TwgId37pFL5clmyjx98ZJs\\nLk8sHqPRqJHJZLATCQ72D5A1hU6vy+ryGtPpeL63XuPgYJ9E3GLkupimznTiBYWq10OWZFrNFr/6\\n9AuWS3m2tjbRDJ0b79xAkWVajSbLy8usra6i6zp7u7t0Ox18zyMRT+C6Q0zTwul3efr0Cc1mk77j\\nsH3pMleuvsPUh0G3Q7lcpNVqcGH9IscHx9TrFaJWsGZXAsrlMn/2Z/8Xf/4X/zeHR8eMRwHb1O0F\\nKW6ZTJb9/QNyuTx2KslkMsXHR9V18oUiztBBUWSm0zEJO4mERLVaIx63efz0GaWlIjMCAWc8brOy\\nshqMfqw4mUyGer3BZDLFnY5JJtLcuXeXXC6LLEv0e13yuSzNeX65OxqgaQq1aoXy8hK3b3+B70+J\\nWBFW1lZZKpcZ9Pq0Ox3sRApn2OfpkyfBGW+atJsttrd3cKdjao0ayUQKVVaZDifouoE3nXFWqaAY\\nJvV2j6XVDQ5Pz/ntf/iPv/wF/M//7H/7KJ3NkkplGDgOg4HL4cERz589J5FIoCoq6+sbHB8dk0ln\\nuH//fihomUwm6HPRzObFi/zoRz/ENCNMpxNMyySRSOH0XV48fY4di1OrnQEesWiEjfU1ctk0H3zl\\nfT784Kv0ej2OT07R9QhGxGQmyVSqNSQlUMh2ez0mc4QkFNZiriuKn0A+wgKlzZFTLBYFJIINYZPA\\nAmGZIXX7OjDFmB/C4mA1cZxBIG6JGHMrmYZhRMIFAYJSFXNHcZBPJt4bUaNBpGZAxzP3fgvaWxQE\\nIfYS4SBCkDQcDsNZt0BQgkJdnKeKebegeuF1kIxoUjKZDOl0OkRmgpoX1L8oAvF4/I1CI1TxArGL\\n0YKwYS2qzAeDQRggItC7KNzi3gnWQFDEQigmHkM8p8UMb0HRCfuWuHciLlVcH9EMiOYCXgsMFUUJ\\n58ei2E+n02Dv8rxgimssmAvBOAi/uPi9RVGZQNJi7m2aZqj2FwyHJAU73gV1LoqxsByKXPjXdkUt\\nbGCA8HksriBNJBJvKN2FnVCI24QWotvthk3g7u5u2CSZpslk6tHttJEliVTCppjNkkomKC2VkGSZ\\n58+fE9GC9/H169eJRCIMBn1OTk+D12FE2N7ZZv/ggHQ6w49+9CPisTg3rl/HGbpIM9jd28OOJ1ha\\nWiGfK7G7e0A8leLK21d5+uwJR0dHVKtVABxnOHcejMPRg6Zp88/AjEajQTabQVM0KufnPLj3kEa9\\nSdpOkSuW6PYGHJ+cMpNmTJmgWzrX3rmMrEoMm32a1SajoUS7O+EnP/kFM9nn8OA56lzYqUoq2XwO\\nZ+jS7QfNsaJqOMMA6JwdH7NcKoDvMxj0SaYyQWF1Av/1xsYGyUSKy9tbJBPBLmx3EAg4VUXh4sZF\\nnIGDqmioqo5hBDvDZVlmMh4zHU+Jx2KkkykkGbKZDKqukUyl6Qx6RFSFuB3j6YunmIZFKpni+OiQ\\ndqeJ67oslZcwdINWu83W5iZ7e3soisLq6irjyYijkyOu3XgHZzjk4PCQ1dU17GTgKomYFo47JJnJ\\ncHh8RMZOs7+/G+xXUFRevHzF+XmF7Z0dIhEDz5uQTmeoVqshSLl37z4A2WyabCHPvbv3KJeXME2T\\naNSicn6GLEmsrS3z8OF9SqU8qqrQbrcDC2sihj/zyReLKKqKBNRqTUbumCePHtNs1UkkElhRC90w\\nqFXqlEpFKpVzlgpFsskUTx89xnVcZEnm89u3KZVX0cw4L/eP2T045bRS57/6p//dl7+A/7t/88cf\\nnZyfs390xEySGQ7HrCyvUCwWSSQSOM6QyWSKoqgsLZXnHukpMgrJVIJeLxCf+ONJsC83amHZUbzp\\nhHw+jzzzSKds0mkbz5tSr9dI2AlWl1eIGAb93oCf/+KX+P6MWr2JLGvIqsbAnTIjSNWq1Bt4sxmF\\nQrB0fjAYhJanRU+taVmYc1QNICPNKcsBvV6feNxGliXi8dhcREZIAeu6Pp9b6/PtYsEaTvDmhXEy\\nfxyd2cxnNvPRtNcBJeLgF4ezJL1eFCIKgqYFKEpRlbDATiaTsOEQCFIUN/G3BHru9/s0Gg3S6TT9\\nfj+w16TTeJ6H4zgMhkPsRIKp582T54ImK5VKhfQtEBZz8bpF4RJqYoGU6/V6OJcV9LCY9YuisYic\\nxesVqFE0OIv+caHcFoVONDyqqhKPx8N5rUCR4udiNicKtvjZolhOkiS63e4bme3CWiWU1wGj8jq9\\nTKB5ca06nU5Y/MWMWETDipGBQMBiz3YQKfraSy7WxgLhc5tOp6FlTzRZgkqHgIlpNpvh51I0R8JB\\nIASNi5YwoX1YbCQFGyGukxo2sbGwARDXWMzm47ZNp9tBkSTW1Pry4AAAIABJREFUV1c4Oz7mwf17\\nZHNZbNvm7bff5mBvn2KhQL/f5/T0mFwux9n5GaVSgUw2QywWp9ZsoOkGV95+m4HjkEmlGI8nKLKM\\nYeg063VUTeP09IR8PkfcjmFZEaKx4P198+ZNjHnQh6podLptVDUQH21tbQWpcYkEnU4XQzcZuS4v\\nn7+g3WpzcX2dzc1NDvb2+au//Cvc0YhUMkXEiuA4PU5OD6nVOpzvV9jbO+Tf/fvv8YvP7qLoCqoB\\na+USS6US2VSW8cTj6PgESVZIZdJohkG1WsOfyfz4Jz/l5o3rPH78ENOM8OjRYzK5IqYVIxKxaLXb\\nGIqKO+xSrZwTi0XpdTvoeuB+mc31OM5wTDQap1KpBmKtVpvj4xNMK8rPPv6YS5d3WFte5uzkmFjM\\n4uTkiP6gR8K2UWSVkTfGGTlMnDGV03MkWSJXysMsEBw6wwGZbIbtnS3e+8otkCFuB/u+kaC0tESt\\n1iCdyRKP2+TyWfZ2D4Ic+GngQTd0k3/+P/5zyuUSr3Z3iUQsOp0uX//6r2NEDLq9DpubWxweHuJ5\\nHu12m2KxyNB1UNQgW3/9wkXuP7hPoVDk1f4ev/71X0PXNFaWl/jZT3/KYODw2Se/wtAtEskUn3zy\\nKZpuoqnBNd/bO2SptEKz2eL09Izr129wfHRAsVgkk8vx9Mlz3JFL0o7TqFX46d/9kIk74utf/xp/\\n93d/SyoTLKjxZlAoLfPZZ5+ztXWZd2+9x/X3f/3LX8Bn/vCjZrvFeDJhMvWonFV5/OQha6tr/Oxn\\nP8WyLB4/foqm6QwG/QB9j4ckEwk63S75YoGoZZHJZlkuL5HNZmnUG9y6dRNNhcm4x1fevcIXX3zC\\ng/v3WF29yMlRhb/48+9Sr7V59OgFxdIKzXaf4tIanYFL13Hp9QfIqoo8TwuKGFHAD+k/4SMWoqCY\\naeHOxUPC7uJNpnNbUITxeAJIGPMtNePxKFw8IQ7y3txv7LpDJpNgt66g5lVVw/c9fD8I6NRUgxmz\\nsBCLw16gaklSwp3VAumIlaQDZ/AGClxcWSkKkfDruq77xoy0VCrR6XRQ1SDH23WGKLKMrmnM5oK4\\n+BytD4fB7lsRDiMea9EWJ9TK4jFEYwTBylZhxwJotVphqpwoGqZphsyBoJDFYg1B5YvHXkS2gjYW\\nqFg0EYqihMs2Ful7SZJCqlsUQDF/FjSyKK6LSvuh42JGTCbz+x+JmLjuMHzv9Hq98HUIv7VQuYvX\\nLYq3eH6iQIr7ZpomqVQqfN6LanBh59M0LbTZCcW72Hrm+6830AFvLCcR/8+yrHD00O/3Q5+usAB6\\nnhf64MU9zWQyIc0vRIwijQ4Iv//i2XMu7Wzjjlzc4RDXHfKb3/om49EIfzzFMiJYpknKjrO2ukIy\\nlaHXD5ri8soa+VyOVqfDN775Tf7Df/hzioUiF9bWA5pfUUimbAxDRVam+Izp9hqk0jEs06DXbaOo\\nWrBz2jR58XKXhB0PdCeej67ppJJJCvkC9+7eRZZUUskknufTbDQoLy+Ty2Uplos8evSIhB3HcQYo\\nSFza3qHT7BCNmLi9EYX8BfruhFpnyIff+AallSx/9Ef/hK++/xViZhxnMKJ+XmeKxHQ6wzItapUq\\nDx89YXtrm73dPS5d3mEydjmvnKPPm5VGs8WL3X0SqSTtZgNJ9nn44D62ZVOv1Wk0WzQbDR4/ecKv\\nPvsV773/Ho8fP+P09JRcLo8kybRaLUzT5MKFCxQKOdxBn3g8OJM0VULTVTKpFNGIwfP9g2DZi6Lx\\n4x//mPff/yoDp893/+av+fqvfY3ZbMbJ+Tm5Uh5NV0GWsO0YdiJBOpNhNPFYXd9gPJ0SsSwSqQz9\\nTiBCc0cjFFXl7PScGVAurSBL8M1vfotMNks6kyZi6UymEzqdLpXzKqVSMWw2gwZ9Rq1WpVjMcefu\\nfW7dusnxyRH4EnY8RbvV5OjwkJ2LOxRyRZbLa/zt3/yYq1dv4fsGD+4/5cqNW+TyJUq5JcyISbfX\\nYWNjA8uyeGvnKrFYjEa9yvWrb7G0VKLRbHH06oB/9Lv/gKfPnuNLkM5nyJdKDPo9vPGE87Mj1srL\\n5DIpjg/2+ODb/+DLX8D/5F/+Lx9NpxPiVpTqaYXLW9uslJeZTkbcuHEDCOJDFUVibW2N8dhlc/Mi\\n5aUy+wd7TCYTlpaWePn8Bd//ux8wcAZcvHiRVrtNu93FjEQ4PTnl9ue3WVpa4emzF5SXV2i225i2\\nzdLKCrKhEU3YPH3+lHavh6woqJqMbSfQNJWhM0DVNFK2jaaoTEZjlkolvOkU04gEu73dEdPJBFmS\\nmPk+3mQaLvpotZoYRgTDiKAoMo4zwDD0ENEJe49hGIzGI6JRi2gsimmZjMcjfF8UGhVN1eZJRCMm\\n40mgXJ+BhMTIHSHNt1y12503VokGNqoA5Y3cEdFojIgRLNKYjCdEjEio2I9FY/R7fTzPx4yY9Lo9\\ndN0gakWDx5JkDM1AJvBOepMpI3eEZVok7CSdbjfwmssK7vz7U28aUs7dbjdUTguUJlDyIiX9nyqy\\ngZDyFchPKKZFIRGFT6BYgW5FM7K4GKTf74dzehGgs4hyBaIUQjeBjoUQTKB+gYSFGK3T6YTKcllW\\n5tSxjiQFxS9iREJGJRqNhop9gYoFNS0aGTECEMVQWOLERjZFUajX6yGyFY2SQMulUikcOfi+Tzab\\nJRKJhM9XjHKEAFFcc/E6BDMhfPniegmK3HVdUqlU+HOBtMVzF4tPhCe/1WqFiNzzPMbumLgdp16v\\nk8/niBg69UqFyukpvuexsb5Ou1FnPB5zdHSEbkR4/uIFxWKRWzff5U//9N+ws71D5azC25ffwjIi\\nPHnyBEPVqNVqLC2VabUaGGbQ9PS6ferVKtvbO9h2kl6nz41rNzg5O6GQz+DN36t7e3uk02l2d3fZ\\n3d0lmUxiGBqVs1M67RalUolyeYlOt0fENDGtKP1Bn/LKMhc3L9KoN9jZ2WZvbw9D1YhFY6xtrLG2\\ncoGIriL7DroKjx8+Y29/D9dx6Xa6QfLZ55+yt/uKdDLN4cE+/nRKOpUglYojSwGtfX56SrV6hqpr\\nOM4AXdWQFDg5OePl7h43btxk6s/Y299jf3+fs/NzCrk8H3z4If1+D/BR57ntI3dIrVblwsYqn3/2\\nKd/+1jdwxyPito07nTJDYYrMeArD0QhN1TFVlf3dAzYvXuTo6JBcNsfq6jqypOLPZhhRE9+bBWzc\\neEKj2aS0VEaWNcbTKe7IJV8oIssaqjzhxcsXFIsFvFnwubXMCL4/48WzZwF7NB6yu/uK8XhEMpkK\\n2MBMhle7e0w9n9F4QiqdRpZV7j94wO7uPt/4jV8nadt0Oj329o6w7QTpdApZmnF6ekomm+GLO/eI\\nWBbZQoHzaoWbt96j0W6gSDLSbMp4PMSMB57/WCzGeb3OeOIwHnQZNpt4oxErW1dYW9vkF599yslZ\\nheFwxN9+7/tsXthk0Hf43ve+RyqXZgacVWqUSktcef+bX/4C/m//1f/6kaFrXL1yBVWWmI4nPHv2\\nFMPQME2L09NTTDPC5uYmn332GZlMmhke1UqV977yHrVaDQBFVbiwcSHY2z0Zc3pWwXVGxKIJbn92\\nm1JpifLyKrKi0ul1WdvcJJnJ0u52GU8nnFYrKFqwZEIzNLR5XJ8sS8iygu9Nw9msWIghCpDv+xi6\\njhkxGc9tRyIrWpIkbDuOoqhB4TP0+d+UwsjQSCRCtVp9wzYkZqYBWiUUBfX7Trj7OPCPewyHLrMZ\\nuO6ISMTE9wNkI1CmoH+DebqKZcXwPJ/BwEHTdDzPx/N8ZFkJXqs/Q1HU+Sx+jKpqwarQ+eMEwgwP\\nSZKZjoOYSkWSmSEFIqC5eEs0Le12B3/mvZEuJr4W57YiAnUxXlYo2UUBEqI3YY8TBUzM8cXffj1/\\nDpCeEKqJ4qzrOul0Opyni8cVCWTAGyluguIX11JQ9IIuF68rHo+jKEqo0va8oElrtYLd79FoDLFK\\nUdj+FtkC3/fDJLR4PB7OlBcpZxFTKn4mBIRiNr0o/hPPTyB2IZwTr0vsBBf3Zjweh3YxoSkQwTCC\\nARDXQmQbLNLthUIh9PYDoQ5DoH3BoAgk3mw2KeYLqKpC1LbZ39+llM/Ta7e5tL2NikQ+mwmYBVlB\\n1VQKxRLNZpNsoUC9UuNg/4BsNkM2myWXzTIdjYkaJjNm4fM3jUhgF9IM0qkcmqqQSqbZ3zug1e5y\\n+/YdcoX0/DMvh7768/Nzrl+/TjKZpFQqc3q8x/HxId/5znc4Ojrkzp17/Off+V1i8QSDocPdh/e4\\nsHmBWq3G3v4ezGasLC8Ti9vkswm++9ffxVAtXj5+RNrWqVTOqdZ6KKpMNpPh1cuXGKbJw4cPsW2b\\n9969yQ++/3c0m03+m3/2XwOBHc7Q9Pla0QjjyTj4zM/HSR//7BfcfPcraHM/8unpGb/9O7+Noigs\\nryyRz2VRZQXTMNh79Yq3Ll1i79Ur3rv1DqoiY0Z0vPGEw+MjpsyoNdrkl5aZoSIrJtlsCiYeZ0cn\\nbG1tUiqXGY/G2LEYvZ7D+fk5qWyOeCJGbzBAQmI69VAUmfFoSjqb5eDgACsWZ9DvB03uqEu/351v\\nWdSQFJmff/xzNFXj4toGh8f7GIbOL3/5SxzH4dq1q1SrwbpSO55ElhUiEZOoFUNRg+jqWCzO+voa\\nT589Jh5P0mx2uHDhIufnpxh6YMHtD/pYiRhvX71CPJkAGcyoxe3bX7C2WiZpx/H8CT4Sjx48otPq\\nIkc1KpVT3rt6iYeffgYz+J//5f+Oose5fO0KhVKJw/0jdjYvcXZ6Hoh3JQkzbpDOFvB9ie/+zff5\\ngz/8oy9/Ab/3xacfRc049+4+pHJWIx6PMxq5XL16hV6/w2DQI27HODg6YIZPMpngs88+p9vpsr+3\\nF0QydgK0WSwUsKMxKrU668srlIpF6vUKFy5e5OjojIPjE2rVJmokQjKVms+ZJDrdDoN+H03XUTQN\\nWVKJ6BFMw6Tb64X7lgVdLma1ggYFmHrBbFmkbbmuSzabDb2MwVx2iu8H9KsZMcOC2u12Q9EREM5d\\nRVHw/RmaZtBud0LBmudN0DQ9RGdCfS6+FhPYBH2saQEiFAc3EFp8RqNRmDct1MVCaS+QrrBrTccB\\nDa4prwNQRvNdz/3BAEl+vftalmVkggPbn8/931iuMkd2YtY6Ho8DpItMt9OlP+iHRXdRrR7YyFxU\\nVWEyH1UIEaHwsZumGf5NIfISAq1FIZuYMS9atoSoTszqxfcWmwRRvASSj8fjdDqdN0JrdN2Yz+2N\\nULTX7w9I2HawjU1Vic3HLkJpHnrzFx53cQ+3uEdiv3ar1Qppe3i99CQ698cKdqff74eFVFjEBLJf\\nTNdbtLOJvydEgqLhECMCMW+3bTuk70XinVhwIp6zuF7tdjtkLRRFwen0GE+mJBI2vV6biTuk22rz\\n7vV3MHSNg+NDapUquqahaxq5bIaz8wrPnj6lUCxw892bKLLMJ598gq7rJO0E6xvrPH36FF3Xefbs\\nGZ7vkUkH6uR6vc7u3hGaZhC3E5RKJYrFPLIkYccTPHhwl6WVZTTLpNFtY8xdBCNvzPala3QHI7Yv\\nX+H5q31evNhleWWNf/2v/jWffvoF6WSWkTNm7E4oFcqosoJtRxn0WmTyGZ48esnz589ptBpM/BmK\\natBonGOZJrad5Oy8yru3bmHHYmRSaZ48eUY6FWdjY421C+uomsrQGdFzBswkUCQVwzRxnCE/+8nP\\nMCNRtra2WFlfx+32GTpDXrx4ScyKEo+brKwuBwmFowmtZpPNra1QgOnNfKaex8AZsvvsOd1Oj4Sd\\nQtJV7EQSXdNRpRmGGpwHL56/4MaNdzg5OUZSFPqOQ6/boVGr887Nm4xnM7zRmH67g2FG8DyfXCHP\\ngwcPGA5djDlYaXda+N0Og55Dv+2wWl7l9OiI6lmF7Ytv4QwmNGot7HgCCYliscBSaYmZJzHzZqQz\\nGQaDAefn50hyAFgODw+QJIlyeYVup8OFi5t89vkddN2g2+1gRU0imoYdjzMcj8gVisTMKJ12F2/q\\nMej1Odg/IBGP0ew0OTk9oVDIIuFRLq1zfnjChfUNHj17wv/xb/89PcdjNB3SazVwnB75Yo4Z02Ah\\nT7PJW29dolKrkU1lKS8tE4/b3Pr673z5C/if/sm/+Ojunbu4Q5dYNMaFjQuk0oEiNzD5z60V8Ti3\\n3n0XTdNIJpN853e/gz8LEp76/T5bW1usrqzwx3/yJ7zz7jvsXL7EvQf3GU+njKYeaxcu4E48llZW\\nWVpe5qxSC95ERoTJxKdUXmY0clGVgIodu4GiM2Ka4TxYFAih7BX2HnFwiQNqkQIWc0KBrDwvKODu\\nKEBYJycn2LZNoVAIBVnicBaFd+bPQiQnikckYlKv10MaUhQfUeBEIVvcVy0QlPi5OKTF98XhvDhv\\nFd8Xs19d12nUG0FR1fQ3lMaSJDFwHEzLDGfUEDQ3njcNrXDi4Pe8IK/9P037ms1muCOXbC4bzmwX\\nkblQiVuWiarqIVJe3N8t1OaLOeLioBIoUtxL4XUXqW2L/nORPy+K/qJgS8xwReMly3KoEh8MBnP/\\nvTG/pkG62WuaW2I4dNDn97TdbodFfNFTLTQNgsqfTqdhxrhwC4jxw+LWNaEeF+JBIUITGg2h3m82\\nm2HErXg88X4WDUskEgnT7ATjJN4TQmG+KFwTf08o1Hu9XjjiGAwG4bUWq0UlH9LZLO7YRVEkdEXh\\n7PiEQa9LPBpD1VQ++/RTjg6PsCyLdCbDD370I5LJZDiS6fV6HB8ccvnSJU7n6vSzs7OQ3YnHg01h\\nkYhJs9lA1w1arRa3bt3CMAweP36ErEg8efKEeDzK1PfoDQbkCwXGoxH1Wi1Quh8ekS8WODs/p95o\\n4Etw7fp1jGiE9Y0NBoM+sVgM1x3iTz1mvofjDNjc3uTzzz/FccYwkwLvcKGEoipBrKoR5KenUkme\\nPX9EoZAnkbB5/PgxN268y/Xr72DbKXZf7LG2soYqKdQqNexEnMHQAUnGTiRJJtPEYjHiiSQx0+Ll\\ny5cUi0VevnhOeblELB6n3W4z86Hb75FOp4O91t6U4+MTHGfI3t4+lbMq4/GEwvIyyXQOO5GgWqlh\\nx+J0O51gEZMi4898Go0GpWKRoeMEnufRiLevXsWIRjk9OuTu7dtkc1kkH1ZX1zg4PGA2g0uXLgVB\\nTJZFVJGpVOuoqk632+Pk9IzpxMc04/TcAa1uB0lRWVvdwDTjRGM2XadLMpNmMhpzcnKCJAWrlweD\\nIAei1+vOA5fAiER4+uwlnudjx6P4/pRyocxsJqFbJrphMBpPqJyfMRj0WL+wRUTXMU2LVy/3ufzW\\nVbzJDEXWsdM2teo5jx49QlFl1i9s8Tvf+S9RDZm4FQMkCsU8nXaHiTshYhrIioQ088im0jCTyGay\\nbFz56pe/gP8P//1/+5EZ0fjOd/4L4lGL8/MTut0OruvM08kiYW61LMu8ePGCr33tazSaTXZ3d2k0\\nm0QiEa5fv87HP/85G+vrnJ6cBpvGmk3Kyyt8+vkdMvkikahNpdZA0UyajRaKrCMR7HOWZRWQAwWj\\nEmQSK3KQkWtZVijKEVShZVlhlragN4X6WXyJg3hxTWOAwn00LTggY7HYG78rvNKLliwIUoOEihsI\\nbV5AaBOTZTmc8wpBkUCci0VdHMRizuo4TkjbitluPB4P1caLanTf91GVoGAMnWHIHIzH4+BwNs1g\\nCfb8SwjLVFVh4AzCvysQt0Bri5540ciIQiOU28PhMPQUB+yEEnrJBSIUoizBLIimRxR+MW+G1x54\\n0eAAobpeWOFEdKlA9It2KfE7wgomxh7iGhpGZE7tS2FTICJSjXmkrwg9EayHYDlE8RSoWLxuQXEL\\nv7xlWWG2ueM4c6tMNMwLEPdGXCPBDNi2HTYxokEQqF8E6ghvuuM4DAYDms1msJd+Mgl1DKLBEjNw\\nMcoQvyfoevE+EOtzRWNoGAZu36HWDIrRZDImZducHh3j9PsU8wUUOXj/xW2bWDxOtVrl5js3iVlR\\nfvSDH5K0E5ydnvLhhx/ieR6NRpCBfu3aNT755BN2dnZoNJqsr2/QbLaIRCzW1lbpzTPW2+02Dx7c\\np1gssLJSZjrzuXrtGrlcnsePH1PI5bl8+TKaqjH1pliWSTJho8gSW1ubVKsVcrkcS+UypqEzm01h\\nnrx35eqVudulx9nZMTNf4ZNPPsV1xywvr+DNXTX1epNYLBjPHZ8ckkqlaLe73Lhxg8pZhRvv3kSN\\n6FRbDTLJBE8fPWIyGjEYOqRTaSaeR73ZYn11nbPzcz791aesr67SaDQ4PDzkw699SKvd4MWLF8HZ\\ngBSCi/39fVbX1zg9PSedzrC2ts71t2/Q7zk0Wm1S2SxWNEq/18PpD9DmDE+1UgFeZwlomsbS0hLu\\nyMWKxnj26gWbGxcwVI2T81MS8TgDx6XZeu2aaDabnJ2f8/zhE0DGHU0CF46sMJ54dHsO/szjgw8+\\nIJlMkM5mMHQNRZHod7v4nketVkOW5dBlYVkmtm3z/e9/n0Qqwxeff0YukyeVyhCNxVhfW8WfTJmO\\nR9iJOF/cvYuqKDhDly8+u40sK7x15TKdVouZ75O0E9y5fZed7R2isRgxO8r5aZVup8f2pbdIZ0vk\\nC2Wy2RT7+0dcv3aNqBXFnDuLuu0WL1+94Mrb27Q7HVKpDO1mh8vv/T3YRraxYn+0vXWBH/7g+1y5\\ndplyqUAiYaGqEslkkkTCJpVKoszFYdeuX+P/5e7NYizL7/u+z9nvvt/a962r92V6Vg6HQ1IiKVJL\\ntDmyLCRB7LcYSAIYRp6CgR8MBAkQIE9OLMWyLBiKZUmWTEsURc6QnIUzPd09vXd1dXXXvt19v2e9\\nJw/n/k/fdvIS5CGhGmh0NarqLuece36/3/f3XcbGxtndDSCSiGGQTWfYfv6cne1tLl26RLvR5Mql\\ny8TiCSRJYWZunuPjEpKkIqsqt+7eo5AvYOhGeNOtNxv4EkQiBrIP+XyeTDYbFq9kMo5lvcg/Fjdx\\nceGKqVtMd0IiJYhB4ibc74tJ+YXBipBpiZs1vHBUCybqYC8t9qSC6TsaBCKgTzGFjiZzCX/z0ekK\\nCNPChCmHYJeLHb8o9AI6Fe89n8sFr2v4+kTBFF7Pkiy9NI0FRTAo7LZth17VAuoV77XVaoW704Dh\\n3QOksJAlk8mXWNOj5iyiIRDHRhQfMT0LwmC326Xb6ZGIJ3DcgFwlCoqYesUuWBRIgUYIQxeBqIhj\\nKIq3mMQlSQrIOyFRLzh3R0dHJJPJ4QoBNO1FIpn43dGUNyC8psR5FiYwwhNArFHgxfV0enoaNkQC\\nVRGNpGmaZDKZ8LUL+ZywvI3HEihKAJE7jkOhUAgd3cS1LpoRce5E06HreogOiKZAKB1Eapi4TsU1\\nVavViKg6yBKyElx7nWaL06Mjzqys8Nabb1KrVHiyuQkEJMaDgwNUWSWdSrH19Cnf+ua3SCWT5LI5\\nusNmVKxczp49y+bmJslkkqXFZf74j/8tc7NzqKpKIpHk8eNHzM/Ps7a2yu3bnxOLRCkU82xv7wRk\\nu1iMWzdvEo/FiEWjKJ5L+fSEnWdbjOXzOGafaqlERNP48EcfUCzmmZgYI59Lk8umcWybWq1Co94k\\nFotQrTao1apkM3kmJiYolcrc+eIOb7zxZdZWz3JyfEo0FjRFv/RLv8InH3/Kq9ev8eDBfb744jYT\\nxTzPNjf53d/7X5mcGieVyqDpEe4/fMQHP/ox3sCnUi7z1ltfIpVIMjs7y6VLlyiVTmm1G7RaTcbH\\nJ0gmUmxtbRGLxVg7u85gEBjtZDIZWs0OrmVTHB/j8tWreL7H7u4uZ9fXh46DwbVRLp2gyjKKqoX3\\n882tLZKJBO1Oh6OTYzqtFnPzc0xNzxDRAm14u9Oh0WjQaDSIJ5Ps7u6yMDXDg4cPKddqNNotEsk0\\nlVqdTz77lCsXLjI1UaRRLxGNyCiKS6l0jOu49Ds9Or0uiUSCVCrF8fExjx9tBLLCUoULFy+jSDJj\\nYxN0uj2SiQSHRwekkglarSqqrnB0dEw2W2RqYhrb8lAVDccOMufTyTQP7j+gb/bZ2toim83y8P4j\\nrl69zgfvf0QuN879+xucu3CJer3M8uoyz7a2aLfblE9PyWVzKLLP8uoif/PB35BIJqk1mpyclvnS\\nN37tZ7+Af/qT777X6bRxXZfr119hb3eHiclJxsfGabWauK7DzPQUM7PTHBzuMzk5wUcff0yn3eEH\\n3/8bvvz220GHt3/AlcuX6fR6XLt+nZ39fSRVI5nJ02p3Kdcb9E0LSVHwkMjnsgG85nk4joUeNUgm\\nE2QyGQauF6ZaCSmTafbpDt2ggJd2zmKCE0xmUdREAQVGYPTk8Otgv61rBp4XkL5c10NVteGEG0yY\\nruMOp8loSLgS05m4eZqm+RIMrGkasqbiS+B63uhAHELpwoBGwMSjCViiQGqaFjqZpdPpcHVg2Tb+\\nwEdRFdwRApRmDCVU0cgwqECke8VwXQfHcfH9ARMTE0ET4Utomo5lB1NhNBJDluTAucz38X3Cojsa\\nhCEgfkHMEtNxp9MJ0Q7h9CYQDjGdtrvd0I9+4A/CIjsKGwfuTklsKyChWaZFZPiaIkYUzxugyAqe\\n6+G6Hul0BkVR6ZtBQ2TokRAdCR430PFHoxFkWaLTaQfTuNkPC7TQqQszm1G3MnEuhCRPTNSO44ZN\\nnOt4SEhBIIii4g08crlcWKRFMpvYz4spWaxdut0uiXhyyCLXwutJrInEexF+6mJlJBzjCoVCaIYz\\nytAf/RwI+ZplWdTrdTzPo1arEdUMNN2gVCkhyxJ2v4/vuqQTCfB9+r0u2VwQfvGd73wHz/M4LZWZ\\nnJjk8uXLPH36lMnpKSrVCpubm7RaLb787lfYePQ4XHXs7u5i2w6XLl0KmtVWk6OjI+bm5hlIPolk\\ngkcP77O3v8fS0iLlUgm7byH7sL66xtH+Aa7tEI3qTE4Ctvf+AAAgAElEQVSMM1YssPH4EU82HhOP\\nGUxPjDNeLNDrt6mVSzSbNQq5LPfvfIHk+3iOzQAPs+9QLpcxTZN2u0MqlWJqappkMjFkV6c4Otnn\\nN37jNzg6OqZSruC4Lh/88AP6rS71Uo3joyPOXrrM+sXzzE3OUalWSWYyXL32Cgtzs8PrUebpkw0s\\ny8SyTCqVEpNTE1y7dhXHcZifX+Tk5CQwqInFyRVy7O8fkM/nufHZ5zi2zfziHD2zRzaTBgn6nQ6l\\n0inJZILnz59RKZXRDR3HcTktVxj4Mg8ePWBlcZlKrYoz8ChkchweHmK5DmanSyQWpVAsMjUzg+s4\\nnDt/kZ2dXWamJ/n2L36HTD7D8ekxk7Mz6IbB5PQkV69cpN1tkc2l6Vo9Go06H3zw44DRLgUmV77v\\ns729jSzLVCoV8vk8Fy5cYGxigoiuo+sG9x7c4969uySiMTRN5fHjDZrNNt2+yeHBPrIkMTkxwdHR\\nAaWjEseHxxSLYzzdfMLi4iwbGxscHhwSjWqcHJXY2z9iaWmRp0+3+Dd/9K85PT4gkYpxdHxIp9MM\\nMtNnZ9jf26V0ekxxrECva9GsN1ldXeP8638L4kT/l//pn743NTnL17/281imw/zsAk+ePOXkpMST\\nzSdEDCNIg+n1iRgGPvB8d5ej/aNgMkPi3t27XHvlFQaSxP7BEa6m0LVdupZDs9tl+/CQ4uQUXbOP\\n7QYQWNSIoCoSruvQM/ukEkkGBJBoLp9ngI/nD2g0mwGr2nZQZSV83WJKEhprsVMU6VDxeJxGoxEW\\nPuEKlkgkQ7vCSOSFRlZM3+JnX7bGVMPpZtRC1Bl4IEuYloURiWBEIiiqijOcCMXO1nad4WQsE4tE\\nX2K4jzKpxfQv9uiieIpJVxRIXddxRPa052JEDAbDxoAhu1rI414wxxXi8Ri2ZROkoL2YLPEhmUwR\\njydpt1tYlo2mvcgjF6SqUZOWUetXUTB0XSeXzTMYNkSWZeM6LrYdWBp2h2zxWDTOYBA0SwP/RZKY\\ngL+TySQS8vB4yCiKHLJc+7YVkJoksF2HAT7uwCMSi2I7LpIs0+kFueueP8AR02ssihGN0Ol2URWF\\naCzykoOZsMUVKIPY448a64h8YmFOIyRs1UpteIyMoexwQDKRpF6vhXtmsV4RvycmZtHAKIoCPkPN\\nd5x4PEG1WqNvvuyFL86tQB5SqVTIZxjNQRfrC3F+hE2s2NuL9Dnf90nFEtiugztwSaXSHB8cMj8z\\ni9M36XW7pDMpZmdnmZ2dpdfr8cUXX2DbDrF4nIePHpHOpNl4uok78JiemWFmbpZbt2/TbjZDx8J4\\nPI5u6Ny6fYtms0Gr1eLg4ICxyQnq9Rrf//5f8+rr17l67Rq9fhCnOT0xRbvVJplKBchAq43puXR6\\nXdrdDgvLC0xPTzE1M0mn06LdbTM3NU2tWmNqcoxGrUy1VmZpcZF+rx8SuGZmZlhfP0MqleLk5Ji5\\nuVk0XaLdbuAzYGV1CcvuUSqdousaR/tlIokU737n22Smp/g7v/M7XLl6nUa9iecOMC2LUrlMrVZn\\n5/l2YHQlS8i+j6FrJJJxZmanaLSbdLptVE2l1zGZX5jFZ0AsHsV1bKJGBNd2iUUjFAsF+mYXy7bo\\ndju0GnVazRoQoCanR0fouobreviSwvbOAcWJaWZmJvFsh1anzdTMFO1moC8vjo9hdnuomobtOBwd\\nH3PpwmVOSmWKxTF8z+a0WqY4VkTXDQYDj7W1NZaX52i0m2RyOdLZAqYD7kBDi8QYn5jAiBnI+Miy\\nhGn2iUQNNE1nfGj64w7cIdl5nB9/8AGffvYp165cod/vs/v8CG8gkc2k2Xn+nEhEJaIrVMrHdFoN\\nms0qV69doto44ee+/haq5OHYfWLxKK12h0azRiab5MrVizTqVX75l77J4ckh8XiUZrsdDASuz+H+\\nLifHR7x67RWcvk0mmULyXC6984s/+wX8h3/5p+8dHR5xcnLCzMwUlm0xMzuDqqtkUgUUWScRT2Kb\\nDs1Gm+2dfbyBhGFofOWdr8DAZ+3MGfYOD7nxxR2W1tc5rtY5PC7R7ph4A9C1KI5pEtEN0vE4mmIw\\nwKff75LJZkLvbKfvoEdUotEANnWHU1lEwNHDCUZArWInLCY2cbP1fZ/2kL0ubDoFlBikfcXodLoM\\nBj6SJBPAxIE8y7YdVDX4YMTjCXyfcOcpiGRiL22MmGI4jhMWTMFWFoVewPn9fh/PH4SsZ9E4COON\\nUctQARELEpZhGKE/u+/7JFNJur0eSBKmawfs6eF7B8LXIPTCQfG18bwBg4EfyuBqtTqpVBrLcun1\\n+vR6geRJAvxBEPQioFfhkT4aUypuzoLR3u30kSQZTdNJJlN0u0PZnaaFEjtVUYLJ3zIR/vCj0Zmy\\npOA4Xsj+Dwh/OrVanWgsjiTJtFptkskUmhYkI3W7PYxIYAsajcaQpACZkVUZPWLgSz72cD/XN/vo\\nhoGqBHLCXi8IhxnVYgu/eXHsRFa8KI7CREbXDFx3QLE4husOhioFlXK5QjT6IppRmLGMcisE2UyS\\nJOq1xhCe1zGM2HBdEFw73V43RGvEeRA7crGDF0oFQZIc9Y4X70NwEKrVanguAayuharrFItFTk9L\\n7O/skctkOD06Jp/N0jW7RFSdWrXGs+fPaHU72J5HIpsGRebmnS84e/4cDx8/RlYVPv7pJ7xy/Tpm\\nN8gKf+WVV9jYeEQ6nSCZjLOyssSVS1dJJGNousbRyTEXrlxkY+MRyVQKy3P46KOPsE2LldVVDo6P\\n2NnbxXVdmj2LfKGIphusrZ5hf2+PTr1Nr91hfnaeJ48eMfB9ms0Gz7e3mZya5E/+7b9DkjUihjFs\\n0hLs7Dyn02mTzabRDY1yuUK5VKPbtXj8eIsL56+gqRG2nu3x6luv8M3vfIP19TM8uHeXjfv3+Zu/\\n+itUBXQjDr6E2Tcxe33WV9fIpNIM8IhoGolEnFarydazJxTH82i6TDQeZF53u11mZqbpdNpDOWIK\\n0+zhD3wuXrrASblEr90hm00xNT6OpiiMF8dYXV3i+vVr3PniLqoe5cmzXT76+HMsZ8B3vvPzVEtl\\nSqenFMfydNptksk4kiYzOTGG47kcnhzhuh4RPYLnDFBVA7yAFf/v/uzPePvNN7l29RqNahXH7FA5\\nOmJmaoqtzQ3yqRQHe9tkU0lc02TgOCwsLmLbFu12m+npaXq9/lB6ahGNGZSOTrl96xbnL57jt3/n\\n7/Knf/anaKrC1Nw0ekQjGYvx5be+jCKp5NNFImoUp2PxjZ//Jtu7u0xNT7L/bAe7N6Bbd1g6d5ZY\\nLMlJucx3fvHbxBJRFueXeP58G0X22dnf5e/9Z/8503OLyIrGRHGKTCqLooBl98nlE0SiEuuv/y0o\\n4Han/N7ly5c5f/48qmZQa9Q53D2gXWvi2zb7B/s8e/aMZCbHablCPJGkkMuQTCSpVGv0LYtEJk00\\nmQJZQZI1LDcIjBj4Hp1uYKunKAqO6+IOfGRFJZ6I4A8GqKpMNBobGtNHhjCkR7fXIx6LBTfQIWEp\\nNpwqBAlLSGYEzDiaTS3ytIFwyhV7XzHNiEZATPPi5jrKOE4OSTu6qmH2TRRVCQttKpN+CSYWxCux\\nHxds9tAww7YDNrFlh1C62FWKEIvRBmGUnS5cvoTMSRh2AAyGN3aBQgBhgRGscoFKjJLvRGEKcp0D\\nZrDve0NHORtNV0MI3jAMSqVSCJGLXbl4X4lEIpBxNVo0m218fxAeA0VRGAyPSYB4SPStPrFEDPzB\\nS/v4oID6Q3RCR9jPBquKgO07Sl4bRU6EvjyIT/Wx7RfOfJ1uB/wXRjSKJA2nBnPY2HjhDl9wFcRq\\nRjy+WM2MoiWeG0D0vg+dzougEk3T0A0tNLkRvyu03cH1quF5QdpXELYTQOJBnoA7hLtNQEJRlbAh\\nFBwPwS0Q/4pCLs5Hq9WiWq2GxDWhzbesPrIsYVnBtH94cEwmn8NybdLZNDNT0yzMzGB22ywvz2P2\\n+kiawptvvMH777+P7wWBPKquMTc3h6Fq3L51i0KxSCwRZ2Z6mnK5THFsjJXVVQ73D9A0HUlVOC2V\\nqVVqtFp12u0OB4fHZNN5VpYDOdXAG5BKZxkvjBHRdTKZDMlUitdee42xXIG90yMODw7QFQ3Hshi4\\nHp12i7n5Bfb2dml3ugwGHpl0Bsu2aLU6nDt/ntUzq7S6HdK5LKqhsrC8zMUrl8jm8jRaLZ5tPcd2\\nPFZXz9I1LW7fvQeqwle//i7vfPMr1Gs17n5xB0WW6bTbJOIJFhYW8WUZx7NRFZluJ7jur71ylQd3\\nH6IrEt5w7aZpQcOkG1FSyTTNSot0OkWv36PdbrOzs0NhvIiPQiqTxXZcmrUab731FrpuYFo2luVi\\n2y6yr/D48SatVhtDN0glE1x79TV+47f/U+onJ/yL3/+X/P1/8PdpdzoUx4pksmlkfE5PTgDIZ3PU\\nqjUmxidxHI+xYoFWo8bh3iGL8yvcu/uA5ZU19nb2adVaPH74AKvXp91oMj42jqFHKFdqpPN54qks\\n+9uHzM4tsre3S7VSo9Xq0Wi0KBTT7O+esLa6yuHxIdmxPJMzM0xPzzI+Pk25XGJhfoXPbv6UV199\\nlVs377OxscnSygK5QpF7j+9wUt5neXGZk/063b7J7bs32T86pml2OTw9YnFhgadbz5mcnCAdSdDt\\n23z6+W2+/O5XiagavUaTx/fuY/f7pHJJxifG8XyX49MjXv3ab/3sF/AbH/7Ne61WC9/3+cH7PyAW\\nifLxRz9BBmrVCtvPtrl+/U1KpRp7e4dcvHiZT376IaelCqtnzjDwffaPj3EGPj3Lxh0MUFSDft9E\\nkmSymQyqoobQdpDQ5OH7HplUGkmCRqMZkskguMmawwnVsizS6XSoQRXFWxTYUUcp4X4lJm5hSSmK\\nJRDegAUbXEzygiU96vEdjUZpt4PuOBaPEYsHE5TQ1vYt8yUWsSBzCfb4aKEBwoahOczQFlMZvAiw\\nEGYyjUYj3JeOQqgC8hX7WsH8FK9BkKeSyWRowiImZFFYYrEYiUQifF3iuUzTIplMDB/fA/zw+ArL\\nUcH4Frv7UYlbIJWySSZTwIs41mDf7Yf7W1mRcJxg4tZUBVWVCZLigvNm9oV//CC0fRVkQn3YSAgy\\nnfieeC5xbIKmzg4n18iIAUw2m0VTVTKZLKqqhXtswYgX50oUS/H+RNEU10omk6HXC+SIQVjOy9eA\\nYWjh1C7OgZB+BQiLNER6grAdVdXC6VnouyMRY7g2CWBz0WiI4y2Y+oIVLxAfse+emZkJv/9CyqjS\\n6XSJxxOMjY3RrDaYnJoAoNNukYrGefL4EefOrLG7s0Muk+H4+DjgV+gGk1NTKJqKLEkU8nmOjo4w\\nDIOpmWnOrq/T7XY5d2adn37yCbvPt+m2O+xs76DIEgtz8ywsLNBotmi1Orz77tfI5/M8e/4cSQ68\\n/V3HpNVs0G93ScTjTM9McXJ6yh//0b/hzsPHdHt9srk8mXSGg4MDstksSDK3bt8ikUxQKp1yeHTI\\nuQvnmF9YoNlq4nou2zs7XLl6hQcP7zMxOcnx8SmffnaD1ZU13vzSlzh77jLf++sfceWVSzx4dI9f\\n/fXf5O2vf4O/+JM/5cGdByTjcbY3nzIxNoYvSxwcHgI++Vyerc1NPNdF1zQa9TqKInF6vM+TjUek\\n02lymQz1WpPJ8Ul63R6NVoN2p83t27dRFIWVlRVkSebSlWtossLB3i7NZpP9/f2Ra1KjXq9zenLK\\n062nnD9/Edfx6Fs2kqIhySqteo0nG4+ZnBzn+OiIpeVFTCvgEN25eyckhk5OTrG9d0BxbJynW1tU\\nTk8Z+D5Tk1MAbD7dZGFxgdOTEr2uRSZfYHZhiZNqiWgywUnpBF3ROD48JJXOEI1G2dzcYHp6msmJ\\nGcrlCktLS5h9m1Qizb2Hj4inUmzv7vH48VMGvsz4+CSzCyssLS3Q6/dxHZep6Ql29jZxLY92p0Or\\n1aNWa6IMmUS/+AvfQTUktp8/4/y5s9j9Pr1Om163h6wqtJttDo+P+flvfoNyqcTe9nPGx4r4DCgW\\ncsRiEcx+oKi6+PbfgjjRv/rzP3pPURT29vZIJpKB9GtiAk3VcEyLQnGMWDxNNJrm8pVrVKs1fMkj\\nEkkyPTeLL8s0Wm26lkWj28Ee+PS7FkEClI5IAdO0gGwR5GK7ZFLJ4Q1TCfe6nU4Q5ynkUELCJG6M\\n3tCQxTCMkKVbr9eBF2lZYuJsNpvk8/nwpit01+IGOKqxHpVqCQhS7LrFFCxgeVEwTNPEtK2XYHqh\\n8xWs4XDaG9p8AhiqFjKBxXQuCrRoJAQrXUyX4n2JXb9oIMTvjdp2ClldvV4Pd6zCPU0Q+oR0TZCj\\nxIQfjb5gTXueiyzJw0nNCtcTYtKOx+PIkoLt2KHRTqfdJZvN0RtCvuKxO50OjheEeQSuYf2QCa5r\\nGu12K/haDxoW07SG07Ua+suLIm0OXcgE6U94k4u97miYh2HoNJuNsHgBpNPpoFEaPt5ogRZcBMH+\\nF3C5OIeC6S6mb9sKGsBgH+2Fk67gZaiqHPqWt9vtkCBXq9UYeD66EeSKB0iRhOMEiJFwvkskAmJV\\nwMsQxjT6S1O9eF3CnEf4HUiSFK50qtUqIvVOkNgKhQIAlUqFdDxFt98jlohSq9XQFZUvbt1kvJDn\\n577+NUqnJ+iGzsbGBpcuXubg8JDJqSD3oN/v8+DBA959910sJ0AyFhYWUCSZVjMwaFpaXGJubo5Y\\nPEapVCKdTFFvNJmZmeH+gwfs7OxxfHTM7NwskWiEaqXMw3v30DWNL3/5yzx/vs3dL+5QrVR58513\\neffdr3J8fMTHH35MKpWi3WlzdHyCpmhcvHqZJxtPaLaaNFstxsfHcD0Py7Y5e+ZcyAKv1+t4rk+z\\n0WZv74CffvYZr73+FtVai9/+nd/EdPocHB3zz//Z7/H557dZP3OGRqmMDjRbLXb29+laNrlUCtdx\\n2Nvb42vvvkMum+H9H/6A1998ndODfdqtJrF4DAmZGzduBL4JLhjxKJqhs7q8wszM7Au7Ys/n6OiQ\\nwWAQeiAEcc41+n2LpaVlXMdGURUsyx5aNidQjSiddodnzzbxPBvbsmh3moyNjfHo8SN6poWmKjSb\\nLdrtLs12l9/93d9jff0sp6en4A0CJ8moQTqTYWd/j2Qqw+72HoWxabZ398lPjNPs9ShXKwERslLF\\n7JjMzi0MCaHB9bf17DlPNp6SSqZwXJN79x8wNTuPEYmzvXPMp59+wd/9rf+SufkVPv3pHer1Fo8f\\nPSGTydHtttl88oTr1y9w/dU3qZT73LzxiO3tDVTNY+3sPAfbB6wsrvCtr3+Tzz75lMlCkcnCJK2e\\njWO5IT/p2fZzMqk4qVQc33O4cO4M3XaHH/zwh1Srdb7+a//gZ7+A3/zwB+95jsv+zh7RSBQ8D8dy\\nSEbjGEYEWVb4/OYXNJpNbn9xB2fgsry2RjyZYvPZcxqtDvFkik6/j6ZF0CMGumowPT1NJBKh3++T\\ny2VIpQKHt2I+F2i8FTmEFG3bDidl4XOtqSpGNPCLFgVPRgr136OuYKKYi5upuPmKG/Lo14HhiU8A\\n09oYho5p9vE8l0jECPehQuI1ShwyTZNUKsVAggF+WHThhfuWIAmJYqooCtLAx9B0NDXI8ha65kQi\\nEWaB12q1sNCOumZFIhGq1SrpdDr0tR5FIcTziucUxigCjheIg+M4pNNpisUi1Wo1tKUFQnhYaLSF\\nbjx4zBdQsphufd8nm80iSTK9bg/Hcen3zSHcHg8Jg2Li1XUd3xsgySI5LkKv18WzXZACu9hYLHhu\\nTdWH7z9odDKZzEtwNtILH3VRYAV/QPilv7BIldB1LZSACVRGGRIVdc3A9xmiDfzfNngCtXhBaJRf\\nyAcHfjhVR6PBe2q1msO9vo8RMcJJWGjChRmOrhtDR7g0/eE1Z5pW2GiJa8CyArRIkl844I2mo4nP\\ngPBqEPnwotETnwuBLLVaLRQ58PQPrOhkBraLZmh0egFJst1qM14osLy4iDdwgxhcTSeXzfF4Y4O3\\n336bdq9Lp9/jL7/3V/zKL/9yoP1utZiYmkSWZJ5vPaNQKLCyukqjXieeSNDqdrly9QpPNjYo1yrk\\nC0UGA5+Tk2NWVpZptZoYeoRCPk+tWqVSrZFIpjAdh2Q6zeXLV3j7nXdRJYlapUI0EhjnCJSi3mhg\\nWTaLywtUK+UhWe0s7XaHsbHxoW2oxDtvf5lKucLG4ydYpoUsKdy5c4eToxOKxTwHR7sc7O0xMzXF\\nv/rDP2FudozxQpF2s8nC3DyPHj9CUhXefONNlhcXOD09pV6vY9kO0VicbK7A1tZzGs06r73xBjs7\\nexSKYwyQiMeTTE7NYCRixKMJSuUKMjLRSBBjenh0GJq/BKoDk0gkijOctHP5PM+ePQ0srGUFxx2w\\nuLrKzOws+/sH9Hstzqytcu7cGYq5LDOzc9TrDXxJoljIYts2r7/+BhuPNijkCly8cIFkIs7Zs+c5\\n2N/n9PSE5bVV2p0OtXqDpYVl/vy7f8WznW2MWISr167xfGeb2dlFdDXCvYePOLN+Bk2Vuf/gDrdu\\n3ebixct8+ukNUqk0kbhBo9Wj1mxxfFLm137113n86AkT49Pk83lqtTr37z3g3p07bGw85mtffYeB\\n55NIRqjWOvzk45vkcpOcO7tGfixJtXrImZXzpGJpfvzBh6ytrfEvfvefc+XyVf78L/+an3z4EWNT\\n03z4yUeMjRX5jV//VTRVRtcUPv3sUz77/AZm3+G0XOFX/4v/5me/gH/wH/79e9tb24yPTQZWeq0O\\nMcPAMi1OSxVKpTJrZ9eQZOhZPVZWV1C0KCflEqqqoQ1JO912YOGpqyrxeJxOp43nucQTEeLxGJ5t\\no8gSkgSGEcE0A0MNkdgV5GWbYZGKRCJEDIN+t4eh6ejai9xlMWGJQiEmZHHjFdPZqEOb2JkHE24n\\nhNcFbC4ex3Hc0PVKwLJC8yxrKqquhYV91NITCKd9XdeJGREs0yQyfA5hmiF02VNTAVQldtfie6MT\\n+SgkKuBSMXECIctd2MeKKVlM1gLeF8QswSAXPtrNZjOMtIzFYrRarXACDwqxhaLo9Hom1WqdZDLN\\nYACW5dDt9mm1Agtd4QEekPG6IUtbGNWEdrDDsBnPdcEnJKmJKFfHCYquYM232+3wuIpGqt8NGMrp\\nVArPcXEdh1gkiqHrDFwPyYeoEQk08r5POpVEIuAJ9Hu9YfOokIgnw9eoyBqSpKCqOp7no6k6nU6P\\nTqeHquqYfYtet8/4+CSKotHvmXS7/bC5EQVy1P40m80CL/TamUwmcJPrmYE8cTili/Pr+1LIFBcN\\nbRAEYw7Ja4Ra7lFHtdHrtFQqvYRYCQRn1F9ej0SxHBtkCdfzhsY5gSvY2OQErueB77O7s83c3Cxn\\n1s/QbLZotdtYts3E5CSSIqPHojx5uskvfPvbTE1Ph2qAwWDA/MI89+7f52B/nzffeIPHGxucO38e\\nSZa58flNzp0/Ty6XxfcHRGMxdEPn0uVLNBo1Eok4ExOTRGMJrr16naOTEzQ9kHp6PpjdPu1Wk+mJ\\nMSYnxnj+7CkT42NcvXKZV69fp1ypIA18Lpw/iz8YkM1k2X7+PNibqxqFTAZFgmQiyec3PkdXdRbm\\nF0hEYlw8d4Zet86Pfvg++/v7JONxLp1f4uqVs/R7XTrdLtnCBMlslleuX6VerzI5NUnfNLlz9y6u\\n64OkkskU+MN/9Ud86zvfQlI0ao0m+cI4V65d49btOwxkSCQyvPra67RbXbKZDDIB4TSTSWHaJpMT\\nM/RtmzPr63R7Jo7rMRgETd7N258TTyaIRGMsLK9QrdUJpMA205NjKLKPpimMj42xvbvLpctXiUai\\nqMNs8aPDY1LxJBfOXeDJ4w16nQ6KqqLIErbt0O60mZ1b4MzZcwxcKDfKnD13hm67Ta1S4Y1X32B7\\new/T8bj/8Alfe/cNLMfC0AxqtQbRaARNU8lkcpRqfbb3DpidnSWbSfDs2QaJhA6Szfr6AhOTOba3\\nt3n7S68Ri6mkk3E0TWX7eYm7Dx5xfHqEafd4+PA+//gf/yO+/8PvM1Ai/OD9H/HTmzeRDZ1MLksm\\nn6c7cFk5c47182exHQvL6pPLpWnV62TTKfb2D9CjUcYnpihOTPLmN37zZ7+A/9kf/v57S0tLAIyP\\nj9Pt9lAVhUw6Q9e0KYwX2d3fY3puhompSZAV9g9OiKcSWLZDp9MdWmoGO7GoEcFyAoeddDoVRPn1\\nu0NCUaBHFvCuruthprH4I4qOKGZi16jrOrVaLZyKBEFN3NAEWWlUMwu85KcdmKwE0HYikcBngCRL\\nWLYFPmE33+32Xir+lmVhe244BY4GeLxgtkfDm6TneXhDRrMoqMKzW8DNgh0uJj5h5vFi3/tiCh/9\\nK1ALEZs5ai8aMqOHO+vA194KmxRx7ETSlUjUEhakgqcgmijPG4RwsWDAC226CD0I9N4mqWHWsDgG\\no3afYictUrdSqUzAgo1EsSwTx3HJZnND5UB0GMLyIodcGJWIxk6WZcrlMrquk06nX/JSF8dc7PEd\\n13kJYRDHqtPu4DgetVo9jJQdnXxfTMov/O2FWkCsJhiGdaiqijdwkRWZSCSQ4JiWGa43xPXa6XRI\\nJJJDvkeCTqcLSMOAlUFYtIFwbRGNBtdGr98Lr71ut0s+nw/tc4XL28TExJCUZ4V+CMKxrWeayIoy\\nbB4GQ2vZBNlsBs920KIGjufS7nSIRaNIA4lut8WD+/eJRCOUqxVmpqeJDVcjjudx/dXrJBIJnm1t\\nBWsfXSORSHD/7j3Gx8eZnpxClmUODw8xDIPdwwMisSiTExOUyqe0Wy0WFxfQNBWz2yWiG+SyWT76\\n5KfMzs/S6fV5/4Mfsba6Qjab59MbnxPXNDY3HrO3u0O320FTZbyBR7vd4uBgn75pYfZ7VKtlWq0W\\nsUiUaq3GysoKESPKzPQUR4cHdLsdokaEDz74gGA9KxQAACAASURBVLnZOQ72dslmkiSSMSqlGrNT\\ns0xPjeNYfdLJKLdu3SKdLTC/tMzGk02ymSSaonBaKaOoGq+//iYLi8s8e77N2NgEZ89dQNIUbHeA\\n50s8e/ac66+/Tr5QQFFVUpksB/sHvPLKFTqNBgPfxbJNJEUmkUjwePNpeB/QNSOUarbbHZaXF5ga\\nH6Pb7wVN1fg4lcopc9NjGIpCv9chmYyxt3vIk80tLNNi4PscHezSaXWplKs8ffqMTqfDxx9+hNnv\\nk0qmUFWFnZ0dbt3+gqm5WY6PjoPPWUSl1+9xfHBIOpVmZnoGz4d0Lsf5yxeI6WDbDvMLKwHbW9PI\\nZLO4nksyk6XVabG8tMAbb7zK4f4eiZjBK69c48aNj8nnsnz3L/6Sn37yIW+8do0H9x/w2ac3mZ8/\\nQ7Ve4xvf/gZ3791jbW2N09IBi0vzDLQskUSK+eUzHFeb6Iksdx8/5b//J/+EfC7DvXt3SMSiXDx3\\nlng0wvOtLZ483uCdr30NXY+wMozOvfzWt3/2C/if/x9/+N71V18lnUqzu79Hq9Ph+fYOiWQCxw8i\\n51KZHJoepVStEY0lqDcbIWksKBwyqqogqUrQnQ8JVbZto6sa3W6PSCSKSNsKA0hGLDgFY3nUJ1xR\\nFAqFQmhxKaZDUSwFRCgY50KXK2BbsQ903cFQM+nS6wWTizcICqzYr8ZiUVw3KPq+z7CgBh7iKHK4\\nYwTCyVsUOzERjWrEfW8Qwqti2hJ7SyE9E5PxKHtePJZgfreHekaxrxUwrth3jjqQiWMmyH2j++BC\\noRAWdNF4FIvF8HHEvk3I1hzHodPpkM1mwyxqkRomlAC27YTQea1WC68JUWRHmxBhGxpA+w66roUs\\ncV3XKZfL4XsURUkQs8SxEeYxYjLPZDKhtlrssEXhE17tgdGFAwyG8sEO8VjgECjkemLtIv4/igKJ\\n4i8KYighNAxAGmrUX3AFxGpCNJWyLJNIxICAoBWNxtC0gOgmyIMA1Wo1lIUJglo0GqVSKZPNZrEs\\nM2wghKWssFgVqW4CQhfnRxwXgGwuh2XZYWMmwlUMI4JjWfgEaXgS4NkOChLHh/ucXT/D2toqnu3Q\\n7XT46Mc/IZlIYA/JeBuPH3NyeISh68zNzVEsFCifnDI/O0shn6dRrWH1TSzTJFcsMBgM+OjDD1la\\nWiaZSgfGJLE4BwcHoVHN1GRgNNTsNLl69QqxaJR0OkUkFmGiUETTVHx/EDTb/eDznMpmqDXqZDJp\\n8D2y2UxwzaeSRKPRgN0fidJp1/nL//DvGS8UWFxeot/vs7K6wvTkBGtn1ymXK+zsHVIoFrhw4Rzd\\nbo9nW8/JZPPoegRNNSiOFbh4/gKz87OoisKZ9XUUWaVebzA1Nc3s3CylUolGq0oqlWRlcZFGo87y\\n4iJjE2NIEszOz9HtNHAdk3aryoMH97h65SqHR0fcufcAQ5PpddvUShVmp6eQfOh1uiTjMTRFYX9v\\nF/wBrjdA1xRKJ/voik8um6HXbXP/wX1kzWBjY5Ner8fS4iKbTx7hD2Br6xkgMTU5zR/8wb8kl8sw\\nwGdubo7Z2VnKlQpTszNMTk2ys7dNPpPj5o3P+a/+4T+kUCxSKpeZGC9yfLLP7NQ409PjNOptPvrJ\\nTR493uLk5IBMLsPY5CTxhIHtODQaFZYXFjA0nVeuvYrr+gzcAbV6nZOjKtdfucbW1gbTM3M8erBJ\\nu91gbXWFXL5AuVInGdFZXpolFjPQlSStVpOxQpG//v77/MX3fkS71+Ov/uK7JKMq28+2eProIal4\\nnPXVNba2tvCRaLe7jI1PUCgUsRyb1ctf+X9VwCUxrfx/+ed/+x//qf/5zU/JJBOoWhA3Z8SitFoN\\nbGTSqQwDJCzHC2VM0hCygxcTczqXHt4QNDqdXghlRyIRPDvIIZb8oFj5w1xmoSceJZkJ2FgUEQHv\\nimZB3JwDhm7kJUnPS5aiQ0a3mNyEdAcGDIZ5t5nMC7KPaQbwZgCXe4yPTw7lYD2cIVQ68H3cEcIY\\nkoQ+lI+5rhummuXzeVr1Rvi6xEQsXs/oTn20iWm323iex+TkJNVq9SVp2uh0KEhOYvoTmdKj/xf7\\ncEGqymQylEql8PgcHR29xEYX5yCZTFKv10NVgDBXgRekOQHNirCM/f39sOAIktfo62u1WuFjWZYV\\nBqiIojy65hD/CkZ6wHcIZHtBc2KH6IMo2qNfi11vEH7SJRqLvLRiEBK6eq1BrdYIiXUC+haSLPF8\\nmqYMofRgIu90OmGDaBgaqqaEr0E4ygnGumEY9HsmiWQ8lAGqioauR15CiASULo6FkP31+32yuRSn\\np6dDeF0L4fVarUYqlfm/fCYC2D1ojESGgKIoyGrgLtjtDt3nZI1avUIikcBqNIimErhyoH83ZI18\\nIkm/3aDdbqLIEsvLy3SaLQxdx+z2yGQyxFOBr3673aZYLAYmLzdv8c477+C6LqWDIw4ODnjnnXc4\\nrVbYPtxnZmYmIJ4qMsVcHss0mZub4eTwiMuXL7O3t0e1WsWTHMbGCjTbLXb2DvB9n/Wzq5h1k0w2\\nhYyEbZuMT4zx9NkW8WSSWCzG7v4ettVnrJDBc1xarSbZbBbHccnnc2zce0A8YXD5yrngHOoRms02\\nxycljg/LfPThDZ48fcq5i2d46/W3+OHfvM+v/PKv8fFPP2JucYFXX3uNfCGD5zkcHx0wszDHxMQE\\n21s7JFJJvnhwh4WFedaXF3Bsn3qzSbvVp1AoICPR6bVBkXnw8AnPnj/m13/11/jwgx/RatZQFYOL\\nV14lk8/Qqh6Ti0dxXIt6s027Y2L2g6ZS0VVyuSJ922Nyeppn21sszMzSaXWJRFQUXeLug/u8/ubX\\naDQ63L9/l/Wzqxwe7mN2e0jAs83Aye2XfuWXWVxZpHRaCbzRGw3mFhaIJxNUKhVc28HsBYoFx/Op\\nVqvUq2UuXTzHwtwU3//e95hbWsFxFCKxDLduf8HZC2d4663X+NGPf0gqHiOXSlNrVEllMmQyGcrl\\ncrBm7fXpdyx816fRKJFOxNGUwHylb/p89OFnfHHnHv/ov/uvefb8c4qZcTQ1gdJt4foOU3Oz/PGf\\n/TmdjsPYzCz58TEmJovUKlVkoHxc4ty5s2xtbQX3h6jC3OISq2fX2d3d5ed+878dNcn8f/zn/xcT\\n+D/7n/+H99ZWlzktHdPt9xmbmKbZapPNZUln8nR6XRRFxbKdcAphZN8c3BCDYgYBk1iQpyzLwjED\\nCDeXy6FqQVa18GsW+18xLY5GJo7uukWBFIYvIqhBTCzi+wKOFtDwKMlN6LVVVQEkDENHkgL5ksjv\\nHoSJYwqqquB5A1RVwXLs4DFjsfDmLXTHwjBGFK5wBTC0VxUMZDGViolXkMuEJAgI4W+A09PTEFYX\\nU1oikQgNYMSkJZjyAl4XRCdBdhJ+8KLoj5LyBGQuWPIiCUyw4cUUJ6ZJgXKIya7b7YbucEKHL867\\nQEDE+xERr8IrXky4QNgkiGldGJYI1rXvD7AsE11/sWMW10kmkwlT3wSKIBzjTLNPvpAPm6XRAJRe\\nt4ckBWiOIEaKcyEKYcB1CIx+xM+kUikGg8GQvOijyAo+ftiMCTJmPJag2+2QSWfo9bqhfC+fyyNJ\\nclhYR9cmYpctjFeE3j6bywLgOC6SJOO6gcmNWBuNShYFPyKbzb7U3LaH07aiBCqHXrdPMpUI3rdh\\n0Oy0kFUVVVaxun3Mfp9K5ZTLVy6RTibJJlM823zK+YsXMC2LXCHPlYuX2DvY5969e2Sz2eBvLkdx\\nfIxILIoqKURjMT7+5BNyxQITU1PU6/WgEWq3WV9fD1An1yWbzYbX6+7uNtdfu4Zh6ERjBrIEnusg\\nSz6teodCMR9o7F2HTq/DH/zB7zM+Oc7C4jy7O9ucWVlmMDwmV69eZWVldYjUKBSyGR4/fsBg4FIu\\nn/LRRx8xPjbGaanCRx9+wuTkFF96+y1s16bT6vCVd75CrV7j0qVLPHn6hFQmRTafZeC56BGdeqPO\\n9NQs9VqT05NjpqcnWZib4e69+wx8MPQoHnBydIjrOAE7vNvBs5UgnMX1yaWzJOIxHM/l1VdfY/9g\\nh8vnznHrxmf4A4+xsQLtbg/Xc6k3GkQVjWq5hjOAze3nrJ0/x827dynXa1y8cIlytUY6myWZKpJO\\nZ4knEtRrderVKlMTU6yvnuHenbu88fqbSLJEvVonm0mhaAr9fpd0KsnM7DSFQo4nTx7DAObm5kjE\\nYyRiCYrFAslEErPXwzT7OL7PyvpZ6s0Gr3/pTWqNKqbdY3v3GefXzwTkv/l56vU6/eFnMB6NMvBt\\nyuUSuqISixn0+20GQ/fNdCKFZZqMjecp5NI8fXKfdCLN6fExpbbFQaXBzMoaEzPzfPTZDTpWDy2q\\nEU+nsF2P1TNnWF5bIZ5MougaetRgcWGJqdk5DCPK7Mwc8cLCzz6E/hd/9L+/l0zFOS1X0CNR4ukM\\nK2trdLp9Hj9+TDwxzBnW9BfMX0MbmlcMhjGBahhm73ke6USSiG5gaHp4wxXOYILVK/bJQfSfGXqD\\nV6vVkFGcy+XCoickM0KzHkxAAcQsCEKC+SwIYIJAJWDl4GcGxOMxFFnBdT28IQPZtkaDQ/zhFBns\\nnZyRfbYgu8myTL1eJ5tK43sDGrU6iiwTTyaGUZaEdqOiIISTOwFhRfhgRyKRMMdaTIpi0haEsF6v\\nR6/XI51O0263wwm1Xq+HARmCpBYUryAVTTjUbW1tAYTFZ5QIKORhQp4njmO9Xg8LiZgOBRrS7XZD\\njoJACETYhrgOQmkMBBnjQwh9VGP9H1t7GoZBr9dBUWR8f4AkEbK6NU0deta7YWPRbDbD8ytei+M4\\nQ418ioH/QksuGs6gmUuGO3uRaieuG1kGx7FDeFxkngsURKwm1GH+/GDgI3bZmqYjDzXtA3+A2TdB\\nIgwY6fdNfD+YvFOpVKgrt207bOh83yedTtNoNHAcG9uycRyXeCwI9PFcj1QyRSRihMdSNK6iebUs\\nC88d4A8C1r8qB775EhKST6C/HzZx7WaTdDoTvA9vEERTdoOEtvMXz5FOJPj8J5+wsrSMrKk82dxk\\nrFjk+OAwOGe6zvLKCv1+n1qtxv379ymXy5ycnjC/uIBmBPyV4vgYN27cwBgiKDPT0xSyOd5//4cs\\nLi7SaDRoNpuBL7llUa6UiUXjTE5MMjE+wVhxjFazxcT0FD4+kUSURCJJPBlnb3uHYr5AtVIiEY+y\\ntrJKs9FAUzU0VaNcqXB8fIzd7/OtX/gGqVSCXq9LOp1heXkJXdPJ58cYDCSiiQTr62cweyb37z0g\\nX8wyNz/L8ckxmWyaZquB63mUSxW2nm4yOz1Pq9Vh5/k2r1y8QKtRY/uoRCqVwXYdKpUyjx7eZ25u\\nFkWRiUXj6EaCdrtBrVZn4PlEozFazQ7lWoPphXkOtp5x++ZNNEXGMKJEYhGmpqY4PDmk1w2IkHsn\\nZcanp8iPFckXiiwsLXG0v08iEScaT1AoTBCPJ5BliVa7TqFYoFgosL27R3FsnLGJcWYX5jk6OebZ\\n0y3OrJ6hUW/gugNcx6Z0ekoylWJhdjpw8TOiGBEDTdWoVMrEolEW5pdwcWi1AjJyr9NkaWGecumY\\nfDbNZCHP6dEJa6trtFotWq0WvW4XyYdEIsbe9jbLC0s4lsXnt26zvXvA3sERtUYLyzLZ298jnozy\\n2ac3sB2fo5Myd54cMbmwzOziGRKZMVQ1QiQaxdAj5HMZCoUituNQKlXQIxHOnVsnGouRyWSIxeNE\\nYzE6vS65qbWf/QL+4MZP3ut0OyysLKEYMZyBzKONTRzHCZx6hiQgyw52XvGoQW6YhiWm4GB69Ygl\\ngqnac4J9XCaTQZIkms1muGcVRVsYqwiIW0zhYnIWN3jBmhbEtlHHLMFgF7szMX2KHbkoPuIxxE3c\\n96FUKpNKpYewuhTe9IJJCFx/gB4xsC2LxDBQJJDE5YI832FhVYfGMWIdIGJQReESN33xtSCNCWRA\\nTJlC6gSEZLdRBy/x3sTPCWa3LMthlKWYDkURTiaTL+3FM5lMaHRTKpXQNC083hAYuojgkn6/HxbU\\n0alb7IvHx8cDiDebDZ3oBGwvDFvENCjOhUAhRIyiQAZGoWddV1+kaQ1cVE3Fdmw8z0XTA/hZpNCN\\n7snFRC6aieAaGOC4ThiHKsJDguuljz8kLorjmM1m8TxnuB7xicVj2LYz5CV0GM2EDwplbGhKNAjP\\nvzieIvFOQgpe+9DXvt838bxBWHBFA9dut8MoUgFLB8S7xBD+LQzDfBJEozEGA49WsxU2KOI4J5NJ\\nVEUjGomGnxXhlR4QJ19wRizTpNvpUCgW2d/fQ5FlErEknuNxcnzE4fFBoPH+8tvEJZVCIc/nd25z\\n9uIFnm9tgTdgemaG6elpnmxukslkWFhYoG+anDt/HmSZxaVFur0eumEQjUaD9LKf/wbzc3Ps7e5y\\n9+5dLMtkdXWVzc1Nms0m28/32dnZI58d49nT5yRiKfZ2Drhz+y5LS4u0Ox083yMaj1NvNTiztkYu\\nk6HTalIpnWBbgbzzyZMnSJLE4eEx1UoNVVP56jvvYtsWpdMSuWyOZDIRIkwzM3N8fuMmjVabne1d\\nZEVhenoSxzLZ3nmOP/A4OT0JzqnrUSwWWZibo9Xugi/z8P5dpoo5JHyMzBjRSDSQgLk26+tn0DWJ\\nzSebjI2P0Wy1mZufQkLBccDQdZLpIvWWxZ/9xXdZXVrk4cOHjI9Pk0lnaNSbdPsm9+7fY+38eR5v\\nPmV3/whN0zi7fgZD0VAGPtGYiun2Ma0ehWKedrvByfEey0vznL9wiZ7ZJxKLokd0yrUKr73xOp1e\\nj63Hm6yurjExMYnVN9EUnZPDEyKJOAd721j9LulEEte2qdQryLLE0yebVMplzp09Q71cYWDbpJJx\\nPNNExceQVfb2dgja7wH5QpF7d+8SMSLsbe+QTKaxejYP7jzkq+/+HI82tvjz736PpZV1fvzxp/wn\\nf+c32Ts8ZHxymkq9S8d0abR7oNv8vd/5LY6Od3HtPtevXqHTbLCytEAqHsMxTUrHJyiSxPzsLHdu\\n3qJZq1GvB+Ev0XicnZ1tFtZf/dkv4B/98LvvxeJxYukMe4dHZAsTWGafZDoZErOi0Sj4A3RNJZfL\\nYZpmqIEW+tpIJILnuoFcZ8hWHrUoFaYmL6BJJSQdCX2spmnUarWXUq3ELlX8rICeRZ6ykEMJKF3A\\nw+I5Rpm9o25YgpXc6QQrAt8PyGuypuF4LpGIHhiBDG+sL8w51HDaM4YxfqJJEJOq0OgKaFsUFEFo\\nE8dUwLXiWAijj2KxGBbQ0SIuIFMIpvv/eA9eqVRCEp9YUYjkrFHZmHBwE4xsEZgyOgmLpkMULFEk\\n4/E4k5OTIUwtiqZYA4iiL/bCotkQU71gcYvzLcx7XjznkLhm9UPVgOs6xGJRJAkajXb4fkaP66jh\\nTiQSpLG1Wk1y+dzwPHfC5iewjq2HELpt2/R6PTqdDuCj6epwD28hyTKyJA93+GrYbMTj8cBEpN3G\\nsuxAvaFqmKY1LPoDBgMPSfo/2zvPH0nu/Lx/uqq6ujp3z/R09/Ts5A0zs2l2uSTvmETyAnVBCbZ8\\nlgxZFmzDkg0D+hNoAxYgwPALR8C2YEBnCIat4JNE6nRMR3KPXB6XJrlhNs1ODh2mc6qu6qryi+pf\\n7azeCX5hr1EPsOAbctk7XVvf9AQ3jEU45alBdeS1P/QS24QLnNiIiAYhmUwCPKZvF9+LG3FrkcmM\\ne42teNbcFbuNJCn0en3a7Q79vs7BwaG3tq9Wq56/+2G5hGmYpJNufG8kHOHLGzdpt1q8+urL1IpF\\nZiZc85OxiQyNdovx9BjxSJR4Is7Vq1cZy4xTqVT4/ve/TyqdZnV1lT978w12d3ZYXFx0Xb5G5i/j\\n6TEiWphSscjc7ByThTy5XI7PPvsMwzC4fPkKa7fXCKkhdnf2mJ6ZYWqqwHBokh7PcFg8BElme3uL\\n4dBke2uTZDzOw/UH/JN//FscHh5im0NsHBKJFAcHB8SiCX7+F36Bf/HP/xm3b90hlUrT6XYZGxvn\\n4cOHDIdDbty4yccff4IxhHsP7rlkvcGA2ze+IOA4qKrG0DA5MTXFytIyP3zzTe6u3eXzz79kd2+P\\ny6urDI0BnW6XielZEiN+SV/v0e93sYdDxsbSSLLM3kEJSYa333mPt95+l2arSTqdo93Xube+zurF\\nVR6sP2R+8TTvv3+VcvmIVrvN3v4hU3MznDq9xGT+BHt7O6hKkNXz57l7+zbRuKuj77Y7xCIRolqY\\nXq9NPBbj4LDk+pk5DpGIhuPY5LITZMbHyI5nGMuM0+60icdj9Ltd4sk4zV6X+/fWWJiZY2N9HWPQ\\n5+DggKPqEY5t0e10yOey6P0eqWScZr2Gaejk8zn2dnc4LB4wMz1DrVpjZ2eLF59/Eb3fxx5a/PSn\\n15k5MUtQCXLr1hq2E+CX/sYvI8kKZ86d4+y5Fc6dW3Etmps65y6s4gDPfeUSDx/co9Wok4zH6bdb\\n9NpNDNNAdmxu3biBZVlsbmwwlk5RrZTY3nrIqVOLlEtH7O7uEdY05laeefIL+I3rH7/uSBKlSpXB\\n0OKo2kANKti2xWB0rwSXlZ1MJj39qXipi3WvmIgMwyCfzz/2/xCrYmEpeVwnLO5+hmHQaDSIx+Nk\\ns1nvbiyMUsSNVEywwhBFNABikhPFUEytx+U5ohCKO2+r1fJ01F6WtOy+BMWqE/CIW/B4aIkzmsiE\\nTOu4rEcUO1G8/qqdpmhmxDQumhZRUMTWQBT9TqfjGcCItav4mQsZlDA9EcVUFFCxnhVyt+O6ZX2U\\nENbr9bwmSJCoxJpdTOqiaPf7fW8dJv7bdrvtnQvEZ6jX649J5MQdWJwRgMcaQPcZcZ+nbrcz+t67\\n3pTtFruw9/MTf8ZWq0U6nfYmfpFoFotFPXc58b0AIwJYEm00pTabTQqFAqGQSzB8xFx3UNUQakhl\\nOBTF+NFpo9vtks1mHyMZinOPO7EP3WcogKexd3XyPLbyF82aeB7AbQqr1Sqaprle/H/lXGGaJs1m\\ng63tLfr9vvf99ft9TMMkFAo/MtFxHI6OjpiZmXFTvUYJYWKjM7QtTp86hRYKMejr3u//wvPPkR1P\\n88lHH5EZG6Pb67J3eECtWmVtbY2p/CR37t7l/PnzSLJrhvLp9evous7BwQEBWWJ7a4tPr18nnU57\\nzaGhD7BHxMLBYECtXvW2OJlMhv2DHdJjafTBgM3NDRKJCLICe3vbVI5q9HWd3b19isUiwiaXgEOj\\n3uDO2i1UVePW2hq67j6v7U6P73z3u7z99lsc7O6yvLzMzMws2YksmqZRqx2xeuki9+7dJxSKYNiQ\\nSCQ5f+4sN778nO9++2fpdrucXVlmIjPB1GQBczBga2OD06fP0Ol0XHfCiMbLr77CB1d/ws+8+irR\\naJRms0mz1WBhYY7EaEsjIZEam0SW3ZzsjfWHhMMh/s6v/xq1epO1+7fZ3t7n3MULyIqCbQfciOdC\\ngSFD8pkJUvEUhclpzl+4CDjoA516vU5IDVGr1jnYK2MOLLrNDpIjY+gGzUqVh/fuU5iYoFVvMNQH\\nNCpVjJ47sVvOkK7eIRiUiUY0KpUSd9bvcvH8BTqNNr12l4+uXmXu5DzpsTSmZbKytMJ+uYSqqUiK\\nQk93vT3iiQTbO9sszM3x4ME6juNw995dSiU3T+Hw4IBGs04sGkZVZdSgwr//D/8WLaLy1ee+gizZ\\nfPThj4nHIuQnxmk1mvzwjTepVCqkk+O8+cZfMjRsfuPXf531B3c5feokhmnSbbfo9nvMzMyysbHB\\n7u4u584vAzaZ8Tx7e4fk89MoSoiZ5ctPfgF/40//5PX+wKRaq2OYFp22yw5XpACZTIaxsTFvejie\\nBy2KiZiSRbjFcXMUsXIVDGtBcBIvX3F7FEQ28ZKWZfmxF7J44QsPajGViibiOOv6uP5Y3KrFS+94\\nTKjIaT5+qw0osndDFcxoUcyEnlrkc9u2DVIAczQxCamUCDqBR7GT4p+iMB/XFwvynVijDwYD7z4u\\nLE9DoRDj4+N0Oh1vVS6Y2OLnfJx4JaRZoqE6PgF3u93HDFbE5xAkNXEHF1Od+PkL9rX4GQtug5BM\\nuQRB5bHmQPi0i02A2LSIqfk4n+DR9zZa2yvyqEGUvRuzLCvIctD7/sGdTlOp1Gg93feKtHtjdt3/\\nJEliZ2fHazZdJUDIs2wVXIVIJDySgpn0+71Rmlr3GMFR4Y033iSZTHrcgsFgQKPR8LYlQj0ArlVq\\nWAvT7XW9RnNoDqmPFArCMU08b+LZF37V4lkUtrTHFQeu5j1CLpclEAhQKBSIRsMIC+NQyDV5SafT\\nBINulrlobKPRqGf7ure3R2TE/u12OpiGSTKdojA5yfWffkL58IDf+PW/yw//4ofe93p4cMCVZ54m\\nkUoSHilBSpUyw+GQfC7Hyy+/zNTUFDt7u8iSRKFQ8P7eFYtFpgoFup0uH3zwAa1Wi5OnTpJKuYz6\\ng4MDcrkssqzw3HPPE3DANAfogx5zc3MsLJ7Bsm1KxRKvvvwKrWYTNaSydHqJqekpLHPI+PgEDx6s\\nMz09QyYzwccff8zu7i7nz58jFtZYWloiFouzu7tHt9vms88+BSwWFhb55NqnLJ9bZXd3l3t373Lu\\n7ApKUKbebKBFIrSaDSKRKOvr664JUEAhmUoQiagMhkOCkRjhWJxwJDg638TodbpYxoBw2CW7Bkd2\\nqqdOLzBVKCAj8dprX+PGlzcplWusXlxla/Mhp06dpNXp8MlPr7G4uMBEIct+cZ+xxBiXLz/D//yz\\nN9k/OODipUsUS0Vu377FzZu3mJo8wXvvfYAztCkUpnAcuP7Tz3Bsm1w+RyKR5P69e1TKZVqNJsXD\\nQ6SQwtrdO0zNznDr9m2KxTLJVJpGp4OExu/9x99jcW4RyxrS6+u89PJL3L55k07LtUk2zSErK2fB\\ncrBttzkbGxsDB4rFIq1Wi1xukp39PWamPNwBDwAAGhpJREFUp+l2Opw8vUilWiE3kWFze4PpqSk2\\nNx6yv7/DP/iHf592o048GqNeq7G8fJpYNIZEgKlClp/77rd5/vln6PebHB5soioBGvUj1KDG0pkV\\nIuEI5sDka197le2tTeqNGkPTRh8Oebixxe3bd3j15/4/MHL5sx/8yevNVhNFCRKORBhLptH7XTKZ\\nzGP3ZfHCFKtsUTQEc1oYaojuXbygRQET/tVi/Shu1qKIBINBqtUq+Xzem+LEhC/W1mLyFExmcesV\\nhUCQzMTtT6y0xfpeFCRxd3QcNx8c3EKqj4qWV1wtC2NUlIXDlXjZeoQtx2WHC4JfpVLxplzxUhdN\\nR6/Xo9FoeBO8KFri84liK2744pcofGKyFylp4hZ8PGlNTMFi86DruldI0+m0VygE8zkUCnkNipgg\\n2+22p68W/AOxCRHEwuOsf9FUHDeLEQVefH4xoYrNiJg2RVMlPq8sSyOJYdST2Il1svs9Bz2tutje\\niG3GX916xOMxlxB1jKAmmrtms0UgILG7u4thGHS7Xba3tzg6OuLmzZuMjY9RLrtFqTLKenYch+Xl\\nFU+P3e12OTw8PNYUqKPbfni0XXILf3TEDXF9CpIjLbjb4GQyGU9dkUqlvCZV/MyFemI4NEgmE4RC\\n7s8jkYiNmojASDtujP6cJuGR54JYx4sJXmjoo9Go14gCKGoQyYFysYQ8OpO1mk0+u3aN5559hukT\\nU/R7PVeP3tdZWVnBsize/+ADDg8OXJOlRt0LFsnn80xOTvLg/n1OnDjBxQsXqNVqrrlLocCdtTW6\\nHdfr4NJTl7l79w737t0jn89TKpVYWlrGGsKXn98mEom625JwGGQFB5mQqhEOazQaddLpFMlUglw2\\nS6fT9fg3siyztbPN0pklXnrpJXfrkoxy+8YXqGqI/f19Njc30cIqYNNuNtjdO0CSgqjhONbQ5sL5\\nc2ghhVqjRiAgE41EiETiDE2TRCJBuVzm4cMNpIBNrpBn6ewyb771Lt/41rcZ6iP5XkBG13Ua9Rr7\\n+7vEozHKlRKpZJyH63dp1uqEtTC2ZfMH//W/8/3f/xP+0W/9JjMzeS5cuIA5HJBKJJmZnyES01g8\\nvUh+cpqQFmFvr0xAkvjxB+8jBxzkgMylyxdptlpsb22hSBLvvfcu2ewEIVXjzr07DKwhpUoZ3TBQ\\ntTCHxSIr585RKlW5/PSzKCGN7e093n7rHerVFn1jyFe+8gI/futdJNvmmaefJaAEGJgmrUbLdUIM\\naZxaWMSxbO6u3SEajjKWSvHh+x9QmD7BzRs3mZ6Zozdwn53aURUtpKFoEeLRGANjwPLSWarVOk9f\\nuUIiGqPTaiHJQWKxOPv7+zgBSCXSvPjiy6STEXqdDu1Wk3ajRrdZo1auEQyE+PDqR0RUDWdos7u9\\nQ0TTONjf58H6fXKFLAPTpHRU4eHGQ/7mr/3mk1/A//yNH7yOJGE5EIm6U1QwFERWFPqdNtmJDNbQ\\nRpFlwprG0DTBcZBk2VvxPh6RGPJunKIQCQ2wmNZcQk+bYFAhEgkjy4pHSAsGZVQ1OJLa9JCkgEfC\\nOs50dtfHtmcO4wZiBLziIbyhxZperCbF1Aq49++AhGmYDC0LKRAY3b81b8qMjO7eImBEaGwDgQB9\\nXUceNQmDwQBZDRIf3b1EkRHrZEmSSCQS7pQaVOgPdOSA24Q4lo0+GHmJa2GCqsrQsdFCIRKxuDsZ\\nDU1v29DqdtysdFl2P//QJBRUIcBj1qXC+MUeuo5kxxsdMU2JLYgg+GUyY9i2Rb/bJxaOMTD7BIMK\\n5sAkrGlIssu+dySHiBbx+AWKItFo1Ece3x3vtgt4gR3ieRGrf7fgu+vmQACi0RhDy/B82I+T/8TN\\nvtXqMBwO2d3d9VzGxFmh1Wqh6/qIJOfqz3u9LuVy2VM4FItFqkc1ej3dazYE70DTwgSDCvl8nvHx\\nMeLxOKlUipMnTxKNugUzEnGLo2vKEiafz6IoMqlUckRoc0NYwCW+KUF38yI2FKoaZGiZOI7L6+gN\\nXCKZ8LtXlSAOLkGz2+1SKBRGPAHHu5275whlJJnTR3+TA8Tj7vOFA+FwxGu+XYJdEFUNYttDjo4q\\nFIslVFUlm81SKdaQJJtYMoksyYTlIKoWY3Nrk9deeZpyqcqZk2d47933MEyXU5GIx4hFIhiDAS+9\\n+DNsbe+ghtxGvlatkkykvImzXKkwOzvL0BgyHFpkxsaxrCELCwt8cu0a2zu7XLnylMsLCGsUK0dI\\nisT+/g6BgEMkFiYej7Hz8CE7B0W0UAhFDnJidhbTGHJ+6Sy7+/toiSg7Dx9y1KyzdGaZ3GSB/VKJ\\nvc0tLl04h6qpzBYK9AYD9vcPePmFl5CQ2T88YG5xnmQiyfzMHNdv3OLrr73G4cEhvU4XRXJX6nNz\\nC9y5/4DxbB4roPD9P/hvPP3sRb7+jW8yfWIW05aYOblIJpfBHvRxAkH6g757igxA8bBELB7n4KCI\\nFg7SbLQ5fWqZUEhja2uHUEghmVR54wd/hN5rcen8ecbHMuRO5NFiUcbGJzh39jwYQz764BoBW+be\\n1gaqHeDaZ5+xevEiDpDL5TFth4XTS+yXyrT7Bp2BgRIKsrWzz/bOIVu7B7z27Z/jB2/8iMmpeTp9\\nAweF+/c2mD4xz6XLV2i0Ovzwh+9ytLPHz7z0IqlkHEmBkKaQSCSJJuIk0mNIUoBoLO7e+o0ek1OT\\nbG9vc3RU48ozV1AVjfPnzlGv1tjd3WV6eppIIobe7bO6epHDwx20sIwalJidmabVbPCXb/2I9959\\nl6l8jkqlwvvvv4+hDzB0nU+vf8JPPrzK3MwM0ZDE/u4DZqen+fDqVQ73ikxO5qlWaxjGAMe22d8/\\nJDOWI6Bq9HodMmNJLl0+z7mnv/7kF/A//eM/fH1oDpECARLxBKl0gmBQBhy0WIRKrQpyACfg0O13\\nsQMOAUUiKAe9m6qY6I7LpFRV9Sw3j7uVGYZBs9kkGHTXyLIiYRg6YU2j3+95rkn9vk4k4jKLxa1V\\nkl1jFdseYpoDbGeIqgaxLBNNC3ufRxQqYRwiph9xQxdrVgsHOaiALIEUQB41HWJSN0cSK6GHFmQ6\\nYUQA4Iwm/kgkghoK0RrJwUQhFRMn4G0lBn2dcEjz/p2BYbgpZparn+8N3K1Gt91xY/IkCduyIACG\\nNfRY4rZlYVsWDg6242BYQ6LxGAFZoqv3UTUNy3FQtRByQPIm7uMnDoBoNIyqBrn/4C663qXZrGMa\\nBoahE4vEXW2/bVGuHaFqQYKKzNA0UFWFvt5DkgOIJLFA4JF5jpjaRfMmvgt33e3a64ptiq7rVCpl\\n+r0+lcoRtWqNXrfHQDdHZCydTrvrmc+EQiEmJydHCoAI4bBGMhUnHNGYmBgnNiq4hmGSTKZQgyqx\\nWJxUKk08nnS9mUcbiePbiHg8gSTJ9LpdZElGUULs7x2gKEFkWRnF5D6y++12O8iKhGka4DijG76E\\nPnh0lw6HwzQa7trcM3QJyrTaTUJqkLCmEY/H3AYAx2tC3QncldTZtoVlmxjmgEDAIRzWGBgD71mz\\nLAtd10dkN9cOWLgbuva3MSzLpNNtY9sW2YkJ1+NgoBNLR+kN2gS1EKoWotPq8Lv/8l+RSo9TyE0g\\nh4JIIYXZUwvopsFBucS9zXUmCnni4RitdhvdHKCEFA6LJRLpNJlcnq7lul999tEnfOP5l9jYuM9B\\nZZ+Pv/gpAWQuPbXKnft3Wb14wTt/hEMaoWCQ7a1NxlJjzC8usHLxHDOnTnJYrdBotzAGOrZjEQmH\\nufHFlzimSXHvEMMwODo8YmJigqNKhWg0wn5xh0IuQ09vc3hwyMREmodbD5kqTFKtFIlGVaamC2A6\\n3Lm9Rr/XpVqt8tn1n3Lx7DLFvR2mp6YZWiYHh/t862e/Rs/o4TDkhRe/yotf/xZf3rjN0LJ478fv\\n0u/2iYY0ZibzyKqM3u1gDHSWls64VrvhMJIskc7mKExPc1DaZ3vrAWrQ5sqlc4RDMvPTBZ66/DSp\\nVIpbN26ghTUky2bQafP+j37k+iKYBjPzi9x/cJ94LMJ3vvMt3nn3LZ5//jlmZmawTROj3yOsaWQm\\nxllaOk1+Zob5+XnmFhaYn5shqDgEJRM1aFMp7TKZzbA4P82JE3kymTS31m7xO7/zuwwDEj98+0co\\nkRC5mUlyhRzF8iFyALqNOrnJAtXqEfFElM3NDcChWCpi455rJEXi2rVPyOVz5PN5bt++TfXoiPn5\\nOa5evYo+GBCOxGiPtjLxRJzTJ08zc+IEAdtGxmJ+bprGUQUFh3a/x2//9j/l448/oFop0e+1kYNh\\nBvqQn3z0Cbfv3WVgWlz/4gv6loUZsJlZmGc6X2Bhbp6IFsLU+5x99ptPfgF/90dvvJ5OJZmZPkFQ\\nkcFxsK0h0XCEVrftTkMjdre73huO3NhcUlCj0Rit+IZeARdMYLGuO+6K5iU5OTYOtseENkY3ItN0\\ntdpuPrLiEagikQhKMICiuCYryWRixBy3MQ2TweCRg9bxQqUoCtFwxJuEul036tI8lkAlJmbx+YDH\\n/KTFDVvccsXnsh0Hc8SE1zSNgWkg8Si2VPiGey/t0e8hGN3iFCHLMp2+6+NtjjTO4K42o1oYOSC5\\n1pWphHvOkFxWsyLLxKIxTMMgqLppZ5Zjs7e3RwC8abTdbtPrundYsR4X7HT33DHEtl32daEwSTDo\\naoZlSabTbHNwcIiNw607a3S7HarVirdq9xjc3Q6KrNBqten1elQqFa9YdzodTzvebDYBV9udTCYJ\\nSKCFXUaspoXIZXPkcjnC4TDpdBpZlkgkkt75QJD8ROEdDg2Pi2FZBoYxQJLcLU693hhp61NIkpuU\\n5k6yiqdmADxOgWEYHlsdAt55RRD9hCQyGo16xDNJChCLuUlqWljDGrrFWYTSCPMYcX8XnvAuJ0Jn\\nODSxRjf2fr+HsFwVLnXDoYkaCjIwBp5+X5A4x8bGRmEkAY/0CRCNRLEs29taBAIi1W1IZiJDNptl\\nODTpdXtEomEkJUCr1SQYDKH3dSqlIs1Wl+9975eZSMXo9d1m4MMPP6TT7dDtdZmbm2dyskC/0UYN\\nhfjiy89ptVpMz0wzP7+IoqpIaohCNsudmzc5KpXITmYpVUq89q1vYTsOjUaD9QcPWFpeRu+7EbNv\\nv/02zzzzDPu7ezz99BWarQbNTpvcZJ7Z+Vleev4l1m7eYu32Gg4OL371q7SbLba2tphbXCQoSXz+\\n5eecPn0KWZGYyGcxej3GM2MszC9SLpdJJFNIODC0GA5NdvZ22dk8AEdCDarUW20WT57kYG+f3b1d\\n1m7dxxxYHBwc8rWvfZ3/9J//CxcvXuHy5Wc5dWaZ3//+71Mul1lZWmJhYZ5Ws0FmPIMkuZsQYVQk\\nTk87Ozt0R0qQeq1GuXzIwtwsd9ZuY1kWp06eZntvlwfr6yyvrDAYDNhYv8/Ww3VUKQBKgHa/T7Pb\\no96oEQ4FWVo6TeWoQqvV4I/+8H+QiEaRJZmABM1mg1w+x/LyMvV6g4mJDP1+l0q5zFOXLvDOO29z\\n4sQUL7zwHO12m2Qyjt7v0ajVqFZr7O7tk4hHuH3nJrF4lKeevky71SIZjaHKMrl8ju3tLR7cf0B2\\nYoJa/YgzZ06zvLyCZY22LpkJarUa0WjUIyxaluXG16phNje3WVhYYGHxJPVGE3U0KCViUZKpBKbR\\np9/r0et1SGXH+cEf/zEn52fp9ztcvHCO4VAiHE1QLpaYmZ7GcmyOGnWufOWr3Ly1RjgaY7owxYP1\\n+1hDky++/Jyv/9Lfe/IL+Kcfffh6YnS/Fi9Ecf+UZRnLMHBGOm/Htul1euAEHjPHEIVS5BALdinw\\n2Jqy1WqhKArpdJp2u4UbBuFGezrOowAMWZIJhTRvbe7+HmIFrGBZNqrqrqhxhAuba1Ai1rbivgog\\nS25utbhFO5K7arcEGSvwyJ9cGGzIsuy5RgHe/Vvc3nVdR1YU4iPdbq/Xo9lukYjFvUbiuMuWYP2K\\naUl4WAOeVau411sjslmn3QbLBsdBCSoc1Wveur5cciUhvW6PZrOJw8jApOeG0QQVBWMwYGI8g2UO\\nvQxvcVIQDUsulyMQcIli4Uho9L0EiISjRMJRFEklEo2RSCUYy2RIJV2HJPccYuEarAhSoUy328NN\\nGHP/vLFYzJPNidure98Nj7TPmqfPdpu+FpIUwLItDGNALBalO0rMCoVU+n3dI36592/Jy/i2LHPE\\nRXDd9dSgIApaHlNfVVUajYYnWRTTruA+HFccuN+VQzabw7YdJCngPdPC2z4YVDCMwSNOB87oOeoS\\nDIqm95HaQPBEYpEosiShyAr6YIDt2KNn3vKeNfd0ZKIEFWQ5gKaFcBzXHdCV1UleRO5xFrfe111D\\nmRHJU1VVms06QdVteur1+mg7ZRBUg9TrTbBtwuEo7UaTdDzO3NwcjXqdF56+RL/bIzueod1oYg8t\\nLl1c5czJU1jmkOz4BOVSmYmJcX7le3+Le3fv8ODhBptb27z31tvkJjK88uILVKuudvjUqdP0dR3D\\nGvKTn3zEd77zHQb6gJnpaTY3N5mZmSEcDhOPxWk0Gnz5xZe0ux02Nzaolo8w9T6OabK1sYEaUpmb\\nmaV0WKRUKXPj3h1S0TALi/P0Ol2mpqfITxaIaiG6/T4P7j/g5KnTKKqKIklEQiEUSaLd65GIj7N6\\n6RKOrPDUM88yHNoYus7q6kVCSpBsboK//avfwwE+unadE1OzJBLj/Ot/8++whybRsEY+P8npM6eY\\nnp6mr/cJOAFsyyVgloplLMtBDYZot7vEY1EatRoRLUS76WaGB4NBrn92g6WVsyyvnCWVSmOPuC22\\nNSQ7MUY+O8Gdu3cZm5ggnZ2kuLtPPpehkMuzemmVmdlpls6cwhjoPP/V57h9+yanT58hGo3gOAFO\\nnTyFZVnMTM9Sq1aZyOXIZfMkkkl2dvdYmJ/n6KhMWJXZeHif2akpBrrO4sIMuXyOoTVwTYViUfLZ\\nHIcHRQgEqNVqVKtHnL9wlkQ87iblGe5G7tq1Txia1mOOj61Wi6kTkyydWfYinFOpNB9++AHhmEvC\\nrdVqTJ8ocO/eHdJjcUIhjUq5AsEgN29+zur5FdbWbnH50ipOQCWWGGMqm6XVbrK3v8vM/DzbO4es\\n3bmPYwe4detLwppKo1lnqpDnyiu/+OQX8KsfvPP6YDCgXq+TSCTo913Hr3A4zFgq5a4RZYWwFiYe\\ni5GIJVwW5Uj7K9afgjTmOA61Ws2TZZXLZa8ZENpuN5s6QTgSxjAGo9tgyGNBm+aQYFD1WLhu5+pO\\nhPKIKd7r9r08ZklSvJeuIEodZ1CL2ERPwhVUvNu9Pbp9C6cwcW/vjyaC4x7Xx7XqwjKTkWtbPB5H\\nHwwIKm7Sl5i2O52Oly1eKpW8l7Ou67Tbbfc+G408pkV2cJmbtm0T1cIEgG6vB5JLTkskEi7ZTdOI\\nRWMk067vc3r0fU1kMl5kZzwWR5YkDNPwVADHmxJwuQDdbhdzOECW3Ze+Iiv0e33CkQhOwKHX7yMF\\nAxgDA2U0SY6NpUmlxjyiW28UWhONxkgk4l4IimikhBZduNzF43Fk5VHudq/XYzDoMzVVIBh0C62r\\nFBgS0kKYQxN4lMjmjDRabp67RafbJRZz79SWZWM79shIxfZIgoDHxhaEvOM+4uKXa+piYRgmuu5y\\nMQT7Xejoa7Ua3W6HUEhFlt0oRhESAq67lqZppNPpx5QAAENTpL1BSA0hB2QIPAq/eUTC1JEVefT3\\nwR5tM9wkN0HME81yLBYDoN1qMz6e8aZ427ZpNhuEQiqDQZ9QyNWiE3CbzKAaIqSqOMgkYzFsc8C9\\n9QekEjHqpV2q5Sqnz5ymXCqxvLxMJjXOX/z5m2xvbHFpdZVPr1/nV3/ll6mUiszOTCPJKlo4zMLs\\nHLGIxrVPPubihXNu8xty7WQ3t7d45ZVXkAmwv7PLpUuX6Pf7rKysuAx82+Haxx+Tz+U4ubBIf6Az\\nfWKao2KRsVSahfl5avUj7q6t0Wy0aHc7lGtHPL26yvrDddKpJHJQoWcaTKTHyUxkKBVLVGsNunqP\\nyxcv0W+3MM2hu/LP5pEUhVAkQqetc2ftLt/62W/S03vMzUyRHk+ycnaFN958k2+89m3eeec9rl27\\nxt0793jtm6/R63dZPLXI9PQMOzvbjGczaGqY++vrhMMRotEY9bqbVz43N8snP/kJhfwk3V6HbNbN\\nRZ8sFJAkBcdxG71cLs/GxibVWo3xsSRjY2lsyyIWjZItFJicXeDGp/+LZ569gt7tUa6UOHlqEds0\\ncSw3wKk2yikfGAbNRoN4NMHNm2vs7+2jGwO2tjeZnJpmPDPOZG6Szc0NYuEwxqCPIssMej2KB0VC\\nqsJnn13nG9/8BvMLC0S0CJribmzeefcdTp48yfj4GPv7e0xMjFMsljAMg4frG9RrdSayGdrtDrqu\\ns7CwwMHBAZXyEQC9Xp96vc7k5KSb5mdZLC8vk0q5XiSbGw+YmjlBv9ej2eqgaGF+6Rd/nl67xdml\\nU+zvHWLZCp/fuMmg02VyMktQUZidm+fLW3e4dOkp9L7BztYGK+dWGEsnKUzmWbryf3YD/38izMSH\\nDx8+fPjw8deD9H/7A/jw4cOHDx8+/vrwC7gPHz58+PDxBMIv4D58+PDhw8cTCL+A+/Dhw4cPH08g\\n/ALuw4cPHz58PIHwC7gPHz58+PDxBMIv4D58+PDhw8cTCL+A+/Dhw4cPH08g/ALuw4cPHz58PIHw\\nC7gPHz58+PDxBMIv4D58+PDhw8cTCL+A+/Dhw4cPH08g/ALuw4cPHz58PIHwC7gPHz58+PDxBMIv\\n4D58+PDhw8cTCL+A+/Dhw4cPH08g/ALuw4cPHz58PIHwC7gPHz58+PDxBMIv4D58+PDhw8cTCL+A\\n+/Dhw4cPH08g/ALuw4cPHz58PIHwC7gPHz58+PDxBMIv4D58+PDhw8cTiP8NsRts38nnu7cAAAAA\\nSUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11fd78190>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# load and display caption annotations\\n\",\n    \"annIds = coco_caps.getAnnIds(imgIds=img['id']);\\n\",\n    \"anns = coco_caps.loadAnns(annIds)\\n\",\n    \"coco_caps.showAnns(anns)\\n\",\n    \"plt.imshow(I); plt.axis('off'); plt.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.13\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocoEvalDemo.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"from pycocotools.coco import COCO\\n\",\n    \"from pycocotools.cocoeval import COCOeval\\n\",\n    \"import numpy as np\\n\",\n    \"import skimage.io as io\\n\",\n    \"import pylab\\n\",\n    \"pylab.rcParams['figure.figsize'] = (10.0, 8.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Running demo for *bbox* results.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"annType = ['segm','bbox','keypoints']\\n\",\n    \"annType = annType[1]      #specify type here\\n\",\n    \"prefix = 'person_keypoints' if annType=='keypoints' else 'instances'\\n\",\n    \"print 'Running demo for *%s* results.'%(annType)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"loading annotations into memory...\\n\",\n      \"Done (t=8.01s)\\n\",\n      \"creating index...\\n\",\n      \"index created!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"#initialize COCO ground truth api\\n\",\n    \"dataDir='../'\\n\",\n    \"dataType='val2014'\\n\",\n    \"annFile = '%s/annotations/%s_%s.json'%(dataDir,prefix,dataType)\\n\",\n    \"cocoGt=COCO(annFile)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Loading and preparing results...     \\n\",\n      \"DONE (t=0.05s)\\n\",\n      \"creating index...\\n\",\n      \"index created!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"#initialize COCO detections api\\n\",\n    \"resFile='%s/results/%s_%s_fake%s100_results.json'\\n\",\n    \"resFile = resFile%(dataDir, prefix, dataType, annType)\\n\",\n    \"cocoDt=cocoGt.loadRes(resFile)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"imgIds=sorted(cocoGt.getImgIds())\\n\",\n    \"imgIds=imgIds[0:100]\\n\",\n    \"imgId = imgIds[np.random.randint(100)]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Running per image evaluation...      \\n\",\n      \"DONE (t=0.46s).\\n\",\n      \"Accumulating evaluation results...   \\n\",\n      \"DONE (t=0.38s).\\n\",\n      \" Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.505\\n\",\n      \" Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.697\\n\",\n      \" Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.573\\n\",\n      \" Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.586\\n\",\n      \" Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.519\\n\",\n      \" Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.501\\n\",\n      \" Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.387\\n\",\n      \" Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.594\\n\",\n      \" Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.595\\n\",\n      \" Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.640\\n\",\n      \" Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.566\\n\",\n      \" Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.564\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# running evaluation\\n\",\n    \"cocoEval = COCOeval(cocoGt,cocoDt,annType)\\n\",\n    \"cocoEval.params.imgIds  = imgIds\\n\",\n    \"cocoEval.evaluate()\\n\",\n    \"cocoEval.accumulate()\\n\",\n    \"cocoEval.summarize()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.10\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocotools/__init__.py",
    "content": "__author__ = 'tylin'\n__version__ = '12.0.2'\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocotools/_mask.pyx",
    "content": "# distutils: language = c\n# distutils: sources = ../common/maskApi.c\n\n#**************************************************************************\n# Microsoft COCO Toolbox.      version 2.0\n# Data, paper, and tutorials available at:  http://mscoco.org/\n# Code written by Piotr Dollar and Tsung-Yi Lin, 2015.\n# Licensed under the Simplified BSD License [see coco/license.txt]\n#**************************************************************************\n\n__author__ = 'tsungyi'\n\nimport sys\nPYTHON_VERSION = sys.version_info[0]\n\n# import both Python-level and C-level symbols of Numpy\n# the API uses Numpy to interface C and Python\nimport numpy as np\ncimport numpy as np\nfrom libc.stdlib cimport malloc, free\n\n# intialized Numpy. must do.\nnp.import_array()\n\n# import numpy C function\n# we use PyArray_ENABLEFLAGS to make Numpy ndarray responsible to memoery management\ncdef extern from \"numpy/arrayobject.h\":\n    void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)\n\n# Declare the prototype of the C functions in MaskApi.h\ncdef extern from \"maskApi.h\":\n    ctypedef unsigned int uint\n    ctypedef unsigned long siz\n    ctypedef unsigned char byte\n    ctypedef double* BB\n    ctypedef struct RLE:\n        siz h,\n        siz w,\n        siz m,\n        uint* cnts,\n    void rlesInit( RLE **R, siz n )\n    void rleEncode( RLE *R, const byte *M, siz h, siz w, siz n )\n    void rleDecode( const RLE *R, byte *mask, siz n )\n    void rleMerge( const RLE *R, RLE *M, siz n, int intersect )\n    void rleArea( const RLE *R, siz n, uint *a )\n    void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o )\n    void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o )\n    void rleToBbox( const RLE *R, BB bb, siz n )\n    void rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n )\n    void rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w )\n    char* rleToString( const RLE *R )\n    void rleFrString( RLE *R, char *s, siz h, siz w )\n\n# python class to wrap RLE array in C\n# the class handles the memory allocation and deallocation\ncdef class RLEs:\n    cdef RLE *_R\n    cdef siz _n\n\n    def __cinit__(self, siz n =0):\n        rlesInit(&self._R, n)\n        self._n = n\n\n    # free the RLE array here\n    def __dealloc__(self):\n        if self._R is not NULL:\n            for i in range(self._n):\n                free(self._R[i].cnts)\n            free(self._R)\n    def __getattr__(self, key):\n        if key == 'n':\n            return self._n\n        raise AttributeError(key)\n\n# python class to wrap Mask array in C\n# the class handles the memory allocation and deallocation\ncdef class Masks:\n    cdef byte *_mask\n    cdef siz _h\n    cdef siz _w\n    cdef siz _n\n\n    def __cinit__(self, h, w, n):\n        self._mask = <byte*> malloc(h*w*n* sizeof(byte))\n        self._h = h\n        self._w = w\n        self._n = n\n    # def __dealloc__(self):\n        # the memory management of _mask has been passed to np.ndarray\n        # it doesn't need to be freed here\n\n    # called when passing into np.array() and return an np.ndarray in column-major order\n    def __array__(self):\n        cdef np.npy_intp shape[1]\n        shape[0] = <np.npy_intp> self._h*self._w*self._n\n        # Create a 1D array, and reshape it to fortran/Matlab column-major array\n        ndarray = np.PyArray_SimpleNewFromData(1, shape, np.NPY_UINT8, self._mask).reshape((self._h, self._w, self._n), order='F')\n        # The _mask allocated by Masks is now handled by ndarray\n        PyArray_ENABLEFLAGS(ndarray, np.NPY_OWNDATA)\n        return ndarray\n\n# internal conversion from Python RLEs object to compressed RLE format\ndef _toString(RLEs Rs):\n    cdef siz n = Rs.n\n    cdef bytes py_string\n    cdef char* c_string\n    objs = []\n    for i in range(n):\n        c_string = rleToString( <RLE*> &Rs._R[i] )\n        py_string = c_string\n        objs.append({\n            'size': [Rs._R[i].h, Rs._R[i].w],\n            'counts': py_string\n        })\n        free(c_string)\n    return objs\n\n# internal conversion from compressed RLE format to Python RLEs object\ndef _frString(rleObjs):\n    cdef siz n = len(rleObjs)\n    Rs = RLEs(n)\n    cdef bytes py_string\n    cdef char* c_string\n    for i, obj in enumerate(rleObjs):\n        if PYTHON_VERSION == 2:\n            py_string = str(obj['counts']).encode('utf8')\n        elif PYTHON_VERSION == 3:\n            py_string = str.encode(obj['counts']) if type(obj['counts']) == str else obj['counts']\n        else:\n            raise Exception('Python version must be 2 or 3')\n        c_string = py_string\n        rleFrString( <RLE*> &Rs._R[i], <char*> c_string, obj['size'][0], obj['size'][1] )\n    return Rs\n\n# encode mask to RLEs objects\n# list of RLE string can be generated by RLEs member function\ndef encode(np.ndarray[np.uint8_t, ndim=3, mode='fortran'] mask):\n    h, w, n = mask.shape[0], mask.shape[1], mask.shape[2]\n    cdef RLEs Rs = RLEs(n)\n    rleEncode(Rs._R,<byte*>mask.data,h,w,n)\n    objs = _toString(Rs)\n    return objs\n\n# decode mask from compressed list of RLE string or RLEs object\ndef decode(rleObjs):\n    cdef RLEs Rs = _frString(rleObjs)\n    h, w, n = Rs._R[0].h, Rs._R[0].w, Rs._n\n    masks = Masks(h, w, n)\n    rleDecode(<RLE*>Rs._R, masks._mask, n);\n    return np.array(masks)\n\ndef merge(rleObjs, intersect=0):\n    cdef RLEs Rs = _frString(rleObjs)\n    cdef RLEs R = RLEs(1)\n    rleMerge(<RLE*>Rs._R, <RLE*> R._R, <siz> Rs._n, intersect)\n    obj = _toString(R)[0]\n    return obj\n\ndef area(rleObjs):\n    cdef RLEs Rs = _frString(rleObjs)\n    cdef uint* _a = <uint*> malloc(Rs._n* sizeof(uint))\n    rleArea(Rs._R, Rs._n, _a)\n    cdef np.npy_intp shape[1]\n    shape[0] = <np.npy_intp> Rs._n\n    a = np.array((Rs._n, ), dtype=np.uint8)\n    a = np.PyArray_SimpleNewFromData(1, shape, np.NPY_UINT32, _a)\n    PyArray_ENABLEFLAGS(a, np.NPY_OWNDATA)\n    return a\n\n# iou computation. support function overload (RLEs-RLEs and bbox-bbox).\ndef iou( dt, gt, pyiscrowd ):\n    def _preproc(objs):\n        if len(objs) == 0:\n            return objs\n        if type(objs) == np.ndarray:\n            if len(objs.shape) == 1:\n                objs = objs.reshape((objs[0], 1))\n            # check if it's Nx4 bbox\n            if not len(objs.shape) == 2 or not objs.shape[1] == 4:\n                raise Exception('numpy ndarray input is only for *bounding boxes* and should have Nx4 dimension')\n            objs = objs.astype(np.double)\n        elif type(objs) == list:\n            # check if list is in box format and convert it to np.ndarray\n            isbox = np.all(np.array([(len(obj)==4) and ((type(obj)==list) or (type(obj)==np.ndarray)) for obj in objs]))\n            isrle = np.all(np.array([type(obj) == dict for obj in objs]))\n            if isbox:\n                objs = np.array(objs, dtype=np.double)\n                if len(objs.shape) == 1:\n                    objs = objs.reshape((1,objs.shape[0]))\n            elif isrle:\n                objs = _frString(objs)\n            else:\n                raise Exception('list input can be bounding box (Nx4) or RLEs ([RLE])')\n        else:\n            raise Exception('unrecognized type.  The following type: RLEs (rle), np.ndarray (box), and list (box) are supported.')\n        return objs\n    def _rleIou(RLEs dt, RLEs gt, np.ndarray[np.uint8_t, ndim=1] iscrowd, siz m, siz n, np.ndarray[np.double_t,  ndim=1] _iou):\n        rleIou( <RLE*> dt._R, <RLE*> gt._R, m, n, <byte*> iscrowd.data, <double*> _iou.data )\n    def _bbIou(np.ndarray[np.double_t, ndim=2] dt, np.ndarray[np.double_t, ndim=2] gt, np.ndarray[np.uint8_t, ndim=1] iscrowd, siz m, siz n, np.ndarray[np.double_t, ndim=1] _iou):\n        bbIou( <BB> dt.data, <BB> gt.data, m, n, <byte*> iscrowd.data, <double*>_iou.data )\n    def _len(obj):\n        cdef siz N = 0\n        if type(obj) == RLEs:\n            N = obj.n\n        elif len(obj)==0:\n            pass\n        elif type(obj) == np.ndarray:\n            N = obj.shape[0]\n        return N\n    # convert iscrowd to numpy array\n    cdef np.ndarray[np.uint8_t, ndim=1] iscrowd = np.array(pyiscrowd, dtype=np.uint8)\n    # simple type checking\n    cdef siz m, n\n    dt = _preproc(dt)\n    gt = _preproc(gt)\n    m = _len(dt)\n    n = _len(gt)\n    if m == 0 or n == 0:\n        return []\n    if not type(dt) == type(gt):\n        raise Exception('The dt and gt should have the same data type, either RLEs, list or np.ndarray')\n\n    # define local variables\n    cdef double* _iou = <double*> 0\n    cdef np.npy_intp shape[1]\n    # check type and assign iou function\n    if type(dt) == RLEs:\n        _iouFun = _rleIou\n    elif type(dt) == np.ndarray:\n        _iouFun = _bbIou\n    else:\n        raise Exception('input data type not allowed.')\n    _iou = <double*> malloc(m*n* sizeof(double))\n    iou = np.zeros((m*n, ), dtype=np.double)\n    shape[0] = <np.npy_intp> m*n\n    iou = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, _iou)\n    PyArray_ENABLEFLAGS(iou, np.NPY_OWNDATA)\n    _iouFun(dt, gt, iscrowd, m, n, iou)\n    return iou.reshape((m,n), order='F')\n\ndef toBbox( rleObjs ):\n    cdef RLEs Rs = _frString(rleObjs)\n    cdef siz n = Rs.n\n    cdef BB _bb = <BB> malloc(4*n* sizeof(double))\n    rleToBbox( <const RLE*> Rs._R, _bb, n )\n    cdef np.npy_intp shape[1]\n    shape[0] = <np.npy_intp> 4*n\n    bb = np.array((1,4*n), dtype=np.double)\n    bb = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, _bb).reshape((n, 4))\n    PyArray_ENABLEFLAGS(bb, np.NPY_OWNDATA)\n    return bb\n\ndef frBbox(np.ndarray[np.double_t, ndim=2] bb, siz h, siz w ):\n    cdef siz n = bb.shape[0]\n    Rs = RLEs(n)\n    rleFrBbox( <RLE*> Rs._R, <const BB> bb.data, h, w, n )\n    objs = _toString(Rs)\n    return objs\n\ndef frPoly( poly, siz h, siz w ):\n    cdef np.ndarray[np.double_t, ndim=1] np_poly\n    n = len(poly)\n    Rs = RLEs(n)\n    for i, p in enumerate(poly):\n        np_poly = np.array(p, dtype=np.double, order='F')\n        rleFrPoly( <RLE*>&Rs._R[i], <const double*> np_poly.data, int(len(p)/2), h, w )\n    objs = _toString(Rs)\n    return objs\n\ndef frUncompressedRLE(ucRles, siz h, siz w):\n    cdef np.ndarray[np.uint32_t, ndim=1] cnts\n    cdef RLE R\n    cdef uint *data\n    n = len(ucRles)\n    objs = []\n    for i in range(n):\n        Rs = RLEs(1)\n        cnts = np.array(ucRles[i]['counts'], dtype=np.uint32)\n        # time for malloc can be saved here but it's fine\n        data = <uint*> malloc(len(cnts)* sizeof(uint))\n        for j in range(len(cnts)):\n            data[j] = <uint> cnts[j]\n        R = RLE(ucRles[i]['size'][0], ucRles[i]['size'][1], len(cnts), <uint*> data)\n        Rs._R[0] = R\n        objs.append(_toString(Rs)[0])\n    return objs\n\ndef frPyObjects(pyobj, h, w):\n    # encode rle from a list of python objects\n    if type(pyobj) == np.ndarray:\n        objs = frBbox(pyobj, h, w)\n    elif type(pyobj) == list and len(pyobj[0]) == 4:\n        objs = frBbox(pyobj, h, w)\n    elif type(pyobj) == list and len(pyobj[0]) > 4:\n        objs = frPoly(pyobj, h, w)\n    elif type(pyobj) == list and type(pyobj[0]) == dict \\\n        and 'counts' in pyobj[0] and 'size' in pyobj[0]:\n        objs = frUncompressedRLE(pyobj, h, w)\n    # encode rle from single python object\n    elif type(pyobj) == list and len(pyobj) == 4:\n        objs = frBbox([pyobj], h, w)[0]\n    elif type(pyobj) == list and len(pyobj) > 4:\n        objs = frPoly([pyobj], h, w)[0]\n    elif type(pyobj) == dict and 'counts' in pyobj and 'size' in pyobj:\n        objs = frUncompressedRLE([pyobj], h, w)[0]\n    else:\n        raise Exception('input type is not supported.')\n    return objs\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocotools/coco.py",
    "content": "__author__ = 'tylin'\n__version__ = '2.0'\n# Interface for accessing the Microsoft COCO dataset.\n\n# Microsoft COCO is a large image dataset designed for object detection,\n# segmentation, and caption generation. pycocotools is a Python API that\n# assists in loading, parsing and visualizing the annotations in COCO.\n# Please visit http://mscoco.org/ for more information on COCO, including\n# for the data, paper, and tutorials. The exact format of the annotations\n# is also described on the COCO website. For example usage of the pycocotools\n# please see pycocotools_demo.ipynb. In addition to this API, please download\n# both the COCO images and annotations in order to run the demo.\n\n# An alternative to using the API is to load the annotations directly\n# into Python dictionary\n# Using the API provides additional utility functions. Note that this API\n# supports both *instance* and *caption* annotations. In the case of\n# captions not all functions are defined (e.g. categories are undefined).\n\n# The following API functions are defined:\n#  COCO       - COCO api class that loads COCO annotation file and prepare data\n#               structures.\n#  decodeMask - Decode binary mask M encoded via run-length encoding.\n#  encodeMask - Encode binary mask M using run-length encoding.\n#  getAnnIds  - Get ann ids that satisfy given filter conditions.\n#  getCatIds  - Get cat ids that satisfy given filter conditions.\n#  getImgIds  - Get img ids that satisfy given filter conditions.\n#  loadAnns   - Load anns with the specified ids.\n#  loadCats   - Load cats with the specified ids.\n#  loadImgs   - Load imgs with the specified ids.\n#  annToMask  - Convert segmentation in an annotation to binary mask.\n#  showAnns   - Display the specified annotations.\n#  loadRes    - Load algorithm results and create API for accessing them.\n#  download   - Download COCO images from mscoco.org server.\n# Throughout the API \"ann\"=annotation, \"cat\"=category, and \"img\"=image.\n# Help on each functions can be accessed by: \"help COCO>function\".\n\n# See also COCO>decodeMask,\n# COCO>encodeMask, COCO>getAnnIds, COCO>getCatIds,\n# COCO>getImgIds, COCO>loadAnns, COCO>loadCats,\n# COCO>loadImgs, COCO>annToMask, COCO>showAnns\n\n# Microsoft COCO Toolbox.      version 2.0\n# Data, paper, and tutorials available at:  http://mscoco.org/\n# Code written by Piotr Dollar and Tsung-Yi Lin, 2014.\n# Licensed under the Simplified BSD License [see bsd.txt]\n\nimport copy\nimport itertools\nimport json\nimport os\nimport time\nfrom collections import defaultdict\nfrom urllib.request import urlretrieve\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.collections import PatchCollection\nfrom matplotlib.patches import Polygon\n\nfrom . import mask as maskUtils\n\n\ndef _isArrayLike(obj):\n    return hasattr(obj, '__iter__') and hasattr(obj, '__len__')\n\n\nclass COCO:\n    def __init__(self, annotation_file=None):\n        \"\"\"\n        Constructor of Microsoft COCO helper class for reading and visualizing\n        annotations.\n        :param annotation_file (str): location of annotation file\n        :param image_folder (str): location to the folder that hosts images.\n        :return:\n        \"\"\"\n        # load dataset\n        self.dataset, self.anns, self.cats, self.imgs = dict(), dict(), dict(\n        ), dict()\n        self.imgToAnns, self.catToImgs = defaultdict(list), defaultdict(list)\n        if annotation_file is not None:\n            print('loading annotations into memory...')\n            tic = time.time()\n            with open(annotation_file, 'r') as f:\n                dataset = json.load(f)\n            assert type(\n                dataset\n            ) == dict, 'annotation file format {} not supported'.format(\n                type(dataset))\n            print('Done (t={:0.2f}s)'.format(time.time() - tic))\n            self.dataset = dataset\n            self.createIndex()\n        self.img_ann_map = self.imgToAnns\n        self.cat_img_map = self.catToImgs\n\n    def createIndex(self):\n        # create index\n        print('creating index...')\n        anns, cats, imgs = {}, {}, {}\n        imgToAnns, catToImgs = defaultdict(list), defaultdict(list)\n        if 'annotations' in self.dataset:\n            for ann in self.dataset['annotations']:\n                imgToAnns[ann['image_id']].append(ann)\n                anns[ann['id']] = ann\n\n        if 'images' in self.dataset:\n            for img in self.dataset['images']:\n                imgs[img['id']] = img\n\n        if 'categories' in self.dataset:\n            for cat in self.dataset['categories']:\n                cats[cat['id']] = cat\n\n        if 'annotations' in self.dataset and 'categories' in self.dataset:\n            for ann in self.dataset['annotations']:\n                catToImgs[ann['category_id']].append(ann['image_id'])\n\n        print('index created!')\n\n        # create class members\n        self.anns = anns\n        self.imgToAnns = imgToAnns\n        self.catToImgs = catToImgs\n        self.imgs = imgs\n        self.cats = cats\n\n    def info(self):\n        \"\"\"\n        Print information about the annotation file.\n        :return:\n        \"\"\"\n        for key, value in self.dataset['info'].items():\n            print('{}: {}'.format(key, value))\n\n    def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None):\n        \"\"\"\n        Get ann ids that satisfy given filter conditions. default skips that\n        filter\n        :param imgIds  (int array)     : get anns for given imgs\n               catIds  (int array)     : get anns for given cats\n               areaRng (float array)   : get anns for given area range\n                                        (e.g. [0 inf])\n               iscrowd (boolean)       : get anns for given crowd label\n                                        (False or True)\n        :return: ids (int array)       : integer array of ann ids\n        \"\"\"\n        imgIds = imgIds if _isArrayLike(imgIds) else [imgIds]\n        catIds = catIds if _isArrayLike(catIds) else [catIds]\n\n        if len(imgIds) == len(catIds) == len(areaRng) == 0:\n            anns = self.dataset['annotations']\n        else:\n            if not len(imgIds) == 0:\n                lists = [\n                    self.imgToAnns[imgId] for imgId in imgIds\n                    if imgId in self.imgToAnns\n                ]\n                anns = list(itertools.chain.from_iterable(lists))\n            else:\n                anns = self.dataset['annotations']\n            anns = anns if len(catIds) == 0 else [\n                ann for ann in anns if ann['category_id'] in catIds\n            ]\n            anns = anns if len(areaRng) == 0 else [\n                ann for ann in anns\n                if ann['area'] > areaRng[0] and ann['area'] < areaRng[1]\n            ]\n        if iscrowd is not None:\n            ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd]\n        else:\n            ids = [ann['id'] for ann in anns]\n        return ids\n\n    def get_ann_ids(self, img_ids=[], cat_ids=[], area_rng=[], iscrowd=None):\n        return self.getAnnIds(img_ids, cat_ids, area_rng, iscrowd)\n\n    def getCatIds(self, catNms=[], supNms=[], catIds=[]):\n        \"\"\"\n        filtering parameters. default skips that filter.\n        :param catNms (str array)  : get cats for given cat names\n        :param supNms (str array)  : get cats for given supercategory names\n        :param catIds (int array)  : get cats for given cat ids\n        :return: ids (int array)   : integer array of cat ids\n        \"\"\"\n        catNms = catNms if _isArrayLike(catNms) else [catNms]\n        supNms = supNms if _isArrayLike(supNms) else [supNms]\n        catIds = catIds if _isArrayLike(catIds) else [catIds]\n\n        if len(catNms) == len(supNms) == len(catIds) == 0:\n            cats = self.dataset['categories']\n        else:\n            cats = self.dataset['categories']\n            cats = cats if len(catNms) == 0 else [\n                cat for cat in cats if cat['name'] in catNms\n            ]\n            cats = cats if len(supNms) == 0 else [\n                cat for cat in cats if cat['supercategory'] in supNms\n            ]\n            cats = cats if len(catIds) == 0 else [\n                cat for cat in cats if cat['id'] in catIds\n            ]\n        ids = [cat['id'] for cat in cats]\n        return ids\n\n    def get_cat_ids(self, cat_names=[], sup_names=[], cat_ids=[]):\n        return self.getCatIds(cat_names, sup_names, cat_ids)\n\n    def getImgIds(self, imgIds=[], catIds=[]):\n        '''\n        Get img ids that satisfy given filter conditions.\n        :param imgIds (int array) : get imgs for given ids\n        :param catIds (int array) : get imgs with all given cats\n        :return: ids (int array)  : integer array of img ids\n        '''\n        imgIds = imgIds if _isArrayLike(imgIds) else [imgIds]\n        catIds = catIds if _isArrayLike(catIds) else [catIds]\n\n        if len(imgIds) == len(catIds) == 0:\n            ids = self.imgs.keys()\n        else:\n            ids = set(imgIds)\n            for i, catId in enumerate(catIds):\n                if i == 0 and len(ids) == 0:\n                    ids = set(self.catToImgs[catId])\n                else:\n                    ids &= set(self.catToImgs[catId])\n        return list(ids)\n\n    def get_img_ids(self, img_ids=[], cat_ids=[]):\n        return self.getImgIds(img_ids, cat_ids)\n\n    def loadAnns(self, ids=[]):\n        \"\"\"\n        Load anns with the specified ids.\n        :param ids (int array)       : integer ids specifying anns\n        :return: anns (object array) : loaded ann objects\n        \"\"\"\n        if _isArrayLike(ids):\n            return [self.anns[id] for id in ids]\n        elif type(ids) == int:\n            return [self.anns[ids]]\n\n    load_anns = loadAnns\n\n    def loadCats(self, ids=[]):\n        \"\"\"\n        Load cats with the specified ids.\n        :param ids (int array)       : integer ids specifying cats\n        :return: cats (object array) : loaded cat objects\n        \"\"\"\n        if _isArrayLike(ids):\n            return [self.cats[id] for id in ids]\n        elif type(ids) == int:\n            return [self.cats[ids]]\n\n    load_cats = loadCats\n\n    def loadImgs(self, ids=[]):\n        \"\"\"\n        Load anns with the specified ids.\n        :param ids (int array)       : integer ids specifying img\n        :return: imgs (object array) : loaded img objects\n        \"\"\"\n        if _isArrayLike(ids):\n            return [self.imgs[id] for id in ids]\n        elif type(ids) == int:\n            return [self.imgs[ids]]\n\n    load_imgs = loadImgs\n\n    def showAnns(self, anns, draw_bbox=False):\n        \"\"\"\n        Display the specified annotations.\n        :param anns (array of object): annotations to display\n        :return: None\n        \"\"\"\n        if len(anns) == 0:\n            return 0\n        if 'segmentation' in anns[0] or 'keypoints' in anns[0]:\n            datasetType = 'instances'\n        elif 'caption' in anns[0]:\n            datasetType = 'captions'\n        else:\n            raise Exception('datasetType not supported')\n        if datasetType == 'instances':\n            ax = plt.gca()\n            ax.set_autoscale_on(False)\n            polygons = []\n            color = []\n            for ann in anns:\n                c = (np.random.random((1, 3)) * 0.6 + 0.4).tolist()[0]\n                if 'segmentation' in ann:\n                    if type(ann['segmentation']) == list:\n                        # polygon\n                        for seg in ann['segmentation']:\n                            poly = np.array(seg).reshape(\n                                (int(len(seg) / 2), 2))\n                            polygons.append(Polygon(poly))\n                            color.append(c)\n                    else:\n                        # mask\n                        t = self.imgs[ann['image_id']]\n                        if type(ann['segmentation']['counts']) == list:\n                            rle = maskUtils.frPyObjects([ann['segmentation']],\n                                                        t['height'],\n                                                        t['width'])\n                        else:\n                            rle = [ann['segmentation']]\n                        m = maskUtils.decode(rle)\n                        img = np.ones((m.shape[0], m.shape[1], 3))\n                        if ann['iscrowd'] == 1:\n                            color_mask = np.array([2.0, 166.0, 101.0]) / 255\n                        if ann['iscrowd'] == 0:\n                            color_mask = np.random.random((1, 3)).tolist()[0]\n                        for i in range(3):\n                            img[:, :, i] = color_mask[i]\n                        ax.imshow(np.dstack((img, m * 0.5)))\n                if 'keypoints' in ann and type(ann['keypoints']) == list:\n                    # turn skeleton into zero-based index\n                    sks = np.array(\n                        self.loadCats(ann['category_id'])[0]['skeleton']) - 1\n                    kp = np.array(ann['keypoints'])\n                    x = kp[0::3]\n                    y = kp[1::3]\n                    v = kp[2::3]\n                    for sk in sks:\n                        if np.all(v[sk] > 0):\n                            plt.plot(x[sk], y[sk], linewidth=3, color=c)\n                    plt.plot(x[v > 0],\n                             y[v > 0],\n                             'o',\n                             markersize=8,\n                             markerfacecolor=c,\n                             markeredgecolor='k',\n                             markeredgewidth=2)\n                    plt.plot(x[v > 1],\n                             y[v > 1],\n                             'o',\n                             markersize=8,\n                             markerfacecolor=c,\n                             markeredgecolor=c,\n                             markeredgewidth=2)\n\n                if draw_bbox:\n                    [bbox_x, bbox_y, bbox_w, bbox_h] = ann['bbox']\n                    poly = [[bbox_x, bbox_y], [bbox_x, bbox_y + bbox_h],\n                            [bbox_x + bbox_w, bbox_y + bbox_h],\n                            [bbox_x + bbox_w, bbox_y]]\n                    np_poly = np.array(poly).reshape((4, 2))\n                    polygons.append(Polygon(np_poly))\n                    color.append(c)\n\n            p = PatchCollection(polygons,\n                                facecolor=color,\n                                linewidths=0,\n                                alpha=0.4)\n            ax.add_collection(p)\n            p = PatchCollection(polygons,\n                                facecolor='none',\n                                edgecolors=color,\n                                linewidths=2)\n            ax.add_collection(p)\n        elif datasetType == 'captions':\n            for ann in anns:\n                print(ann['caption'])\n\n    def loadRes(self, resFile):\n        \"\"\"\n        Load result file and return a result api object.\n        :param   resFile (str)     : file name of result file\n        :return: res (obj)         : result api object\n        \"\"\"\n        res = COCO()\n        res.dataset['images'] = [img for img in self.dataset['images']]\n\n        print('Loading and preparing results...')\n        tic = time.time()\n        if type(resFile) == str:\n            with open(resFile) as f:\n                anns = json.load(f)\n        elif type(resFile) == np.ndarray:\n            anns = self.loadNumpyAnnotations(resFile)\n        else:\n            anns = resFile\n        assert type(anns) == list, 'results in not an array of objects'\n        annsImgIds = [ann['image_id'] for ann in anns]\n        assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \\\n               'Results do not correspond to current coco set'\n        if 'caption' in anns[0]:\n            imgIds = set([img['id'] for img in res.dataset['images']]) & set(\n                [ann['image_id'] for ann in anns])\n            res.dataset['images'] = [\n                img for img in res.dataset['images'] if img['id'] in imgIds\n            ]\n            for id, ann in enumerate(anns):\n                ann['id'] = id + 1\n        elif 'bbox' in anns[0] and not anns[0]['bbox'] == []:\n            res.dataset['categories'] = copy.deepcopy(\n                self.dataset['categories'])\n            for id, ann in enumerate(anns):\n                bb = ann['bbox']\n                x1, x2, y1, y2 = [bb[0], bb[0] + bb[2], bb[1], bb[1] + bb[3]]\n                if 'segmentation' not in ann:\n                    ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]]\n                ann['area'] = bb[2] * bb[3]\n                ann['id'] = id + 1\n                ann['iscrowd'] = 0\n        elif 'segmentation' in anns[0]:\n            res.dataset['categories'] = copy.deepcopy(\n                self.dataset['categories'])\n            for id, ann in enumerate(anns):\n                # now only support compressed RLE format as segmentation\n                # results\n                ann['area'] = maskUtils.area(ann['segmentation'])\n                if 'bbox' not in ann:\n                    ann['bbox'] = maskUtils.toBbox(ann['segmentation'])\n                ann['id'] = id + 1\n                ann['iscrowd'] = 0\n        elif 'keypoints' in anns[0]:\n            res.dataset['categories'] = copy.deepcopy(\n                self.dataset['categories'])\n            for id, ann in enumerate(anns):\n                s = ann['keypoints']\n                x = s[0::3]\n                y = s[1::3]\n                x0, x1, y0, y1 = np.min(x), np.max(x), np.min(y), np.max(y)\n                ann['area'] = (x1 - x0) * (y1 - y0)\n                ann['id'] = id + 1\n                ann['bbox'] = [x0, y0, x1 - x0, y1 - y0]\n        print('DONE (t={:0.2f}s)'.format(time.time() - tic))\n\n        res.dataset['annotations'] = anns\n        res.createIndex()\n        return res\n\n    def download(self, tarDir=None, imgIds=[]):\n        '''\n        Download COCO images from mscoco.org server.\n        :param tarDir (str): COCO results directory name\n               imgIds (list): images to be downloaded\n        :return:\n        '''\n        if tarDir is None:\n            print('Please specify target directory')\n            return -1\n        if len(imgIds) == 0:\n            imgs = self.imgs.values()\n        else:\n            imgs = self.loadImgs(imgIds)\n        N = len(imgs)\n        if not os.path.exists(tarDir):\n            os.makedirs(tarDir)\n        for i, img in enumerate(imgs):\n            tic = time.time()\n            fname = os.path.join(tarDir, img['file_name'])\n            if not os.path.exists(fname):\n                urlretrieve(img['coco_url'], fname)\n            print('downloaded {}/{} images (t={:0.1f}s)'.format(\n                i, N,\n                time.time() - tic))\n\n    def loadNumpyAnnotations(self, data):\n        \"\"\"\n        Convert result data from a numpy array [Nx7] where each row contains\n        {imageID,x1,y1,w,h,score,class}\n        :param  data (numpy.ndarray)\n        :return: annotations (python nested list)\n        \"\"\"\n        print('Converting ndarray to lists...')\n        assert (type(data) == np.ndarray)\n        print(data.shape)\n        assert (data.shape[1] == 7)\n        N = data.shape[0]\n        ann = []\n        for i in range(N):\n            if i % 1000000 == 0:\n                print('{}/{}'.format(i, N))\n            ann += [{\n                'image_id': int(data[i, 0]),\n                'bbox': [data[i, 1], data[i, 2], data[i, 3], data[i, 4]],\n                'score': data[i, 5],\n                'category_id': int(data[i, 6]),\n            }]\n        return ann\n\n    def annToRLE(self, ann):\n        \"\"\"\n        Convert annotation which can be polygons, uncompressed RLE to RLE.\n        :return: binary mask (numpy 2D array)\n        \"\"\"\n        t = self.imgs[ann['image_id']]\n        h, w = t['height'], t['width']\n        segm = ann['segmentation']\n        if type(segm) == list:\n            # polygon -- a single object might consist of multiple parts\n            # we merge all parts into one mask rle code\n            rles = maskUtils.frPyObjects(segm, h, w)\n            rle = maskUtils.merge(rles)\n        elif type(segm['counts']) == list:\n            # uncompressed RLE\n            rle = maskUtils.frPyObjects(segm, h, w)\n        else:\n            # rle\n            rle = ann['segmentation']\n        return rle\n\n    ann_to_rle = annToRLE\n\n    def annToMask(self, ann):\n        \"\"\"\n        Convert annotation which can be polygons, uncompressed RLE, or RLE to\n        binary mask.\n        :return: binary mask (numpy 2D array)\n        \"\"\"\n        rle = self.annToRLE(ann)\n        m = maskUtils.decode(rle)\n        return m\n\n    ann_to_mask = annToMask\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocotools/cocoeval.py",
    "content": "__author__ = 'tsungyi'\n\nimport copy\nimport datetime\nimport time\nfrom collections import defaultdict\n\nimport numpy as np\n\nfrom . import mask as maskUtils\n\n\nclass COCOeval:\n    # Interface for evaluating detection on the Microsoft COCO dataset.\n    #\n    # The usage for CocoEval is as follows:\n    #  cocoGt=..., cocoDt=...       # load dataset and results\n    #  E = CocoEval(cocoGt,cocoDt); # initialize CocoEval object\n    #  E.params.recThrs = ...;      # set parameters as desired\n    #  E.evaluate();                # run per image evaluation\n    #  E.accumulate();              # accumulate per image results\n    #  E.summarize();               # display summary metrics of results\n    # For example usage see evalDemo.m and http://mscoco.org/.\n    #\n    # The evaluation parameters are as follows (defaults in brackets):\n    #  imgIds     - [all] N img ids to use for evaluation\n    #  catIds     - [all] K cat ids to use for evaluation\n    #  iouThrs    - [.5:.05:.95] T=10 IoU thresholds for evaluation\n    #  recThrs    - [0:.01:1] R=101 recall thresholds for evaluation\n    #  areaRng    - [...] A=4 object area ranges for evaluation\n    #  maxDets    - [1 10 100] M=3 thresholds on max detections per image\n    #  iouType    - ['segm'] set iouType to 'segm', 'bbox' or 'keypoints'\n    #  iouType replaced the now DEPRECATED useSegm parameter.\n    #  useCats    - [1] if true use category labels for evaluation\n    # Note: if useCats=0 category labels are ignored as in proposal scoring.\n    # Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified.\n    #\n    # evaluate(): evaluates detections on every image and every category and\n    # concats the results into the \"evalImgs\" with fields:\n    #  dtIds      - [1xD] id for each of the D detections (dt)\n    #  gtIds      - [1xG] id for each of the G ground truths (gt)\n    #  dtMatches  - [TxD] matching gt id at each IoU or 0\n    #  gtMatches  - [TxG] matching dt id at each IoU or 0\n    #  dtScores   - [1xD] confidence of each dt\n    #  gtIgnore   - [1xG] ignore flag for each gt\n    #  dtIgnore   - [TxD] ignore flag for each dt at each IoU\n    #\n    # accumulate(): accumulates the per-image, per-category evaluation\n    # results in \"evalImgs\" into the dictionary \"eval\" with fields:\n    #  params     - parameters used for evaluation\n    #  date       - date evaluation was performed\n    #  counts     - [T,R,K,A,M] parameter dimensions (see above)\n    #  precision  - [TxRxKxAxM] precision for every evaluation setting\n    #  recall     - [TxKxAxM] max recall for every evaluation setting\n    # Note: precision and recall==-1 for settings with no gt objects.\n    #\n    # See also coco, mask, pycocoDemo, pycocoEvalDemo\n    #\n    # Microsoft COCO Toolbox.      version 2.0\n    # Data, paper, and tutorials available at:  http://mscoco.org/\n    # Code written by Piotr Dollar and Tsung-Yi Lin, 2015.\n    # Licensed under the Simplified BSD License [see coco/license.txt]\n    def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'):\n        '''\n        Initialize CocoEval using coco APIs for gt and dt\n        :param cocoGt: coco object with ground truth annotations\n        :param cocoDt: coco object with detection results\n        :return: None\n        '''\n        if not iouType:\n            print('iouType not specified. use default iouType segm')\n        self.cocoGt = cocoGt  # ground truth COCO API\n        self.cocoDt = cocoDt  # detections COCO API\n        self.evalImgs = defaultdict(\n            list)  # per-image per-category evaluation results [KxAxI] elements\n        self.eval = {}  # accumulated evaluation results\n        self._gts = defaultdict(list)  # gt for evaluation\n        self._dts = defaultdict(list)  # dt for evaluation\n        self.params = Params(iouType=iouType)  # parameters\n        self._paramsEval = {}  # parameters for evaluation\n        self.stats = []  # result summarization\n        self.ious = {}  # ious between all gts and dts\n        if cocoGt is not None:\n            self.params.imgIds = sorted(cocoGt.getImgIds())\n            self.params.catIds = sorted(cocoGt.getCatIds())\n\n    def _prepare(self):\n        '''\n        Prepare ._gts and ._dts for evaluation based on params\n        :return: None\n        '''\n        def _toMask(anns, coco):\n            # modify ann['segmentation'] by reference\n            for ann in anns:\n                rle = coco.annToRLE(ann)\n                ann['segmentation'] = rle\n\n        p = self.params\n        if p.useCats:\n            gts = self.cocoGt.loadAnns(\n                self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))\n            dts = self.cocoDt.loadAnns(\n                self.cocoDt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))\n        else:\n            gts = self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds))\n            dts = self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds))\n\n        # convert ground truth to mask if iouType == 'segm'\n        if p.iouType == 'segm':\n            _toMask(gts, self.cocoGt)\n            _toMask(dts, self.cocoDt)\n        # set ignore flag\n        for gt in gts:\n            gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0\n            gt['ignore'] = 'iscrowd' in gt and gt['iscrowd']\n            if p.iouType == 'keypoints':\n                gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore']\n        self._gts = defaultdict(list)  # gt for evaluation\n        self._dts = defaultdict(list)  # dt for evaluation\n        for gt in gts:\n            self._gts[gt['image_id'], gt['category_id']].append(gt)\n        for dt in dts:\n            self._dts[dt['image_id'], dt['category_id']].append(dt)\n        self.evalImgs = defaultdict(\n            list)  # per-image per-category evaluation results\n        self.eval = {}  # accumulated evaluation results\n\n    def evaluate(self):\n        '''\n        Run per image evaluation on given images and store results\n         (a list of dict) in self.evalImgs\n        :return: None\n        '''\n        tic = time.time()\n        print('Running per image evaluation...')\n        p = self.params\n        # add backward compatibility if useSegm is specified in params\n        if p.useSegm is not None:\n            p.iouType = 'segm' if p.useSegm == 1 else 'bbox'\n            print('useSegm (deprecated) is not None. Running {} evaluation'.\n                  format(p.iouType))\n        print('Evaluate annotation type *{}*'.format(p.iouType))\n        p.imgIds = list(np.unique(p.imgIds))\n        if p.useCats:\n            p.catIds = list(np.unique(p.catIds))\n        p.maxDets = sorted(p.maxDets)\n        self.params = p\n\n        self._prepare()\n        # loop through images, area range, max detection number\n        catIds = p.catIds if p.useCats else [-1]\n\n        if p.iouType == 'segm' or p.iouType == 'bbox':\n            computeIoU = self.computeIoU\n        elif p.iouType == 'keypoints':\n            computeIoU = self.computeOks\n        self.ious = {(imgId, catId): computeIoU(imgId, catId)\n                     for imgId in p.imgIds for catId in catIds}\n\n        evaluateImg = self.evaluateImg\n        maxDet = p.maxDets[-1]\n        self.evalImgs = [\n            evaluateImg(imgId, catId, areaRng, maxDet) for catId in catIds\n            for areaRng in p.areaRng for imgId in p.imgIds\n        ]\n        self._paramsEval = copy.deepcopy(self.params)\n        toc = time.time()\n        print('DONE (t={:0.2f}s).'.format(toc - tic))\n\n    def computeIoU(self, imgId, catId):\n        p = self.params\n        if p.useCats:\n            gt = self._gts[imgId, catId]\n            dt = self._dts[imgId, catId]\n        else:\n            gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]\n            dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]\n        if len(gt) == 0 and len(dt) == 0:\n            return []\n        inds = np.argsort([-d['score'] for d in dt], kind='mergesort')\n        dt = [dt[i] for i in inds]\n        if len(dt) > p.maxDets[-1]:\n            dt = dt[0:p.maxDets[-1]]\n\n        if p.iouType == 'segm':\n            g = [g['segmentation'] for g in gt]\n            d = [d['segmentation'] for d in dt]\n        elif p.iouType == 'bbox':\n            g = [g['bbox'] for g in gt]\n            d = [d['bbox'] for d in dt]\n        else:\n            raise Exception('unknown iouType for iou computation')\n\n        # compute iou between each dt and gt region\n        iscrowd = [int(o['iscrowd']) for o in gt]\n        ious = maskUtils.iou(d, g, iscrowd)\n        return ious\n\n    def computeOks(self, imgId, catId):\n        p = self.params\n        # dimention here should be Nxm\n        gts = self._gts[imgId, catId]\n        dts = self._dts[imgId, catId]\n        inds = np.argsort([-d['score'] for d in dts], kind='mergesort')\n        dts = [dts[i] for i in inds]\n        if len(dts) > p.maxDets[-1]:\n            dts = dts[0:p.maxDets[-1]]\n        # if len(gts) == 0 and len(dts) == 0:\n        if len(gts) == 0 or len(dts) == 0:\n            return []\n        ious = np.zeros((len(dts), len(gts)))\n        sigmas = p.kpt_oks_sigmas\n        vars = (sigmas * 2)**2\n        k = len(sigmas)\n        # compute oks between each detection and ground truth object\n        for j, gt in enumerate(gts):\n            # create bounds for ignore regions(double the gt bbox)\n            g = np.array(gt['keypoints'])\n            xg = g[0::3]\n            yg = g[1::3]\n            vg = g[2::3]\n            k1 = np.count_nonzero(vg > 0)\n            bb = gt['bbox']\n            x0 = bb[0] - bb[2]\n            x1 = bb[0] + bb[2] * 2\n            y0 = bb[1] - bb[3]\n            y1 = bb[1] + bb[3] * 2\n            for i, dt in enumerate(dts):\n                d = np.array(dt['keypoints'])\n                xd = d[0::3]\n                yd = d[1::3]\n                if k1 > 0:\n                    # measure the per-keypoint distance if keypoints visible\n                    dx = xd - xg\n                    dy = yd - yg\n                else:\n                    # measure minimum distance to keypoints in (x0,y0) &\n                    # (x1,y1)\n                    z = np.zeros((k))\n                    dx = np.max((z, x0 - xd), axis=0) + np.max(\n                        (z, xd - x1), axis=0)\n                    dy = np.max((z, y0 - yd), axis=0) + np.max(\n                        (z, yd - y1), axis=0)\n                e = (dx**2 + dy**2) / vars / (gt['area'] + np.spacing(1)) / 2\n                if k1 > 0:\n                    e = e[vg > 0]\n                ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]\n        return ious\n\n    def evaluateImg(self, imgId, catId, aRng, maxDet):\n        '''\n        perform evaluation for single category and image\n        :return: dict (single image results)\n        '''\n        p = self.params\n        if p.useCats:\n            gt = self._gts[imgId, catId]\n            dt = self._dts[imgId, catId]\n        else:\n            gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]\n            dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]\n        if len(gt) == 0 and len(dt) == 0:\n            return None\n\n        for g in gt:\n            if g['ignore'] or (g['area'] < aRng[0] or g['area'] > aRng[1]):\n                g['_ignore'] = 1\n            else:\n                g['_ignore'] = 0\n\n        # sort dt highest score first, sort gt ignore last\n        gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')\n        gt = [gt[i] for i in gtind]\n        dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')\n        dt = [dt[i] for i in dtind[0:maxDet]]\n        iscrowd = [int(o['iscrowd']) for o in gt]\n        # load computed ious\n        ious = self.ious[imgId, catId][:, gtind] if len(\n            self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]\n\n        T = len(p.iouThrs)\n        G = len(gt)\n        D = len(dt)\n        gtm = np.zeros((T, G))\n        dtm = np.zeros((T, D))\n        gtIg = np.array([g['_ignore'] for g in gt])\n        dtIg = np.zeros((T, D))\n        if not len(ious) == 0:\n            for tind, t in enumerate(p.iouThrs):\n                for dind, d in enumerate(dt):\n                    # information about best match so far (m=-1 -> unmatched)\n                    iou = min([t, 1 - 1e-10])\n                    m = -1\n                    for gind, g in enumerate(gt):\n                        # if this gt already matched, and not a crowd, continue\n                        if gtm[tind, gind] > 0 and not iscrowd[gind]:\n                            continue\n                        # if dt matched to reg gt, and on ignore gt, stop\n                        if m > -1 and gtIg[m] == 0 and gtIg[gind] == 1:\n                            break\n                        # continue to next gt unless better match made\n                        if ious[dind, gind] < iou:\n                            continue\n                        # if match successful and best so far, store\n                        # appropriately\n                        iou = ious[dind, gind]\n                        m = gind\n                    # if match made store id of match for both dt and gt\n                    if m == -1:\n                        continue\n                    dtIg[tind, dind] = gtIg[m]\n                    dtm[tind, dind] = gt[m]['id']\n                    gtm[tind, m] = d['id']\n        # set unmatched detections outside of area range to ignore\n        a = np.array([d['area'] < aRng[0] or d['area'] > aRng[1]\n                      for d in dt]).reshape((1, len(dt)))\n        dtIg = np.logical_or(dtIg, np.logical_and(dtm == 0, np.repeat(a, T,\n                                                                      0)))\n        # store results for given image and category\n        return {\n            'image_id': imgId,\n            'category_id': catId,\n            'aRng': aRng,\n            'maxDet': maxDet,\n            'dtIds': [d['id'] for d in dt],\n            'gtIds': [g['id'] for g in gt],\n            'dtMatches': dtm,\n            'gtMatches': gtm,\n            'dtScores': [d['score'] for d in dt],\n            'gtIgnore': gtIg,\n            'dtIgnore': dtIg,\n        }\n\n    def accumulate(self, p=None):\n        '''\n        Accumulate per image evaluation results and store the result in\n        self.eval\n\n        :param p: input params for evaluation\n        :return: None\n        '''\n        print('Accumulating evaluation results...')\n        tic = time.time()\n        if not self.evalImgs:\n            print('Please run evaluate() first')\n        # allows input customized parameters\n        if p is None:\n            p = self.params\n        p.catIds = p.catIds if p.useCats == 1 else [-1]\n        T = len(p.iouThrs)\n        R = len(p.recThrs)\n        K = len(p.catIds) if p.useCats else 1\n        A = len(p.areaRng)\n        M = len(p.maxDets)\n        precision = -np.ones(\n            (T, R, K, A, M))  # -1 for the precision of absent categories\n        recall = -np.ones((T, K, A, M))\n        scores = -np.ones((T, R, K, A, M))\n\n        # create dictionary for future indexing\n        _pe = self._paramsEval\n        catIds = _pe.catIds if _pe.useCats else [-1]\n        setK = set(catIds)\n        setA = set(map(tuple, _pe.areaRng))\n        setM = set(_pe.maxDets)\n        setI = set(_pe.imgIds)\n        # get inds to evaluate\n        k_list = [n for n, k in enumerate(p.catIds) if k in setK]\n        m_list = [m for n, m in enumerate(p.maxDets) if m in setM]\n        a_list = [\n            n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng))\n            if a in setA\n        ]\n        i_list = [n for n, i in enumerate(p.imgIds) if i in setI]\n        I0 = len(_pe.imgIds)\n        A0 = len(_pe.areaRng)\n        # retrieve E at each category, area range, and max number of detections\n        for k, k0 in enumerate(k_list):\n            Nk = k0 * A0 * I0\n            for a, a0 in enumerate(a_list):\n                Na = a0 * I0\n                for m, maxDet in enumerate(m_list):\n                    E = [self.evalImgs[Nk + Na + i] for i in i_list]\n                    E = [e for e in E if e is not None]\n                    if len(E) == 0:\n                        continue\n                    dtScores = np.concatenate(\n                        [e['dtScores'][0:maxDet] for e in E])\n\n                    # different sorting method generates slightly different\n                    # results. mergesort is used to be consistent as Matlab\n                    # implementation.\n                    inds = np.argsort(-dtScores, kind='mergesort')\n                    dtScoresSorted = dtScores[inds]\n\n                    dtm = np.concatenate(\n                        [e['dtMatches'][:, 0:maxDet] for e in E], axis=1)[:,\n                                                                          inds]\n                    dtIg = np.concatenate(\n                        [e['dtIgnore'][:, 0:maxDet] for e in E], axis=1)[:,\n                                                                         inds]\n                    gtIg = np.concatenate([e['gtIgnore'] for e in E])\n                    npig = np.count_nonzero(gtIg == 0)\n                    if npig == 0:\n                        continue\n                    tps = np.logical_and(dtm, np.logical_not(dtIg))\n                    fps = np.logical_and(np.logical_not(dtm),\n                                         np.logical_not(dtIg))\n\n                    tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)\n                    fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)\n                    for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):\n                        tp = np.array(tp)\n                        fp = np.array(fp)\n                        nd = len(tp)\n                        rc = tp / npig\n                        pr = tp / (fp + tp + np.spacing(1))\n                        q = np.zeros((R, ))\n                        ss = np.zeros((R, ))\n\n                        if nd:\n                            recall[t, k, a, m] = rc[-1]\n                        else:\n                            recall[t, k, a, m] = 0\n\n                        # numpy is slow without cython optimization for\n                        # accessing elements use python array gets significant\n                        # speed improvement\n                        pr = pr.tolist()\n                        q = q.tolist()\n\n                        for i in range(nd - 1, 0, -1):\n                            if pr[i] > pr[i - 1]:\n                                pr[i - 1] = pr[i]\n\n                        inds = np.searchsorted(rc, p.recThrs, side='left')\n                        try:\n                            for ri, pi in enumerate(inds):\n                                q[ri] = pr[pi]\n                                ss[ri] = dtScoresSorted[pi]\n                        except:  # noqa: E722\n                            pass\n                        precision[t, :, k, a, m] = np.array(q)\n                        scores[t, :, k, a, m] = np.array(ss)\n        self.eval = {\n            'params': p,\n            'counts': [T, R, K, A, M],\n            'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),\n            'precision': precision,\n            'recall': recall,\n            'scores': scores,\n        }\n        toc = time.time()\n        print('DONE (t={:0.2f}s).'.format(toc - tic))\n\n    def summarize(self):\n        '''\n        Compute and display summary metrics for evaluation results.\n        Note this functin can *only* be applied on the default parameter\n        setting\n        '''\n        def _summarize(ap=1, iouThr=None, areaRng='all', maxDets=100):\n            p = self.params\n            iStr = '{:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}'  # noqa: E501\n            titleStr = 'Average Precision' if ap == 1 else 'Average Recall'\n            typeStr = '(AP)' if ap == 1 else '(AR)'\n            iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \\\n                if iouThr is None else '{:0.2f}'.format(iouThr)\n\n            aind = [\n                i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng\n            ]\n            mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]\n            if ap == 1:\n                # dimension of precision: [TxRxKxAxM]\n                s = self.eval['precision']\n                # IoU\n                if iouThr is not None:\n                    t = np.where(iouThr == p.iouThrs)[0]\n                    s = s[t]\n                s = s[:, :, :, aind, mind]\n            else:\n                # dimension of recall: [TxKxAxM]\n                s = self.eval['recall']\n                if iouThr is not None:\n                    t = np.where(iouThr == p.iouThrs)[0]\n                    s = s[t]\n                s = s[:, :, aind, mind]\n            if len(s[s > -1]) == 0:\n                mean_s = -1\n            else:\n                mean_s = np.mean(s[s > -1])\n            print(\n                iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets,\n                            mean_s))\n            return mean_s\n\n        def _summarizeDets():\n            stats = np.zeros((12, ))\n            stats[0] = _summarize(1)\n            stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2])\n            stats[2] = _summarize(1,\n                                  iouThr=.75,\n                                  maxDets=self.params.maxDets[2])\n            stats[3] = _summarize(1,\n                                  areaRng='small',\n                                  maxDets=self.params.maxDets[2])\n            stats[4] = _summarize(1,\n                                  areaRng='medium',\n                                  maxDets=self.params.maxDets[2])\n            stats[5] = _summarize(1,\n                                  areaRng='large',\n                                  maxDets=self.params.maxDets[2])\n            stats[6] = _summarize(0, maxDets=self.params.maxDets[0])\n            stats[7] = _summarize(0, maxDets=self.params.maxDets[1])\n            stats[8] = _summarize(0, maxDets=self.params.maxDets[2])\n            stats[9] = _summarize(0,\n                                  areaRng='small',\n                                  maxDets=self.params.maxDets[2])\n            stats[10] = _summarize(0,\n                                   areaRng='medium',\n                                   maxDets=self.params.maxDets[2])\n            stats[11] = _summarize(0,\n                                   areaRng='large',\n                                   maxDets=self.params.maxDets[2])\n            return stats\n\n        def _summarizeKps():\n            stats = np.zeros((10, ))\n            stats[0] = _summarize(1, maxDets=20)\n            stats[1] = _summarize(1, maxDets=20, iouThr=.5)\n            stats[2] = _summarize(1, maxDets=20, iouThr=.75)\n            stats[3] = _summarize(1, maxDets=20, areaRng='medium')\n            stats[4] = _summarize(1, maxDets=20, areaRng='large')\n            stats[5] = _summarize(0, maxDets=20)\n            stats[6] = _summarize(0, maxDets=20, iouThr=.5)\n            stats[7] = _summarize(0, maxDets=20, iouThr=.75)\n            stats[8] = _summarize(0, maxDets=20, areaRng='medium')\n            stats[9] = _summarize(0, maxDets=20, areaRng='large')\n            return stats\n\n        if not self.eval:\n            raise Exception('Please run accumulate() first')\n        iouType = self.params.iouType\n        if iouType == 'segm' or iouType == 'bbox':\n            summarize = _summarizeDets\n        elif iouType == 'keypoints':\n            summarize = _summarizeKps\n        self.stats = summarize()\n\n    def __str__(self):\n        self.summarize()\n\n\nclass Params:\n    '''\n    Params for coco evaluation api\n    '''\n    def setDetParams(self):\n        self.imgIds = []\n        self.catIds = []\n        # np.arange causes trouble.  the data point on arange is slightly\n        # larger than the true value\n        self.iouThrs = np.linspace(.5,\n                                   0.95,\n                                   int(np.round((0.95 - .5) / .05)) + 1,\n                                   endpoint=True)\n        self.recThrs = np.linspace(.0,\n                                   1.00,\n                                   int(np.round((1.00 - .0) / .01)) + 1,\n                                   endpoint=True)\n        self.maxDets = [1, 10, 100]\n        self.areaRng = [[0**2, 1e5**2], [0**2, 32**2], [32**2, 96**2],\n                        [96**2, 1e5**2]]\n        self.areaRngLbl = ['all', 'small', 'medium', 'large']\n        self.useCats = 1\n\n    def setKpParams(self):\n        self.imgIds = []\n        self.catIds = []\n        # np.arange causes trouble.  the data point on arange is slightly\n        # larger than the true value\n        self.iouThrs = np.linspace(.5,\n                                   0.95,\n                                   int(np.round((0.95 - .5) / .05)) + 1,\n                                   endpoint=True)\n        self.recThrs = np.linspace(.0,\n                                   1.00,\n                                   int(np.round((1.00 - .0) / .01)) + 1,\n                                   endpoint=True)\n        self.maxDets = [20]\n        self.areaRng = [[0**2, 1e5**2], [32**2, 96**2], [96**2, 1e5**2]]\n        self.areaRngLbl = ['all', 'medium', 'large']\n        self.useCats = 1\n        self.kpt_oks_sigmas = np.array([\n            .26, .25, .25, .35, .35, .79, .79, .72, .72, .62, .62, 1.07, 1.07,\n            .87, .87, .89, .89\n        ]) / 10.0\n\n    def __init__(self, iouType='segm'):\n        if iouType == 'segm' or iouType == 'bbox':\n            self.setDetParams()\n        elif iouType == 'keypoints':\n            self.setKpParams()\n        else:\n            raise Exception('iouType not supported')\n        self.iouType = iouType\n        # useSegm is deprecated\n        self.useSegm = None\n"
  },
  {
    "path": "code/cocoapi/pycocotools/pycocotools/mask.py",
    "content": "__author__ = 'tsungyi'\n\nimport pycocotools._mask as _mask\n\n# Interface for manipulating masks stored in RLE format.\n#\n# RLE is a simple yet efficient format for storing binary masks. RLE\n# first divides a vector (or vectorized image) into a series of piecewise\n# constant regions and then for each piece simply stores the length of\n# that piece. For example, given M=[0 0 1 1 1 0 1] the RLE counts would\n# be [2 3 1 1], or for M=[1 1 1 1 1 1 0] the counts would be [0 6 1]\n# (note that the odd counts are always the numbers of zeros). Instead of\n# storing the counts directly, additional compression is achieved with a\n# variable bitrate representation based on a common scheme called LEB128.\n#\n# Compression is greatest given large piecewise constant regions.\n# Specifically, the size of the RLE is proportional to the number of\n# *boundaries* in M (or for an image the number of boundaries in the y\n# direction). Assuming fairly simple shapes, the RLE representation is\n# O(sqrt(n)) where n is number of pixels in the object. Hence space usage\n# is substantially lower, especially for large simple objects (large n).\n#\n# Many common operations on masks can be computed directly using the RLE\n# (without need for decoding). This includes computations such as area,\n# union, intersection, etc. All of these operations are linear in the\n# size of the RLE, in other words they are O(sqrt(n)) where n is the area\n# of the object. Computing these operations on the original mask is O(n).\n# Thus, using the RLE can result in substantial computational savings.\n#\n# The following API functions are defined:\n#  encode         - Encode binary masks using RLE.\n#  decode         - Decode binary masks encoded via RLE.\n#  merge          - Compute union or intersection of encoded masks.\n#  iou            - Compute intersection over union between masks.\n#  area           - Compute area of encoded masks.\n#  toBbox         - Get bounding boxes surrounding encoded masks.\n#  frPyObjects    - Convert polygon, bbox, and uncompressed RLE to encoded\n#                   RLE mask.\n#\n# Usage:\n#  Rs     = encode( masks )\n#  masks  = decode( Rs )\n#  R      = merge( Rs, intersect=false )\n#  o      = iou( dt, gt, iscrowd )\n#  a      = area( Rs )\n#  bbs    = toBbox( Rs )\n#  Rs     = frPyObjects( [pyObjects], h, w )\n#\n# In the API the following formats are used:\n#  Rs      - [dict] Run-length encoding of binary masks\n#  R       - dict Run-length encoding of binary mask\n#  masks   - [hxwxn] Binary mask(s) (must have type np.ndarray(dtype=uint8)\n#            in column-major order)\n#  iscrowd - [nx1] list of np.ndarray. 1 indicates corresponding gt image has\n#            crowd region to ignore\n#  bbs     - [nx4] Bounding box(es) stored as [x y w h]\n#  poly    - Polygon stored as [[x1 y1 x2 y2...],[x1 y1 ...],...] (2D list)\n#  dt,gt   - May be either bounding boxes or encoded masks\n# Both poly and bbs are 0-indexed (bbox=[0 0 1 1] encloses first pixel).\n#\n# Finally, a note about the intersection over union (iou) computation.\n# The standard iou of a ground truth (gt) and detected (dt) object is\n#  iou(gt,dt) = area(intersect(gt,dt)) / area(union(gt,dt))\n# For \"crowd\" regions, we use a modified criteria. If a gt object is\n# marked as \"iscrowd\", we allow a dt to match any subregion of the gt.\n# Choosing gt' in the crowd gt that best matches the dt can be done using\n# gt'=intersect(dt,gt). Since by definition union(gt',dt)=dt, computing\n#  iou(gt,dt,iscrowd) = iou(gt',dt) = area(intersect(gt,dt)) / area(dt)\n# For crowd gt regions we use this modified criteria above for the iou.\n#\n# To compile run \"python setup.py build_ext --inplace\"\n# Please do not contact us for help with compiling.\n#\n# Microsoft COCO Toolbox.      version 2.0\n# Data, paper, and tutorials available at:  http://mscoco.org/\n# Code written by Piotr Dollar and Tsung-Yi Lin, 2015.\n# Licensed under the Simplified BSD License [see coco/license.txt]\n\niou = _mask.iou\nmerge = _mask.merge\nfrPyObjects = _mask.frPyObjects\n\n\ndef encode(bimask):\n    if len(bimask.shape) == 3:\n        return _mask.encode(bimask)\n    elif len(bimask.shape) == 2:\n        h, w = bimask.shape\n        return _mask.encode(bimask.reshape((h, w, 1), order='F'))[0]\n\n\ndef decode(rleObjs):\n    if type(rleObjs) == list:\n        return _mask.decode(rleObjs)\n    else:\n        return _mask.decode([rleObjs])[:, :, 0]\n\n\ndef area(rleObjs):\n    if type(rleObjs) == list:\n        return _mask.area(rleObjs)\n    else:\n        return _mask.area([rleObjs])[0]\n\n\ndef toBbox(rleObjs):\n    if type(rleObjs) == list:\n        return _mask.toBbox(rleObjs)\n    else:\n        return _mask.toBbox([rleObjs])[0]\n"
  },
  {
    "path": "code/cocoapi/pycocotools/setup.py",
    "content": "import numpy as np\nfrom setuptools import Extension, setup\n\n# To compile and install locally run \"python setup.py build_ext --inplace\"\n# To install library to Python site-packages run\n# \"python setup.py build_ext install\"\n# Note that the original compile flags below are GCC flags unsupported by\n# the Visual C++ 2015 build tools.\n# They can safely be removed.\n\next_modules = [\n    Extension(\n        'pycocotools._mask',\n        sources=['common/maskApi.c', 'pycocotools/_mask.pyx'],\n        include_dirs=[np.get_include(), 'common'],\n        # extra_compile_args=['-Wno-cpp', '-Wno-unused-function', '-std=c99'],\n        extra_compile_args=[],\n    )\n]\n\nsetup(name='mmpycocotools',\n      packages=['pycocotools'],\n      package_dir={'pycocotools': 'pycocotools'},\n      install_requires=[\n          'setuptools>=18.0', 'cython>=0.27.3', 'matplotlib>=2.1.0'\n      ],\n      version='12.0.3',\n      ext_modules=ext_modules)\n"
  },
  {
    "path": "code/configs/_base_/datasets/cityscapes_detection.py",
    "content": "dataset_type = 'CityscapesDataset'\ndata_root = 'data/cityscapes/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True),\n    dict(\n        type='Resize', img_scale=[(2048, 800), (2048, 1024)], keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(2048, 1024),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=1,\n    workers_per_gpu=2,\n    train=dict(\n        type='RepeatDataset',\n        times=8,\n        dataset=dict(\n            type=dataset_type,\n            ann_file=data_root +\n            'annotations/instancesonly_filtered_gtFine_train.json',\n            img_prefix=data_root + 'leftImg8bit/train/',\n            pipeline=train_pipeline)),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root +\n        'annotations/instancesonly_filtered_gtFine_val.json',\n        img_prefix=data_root + 'leftImg8bit/val/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root +\n        'annotations/instancesonly_filtered_gtFine_test.json',\n        img_prefix=data_root + 'leftImg8bit/test/',\n        pipeline=test_pipeline))\nevaluation = dict(interval=1, metric='bbox')\n"
  },
  {
    "path": "code/configs/_base_/datasets/cityscapes_instance.py",
    "content": "dataset_type = 'CityscapesDataset'\ndata_root = 'data/cityscapes/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n    dict(\n        type='Resize', img_scale=[(2048, 800), (2048, 1024)], keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(2048, 1024),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=1,\n    workers_per_gpu=2,\n    train=dict(\n        type='RepeatDataset',\n        times=8,\n        dataset=dict(\n            type=dataset_type,\n            ann_file=data_root +\n            'annotations/instancesonly_filtered_gtFine_train.json',\n            img_prefix=data_root + 'leftImg8bit/train/',\n            pipeline=train_pipeline)),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root +\n        'annotations/instancesonly_filtered_gtFine_val.json',\n        img_prefix=data_root + 'leftImg8bit/val/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root +\n        'annotations/instancesonly_filtered_gtFine_test.json',\n        img_prefix=data_root + 'leftImg8bit/test/',\n        pipeline=test_pipeline))\nevaluation = dict(metric=['bbox', 'segm'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/coco_detection.py",
    "content": "dataset_type = 'CocoDataset'\ndata_root = 'data/coco/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_train2017.json',\n        img_prefix=data_root + 'train2017/',\n        pipeline=train_pipeline),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_val2017.json',\n        img_prefix=data_root + 'val2017/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_val2017.json',\n        img_prefix=data_root + 'val2017/',\n        pipeline=test_pipeline))\nevaluation = dict(interval=1, metric='bbox')\n"
  },
  {
    "path": "code/configs/_base_/datasets/coco_instance.py",
    "content": "dataset_type = 'CocoDataset'\ndata_root = 'data/coco/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_train2017.json',\n        img_prefix=data_root + 'train2017/',\n        pipeline=train_pipeline),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_val2017.json',\n        img_prefix=data_root + 'val2017/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_val2017.json',\n        img_prefix=data_root + 'val2017/',\n        pipeline=test_pipeline))\nevaluation = dict(metric=['bbox', 'segm'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/coco_instance_semantic.py",
    "content": "dataset_type = 'CocoDataset'\ndata_root = 'data/coco/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='LoadAnnotations', with_bbox=True, with_mask=True, with_seg=True),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='SegRescale', scale_factor=1 / 8),\n    dict(type='DefaultFormatBundle'),\n    dict(\n        type='Collect',\n        keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks', 'gt_semantic_seg']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip', flip_ratio=0.5),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_train2017.json',\n        img_prefix=data_root + 'train2017/',\n        seg_prefix=data_root + 'stuffthingmaps/train2017/',\n        pipeline=train_pipeline),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_val2017.json',\n        img_prefix=data_root + 'val2017/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_val2017.json',\n        img_prefix=data_root + 'val2017/',\n        pipeline=test_pipeline))\nevaluation = dict(metric=['bbox', 'segm'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/coco_lsvr.py",
    "content": "dataset_type = 'CocoDataset'\ndata_root = '/home/ma-user/work/duankaiwen/coco/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_extreme=True),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_extremes']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_lsvr_train2017.json',\n        img_prefix=data_root + 'images/train2017/',\n        pipeline=train_pipeline),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_lsvr_val2017.json',\n        img_prefix=data_root + 'images/val2017/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/instances_lsvr_val2017.json',\n        \n        img_prefix=data_root + 'images/val2017/',\n        pipeline=test_pipeline))\nevaluation = dict(metric=['bbox'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/coco_pose.py",
    "content": "dataset_type = 'CocoPoseDataset'\ndata_root = '/home/ma-user/work/duankaiwen/coco/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_keypoint=True),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_keypoints']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=6,\n    workers_per_gpu=2,\n    train=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/person_keypoints_train2017.json',\n        img_prefix=data_root + 'images/train2017/',\n        pipeline=train_pipeline),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/person_keypoints_val2017.json',\n        img_prefix=data_root + 'images/val2017/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/person_keypoints_val2017.json',\n        img_prefix=data_root + 'images/val2017/',\n        pipeline=test_pipeline))\nevaluation = dict(metric=['keypoints'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/deepfashion.py",
    "content": "# dataset settings\ndataset_type = 'DeepFashionDataset'\ndata_root = 'data/DeepFashion/In-shop/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n    dict(type='Resize', img_scale=(750, 1101), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(750, 1101),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    imgs_per_gpu=2,\n    workers_per_gpu=1,\n    train=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/DeepFashion_segmentation_query.json',\n        img_prefix=data_root + 'Img/',\n        pipeline=train_pipeline,\n        data_root=data_root),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/DeepFashion_segmentation_query.json',\n        img_prefix=data_root + 'Img/',\n        pipeline=test_pipeline,\n        data_root=data_root),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root +\n        'annotations/DeepFashion_segmentation_gallery.json',\n        img_prefix=data_root + 'Img/',\n        pipeline=test_pipeline,\n        data_root=data_root))\nevaluation = dict(interval=5, metric=['bbox', 'segm'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/lvis_instance.py",
    "content": "_base_ = 'coco_instance.py'\ndataset_type = 'LVISDataset'\ndata_root = 'data/lvis/'\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type='ClassBalancedDataset',\n        oversample_thr=1e-3,\n        dataset=dict(\n            type=dataset_type,\n            ann_file=data_root + 'annotations/lvis_v0.5_train.json',\n            img_prefix=data_root + 'train2017/')),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/lvis_v0.5_val.json',\n        img_prefix=data_root + 'val2017/'),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'annotations/lvis_v0.5_val.json',\n        img_prefix=data_root + 'val2017/'))\nevaluation = dict(metric=['bbox', 'segm'])\n"
  },
  {
    "path": "code/configs/_base_/datasets/voc0712.py",
    "content": "# dataset settings\ndataset_type = 'VOCDataset'\ndata_root = 'data/VOCdevkit/'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True),\n    dict(type='Resize', img_scale=(1000, 600), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1000, 600),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type='RepeatDataset',\n        times=3,\n        dataset=dict(\n            type=dataset_type,\n            ann_file=[\n                data_root + 'VOC2007/ImageSets/Main/trainval.txt',\n                data_root + 'VOC2012/ImageSets/Main/trainval.txt'\n            ],\n            img_prefix=[data_root + 'VOC2007/', data_root + 'VOC2012/'],\n            pipeline=train_pipeline)),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt',\n        img_prefix=data_root + 'VOC2007/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt',\n        img_prefix=data_root + 'VOC2007/',\n        pipeline=test_pipeline))\nevaluation = dict(interval=1, metric='mAP')\n"
  },
  {
    "path": "code/configs/_base_/datasets/wider_face.py",
    "content": "# dataset settings\ndataset_type = 'WIDERFaceDataset'\ndata_root = 'data/WIDERFace/'\nimg_norm_cfg = dict(mean=[123.675, 116.28, 103.53], std=[1, 1, 1], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile', to_float32=True),\n    dict(type='LoadAnnotations', with_bbox=True),\n    dict(\n        type='PhotoMetricDistortion',\n        brightness_delta=32,\n        contrast_range=(0.5, 1.5),\n        saturation_range=(0.5, 1.5),\n        hue_delta=18),\n    dict(\n        type='Expand',\n        mean=img_norm_cfg['mean'],\n        to_rgb=img_norm_cfg['to_rgb'],\n        ratio_range=(1, 4)),\n    dict(\n        type='MinIoURandomCrop',\n        min_ious=(0.1, 0.3, 0.5, 0.7, 0.9),\n        min_crop_size=0.3),\n    dict(type='Resize', img_scale=(300, 300), keep_ratio=False),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(300, 300),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=False),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    samples_per_gpu=60,\n    workers_per_gpu=2,\n    train=dict(\n        type='RepeatDataset',\n        times=2,\n        dataset=dict(\n            type=dataset_type,\n            ann_file=data_root + 'train.txt',\n            img_prefix=data_root + 'WIDER_train/',\n            min_size=17,\n            pipeline=train_pipeline)),\n    val=dict(\n        type=dataset_type,\n        ann_file=data_root + 'val.txt',\n        img_prefix=data_root + 'WIDER_val/',\n        pipeline=test_pipeline),\n    test=dict(\n        type=dataset_type,\n        ann_file=data_root + 'val.txt',\n        img_prefix=data_root + 'WIDER_val/',\n        pipeline=test_pipeline))\n"
  },
  {
    "path": "code/configs/_base_/default_runtime.py",
    "content": "checkpoint_config = dict(interval=1)\n# yapf:disable\nlog_config = dict(\n    interval=50,\n    hooks=[\n        dict(type='TextLoggerHook'),\n        # dict(type='TensorboardLoggerHook')\n    ])\n# yapf:enable\ndist_params = dict(backend='nccl')\nlog_level = 'INFO'\nload_from = None\nresume_from = None\nworkflow = [('train', 1)]\n"
  },
  {
    "path": "code/configs/_base_/models/cascade_mask_rcnn_r50_fpn.py",
    "content": "# model settings\nmodel = dict(\n    type='CascadeRCNN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=256,\n        feat_channels=256,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[8],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[4, 8, 16, 32, 64]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)),\n    roi_head=dict(\n        type='CascadeRoIHead',\n        num_stages=3,\n        stage_loss_weights=[1, 0.5, 0.25],\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        bbox_head=[\n            dict(\n                type='Shared2FCBBoxHead',\n                in_channels=256,\n                fc_out_channels=1024,\n                roi_feat_size=7,\n                num_classes=80,\n                bbox_coder=dict(\n                    type='DeltaXYWHBBoxCoder',\n                    target_means=[0., 0., 0., 0.],\n                    target_stds=[0.1, 0.1, 0.2, 0.2]),\n                reg_class_agnostic=True,\n                loss_cls=dict(\n                    type='CrossEntropyLoss',\n                    use_sigmoid=False,\n                    loss_weight=1.0),\n                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,\n                               loss_weight=1.0)),\n            dict(\n                type='Shared2FCBBoxHead',\n                in_channels=256,\n                fc_out_channels=1024,\n                roi_feat_size=7,\n                num_classes=80,\n                bbox_coder=dict(\n                    type='DeltaXYWHBBoxCoder',\n                    target_means=[0., 0., 0., 0.],\n                    target_stds=[0.05, 0.05, 0.1, 0.1]),\n                reg_class_agnostic=True,\n                loss_cls=dict(\n                    type='CrossEntropyLoss',\n                    use_sigmoid=False,\n                    loss_weight=1.0),\n                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,\n                               loss_weight=1.0)),\n            dict(\n                type='Shared2FCBBoxHead',\n                in_channels=256,\n                fc_out_channels=1024,\n                roi_feat_size=7,\n                num_classes=80,\n                bbox_coder=dict(\n                    type='DeltaXYWHBBoxCoder',\n                    target_means=[0., 0., 0., 0.],\n                    target_stds=[0.033, 0.033, 0.067, 0.067]),\n                reg_class_agnostic=True,\n                loss_cls=dict(\n                    type='CrossEntropyLoss',\n                    use_sigmoid=False,\n                    loss_weight=1.0),\n                loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))\n        ],\n        mask_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=14, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        mask_head=dict(\n            type='FCNMaskHead',\n            num_convs=4,\n            in_channels=256,\n            conv_out_channels=256,\n            num_classes=80,\n            loss_mask=dict(\n                type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=0,\n        pos_weight=-1,\n        debug=False),\n    rpn_proposal=dict(\n        nms_across_levels=False,\n        nms_pre=2000,\n        nms_post=2000,\n        max_num=2000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=[\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.5,\n                neg_iou_thr=0.5,\n                min_pos_iou=0.5,\n                match_low_quality=False,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=512,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True),\n            mask_size=28,\n            pos_weight=-1,\n            debug=False),\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.6,\n                neg_iou_thr=0.6,\n                min_pos_iou=0.6,\n                match_low_quality=False,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=512,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True),\n            mask_size=28,\n            pos_weight=-1,\n            debug=False),\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.7,\n                min_pos_iou=0.7,\n                match_low_quality=False,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=512,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True),\n            mask_size=28,\n            pos_weight=-1,\n            debug=False)\n    ])\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=1000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        score_thr=0.05,\n        nms=dict(type='nms', iou_thr=0.5),\n        max_per_img=100,\n        mask_thr_binary=0.5))\n"
  },
  {
    "path": "code/configs/_base_/models/cascade_rcnn_r50_fpn.py",
    "content": "# model settings\nmodel = dict(\n    type='CascadeRCNN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=256,\n        feat_channels=256,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[8],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[4, 8, 16, 32, 64]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)),\n    roi_head=dict(\n        type='CascadeRoIHead',\n        num_stages=3,\n        stage_loss_weights=[1, 0.5, 0.25],\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        bbox_head=[\n            dict(\n                type='Shared2FCBBoxHead',\n                in_channels=256,\n                fc_out_channels=1024,\n                roi_feat_size=7,\n                num_classes=80,\n                bbox_coder=dict(\n                    type='DeltaXYWHBBoxCoder',\n                    target_means=[0., 0., 0., 0.],\n                    target_stds=[0.1, 0.1, 0.2, 0.2]),\n                reg_class_agnostic=True,\n                loss_cls=dict(\n                    type='CrossEntropyLoss',\n                    use_sigmoid=False,\n                    loss_weight=1.0),\n                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,\n                               loss_weight=1.0)),\n            dict(\n                type='Shared2FCBBoxHead',\n                in_channels=256,\n                fc_out_channels=1024,\n                roi_feat_size=7,\n                num_classes=80,\n                bbox_coder=dict(\n                    type='DeltaXYWHBBoxCoder',\n                    target_means=[0., 0., 0., 0.],\n                    target_stds=[0.05, 0.05, 0.1, 0.1]),\n                reg_class_agnostic=True,\n                loss_cls=dict(\n                    type='CrossEntropyLoss',\n                    use_sigmoid=False,\n                    loss_weight=1.0),\n                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,\n                               loss_weight=1.0)),\n            dict(\n                type='Shared2FCBBoxHead',\n                in_channels=256,\n                fc_out_channels=1024,\n                roi_feat_size=7,\n                num_classes=80,\n                bbox_coder=dict(\n                    type='DeltaXYWHBBoxCoder',\n                    target_means=[0., 0., 0., 0.],\n                    target_stds=[0.033, 0.033, 0.067, 0.067]),\n                reg_class_agnostic=True,\n                loss_cls=dict(\n                    type='CrossEntropyLoss',\n                    use_sigmoid=False,\n                    loss_weight=1.0),\n                loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))\n        ]))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=0,\n        pos_weight=-1,\n        debug=False),\n    rpn_proposal=dict(\n        nms_across_levels=False,\n        nms_pre=2000,\n        nms_post=2000,\n        max_num=2000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=[\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.5,\n                neg_iou_thr=0.5,\n                min_pos_iou=0.5,\n                match_low_quality=False,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=512,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True),\n            pos_weight=-1,\n            debug=False),\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.6,\n                neg_iou_thr=0.6,\n                min_pos_iou=0.6,\n                match_low_quality=False,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=512,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True),\n            pos_weight=-1,\n            debug=False),\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.7,\n                min_pos_iou=0.7,\n                match_low_quality=False,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=512,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True),\n            pos_weight=-1,\n            debug=False)\n    ])\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=1000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100))\n"
  },
  {
    "path": "code/configs/_base_/models/fast_rcnn_r50_fpn.py",
    "content": "# model settings\nmodel = dict(\n    type='FastRCNN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5),\n    roi_head=dict(\n        type='StandardRoIHead',\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        bbox_head=dict(\n            type='Shared2FCBBoxHead',\n            in_channels=256,\n            fc_out_channels=1024,\n            roi_feat_size=7,\n            num_classes=80,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n            loss_bbox=dict(type='L1Loss', loss_weight=1.0))))\n# model training and testing settings\ntrain_cfg = dict(\n    rcnn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.5,\n            neg_iou_thr=0.5,\n            min_pos_iou=0.5,\n            match_low_quality=False,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=512,\n            pos_fraction=0.25,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=True),\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rcnn=dict(\n        score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100))\n"
  },
  {
    "path": "code/configs/_base_/models/faster_rcnn_r50_caffe_c4.py",
    "content": "# model settings\nnorm_cfg = dict(type='BN', requires_grad=False)\nmodel = dict(\n    type='FasterRCNN',\n    pretrained='open-mmlab://detectron2/resnet50_caffe',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=3,\n        strides=(1, 2, 2),\n        dilations=(1, 1, 1),\n        out_indices=(2, ),\n        frozen_stages=1,\n        norm_cfg=norm_cfg,\n        norm_eval=True,\n        style='caffe'),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=1024,\n        feat_channels=1024,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[2, 4, 8, 16, 32],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[16]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)),\n    roi_head=dict(\n        type='StandardRoIHead',\n        shared_head=dict(\n            type='ResLayer',\n            depth=50,\n            stage=3,\n            stride=2,\n            dilation=1,\n            style='caffe',\n            norm_cfg=norm_cfg,\n            norm_eval=True),\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=14, sample_num=0),\n            out_channels=1024,\n            featmap_strides=[16]),\n        bbox_head=dict(\n            type='BBoxHead',\n            with_avg_pool=True,\n            roi_feat_size=7,\n            in_channels=2048,\n            num_classes=80,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n            loss_bbox=dict(type='L1Loss', loss_weight=1.0))))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=0,\n        pos_weight=-1,\n        debug=False),\n    rpn_proposal=dict(\n        nms_across_levels=False,\n        nms_pre=12000,\n        nms_post=2000,\n        max_num=2000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.5,\n            neg_iou_thr=0.5,\n            min_pos_iou=0.5,\n            match_low_quality=False,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=512,\n            pos_fraction=0.25,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=True),\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=6000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100))\n"
  },
  {
    "path": "code/configs/_base_/models/faster_rcnn_r50_fpn.py",
    "content": "model = dict(\n    type='FasterRCNN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=256,\n        feat_channels=256,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[8],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[4, 8, 16, 32, 64]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)),\n    roi_head=dict(\n        type='StandardRoIHead',\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        bbox_head=dict(\n            type='Shared2FCBBoxHead',\n            in_channels=256,\n            fc_out_channels=1024,\n            roi_feat_size=7,\n            num_classes=80,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n            loss_bbox=dict(type='L1Loss', loss_weight=1.0))))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    rpn_proposal=dict(\n        nms_across_levels=False,\n        nms_pre=2000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.5,\n            neg_iou_thr=0.5,\n            min_pos_iou=0.5,\n            match_low_quality=False,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=512,\n            pos_fraction=0.25,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=True),\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=1000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100)\n    # soft-nms is also supported for rcnn testing\n    # e.g., nms=dict(type='soft_nms', iou_thr=0.5, min_score=0.05)\n)\n"
  },
  {
    "path": "code/configs/_base_/models/mask_rcnn_r50_caffe_c4.py",
    "content": "# model settings\nnorm_cfg = dict(type='BN', requires_grad=False)\nmodel = dict(\n    type='MaskRCNN',\n    pretrained='open-mmlab://detectron2/resnet50_caffe',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=3,\n        strides=(1, 2, 2),\n        dilations=(1, 1, 1),\n        out_indices=(2, ),\n        frozen_stages=1,\n        norm_cfg=norm_cfg,\n        norm_eval=True,\n        style='caffe'),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=1024,\n        feat_channels=1024,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[2, 4, 8, 16, 32],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[16]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)),\n    roi_head=dict(\n        type='StandardRoIHead',\n        shared_head=dict(\n            type='ResLayer',\n            depth=50,\n            stage=3,\n            stride=2,\n            dilation=1,\n            style='caffe',\n            norm_cfg=norm_cfg,\n            norm_eval=True),\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=14, sample_num=0),\n            out_channels=1024,\n            featmap_strides=[16]),\n        bbox_head=dict(\n            type='BBoxHead',\n            with_avg_pool=True,\n            roi_feat_size=7,\n            in_channels=2048,\n            num_classes=80,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n            loss_bbox=dict(type='L1Loss', loss_weight=1.0)),\n        mask_roi_extractor=None,\n        mask_head=dict(\n            type='FCNMaskHead',\n            num_convs=0,\n            in_channels=2048,\n            conv_out_channels=256,\n            num_classes=80,\n            loss_mask=dict(\n                type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=0,\n        pos_weight=-1,\n        debug=False),\n    rpn_proposal=dict(\n        nms_across_levels=False,\n        nms_pre=12000,\n        nms_post=2000,\n        max_num=2000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.5,\n            neg_iou_thr=0.5,\n            min_pos_iou=0.5,\n            match_low_quality=False,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=512,\n            pos_fraction=0.25,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=True),\n        mask_size=14,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=6000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        score_thr=0.05,\n        nms=dict(type='nms', iou_thr=0.5),\n        max_per_img=100,\n        mask_thr_binary=0.5))\n"
  },
  {
    "path": "code/configs/_base_/models/mask_rcnn_r50_fpn.py",
    "content": "# model settings\nmodel = dict(\n    type='MaskRCNN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=256,\n        feat_channels=256,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[8],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[4, 8, 16, 32, 64]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)),\n    roi_head=dict(\n        type='StandardRoIHead',\n        bbox_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        bbox_head=dict(\n            type='Shared2FCBBoxHead',\n            in_channels=256,\n            fc_out_channels=1024,\n            roi_feat_size=7,\n            num_classes=80,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n            loss_bbox=dict(type='L1Loss', loss_weight=1.0)),\n        mask_roi_extractor=dict(\n            type='SingleRoIExtractor',\n            roi_layer=dict(type='RoIAlign', out_size=14, sample_num=0),\n            out_channels=256,\n            featmap_strides=[4, 8, 16, 32]),\n        mask_head=dict(\n            type='FCNMaskHead',\n            num_convs=4,\n            in_channels=256,\n            conv_out_channels=256,\n            num_classes=80,\n            loss_mask=dict(\n                type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    rpn_proposal=dict(\n        nms_across_levels=False,\n        nms_pre=2000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.5,\n            neg_iou_thr=0.5,\n            min_pos_iou=0.5,\n            match_low_quality=True,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=512,\n            pos_fraction=0.25,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=True),\n        mask_size=28,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=1000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0),\n    rcnn=dict(\n        score_thr=0.05,\n        nms=dict(type='nms', iou_thr=0.5),\n        max_per_img=100,\n        mask_thr_binary=0.5))\n"
  },
  {
    "path": "code/configs/_base_/models/retinanet_r50_fpn.py",
    "content": "# model settings\nmodel = dict(\n    type='RetinaNet',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        start_level=1,\n        add_extra_convs='on_input',\n        num_outs=5),\n    bbox_head=dict(\n        type='RetinaHead',\n        num_classes=80,\n        in_channels=256,\n        stacked_convs=4,\n        feat_channels=256,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            octave_base_scale=4,\n            scales_per_octave=3,\n            ratios=[0.5, 1.0, 2.0],\n            strides=[8, 16, 32, 64, 128]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='FocalLoss',\n            use_sigmoid=True,\n            gamma=2.0,\n            alpha=0.25,\n            loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)))\n# training and testing settings\ntrain_cfg = dict(\n    assigner=dict(\n        type='MaxIoUAssigner',\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.4,\n        min_pos_iou=0,\n        ignore_iof_thr=-1),\n    allowed_border=-1,\n    pos_weight=-1,\n    debug=False)\ntest_cfg = dict(\n    nms_pre=1000,\n    min_bbox_size=0,\n    score_thr=0.05,\n    nms=dict(type='nms', iou_thr=0.5),\n    max_per_img=100)\n"
  },
  {
    "path": "code/configs/_base_/models/rpn_r50_caffe_c4.py",
    "content": "# model settings\nmodel = dict(\n    type='RPN',\n    pretrained='open-mmlab://detectron2/resnet50_caffe',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=3,\n        strides=(1, 2, 2),\n        dilations=(1, 1, 1),\n        out_indices=(2, ),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=False),\n        norm_eval=True,\n        style='caffe'),\n    neck=None,\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=1024,\n        feat_channels=1024,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[2, 4, 8, 16, 32],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[16]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=0,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=12000,\n        nms_post=2000,\n        max_num=2000,\n        nms_thr=0.7,\n        min_bbox_size=0))\n"
  },
  {
    "path": "code/configs/_base_/models/rpn_r50_fpn.py",
    "content": "# model settings\nmodel = dict(\n    type='RPN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5),\n    rpn_head=dict(\n        type='RPNHead',\n        in_channels=256,\n        feat_channels=256,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            scales=[8],\n            ratios=[0.5, 1.0, 2.0],\n            strides=[4, 8, 16, 32, 64]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0)))\n# model training and testing settings\ntrain_cfg = dict(\n    rpn=dict(\n        assigner=dict(\n            type='MaxIoUAssigner',\n            pos_iou_thr=0.7,\n            neg_iou_thr=0.3,\n            min_pos_iou=0.3,\n            ignore_iof_thr=-1),\n        sampler=dict(\n            type='RandomSampler',\n            num=256,\n            pos_fraction=0.5,\n            neg_pos_ub=-1,\n            add_gt_as_proposals=False),\n        allowed_border=0,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    rpn=dict(\n        nms_across_levels=False,\n        nms_pre=2000,\n        nms_post=1000,\n        max_num=1000,\n        nms_thr=0.7,\n        min_bbox_size=0))\n"
  },
  {
    "path": "code/configs/_base_/models/ssd300.py",
    "content": "# model settings\ninput_size = 300\nmodel = dict(\n    type='SingleStageDetector',\n    pretrained='open-mmlab://vgg16_caffe',\n    backbone=dict(\n        type='SSDVGG',\n        input_size=input_size,\n        depth=16,\n        with_last_pool=False,\n        ceil_mode=True,\n        out_indices=(3, 4),\n        out_feature_indices=(22, 34),\n        l2_norm_scale=20),\n    neck=None,\n    bbox_head=dict(\n        type='SSDHead',\n        in_channels=(512, 1024, 512, 256, 256, 256),\n        num_classes=80,\n        anchor_generator=dict(\n            type='SSDAnchorGenerator',\n            scale_major=False,\n            input_size=input_size,\n            basesize_ratio_range=(0.15, 0.9),\n            strides=[8, 16, 32, 64, 100, 300],\n            ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[0.1, 0.1, 0.2, 0.2])))\ncudnn_benchmark = True\ntrain_cfg = dict(\n    assigner=dict(\n        type='MaxIoUAssigner',\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        min_pos_iou=0.,\n        ignore_iof_thr=-1,\n        gt_max_assign_all=False),\n    smoothl1_beta=1.,\n    allowed_border=-1,\n    pos_weight=-1,\n    neg_pos_ratio=3,\n    debug=False)\ntest_cfg = dict(\n    nms=dict(type='nms', iou_thr=0.45),\n    min_bbox_size=0,\n    score_thr=0.02,\n    max_per_img=200)\n"
  },
  {
    "path": "code/configs/_base_/schedules/schedule_1x.py",
    "content": "# optimizer\noptimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)\noptimizer_config = dict(grad_clip=None)\n# learning policy\nlr_config = dict(\n    policy='step',\n    warmup='linear',\n    warmup_iters=500,\n    warmup_ratio=0.001,\n    step=[8, 11])\ntotal_epochs = 12\n"
  },
  {
    "path": "code/configs/_base_/schedules/schedule_20e.py",
    "content": "# optimizer\noptimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)\noptimizer_config = dict(grad_clip=None)\n# learning policy\nlr_config = dict(\n    policy='step',\n    warmup='linear',\n    warmup_iters=500,\n    warmup_ratio=0.001,\n    step=[16, 19])\ntotal_epochs = 20\n"
  },
  {
    "path": "code/configs/_base_/schedules/schedule_2x.py",
    "content": "# optimizer\noptimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)\noptimizer_config = dict(grad_clip=None)\n# learning policy\nlr_config = dict(\n    policy='step',\n    warmup='linear',\n    warmup_iters=500,\n    warmup_ratio=0.001,\n    step=[16, 22])\ntotal_epochs = 24\n"
  },
  {
    "path": "code/configs/lsnet/lsnet_bbox_cpv_res2_101_fpn_dconv_c3-c5_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_bbox_cpv_x101_fpn_dconv_c3-c5_mstrain_2x_coco.py'\nmodel = dict(\n    pretrained='../checkpoints/pretrained/res2net101_v1d_26w_4s_mmdetv2-f0a600f9.pth',\n    backbone=dict(type='Res2Net',\n                  depth=101,\n                  num_stages=4,\n                  out_indices=(0, 1, 2, 3),\n                  frozen_stages=1,\n                  norm_cfg=dict(type='BN', requires_grad=True),\n                  norm_eval=True,\n                  scales=4,\n                  base_width=26,\n                  dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n                  stage_with_dcn=(False, True, True, True),\n                  with_cp=True,\n                  style='pytorch',\n                  _delete_=True))\n\n\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_bbox_cpv_x101_fpn_dconv_c3-c5_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_bbox_r50_fpn_mstrain_2x_coco.py'\nnorm_cfg = dict(type='GN', num_groups=32, requires_grad=True)\nmodel = dict(\n    type='LSCPVDetector',\n    pretrained='../checkpoints/pretrained/resnext101_64x4d-ee2c6f71.pth',\n    backbone=dict(\n        type='ResNeXt',\n        depth=101,\n        groups=64,\n        base_width=4,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n        stage_with_dcn=(False, True, True, True),\n        norm_eval=True,\n        with_cp=True,\n        style='pytorch'),\n    bbox_head=dict(\n        type='LSCPVHead',\n        num_classes=80,\n        in_channels=256,\n        feat_channels=256,\n        point_feat_channels=256,\n        stacked_convs=3,\n        shared_stacked_convs=1,\n        first_kernel_size=3,\n        kernel_size=1,\n        corner_dim=64,\n        num_points=9,\n        gradient_mul=0.1,\n        point_strides=[8, 16, 32, 64, 128],\n        point_base_scale=4,\n        norm_cfg=norm_cfg,\n        conv_module_type='dcn', #norm or dcn, norm is faster\n        loss_cls=dict(\n            type='FocalLoss',\n            use_sigmoid=True,\n            gamma=2.0,\n            alpha=0.25,\n            loss_weight=1.0),\n        loss_bbox_init=dict(type='CrossIOULoss', loss_weight=1.0),\n        loss_bbox_refine=dict(type='CrossIOULoss', loss_weight=2.0),\n        loss_heatmap=dict(\n            type='GaussianFocalLoss',\n            alpha=2.0,\n            gamma=4.0,\n            loss_weight=0.25),\n        loss_offset=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n        loss_sem=dict(\n            type='SEPFocalLoss',\n            gamma=2.0,\n            alpha=0.25,\n            loss_weight=0.1),\n        _delete_=True))\n# training and testing settings\ntrain_cfg = dict(\n    init=dict(\n        assigner=dict(type='CentroidAssigner', scale=4, pos_num=1, iou_type='center'),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    heatmap=dict(\n        assigner=dict(type='PointHMAssigner', gaussian_bump=True, gaussian_iou=0.7),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    refine=dict(\n        assigner=dict(type='ATSSAssigner', topk=9),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False))\n\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_extreme=True),\n    dict(\n        type='Resize',\n        img_scale=[(1333, 480), (1333, 960)],\n        multiscale_mode='range',\n        keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='LoadRPDV2Annotations'),\n    dict(type='RPDV2FormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_sem_map', 'gt_sem_weights',\n                               'gt_extremes']),\n]\ndata = dict(train=dict(pipeline=train_pipeline))\n\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_bbox_r50_fpn_1x_coco.py",
    "content": "_base_ = [\n    '../_base_/datasets/coco_lsvr.py',\n    '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'\n]\n\nnorm_cfg = dict(type='GN', num_groups=32, requires_grad=True)\nmodel = dict(\n    type='LSDetector',\n    pretrained='../checkpoints/pretrained/resnet50-19c8e357.pth',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        start_level=1,\n        add_extra_convs='on_input',\n        num_outs=5,\n        norm_cfg=norm_cfg),\n    bbox_head=dict(\n        type='LSHead',\n        task='bbox',\n        num_vectors=4,\n        num_classes=80,\n        in_channels=256,\n        feat_channels=256,\n        point_feat_channels=256,\n        stacked_convs=3,\n        num_kernel_points=9,\n        gradient_mul=0.1,\n        point_strides=[8, 16, 32, 64, 128],\n        point_base_scale=4,\n        norm_cfg=norm_cfg,\n        conv_module_type='dcn', #norm or dcn, norm is faster\n        loss_cls=dict(type='FocalLoss',  use_sigmoid=True, gamma=2.0, alpha=0.25,\n                      loss_weight=1.0),\n        loss_bbox_init=dict(type='CrossIOULoss', loss_weight=1.0),\n        loss_bbox_refine=dict(type='CrossIOULoss', loss_weight=2.0)))\n# training and testing settings\ntrain_cfg = dict(\n    init=dict(\n        assigner=dict(type='CentroidAssigner', scale=4, pos_num=1, iou_type='center'), #center, centroid\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    refine=dict(\n        assigner=dict(type='ATSSAssigner', topk=9),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    nms_pre=1000,\n    min_bbox_size=0,\n    score_thr=0.05,\n    nms=dict(type='nms', iou_thr=0.6),\n    max_per_img=100)\noptimizer = dict(lr=0.01)\noptimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2), _delete_=True)\nevaluation = dict(interval=1, metric='bbox')"
  },
  {
    "path": "code/configs/lsnet/lsnet_bbox_r50_fpn_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_bbox_r50_fpn_1x_coco.py'\n# learning policy\nlr_config = dict(step=[16, 22])\ntotal_epochs = 24\n#multi-scale training\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_extreme=True),\n    dict(\n        type='Resize',\n        img_scale=[(1333, 480), (1333, 960)],\n        multiscale_mode='range',\n        keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_extremes']),\n]\ndata = dict(train=dict(pipeline=train_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_bbox_x101_fpn_dconv_c3-c5_mstrain_2x_coco.py.py",
    "content": "_base_ = './lsnet_bbox_r50_fpn_mstrain_2x_coco.py'\nmodel = dict(\n    pretrained='../checkpoints/pretrained/resnext101_64x4d-ee2c6f71.pth',\n    backbone=dict(\n        type='ResNeXt',\n        depth=101,\n        groups=64,\n        base_width=4,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n        stage_with_dcn=(False, True, True, True),\n        norm_eval=True,\n        with_cp=True,\n        style='pytorch'))\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_bbox_x101_fpn_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_bbox_r50_fpn_mstrain_2x_coco.py'\nmodel = dict(\n    pretrained='../checkpoints/pretrained/resnext101_64x4d-ee2c6f71.pth',\n    backbone=dict(\n        type='ResNeXt',\n        depth=101,\n        groups=64,\n        base_width=4,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        with_cp=True,\n        style='pytorch'))"
  },
  {
    "path": "code/configs/lsnet/lsnet_pose_bbox_r50_fpn_1x_coco.py",
    "content": "_base_ = [\n    '../_base_/datasets/coco_pose.py',\n    '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'\n]\n\nnorm_cfg = dict(type='GN', num_groups=32, requires_grad=True)\nmodel = dict(\n    type='LSDetector',\n    pretrained='../checkpoints/pretrained/resnet50-19c8e357.pth',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        start_level=1,\n        add_extra_convs='on_input',\n        num_outs=5,\n        norm_cfg=norm_cfg),\n    bbox_head=dict(\n        type='LSHead',\n        task='pose_bbox',\n        num_vectors=17,\n        num_classes=1,\n        in_channels=256,\n        feat_channels=256,\n        point_feat_channels=256,\n        stacked_convs=3,\n        num_kernel_points=9,\n        gradient_mul=0.1,\n        point_strides=[8, 16, 32, 64, 128],\n        point_base_scale=4,\n        norm_cfg=norm_cfg,\n        conv_module_type='dcn', #norm or dcn, norm is faster\n        loss_cls=dict(type='FocalLoss',  use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=1.0),\n        loss_bbox_init=dict(type='CrossIOULoss',   loss_weight=0.1, loss_type='bbox'),\n        loss_bbox_refine=dict(type='CrossIOULoss', loss_weight=0.2, loss_type='bbox'),\n        loss_pose_init=dict(type='CrossIOULoss',   loss_weight=1.0, loss_type='keypoint'),\n        loss_pose_refine=dict(type='CrossIOULoss', loss_weight=2.0, loss_type='keypoint')))\n# training and testing settings\ntrain_cfg = dict(\n    init=dict(\n        assigner=dict(type='CentroidAssigner', scale=4, pos_num=1, iou_type='center'), \n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    refine=dict(\n        assigner=dict(type='ATSSAssigner', topk=9),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    nms_pre=100,\n    min_bbox_size=0,\n    score_thr=0.05,\n    nms=dict(type='nms', iou_thr=0.6),\n    max_per_img=20)\noptimizer = dict(lr=0.01)\noptimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2), _delete_=True)\nevaluation = dict(interval=1, metric=['bbox', 'keypoints'])"
  },
  {
    "path": "code/configs/lsnet/lsnet_pose_bbox_r50_fpn_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_pose_bbox_r50_fpn_1x_coco.py'\n# learning policy\nlr_config = dict(step=[16, 22])\ntotal_epochs = 24\n#multi-scale training\nnum_keypoints = 17\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_keypoint=True),\n    dict(\n        type='Resize',\n        img_scale=[(1333, 480), (1333, 960)],\n        multiscale_mode='range',\n        keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_keypoints']),\n]\ndata = dict(train=dict(pipeline=train_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_pose_bbox_res2_101_fpn_dconv_c3-c5_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_pose_bbox_r50_fpn_mstrain_2x_coco.py'\n\nlr_config = dict(step=[54, 56])\ntotal_epochs = 60\n\nmodel = dict(\n    pretrained='../checkpoints/pretrained/res2net101_v1d_26w_4s_mmdetv2-f0a600f9.pth',\n    backbone=dict(type='Res2Net',\n                  depth=101,\n                  scales=4,\n                  base_width=26,\n                  dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n                  stage_with_dcn=(False, True, True, True),\n                  with_cp=True))\n\n\n########### flip testing  #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[0, 10000]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(1333, 800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))\n\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_pose_bbox_x101_fpn_dconv_c3-c5_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_pose_bbox_r50_fpn_mstrain_2x_coco.py'\n\nlr_config = dict(step=[54, 56])\ntotal_epochs = 60\n\nmodel = dict(\n    pretrained='../checkpoints/pretrained/resnext101_64x4d-ee2c6f71.pth',\n    backbone=dict(\n        type='ResNeXt',\n        depth=101,\n        groups=64,\n        base_width=4,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n        stage_with_dcn=(False, True, True, True),\n        norm_eval=True,\n        with_cp=True,\n        style='pytorch'))\n\n\n########### flip testing  #############\n\n# test_cfg = dict(method = 'vote', scale_ranges = [[0, 10000]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(1333, 800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))\n\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_pose_kbox_res2_101_fpn_dconv_c3-c5_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_pose_bbox_res2_101_fpn_dconv_c3-c5_mstrain_2x_coco.py'\n\nlr_config = dict(step=[12, 20])\ntotal_epochs = 24\n\nnorm_cfg = dict(type='GN', num_groups=32, requires_grad=True)\n\nmodel = dict(\n    bbox_head=dict(\n        type='LSHead',\n        task='pose_kbox',\n        num_vectors=17,\n        num_classes=1,\n        in_channels=256,\n        feat_channels=256,\n        point_feat_channels=256,\n        stacked_convs=3,\n        num_kernel_points=9,\n        gradient_mul=0.1,\n        point_strides=[8, 16, 32, 64, 128],\n        point_base_scale=4,\n        norm_cfg=norm_cfg,\n        conv_module_type='dcn', #norm or dcn, norm is faster\n        loss_cls=dict(type='FocalLoss',  use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=1.0),\n        loss_pose_init=dict(type='CrossIOULoss',   loss_weight=1.0, loss_type='keypoint'),\n        loss_pose_refine=dict(type='CrossIOULoss', loss_weight=2.0, loss_type='keypoint'),\n        _delete_=True))\n\nevaluation = dict(interval=1, metric=['keypoints'])\n\n\n########### flip testing  #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[0, 10000]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(1333, 800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))\n\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_pose_kbox_x101_fpn_dconv_c3-c5_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_pose_bbox_x101_fpn_dconv_c3-c5_mstrain_2x_coco.py'\n\nlr_config = dict(step=[12, 20])\ntotal_epochs = 24\n\nnorm_cfg = dict(type='GN', num_groups=32, requires_grad=True)\n\nmodel = dict(\n    bbox_head=dict(\n        type='LSHead',\n        task='pose_kbox',\n        num_vectors=17,\n        num_classes=1,\n        in_channels=256,\n        feat_channels=256,\n        point_feat_channels=256,\n        stacked_convs=3,\n        num_kernel_points=9,\n        gradient_mul=0.1,\n        point_strides=[8, 16, 32, 64, 128],\n        point_base_scale=4,\n        norm_cfg=norm_cfg,\n        conv_module_type='dcn', #norm or dcn, norm is faster\n        loss_cls=dict(type='FocalLoss',  use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=1.0),\n        loss_pose_init=dict(type='CrossIOULoss',   loss_weight=1.0, loss_type='keypoint'),\n        loss_pose_refine=dict(type='CrossIOULoss', loss_weight=2.0, loss_type='keypoint'),\n        _delete_=True))\n\nevaluation = dict(interval=1, metric=['keypoints'])\n\n########### flip testing  #############\n\n# test_cfg = dict(method = 'vote', scale_ranges = [[0, 10000]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(1333, 800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))\n\n\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_segm_r50_fpn_1x_coco.py",
    "content": "_base_ = [\n    '../_base_/datasets/coco_lsvr.py',\n    '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'\n]\n\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_mask=True, poly2mask=False,\n                                 spline_num=10, num_contour_points=36),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5, keep_poly_clockwise=True),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),\n]\ndata = dict(train=dict(pipeline=train_pipeline))\n\nnorm_cfg = dict(type='GN', num_groups=32, requires_grad=True)\nmodel = dict(\n    type='LSDetector',\n    pretrained='../checkpoints/pretrained/resnet50-19c8e357.pth',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(\n        type='FPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        start_level=1,\n        add_extra_convs='on_input',\n        num_outs=5,\n        norm_cfg=norm_cfg),\n    bbox_head=dict(\n        type='LSHead',\n        task='segm',\n        num_vectors=36,\n        num_classes=80,\n        in_channels=256,\n        feat_channels=256,\n        point_feat_channels=256,\n        stacked_convs=3,\n        num_kernel_points=9,\n        gradient_mul=0.1,\n        point_strides=[8, 16, 32, 64, 128],\n        point_base_scale=4,\n        norm_cfg=norm_cfg,\n        conv_module_type='dcn', #norm or dcn, norm is faster\n        loss_cls=dict(type='FocalLoss', use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=1.0),\n        loss_segm_init=dict(type='CrossIOULoss',   loss_weight=1.0, loss_type='polygon', stride=9),\n        loss_segm_refine=dict(type='CrossIOULoss', loss_weight=2.0, loss_type='polygon', stride=9)))\n# training and testing settings\ntrain_cfg = dict(\n    init=dict(\n        assigner=dict(type='CentroidAssigner', scale=4, pos_num=1, iou_type='center'),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False),\n    refine=dict(\n        assigner=dict(type='ATSSAssigner', topk=9),\n        allowed_border=-1,\n        pos_weight=-1,\n        debug=False))\ntest_cfg = dict(\n    nms_pre=1000,\n    min_bbox_size=0,\n    score_thr=0.05,\n    nms=dict(type='nms', iou_thr=0.6),\n    max_per_img=100)\noptimizer = dict(lr=0.01)\noptimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2), _delete_=True)\nevaluation = dict(interval=1, metric='segm')"
  },
  {
    "path": "code/configs/lsnet/lsnet_segm_r50_fpn_mstrain_2x_coco.py",
    "content": "_base_ = './lsnet_segm_r50_fpn_1x_coco.py'\n# learning policy\nlr_config = dict(step=[16, 22])\ntotal_epochs = 24\n#multi-scale training\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_mask=True, poly2mask=False,\n                                 spline_num=10, num_contour_points=36),\n    dict(\n        type='Resize',\n        img_scale=[(1333, 480), (1333, 960)],\n        multiscale_mode='range',\n        keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5, keep_poly_clockwise=True),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),\n]\ndata = dict(train=dict(pipeline=train_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_segm_res2_101_fpn_dconv_c3-c5_mstrain_30e_coco.py",
    "content": "_base_ = './lsnet_segm_r50_fpn_mstrain_2x_coco.py'\n\nlr_config = dict(step=[28, 30])\ntotal_epochs = 30\n\nmodel = dict(\n    pretrained='../checkpoints/pretrained/res2net101_v1d_26w_4s_mmdetv2-f0a600f9.pth',\n    backbone=dict(type='Res2Net',\n                  depth=101,\n                  scales=4,\n                  base_width=26,\n                  dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n                  stage_with_dcn=(False, True, True, True),\n                  with_cp=True))\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_segm_x101_fpn_dconv_c3-c5_mstrain_30e_coco.py",
    "content": "_base_ = './lsnet_segm_r50_fpn_mstrain_2x_coco.py'\n\nlr_config = dict(step=[28, 30])\ntotal_epochs = 30\n\nmodel = dict(\n    pretrained='../checkpoints/pretrained/resnext101_64x4d-ee2c6f71.pth',\n    backbone=dict(\n        type='ResNeXt',\n        depth=101,\n        groups=64,\n        base_width=4,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False),\n        stage_with_dcn=(False, True, True, True),\n        norm_eval=True,\n        with_cp=True,\n        style='pytorch'))\n\n########### multi-scale testing, we follow ATSS, https://github.com/sfzhang15/ATSS #############\n\n# test_cfg = dict(method = 'vote',\n#                 scale_ranges = [[96, 10000], [96, 10000], [64, 10000], [64, 10000],\n#                                 [64, 10000], [0, 10000], [0, 10000], [0, 256], [0, 256],\n#                                 [0, 192], [0, 192], [0, 96]])\n\n# img_norm_cfg = dict(\n#     mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n# test_pipeline = [\n#     dict(type='LoadImageFromFile'),\n#     dict(\n#         type='MultiScaleFlipAug',\n#         img_scale=[(3000, 400), (3000, 500), (3000, 600), (3000, 640), (3000, 700), (3000, 900),\n#                    (3000, 1000), (3000, 1100), (3000, 1200), (3000, 1300), (3000, 1400), (3000, 1800)],\n#         flip=True,\n#         transforms=[\n#             dict(type='Resize', keep_ratio=True),\n#             dict(type='RandomFlip'),\n#             dict(type='Normalize', **img_norm_cfg),\n#             dict(type='Pad', size_divisor=32),\n#             dict(type='ImageToTensor', keys=['img']),\n#             dict(type='Collect', keys=['img']),\n#         ])\n# ]\n# data = dict(test=dict(pipeline=test_pipeline))"
  },
  {
    "path": "code/configs/lsnet/lsnet_segm_x101_fpn_mstrain_30e_coco.py",
    "content": "_base_ = './lsnet_segm_r50_fpn_mstrain_2x_coco.py'\n\nlr_config = dict(step=[28, 30])\ntotal_epochs = 30\n\nmodel = dict(\n    pretrained='../checkpoints/pretrained/resnext101_64x4d-ee2c6f71.pth',\n    backbone=dict(\n        type='ResNeXt',\n        depth=101,\n        groups=64,\n        base_width=4,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        with_cp=True,\n        style='pytorch'))\n"
  },
  {
    "path": "code/docker/Dockerfile",
    "content": "ARG PYTORCH=\"1.5\"\nARG CUDA=\"10.1\"\nARG CUDNN=\"7\"\n\nFROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel\n\nENV TORCH_CUDA_ARCH_LIST=\"6.0 6.1 7.0+PTX\"\nENV TORCH_NVCC_FLAGS=\"-Xfatbin -compress-all\"\nENV CMAKE_PREFIX_PATH=\"$(dirname $(which conda))/../\"\n\nRUN apt-get update && apt-get install -y git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \\\n    && apt-get clean \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Install mmdetection\nRUN conda clean --all\nRUN git clone https://github.com/open-mmlab/mmdetection.git /mmdetection\nWORKDIR /mmdetection\nENV FORCE_CUDA=\"1\"\nRUN pip install cython --no-cache-dir\nRUN pip install \"git+https://github.com/open-mmlab/cocoapi.git#subdirectory=pycocotools\"\nRUN pip install --no-cache-dir -e .\n"
  },
  {
    "path": "code/docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "code/docs/api.rst",
    "content": "API Reference\n=================\n\nmmdet.apis\n--------------\n.. automodule:: mmdet.apis\n    :members:\n\nmmdet.core\n--------------\n\nanchor\n^^^^^^^^^^\n.. automodule:: mmdet.core.anchor\n    :members:\n\nbbox\n^^^^^^^^^^\n.. automodule:: mmdet.core.bbox\n    :members:\n\nmask\n^^^^^^^^^^\n.. automodule:: mmdet.core.mask\n    :members:\n\nevaluation\n^^^^^^^^^^\n.. automodule:: mmdet.core.evaluation\n    :members:\n\npost_processing\n^^^^^^^^^^^^^^^\n.. automodule:: mmdet.core.post_processing\n    :members:\n\nfp16\n^^^^^^^^^^\n.. automodule:: mmdet.core.fp16\n    :members:\n\noptimizer\n^^^^^^^^^^\n.. automodule:: mmdet.core.optimizer\n    :members:\n\nutils\n^^^^^^^^^^\n.. automodule:: mmdet.core.utils\n    :members:\n\nmmdet.datasets\n--------------\n\ndatasets\n^^^^^^^^^^\n.. automodule:: mmdet.datasets\n    :members:\n\npipelines\n^^^^^^^^^^\n.. automodule:: mmdet.datasets.pipelines\n    :members:\n\nmmdet.models\n--------------\n\ndetectors\n^^^^^^^^^^\n.. automodule:: mmdet.models.detectors\n    :members:\n\nbackbones\n^^^^^^^^^^\n.. automodule:: mmdet.models.backbones\n    :members:\n\nnecks\n^^^^^^^^^^^^\n.. automodule:: mmdet.models.necks\n    :members:\n\ndense_heads\n^^^^^^^^^^^^\n.. automodule:: mmdet.models.dense_heads\n    :members:\n\nroi_heads\n^^^^^^^^^^\n.. automodule:: mmdet.models.roi_heads\n    :members:\n\nlosses\n^^^^^^^^^^\n.. automodule:: mmdet.models.losses\n    :members:\n"
  },
  {
    "path": "code/docs/changelog.md",
    "content": "## Changelog\n\n### v2.2.0 (1/7/2020)\n\n**Highlights**\n- Support new methods: [DetectoRS](https://arxiv.org/abs/2006.02334), [PointRend](https://arxiv.org/abs/1912.08193), [Generalized Focal Loss](https://arxiv.org/abs/2006.04388), [Dynamic R-CNN](https://arxiv.org/abs/2004.06002)\n\n**Bug Fixes**\n - Fix FreeAnchor when no gt in image (#3176)\n - Clean up deprecated usage of `register_module()` (#3092, #3161)\n - Fix pretrain bug in NAS FCOS (#3145)\n - Fix `num_classes` in SSD (#3142)\n - Fix FCOS warmup (#3119)\n - Fix `rstrip` in `tools/publish_model.py`\n - Fix `flip_ratio` default value in RandomFLip pipeline (#3106)\n - Fix cityscapes eval with ms_rcnn (#3112)\n - Fix RPN softmax (#3056)\n - Fix filename of LVIS@v0.5 (#2998)\n - Fix nan loss by filtering out-of-frame gt_bboxes in COCO (#2999)\n - Fix bug in FSAF (#3018)\n - Add FocalLoss `num_classes` check (#2964)\n - Fix PISA Loss when there are no gts (#2992)\n - Avoid nan in `iou_calculator` (#2975)\n - Prevent possible bugs in loading and transforms caused by shallow copy (#2967)\n\n**New Features**\n- Add DetectoRS (#3064)\n- Support Generalize Focal Loss (#3097)\n- Support PointRend (#2752)\n- Support Dynamic R-CNN (#3040)\n- Add DeepFashion dataset (#2968)\n- Implement FCOS training tricks (#2935)\n- Use BaseDenseHead as base class for anchor-base heads (#2963)\n- Add `with_cp` for BasicBlock (#2891)\n- Add `stem_channles` argument for ResNet (#2954)\n\n**Improvements**\n\n- Add anchor free base head (#2867)\n- Migrate to github action (#3137)\n- Add docstring for datasets, pipelines, core modules and methods (#3130, #3125, #3120)\n- Add VOC benchmark (#3060)\n- Add `concat` mode in GRoI (#3098)\n- Remove cmd arg `autorescale-lr` (#3080)\n- Use `len(data['img_metas'])` to indicate `num_samples` (#3073, #3053)\n- Switch to EpochBasedRunner (#2976)\n\n\n### v2.1.0 (8/6/2020)\n\n**Highlights**\n- Support new backbones: [RegNetX](https://arxiv.org/abs/2003.13678), [Res2Net](https://arxiv.org/abs/1904.01169)\n- Support new methods: [NASFCOS](https://arxiv.org/abs/1906.04423), [PISA](https://arxiv.org/abs/1904.04821), [GRoIE](https://arxiv.org/abs/2004.13665)\n- Support new dataset: [LVIS](https://arxiv.org/abs/1908.03195)\n\n**Bug Fixes**\n- Change the CLI argument `--validate` to `--no-validate` to enable validation after training epochs by default. (#2651)\n- Add missing cython to docker file (#2713)\n- Fix bug in nms cpu implementation (#2754)\n- Fix bug when showing mask results (#2763)\n- Fix gcc requirement (#2806)\n- Fix bug in async test (#2820)\n- Fix mask encoding-decoding bugs in test API (#2824)\n- Fix bug in test time augmentation (#2858, #2921, #2944)\n- Fix a typo in comment of apis/train (#2877)\n- Fix the bug of returning None when no gt bboxes are in the original image in `RandomCrop`. Fix the bug that misses to handle `gt_bboxes_ignore`, `gt_label_ignore`, and `gt_masks_ignore` in `RandomCrop`, `MinIoURandomCrop` and `Expand` modules. (#2810)\n- Fix bug of `base_channels` of regnet (#2917)\n- Fix the bug of logger when loading pre-trained weights in base detector (#2936)\n\n**New Features**\n- Add IoU models (#2666)\n- Add colab demo for inference\n- Support class agnostic nms (#2553)\n- Add benchmark gathering scripts for development only (#2676)\n- Add mmdet-based project links (#2736, #2767, #2895)\n- Add config dump in training (#2779)\n- Add ClassBalancedDataset (#2721)\n- Add res2net backbone (#2237)\n- Support RegNetX models (#2710)\n- Use `mmcv.FileClient` to support different storage backends (#2712)\n- Add ClassBalancedDataset (#2721)\n- Code Release: Prime Sample Attention in Object Detection (CVPR 2020) (#2626)\n- Implement NASFCOS (#2682)\n- Add class weight in CrossEntropyLoss (#2797)\n- Support LVIS dataset (#2088)\n- Support GRoIE (#2584)\n\n**Improvements**\n- Allow different x and y strides in anchor heads. (#2629)\n- Make FSAF loss more robust to no gt (#2680)\n- Compute pure inference time instead (#2657) and update inference speed (#2730)\n- Avoided the possibility that a patch with 0 area is cropped. (#2704)\n- Add warnings when deprecated `imgs_per_gpu` is used. (#2700)\n- Add a mask rcnn example for config (#2645)\n- Update model zoo (#2762, #2866, #2876, #2879, #2831)\n- Add `ori_filename` to img_metas and use it in test show-dir (#2612)\n- Use `img_fields` to handle multiple images during image transform (#2800)\n- Add upsample_cfg support in FPN (#2787)\n- Add `['img']` as default `img_fields` for back compatibility (#2809)\n- Rename the pretrained model from `open-mmlab://resnet50_caffe` and `open-mmlab://resnet50_caffe_bgr` to `open-mmlab://detectron/resnet50_caffe` and `open-mmlab://detectron2/resnet50_caffe`. (#2832)\n- Added sleep(2) in test.py to reduce hanging problem (#2847)\n- Support `c10::half` in CARAFE (#2890)\n- Improve documentations (#2918, #2714)\n- Use optimizer constructor in mmcv and clean the original implementation in `mmdet.core.optimizer` (#2947)\n\n\n### v2.0.0 (6/5/2020)\nIn this release, we made lots of major refactoring and modifications.\n\n1. **Faster speed**. We optimize the training and inference speed for common models, achieving up to 30% speedup for training and 25% for inference. Please refer to [model zoo](model_zoo.md#comparison-with-detectron2) for details.\n\n2. **Higher performance**. We change some default hyperparameters with no additional cost, which leads to a gain of performance for most models. Please refer to [compatibility](compatibility.md#training-hyperparameters) for details.\n\n3. **More documentation and tutorials**. We add a bunch of documentation and tutorials to help users get started more smoothly. Read it [here](https://mmdetection.readthedocs.io/en/latest/).\n\n4. **Support PyTorch 1.5**. The support for 1.1 and 1.2 is dropped, and we switch to some new APIs.\n\n5. **Better configuration system**. Inheritance is supported to reduce the redundancy of configs.\n\n6. **Better modular design**. Towards the goal of simplicity and flexibility, we simplify some encapsulation while add more other configurable modules like BBoxCoder, IoUCalculator, OptimizerConstructor, RoIHead. Target computation is also included in heads and the call hierarchy is simpler.\n\n7. Support new methods: [FSAF](https://arxiv.org/abs/1903.00621) and PAFPN (part of [PAFPN](https://arxiv.org/abs/1803.01534)).\n\n**Breaking Changes**\nModels training with MMDetection 1.x are not fully compatible with 2.0, please refer to the [compatibility doc](compatibility.md) for the details and how to migrate to the new version.\n\n**Improvements**\n- Unify cuda and cpp API for custom ops. (#2277)\n- New config files with inheritance. (#2216)\n- Encapsulate the second stage into RoI heads. (#1999)\n- Refactor GCNet/EmpericalAttention into plugins. (#2345)\n- Set low quality match as an option in IoU-based bbox assigners. (#2375)\n- Change the codebase's coordinate system. (#2380)\n- Refactor the category order in heads. 0 means the first positive class instead of background now. (#2374)\n- Add bbox sampler and assigner registry. (#2419)\n- Speed up the inference of RPN. (#2420)\n- Add `train_cfg` and `test_cfg` as class members in all anchor heads. (#2422)\n- Merge target computation methods into heads. (#2429)\n- Add bbox coder to support different bbox encoding and losses. (#2480)\n- Unify the API for regression loss. (#2156)\n- Refactor Anchor Generator. (#2474)\n- Make `lr` an optional argument for optimizers. (#2509)\n- Migrate to modules and methods in MMCV. (#2502, #2511, #2569, #2572)\n- Support PyTorch 1.5. (#2524)\n- Drop the support for Python 3.5 and use F-string in the codebase. (#2531)\n\n**Bug Fixes**\n- Fix the scale factors for resized images without keep the aspect ratio. (#2039)\n- Check if max_num > 0 before slicing in NMS. (#2486)\n- Fix Deformable RoIPool when there is no instance. (#2490)\n- Fix the default value of assigned labels. (#2536)\n- Fix the evaluation of Cityscapes. (#2578)\n\n**New Features**\n- Add deep_stem and avg_down option to ResNet, i.e., support ResNetV1d. (#2252)\n- Add L1 loss. (#2376)\n- Support both polygon and bitmap for instance masks. (#2353, #2540)\n- Support CPU mode for inference. (#2385)\n- Add optimizer constructor for complicated configuration of optimizers. (#2397, #2488)\n- Implement PAFPN. (#2392)\n- Support empty tensor input for some modules. (#2280)\n- Support for custom dataset classes without overriding it. (#2408, #2443)\n- Support to train subsets of coco dataset. (#2340)\n- Add iou_calculator to potentially support more IoU calculation methods. (2405)\n- Support class wise mean AP (was removed in the last version). (#2459)\n- Add option to save the testing result images. (#2414)\n- Support MomentumUpdaterHook. (#2571)\n- Add a demo to inference a single image. (#2605)\n\n### v1.1.0 (24/2/2020)\n\n**Highlights**\n- Dataset evaluation is rewritten with a unified api, which is used by both evaluation hooks and test scripts.\n- Support new methods: [CARAFE](https://arxiv.org/abs/1905.02188).\n\n**Breaking Changes**\n- The new MMDDP inherits from the official DDP, thus the `__init__` api is changed to be the same as official DDP.\n- The `mask_head` field in HTC config files is modified.\n- The evaluation and testing script is updated.\n- In all transforms, instance masks are stored as a numpy array shaped (n, h, w) instead of a list of (h, w) arrays, where n is the number of instances.\n\n**Bug Fixes**\n- Fix IOU assigners when ignore_iof_thr > 0 and there is no pred boxes. (#2135)\n- Fix mAP evaluation when there are no ignored boxes. (#2116)\n- Fix the empty RoI input for Deformable RoI Pooling. (#2099)\n- Fix the dataset settings for multiple workflows. (#2103)\n- Fix the warning related to `torch.uint8` in PyTorch 1.4. (#2105)\n- Fix the inference demo on devices other than gpu:0. (#2098)\n- Fix Dockerfile. (#2097)\n- Fix the bug that `pad_val` is unused in Pad transform. (#2093)\n- Fix the albumentation transform when there is no ground truth bbox. (#2032)\n\n**Improvements**\n- Use torch instead of numpy for random sampling. (#2094)\n- Migrate to the new MMDDP implementation in MMCV v0.3. (#2090)\n- Add meta information in logs. (#2086)\n- Rewrite Soft NMS with pytorch extension and remove cython as a dependency. (#2056)\n- Rewrite dataset evaluation. (#2042, #2087, #2114, #2128)\n- Use numpy array for masks in transforms. (#2030)\n\n**New Features**\n- Implement \"CARAFE: Content-Aware ReAssembly of FEatures\". (#1583)\n- Add `worker_init_fn()` in data_loader when seed is set. (#2066, #2111)\n- Add logging utils. (#2035)\n\n### v1.0.0 (30/1/2020)\n\nThis release mainly improves the code quality and add more docstrings.\n\n**Highlights**\n- Documentation is online now: https://mmdetection.readthedocs.io.\n- Support new models: [ATSS](https://arxiv.org/abs/1912.02424).\n- DCN is now available with the api `build_conv_layer` and `ConvModule` like the normal conv layer.\n- A tool to collect environment information is available for trouble shooting.\n\n**Bug Fixes**\n- Fix the incompatibility of the latest numpy and pycocotools. (#2024)\n- Fix the case when distributed package is unavailable, e.g., on Windows. (#1985)\n- Fix the dimension issue for `refine_bboxes()`. (#1962)\n- Fix the typo when `seg_prefix` is a list. (#1906)\n- Add segmentation map cropping to RandomCrop. (#1880)\n- Fix the return value of `ga_shape_target_single()`. (#1853)\n- Fix the loaded shape of empty proposals. (#1819)\n- Fix the mask data type when using albumentation. (#1818)\n\n**Improvements**\n- Enhance AssignResult and SamplingResult. (#1995)\n- Add ability to overwrite existing module in Registry. (#1982)\n- Reorganize requirements and make albumentations and imagecorruptions optional. (#1969)\n- Check NaN in `SSDHead`. (#1935)\n- Encapsulate the DCN in ResNe(X)t into a ConvModule & Conv_layers. (#1894)\n- Refactoring for mAP evaluation and support multiprocessing and logging. (#1889)\n- Init the root logger before constructing Runner to log more information. (#1865)\n- Split `SegResizeFlipPadRescale` into different existing transforms. (#1852)\n- Move `init_dist()` to MMCV. (#1851)\n- Documentation and docstring improvements. (#1971, #1938, #1869, #1838)\n- Fix the color of the same class for mask visualization. (#1834)\n- Remove the option `keep_all_stages` in HTC and Cascade R-CNN. (#1806)\n\n**New Features**\n- Add two test-time options `crop_mask` and `rle_mask_encode` for mask heads. (#2013)\n- Support loading grayscale images as single channel. (#1975)\n- Implement \"Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection\". (#1872)\n- Add sphinx generated docs. (#1859, #1864)\n- Add GN support for flops computation. (#1850)\n- Collect env info for trouble shooting. (#1812)\n\n\n### v1.0rc1 (13/12/2019)\n\nThe RC1 release mainly focuses on improving the user experience, and fixing bugs.\n\n**Highlights**\n- Support new models: [FoveaBox](https://arxiv.org/abs/1904.03797), [RepPoints](https://arxiv.org/abs/1904.11490) and [FreeAnchor](https://arxiv.org/abs/1909.02466).\n- Add a Dockerfile.\n- Add a jupyter notebook demo and a webcam demo.\n- Setup the code style and CI.\n- Add lots of docstrings and unit tests.\n- Fix lots of bugs.\n\n**Breaking Changes**\n- There was a bug for computing COCO-style mAP w.r.t different scales (AP_s, AP_m, AP_l), introduced by #621. (#1679)\n\n**Bug Fixes**\n- Fix a sampling interval bug in Libra R-CNN. (#1800)\n- Fix the learning rate in SSD300 WIDER FACE. (#1781)\n- Fix the scaling issue when `keep_ratio=False`. (#1730)\n- Fix typos. (#1721, #1492, #1242, #1108, #1107)\n- Fix the shuffle argument in `build_dataloader`. (#1693)\n- Clip the proposal when computing mask targets. (#1688)\n- Fix the \"index out of range\" bug for samplers in some corner cases. (#1610, #1404)\n- Fix the NMS issue on devices other than GPU:0. (#1603)\n- Fix SSD Head and GHM Loss on CPU. (#1578)\n- Fix the OOM error when there are too many gt bboxes. (#1575)\n- Fix the wrong keyword argument `nms_cfg` in HTC. (#1573)\n- Process masks and semantic segmentation in Expand and MinIoUCrop transforms. (#1550, #1361)\n- Fix a scale bug in the Non Local op. (#1528)\n- Fix a bug in transforms when `gt_bboxes_ignore` is None. (#1498)\n- Fix a bug when `img_prefix` is None. (#1497)\n- Pass the device argument to `grid_anchors` and `valid_flags`. (#1478)\n- Fix the data pipeline for test_robustness. (#1476)\n- Fix the argument type of deformable pooling. (#1390)\n- Fix the coco_eval when there are only two classes. (#1376)\n- Fix a bug in Modulated DeformableConv when deformable_group>1. (#1359)\n- Fix the mask cropping in RandomCrop. (#1333)\n- Fix zero outputs in DeformConv when not running on cuda:0. (#1326)\n- Fix the type issue in Expand. (#1288)\n- Fix the inference API. (#1255)\n- Fix the inplace operation in Expand. (#1249)\n- Fix the from-scratch training config. (#1196)\n- Fix inplace add in RoIExtractor which cause an error in PyTorch 1.2. (#1160)\n- Fix FCOS when input images has no positive sample. (#1136)\n- Fix recursive imports. (#1099)\n\n**Improvements**\n- Print the config file and mmdet version in the log. (#1721)\n- Lint the code before compiling in travis CI. (#1715)\n- Add a probability argument for the `Expand` transform. (#1651)\n- Update the PyTorch and CUDA version in the docker file. (#1615)\n- Raise a warning when specifying `--validate` in non-distributed training. (#1624, #1651)\n- Beautify the mAP printing. (#1614)\n- Add pre-commit hook. (#1536)\n- Add the argument `in_channels` to backbones. (#1475)\n- Add lots of docstrings and unit tests, thanks to [@Erotemic](https://github.com/Erotemic). (#1603, #1517, #1506, #1505, #1491, #1479, #1477, #1475, #1474)\n- Add support for multi-node distributed test when there is no shared storage. (#1399)\n- Optimize Dockerfile to reduce the image size. (#1306)\n- Update new results of HRNet. (#1284, #1182)\n- Add an argument `no_norm_on_lateral` in FPN. (#1240)\n- Test the compiling in CI. (#1235)\n- Move docs to a separate folder. (#1233)\n- Add a jupyter notebook demo. (#1158)\n- Support different type of dataset for training. (#1133)\n- Use int64_t instead of long in cuda kernels. (#1131)\n- Support unsquare RoIs for bbox and mask heads. (#1128)\n- Manually add type promotion to make compatible to PyTorch 1.2. (#1114)\n- Allowing validation dataset for computing validation loss. (#1093)\n- Use `.scalar_type()` instead of `.type()` to suppress some warnings. (#1070)\n\n**New Features**\n- Add an option `--with_ap` to compute the AP for each class. (#1549)\n- Implement \"FreeAnchor: Learning to Match Anchors for Visual Object Detection\". (#1391)\n- Support [Albumentations](https://github.com/albumentations-team/albumentations) for augmentations in the data pipeline. (#1354)\n- Implement \"FoveaBox: Beyond Anchor-based Object Detector\". (#1339)\n- Support horizontal and vertical flipping. (#1273, #1115)\n- Implement \"RepPoints: Point Set Representation for Object Detection\". (#1265)\n- Add test-time augmentation to HTC and Cascade R-CNN. (#1251)\n- Add a COCO result analysis tool. (#1228)\n- Add Dockerfile. (#1168)\n- Add a webcam demo. (#1155, #1150)\n- Add FLOPs counter. (#1127)\n- Allow arbitrary layer order for ConvModule. (#1078)\n\n\n### v1.0rc0 (27/07/2019)\n- Implement lots of new methods and components (Mixed Precision Training, HTC, Libra R-CNN, Guided Anchoring, Empirical Attention, Mask Scoring R-CNN, Grid R-CNN (Plus), GHM, GCNet, FCOS, HRNet, Weight Standardization, etc.). Thank all collaborators!\n- Support two additional datasets: WIDER FACE and Cityscapes.\n- Refactoring for loss APIs and make it more flexible to adopt different losses and related hyper-parameters.\n- Speed up multi-gpu testing.\n- Integrate all compiling and installing in a single script.\n\n### v0.6.0 (14/04/2019)\n- Up to 30% speedup compared to the model zoo.\n- Support both PyTorch stable and nightly version.\n- Replace NMS and SigmoidFocalLoss with Pytorch CUDA extensions.\n\n### v0.6rc0(06/02/2019)\n- Migrate to PyTorch 1.0.\n\n### v0.5.7 (06/02/2019)\n- Add support for Deformable ConvNet v2. (Many thanks to the authors and [@chengdazhi](https://github.com/chengdazhi))\n- This is the last release based on PyTorch 0.4.1.\n\n### v0.5.6 (17/01/2019)\n- Add support for Group Normalization.\n- Unify RPNHead and single stage heads (RetinaHead, SSDHead) with AnchorHead.\n\n### v0.5.5 (22/12/2018)\n- Add SSD for COCO and PASCAL VOC.\n- Add ResNeXt backbones and detection models.\n- Refactoring for Samplers/Assigners and add OHEM.\n- Add VOC dataset and evaluation scripts.\n\n### v0.5.4 (27/11/2018)\n- Add SingleStageDetector and RetinaNet.\n\n### v0.5.3 (26/11/2018)\n- Add Cascade R-CNN and Cascade Mask R-CNN.\n- Add support for Soft-NMS in config files.\n\n### v0.5.2 (21/10/2018)\n- Add support for custom datasets.\n- Add a script to convert PASCAL VOC annotations to the expected format.\n\n### v0.5.1 (20/10/2018)\n- Add BBoxAssigner and BBoxSampler, the `train_cfg` field in config files are restructured.\n- `ConvFCRoIHead` / `SharedFCRoIHead` are renamed to `ConvFCBBoxHead` / `SharedFCBBoxHead` for consistency.\n"
  },
  {
    "path": "code/docs/compatibility.md",
    "content": "# Compatibility with MMDetection 1.x\n\nMMDetection 2.0 goes through a big refactoring and addresses many legacy issues. It is not compatible with the 1.x version, i.e., running inference with the same model weights in these two versions will produce different results. Thus, MMDetection 2.0 re-benchmarks all the models and provides their links and logs in the model zoo.\n\nThe major differences are in four folds: coordinate system, codebase conventions, training hyperparameters, and modular design.\n\n## Coordinate System\nThe new coordinate system is consistent with [Detectron2](https://github.com/facebookresearch/detectron2/) and treats the center of the most left-top pixel as (0, 0) rather than the left-top corner of that pixel.\nAccordingly, the system interprets the coordinates in COCO bounding box and segmentation annotations as coordinates in range `[0, width]` or `[0, height]`.\nThis modification affects all the computation related to the bbox and pixel selection,\nwhich is more natural and accurate.\n\n- The height and width of a box with corners (x1, y1) and (x2, y2) in the new coordinate system is computed as `width = x2 - x1` and `height = y2 - y1`.\nIn MMDetection 1.x and previous version, a \"+ 1\" was added both height and width.\nThis modification are in three folds:\n\n  1. Box transformation and encoding/decoding in regression.\n  2. IoU calculation. This affects the matching process between ground truth and bounding box and the NMS process. The effect to compatibility is very negligible, though.\n  3. The corners of bounding box is in float type and no longer quantized. This should provide more accurate bounding box results. This also makes the bounding box and RoIs not required to have minimum size of 1, whose effect is small, though.\n\n- The anchors are center-aligned to feature grid points and in float type.\nIn MMDetection 1.x and previous version, the anchors are in `int` type and not center-aligned.\nThis affects the anchor generation in RPN and all the anchor-based methods.\n\n- ROIAlign is better aligned with the image coordinate system. The new implementation is adopted from [Detectron2](https://github.com/facebookresearch/detectron2/tree/master/detectron2/layers/csrc/ROIAlign).\nThe RoIs are shifted by half a pixel by default when they are used to cropping RoI features, compared to MMDetection 1.x.\nThe old behavior is still available by setting `aligned=False` instead of `aligned=True`.\n\n- Mask cropping and pasting are more accurate.\n\n  1. We use the new RoIAlign to crop mask targets. In MMDetection 1.x, the bounding box is quantized before it is used to crop mask target, and the crop process is implemented by numpy. In new implementation, the bounding box for crop is not quantized and sent to RoIAlign. This implementation accelerates the training speed by a large margin (~0.1s per iter, ~2 hour when training Mask R50 for 1x schedule) and should be more accurate.\n\n  2. In MMDetection 2.0, the \"`paste_mask()`\" function is different and should be more accurate than those in previous versions. This change follows the modification in [Detectron2](https://github.com/facebookresearch/detectron2/blob/master/detectron2/structures/masks.py) and can improve mask AP on COCO by ~0.5% absolute.\n\n## Codebase Conventions\n\n- MMDetection 2.0 changes the order of class labels to reduce unused parameters in regression and mask branch more naturally (without +1 and -1).\nThis effect all the classification layers of the model to have a different ordering of class labels. The final layers of regression branch and mask head no longer keep K+1 channels for K categories, and their class orders are consistent with the classification branch.\n\n  - In MMDetection 2.0, label \"K\" means background, and labels [0, K-1] correspond to the K = num_categories object categories.\n\n  - In MMDetection 1.x and previous version, label \"0\" means background, and labels [1, K] correspond to the K categories.\n\n- Low quality matching in R-CNN is not used. In MMDetection 1.x and previous versions, the `max_iou_assigner` will match low quality boxes for each ground truth box in both RPN and R-CNN training. We observe this sometimes does not assign the most perfect GT box to some bounding boxes,\nthus MMDetection 2.0 do not allow low quality matching by default in R-CNN training in the new system. This sometimes may slightly improve the box AP (~0.1% absolute).\n\n- Separate scale factors for width and height. In MMDetection 1.x and previous versions, the scale factor is a single float in mode `keep_ratio=True`. This is slightly inaccurate because the scale factors for width and height have slight difference. MMDetection 2.0 adopts separate scale factors for width and height, the improvement on AP ~0.1% absolute.\n\n- Configs name conventions are changed. MMDetection V2.0 adopts the new name convention to maintain the gradually growing model zoo as the following:\n  ```\n  [model]_(model setting)_[backbone]_[neck]_(norm setting)_(misc)_(gpu x batch)_[schedule]_[dataset].py,\n  ```\n  where the (`misc`) includes DCN and GCBlock, etc. More details are illustrated in the [documentation for config](config.md)\n\n- MMDetection V2.0 uses new ResNet Caffe backbones to reduce warnings when loading pre-trained models. Most of the new backbones' weights are the same as the former ones but do not have `conv.bias`, except that they use a different `img_norm_cfg`. Thus, the new backbone will not cause warning of unexpected keys.\n\n## Training Hyperparameters\n\nThe change in training hyperparameters does not affect\nmodel-level compatibility but slightly improves the performance. The major ones are:\n\n- The number of proposals after nms is changed from 2000 to 1000 by setting `nms_post=1000` and `max_num=1000`.\nThis slightly improves both mask AP and bbox AP by ~0.2% absolute.\n\n- The default box regression losses for Mask R-CNN, Faster R-CNN and RetinaNet are changed from smooth L1 Loss to L1 loss. This leads to an overall improvement in box AP (~0.6% absolute). However, using L1-loss for other methods such as Cascade R-CNN and HTC does not improve the performance, so we keep the original settings for these methods.\n\n- The sample num of RoIAlign layer is set to be 0 for simplicity. This leads to slightly improvement on mask AP (~0.2% absolute).\n\n- The default setting does not use gradient clipping anymore during training for faster training speed. This does not degrade performance of the most of models. For some models such as RepPoints we keep using gradient clipping to stabilize the training process and to obtain better performance.\n\n- The default warmup ratio is changed from 1/3 to 0.001 for a more smooth warming up process since the gradient clipping is usually not used. The effect is found negligible during our re-benchmarking, though.\n\n## Upgrade Models from 1.x to 2.0\n\nTo convert the models trained by MMDetection V1.x to MMDetection V2.0, the users can use the script `tools/upgrade_model_version.py` to convert\ntheir models. The converted models can be run in MMDetection V2.0 with slightly dropped performance (less than 1% AP absolute).\nDetails can be found in `configs/legacy`.\n"
  },
  {
    "path": "code/docs/conf.py",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath('..'))\n\n# -- Project information -----------------------------------------------------\n\nproject = 'MMDetection'\ncopyright = '2018-2020, OpenMMLab'\nauthor = 'MMDetection Authors'\n\n# The full version, including alpha/beta/rc tags\nwith open('../mmdet/VERSION', 'r') as f:\n    release = f.read().strip()\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.napoleon',\n    'sphinx.ext.viewcode',\n    'recommonmark',\n    'sphinx_markdown_tables',\n]\n\nautodoc_mock_imports = [\n    'matplotlib', 'pycocotools', 'terminaltables', 'mmdet.version',\n    'mmdet.ops.corner_pool', 'mmdet.ops.dcn', 'mmdet.ops.masked_conv',\n    'mmdet.ops.nms', 'mmdet.ops.roi_align', 'mmdet.ops.roi_pool',\n    'mmdet.ops.sigmoid_focal_loss', 'mmdet.ops.carafe', 'mmdet.ops.utils'\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\nsource_suffix = {\n    '.rst': 'restructuredtext',\n    '.md': 'markdown',\n}\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = 'sphinx_rtd_theme'\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n"
  },
  {
    "path": "code/docs/config.md",
    "content": "# Config System\nWe incorporate modular and inheritance design into our config system, which is convenient to conduct various experiments.\nIf you wish to inspect the config file, you may run `python tools/print_config.py /PATH/TO/CONFIG` to see the complete config.\nYou may also pass `--options xxx.yyy=zzz` to see updated config.\n\n## Config File Structure\n\nThere are 4 basic component types under `config/_base_`, dataset, model, schedule, default_runtime.\nMany methods could be easily constructed with one of each like Faster R-CNN, Mask R-CNN, Cascade R-CNN, RPN, SSD.\nThe configs that are composed by components from `_base_` are called _primitive_.\n\nFor all configs under the same folder, it is recommended to have only **one** _primitive_ config. All other configs should inherit from the _primitive_ config. In this way, the maximum of inheritance level is 3.\n\nFor easy understanding, we recommend contributors to inherit from exiting methods.\nFor example, if some modification is made base on Faster R-CNN, user may first inherit the basic Faster R-CNN structure by specifying `_base_ = ../faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py`, then modify the necessary fields in the config files.\n\nIf you are building an entirely new method that does not share the structure with any of the existing methods, you may create a folder `xxx_rcnn` under `configs`,\n\nPlease refer to [mmcv](https://mmcv.readthedocs.io/en/latest/utils.html#config) for detailed documentation.\n\n## Config Name Style\n\nWe follow the below style to name config files. Contributors are advised to follow the same style.\n\n```\n{model}_[model setting]_{backbone}_{neck}_[norm setting]_[misc]_[gpu x batch_per_gpu]_{schedule}_{dataset}\n```\n\n`{xxx}` is required field and `[yyy]` is optional.\n\n- `{model}`: model type like `faster_rcnn`, `mask_rcnn`, etc.\n- `[model setting]`: specific setting for some model, like `without_semantic` for `htc`, `moment` for `reppoints`, etc.\n- `{backbone}`: backbone type like `r50` (ResNet-50), `x101` (ResNeXt-101).\n- `{neck}`: neck type like `fpn`, `pafpn`, `nasfpn`, `c4`.\n- `[norm_setting]`: `bn` (Batch Normalization) is used unless specified, other norm layer type could be `gn` (Group Normalization), `syncbn` (Synchronized Batch Normalization).\n`gn-head`/`gn-neck` indicates GN is applied in head/neck only, while `gn-all` means GN is applied in the entire model, e.g. backbone, neck, head.\n- `[misc]`: miscellaneous setting/plugins of model, e.g. `dconv`, `gcb`, `attention`, `albu`, `mstrain`.\n- `[gpu x batch_per_gpu]`: GPUs and samples per GPU, `8x2` is used by default.\n- `{schedule}`: training schedule, options are `1x`, `2x`, `20e`, etc.\n`1x` and `2x` means 12 epochs and 24 epochs respectively.\n`20e` is adopted in cascade models, which denotes 20 epochs.\nFor `1x`/`2x`, initial learning rate decays by a factor of 10 at the 8/16th and 11/22th epochs.\nFor `20e`, initial learning rate decays by a factor of 10 at the 16th and 19th epochs.\n- `{dataset}`: dataset like `coco`, `cityscapes`, `voc_0712`, `wider_face`.\n\n## An Example of Mask R-CNN\n\nTo help the users have a basic idea of a complete config and the modules in a modern detection system,\nwe make brief comments on the config of Mask R-CNN using ResNet50 and FPN as the following.\nFor more detailed usage and the corresponding alternative for each modules, please refer to the API documentation.\n\n```python\nmodel = dict(\n    type='MaskRCNN',  # The name of detector\n    pretrained=\n    'torchvision://resnet50',  # The ImageNet pretrained backbone to be loaded\n    backbone=dict(  # The config of backbone\n        type='ResNet',  # The type of the backbone, refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/backbones/resnet.py#L288 for more details.\n        depth=50,  # The depth of backbone, usually it is 50 or 101 for ResNet and ResNext backbones.\n        num_stages=4,  # Number of stages of the backbone.\n        out_indices=(0, 1, 2, 3),  # The index of output feature maps produced in each stages\n        frozen_stages=1,  # The weights in the first 1 stage are fronzen\n        norm_cfg=dict(  # The config of normalization layers.\n            type='BN',  # Type of norm layer, usually it is BN or GN\n            requires_grad=True),  # Whether to train the gamma and beta in BN\n        norm_eval=True,  # Whether to freeze the statistics in BN\n        style='pytorch'),  # The style of backbone, 'pytorch' means that stride 2 layers are in 3x3 conv, 'caffe' means stride 2 layers are in 1x1 convs.\n    neck=dict(\n        type='FPN',  # The neck of detector is FPN. We also support 'NASFPN', 'PAFPN', etc. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/necks/fpn.py#L10 for more details.\n        in_channels=[256, 512, 1024, 2048],  # The input channels, this is consistent with the output channels of backbone\n        out_channels=256,  # The output channels of each level of the pyramid feature map\n        num_outs=5),  # The number of output scales\n    rpn_head=dict(\n        type='RPNHead',  # The type of RPN head is 'RPNHead', we also support 'GARPNHead', etc. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/dense_heads/rpn_head.py#L12 for more details.\n        in_channels=256,  # The input channels of each input feature map, this is consistent with the output channels of neck\n        feat_channels=256,  # Feature channels of convolutional layers in the head.\n        anchor_generator=dict(  # The config of anchor generator\n            type='AnchorGenerator',  # Most of methods use AnchorGenerator, SSD Detectors uses `SSDAnchorGenerator`. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/anchor/anchor_generator.py#L10 for more details\n            scales=[8],  # Basic scale of the anchor, the area of the anchor in one position of a feature map will be scale * base_sizes\n            ratios=[0.5, 1.0, 2.0],  # The ratio between height and width.\n            strides=[4, 8, 16, 32, 64]),  # The strides of the anchor generator. This is consistent with the FPN feature strides. The strides will be taken as base_sizes if base_sizes is not set.\n        bbox_coder=dict(  # Config of box coder to encode and decode the boxes during training and testing\n            type='DeltaXYWHBBoxCoder',  # Type of box coder. 'DeltaXYWHBBoxCoder' is applied for most of methods. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/bbox/coder/delta_xywh_bbox_coder.py#L9 for more details.\n            target_means=[0.0, 0.0, 0.0, 0.0],  # The target means used to encode and decode boxes\n            target_stds=[1.0, 1.0, 1.0, 1.0]),  # The standard variance used to encode and decode boxes\n        loss_cls=dict(  # Config of loss function for the classification branch\n            type='CrossEntropyLoss',  # Type of loss for classification branch, we also support FocalLoss etc.\n            use_sigmoid=True,  # RPN usually perform two-class classification, so it usually uses sigmoid function.\n            loss_weight=1.0),  # Loss weight of the classification branch.\n        loss_bbox=dict(  # Config of loss function for the regression branch.\n            type='L1Loss',  # Type of loss, we also support many IoU Losses and smooth L1-loss, etc. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/losses/smooth_l1_loss.py#L56 for implementation.\n            loss_weight=1.0)),  # Loss weight of the regression branch.\n    roi_head=dict(  # RoIHead encapsulates the second stage of two-stage/cascade detectors.\n        type='StandardRoIHead',  # Type of the RoI head. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/roi_heads/standard_roi_head.py#L10 for implementation.\n        bbox_roi_extractor=dict(  # RoI feature extractor for bbox regression.\n            type='SingleRoIExtractor',  # Type of the RoI feature extractor, most of methods uses SingleRoIExtractor. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/roi_heads/roi_extractors/single_level.py#L10 for details.\n            roi_layer=dict(  # Config of RoI Layer\n                type='RoIAlign',  # Type of RoI Layer, DeformRoIPoolingPack and ModulatedDeformRoIPoolingPack are also supported. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/ops/roi_align/roi_align.py#L79 for details.\n                out_size=7,  # The output size of feature maps.\n                sample_num=0),  # Sampling ratio when extracting the RoI features. 0 means adaptive ratio.\n            out_channels=256,  # output channels of the extracted feature.\n            featmap_strides=[4, 8, 16, 32]),  # Strides of multi-scale feature maps. It should be consistent to the architecture of the backbone.\n        bbox_head=dict(  # Config of box head in the RoIHead.\n            type='Shared2FCBBoxHead',  # Type of the bbox head, Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py#L177 for implementation details.\n            in_channels=256,  # Input channels for bbox head. This is consistent with the out_channels in roi_extractor\n            fc_out_channels=1024,  # Output feature channels of FC layers.\n            roi_feat_size=7,  # Size of RoI features\n            num_classes=80,  # Number of classes for classification\n            bbox_coder=dict(  # Box coder used in the second stage.\n                type='DeltaXYWHBBoxCoder',  # Type of box coder. 'DeltaXYWHBBoxCoder' is applied for most of methods.\n                target_means=[0.0, 0.0, 0.0, 0.0],  # Means used to encode and decode box\n                target_stds=[0.1, 0.1, 0.2, 0.2]),  # Standard variance for encoding and decoding. It is smaller since the boxes are more accurate. [0.1, 0.1, 0.2, 0.2] is a conventional setting.\n            reg_class_agnostic=False,  # Whether the regression is class agnostic.\n            loss_cls=dict(  # Config of loss function for the classification branch\n                type='CrossEntropyLoss',  # Type of loss for classification branch, we also support FocalLoss etc.\n                use_sigmoid=False,  # Whether to use sigmoid.\n                loss_weight=1.0),  # Loss weight of the classification branch.\n            loss_bbox=dict(  # Config of loss function for the regression branch.\n                type='L1Loss',  # Type of loss, we also support many IoU Losses and smooth L1-loss, etc.\n                loss_weight=1.0)),  # Loss weight of the regression branch.\n        mask_roi_extractor=dict(  # RoI feature extractor for bbox regression.\n            type='SingleRoIExtractor',  # Type of the RoI feature extractor, most of methods uses SingleRoIExtractor.\n            roi_layer=dict(  # Config of RoI Layer that extracts features for instance segmentation\n                type='RoIAlign',  # Type of RoI Layer, DeformRoIPoolingPack and ModulatedDeformRoIPoolingPack are also supported\n                out_size=14,  # The output size of feature maps.\n                sample_num=0),  # Sampling ratio when extracting the RoI features.\n            out_channels=256,  # Output channels of the extracted feature.\n            featmap_strides=[4, 8, 16, 32]),  # Strides of multi-scale feature maps.\n        mask_head=dict(  # Mask prediction head\n            type='FCNMaskHead',  # Type of mask head, refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/roi_heads/mask_heads/fcn_mask_head.py#L21 for implementation details.\n            num_convs=4,  # Number of convolutional layers in mask head.\n            in_channels=256,  # Input channels, should be consistent with the output channels of mask roi extractor.\n            conv_out_channels=256,  # Output channels of the convolutional layer.\n            num_classes=80,  # Number of class to be segmented.\n            loss_mask=dict(  # Config of loss function for the mask branch.\n                type='CrossEntropyLoss',  # Type of loss used for segmentation\n                use_mask=True,  # Whether to only train the mask in the correct class.\n                loss_weight=1.0))))  # Loss weight of mask branch.\ntrain_cfg = dict(  # Config of training hyperparameters for rpn and rcnn\n    rpn=dict(  # Training config of rpn\n        assigner=dict(  # Config of assigner\n            type='MaxIoUAssigner',  # Type of assigner, MaxIoUAssigner is used for many common detectors. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/bbox/assigners/max_iou_assigner.py#L10 for more details.\n            pos_iou_thr=0.7,  # IoU >= threshold 0.7 will be taken as positive samples\n            neg_iou_thr=0.3,  # IoU < threshold 0.3 will be taken as negative samples\n            min_pos_iou=0.3,  # The minimal IoU threshold to take boxes as positive samples\n            match_low_quality=True,  # Whether to match the boxes under low quality (see API doc for more details).\n            ignore_iof_thr=-1),  # IoF threshold for ignoring bboxes\n        sampler=dict(  # Config of positive/negative sampler\n            type='RandomSampler',  # Type of sampler, PseudoSampler and other samplers are also supported. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/bbox/samplers/random_sampler.py#L8 for implementation details.\n            num=256,  # Number of samples\n            pos_fraction=0.5,  # The ratio of positive samples in the total samples.\n            neg_pos_ub=-1,  # The upper bound of negative samples based on the number of positive samples.\n            add_gt_as_proposals=False),  # Whether add GT as proposals after sampling.\n        allowed_border=-1,  # The border allowed after padding for valid anchors.\n        pos_weight=-1,  # The weight of positive samples during training.\n        debug=False),  # Whether to set the debug mode\n    rpn_proposal=dict(  # The config to generate proposals during training\n        nms_across_levels=False,  # Whether to do NMS for boxes across levels\n        nms_pre=2000,  # The number of boxes before NMS\n        nms_post=1000,  # The number of boxes to be kept by NMS\n        max_num=1000,  # The number of boxes to be used after NMS\n        nms_thr=0.7,  # The threshold to be used during NMS\n        min_bbox_size=0),  # The allowed minimal box size\n    rcnn=dict(  # The config for the roi heads.\n        assigner=dict(  # Config of assigner for second stage, this is different for that in rpn\n            type='MaxIoUAssigner',  # Type of assigner, MaxIoUAssigner is used for all roi_heads for now. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/bbox/assigners/max_iou_assigner.py#L10 for more details.\n            pos_iou_thr=0.5,  # IoU >= threshold 0.5 will be taken as positive samples\n            neg_iou_thr=0.5,  # IoU >= threshold 0.5 will be taken as positive samples\n            min_pos_iou=0.5,  # The minimal IoU threshold to take boxes as positive samples\n            match_low_quality=False,  # Whether to match the boxes under low quality (see API doc for more details).\n            ignore_iof_thr=-1),  # IoF threshold for ignoring bboxes\n        sampler=dict(\n            type='RandomSampler',  # Type of sampler, PseudoSampler and other samplers are also supported. Refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/bbox/samplers/random_sampler.py#L8 for implementation details.\n            num=512,  # Number of samples\n            pos_fraction=0.25,  # The ratio of positive samples in the total samples.\n            neg_pos_ub=-1,  # The upper bound of negative samples based on the number of positive samples.\n            add_gt_as_proposals=True\n        ),  # Whether add GT as proposals after sampling.\n        mask_size=28,  # Size of mask\n        pos_weight=-1,  # The weight of positive samples during training.\n        debug=False))  # Whether to set the debug mode\ntest_cfg = dict(  # Config for testing hyperparameters for rpn and rcnn\n    rpn=dict(  # The config to generate proposals during testing\n        nms_across_levels=False,  # Whether to do NMS for boxes across levels\n        nms_pre=1000,  # The number of boxes before NMS\n        nms_post=1000,  # The number of boxes to be kept by NMS\n        max_num=1000,  # The number of boxes to be used after NMS\n        nms_thr=0.7,  # The threshold to be used during NMS\n        min_bbox_size=0),  # The allowed minimal box size\n    rcnn=dict(  # The config for the roi heads.\n        score_thr=0.05,  # Threshold to filter out boxes\n        nms=dict(  # Config of nms in the second stage\n            type='nms',  # Type of nms\n            iou_thr=0.5),  # NMS threshold\n        max_per_img=100,  # Max number of detections of each image\n        mask_thr_binary=0.5))  # Threshold of mask prediction\ndataset_type = 'CocoDataset'  # Dataset type, this will be used to define the dataset\ndata_root = 'data/coco/'  # Root path of data\nimg_norm_cfg = dict(  # Image normalization config to normalize the input images\n    mean=[123.675, 116.28, 103.53],  # Mean values used to pre-training the pre-trained backbone models\n    std=[58.395, 57.12, 57.375],  # Standard variance used to pre-training the pre-trained backbone models\n    to_rgb=True\n)  # The channel orders of image used to pre-training the pre-trained backbone models\ntrain_pipeline = [  # Training pipeline\n    dict(type='LoadImageFromFile'),  # First pipeline to load images from file path\n    dict(\n        type='LoadAnnotations',  # Second pipeline to load annotations for current image\n        with_bbox=True,  # Whether to use bounding box, True for detection\n        with_mask=True,  # Whether to use instance mask, True for instance segmentation\n        poly2mask=False),  # Whether to convert the polygon mask to instance mask, set False for acceleration and to save memory\n    dict(\n        type='Resize',  # Augmentation pipeline that resize the images and their annotations\n        img_scale=(1333, 800),  # The largest scale of image\n        keep_ratio=True\n    ),  # whether to keep the ratio between height and width.\n    dict(\n        type='RandomFlip',  # Augmentation pipeline that flip the images and their annotations\n        flip_ratio=0.5),  # The ratio or probability to flip\n    dict(\n        type='Normalize',  # Augmentation pipeline that normalize the input images\n        mean=[123.675, 116.28, 103.53],  # These keys are the same of img_norm_cfg since the\n        std=[58.395, 57.12, 57.375],  # keys of img_norm_cfg are used here as arguments\n        to_rgb=True),\n    dict(\n        type='Pad',  # Padding config\n        size_divisor=32),  # The number the padded images should be divisible\n    dict(type='DefaultFormatBundle'),  # Default format bundle to gather data in the pipeline\n    dict(\n        type='Collect',  # Pipeline that decides which keys in the data should be passed to the detector\n        keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks'])\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),  # First pipeline to load images from file path\n    dict(\n        type='MultiScaleFlipAug',  # An encapsulation that encapsulates the testing augmentations\n        img_scale=(1333, 800),  # Decides the largest scale for testing, used for the Resize pipeline\n        flip=False,  # Whether to flip images during testing\n        transforms=[\n            dict(type='Resize',  # Use resize augmentation\n                 keep_ratio=True),  # Whether to keep the ratio between height and width, the img_scale set here will be supressed by the img_scale set above.\n            dict(type='RandomFlip'),  # Thought RandomFlip is added in pipeline, it is not used because flip=False\n            dict(\n                type='Normalize',  # Normalization config, the values are from img_norm_cfg\n                mean=[123.675, 116.28, 103.53],\n                std=[58.395, 57.12, 57.375],\n                to_rgb=True),\n            dict(\n                type='Pad',  # Padding config to pad images divisable by 32.\n                size_divisor=32),\n            dict(\n                type='ImageToTensor',  # convert image to tensor\n                keys=['img']),\n            dict(\n                type='Collect',  # Collect pipeline that collect necessary keys for testing.\n                keys=['img'])\n        ])\n]\ndata = dict(\n    samples_per_gpu=2,  # Batch size of a single GPU\n    workers_per_gpu=2,  # Worker to pre-fetch data for each single GPU\n    train=dict(  # Train dataset config\n        type='CocoDataset',  # Type of dataset, refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/datasets/coco.py#L19 for details.\n        ann_file='data/coco/annotations/instances_train2017.json',  # Path of annotation file\n        img_prefix='data/coco/train2017/',  # Prefix of image path\n        pipeline=[  # pipeline, this is passed by the train_pipeline created before.\n            dict(type='LoadImageFromFile'),\n            dict(\n                type='LoadAnnotations',\n                with_bbox=True,\n                with_mask=True,\n                poly2mask=False),\n            dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n            dict(type='RandomFlip', flip_ratio=0.5),\n            dict(\n                type='Normalize',\n                mean=[123.675, 116.28, 103.53],\n                std=[58.395, 57.12, 57.375],\n                to_rgb=True),\n            dict(type='Pad', size_divisor=32),\n            dict(type='DefaultFormatBundle'),\n            dict(\n                type='Collect',\n                keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks'])\n        ]),\n    val=dict(  # Validation dataset config\n        type='CocoDataset',\n        ann_file='data/coco/annotations/instances_val2017.json',\n        img_prefix='data/coco/val2017/',\n        pipeline=[  # Pipeline is passed by test_pipeline created before\n            dict(type='LoadImageFromFile'),\n            dict(\n                type='MultiScaleFlipAug',\n                img_scale=(1333, 800),\n                flip=False,\n                transforms=[\n                    dict(type='Resize', keep_ratio=True),\n                    dict(type='RandomFlip'),\n                    dict(\n                        type='Normalize',\n                        mean=[123.675, 116.28, 103.53],\n                        std=[58.395, 57.12, 57.375],\n                        to_rgb=True),\n                    dict(type='Pad', size_divisor=32),\n                    dict(type='ImageToTensor', keys=['img']),\n                    dict(type='Collect', keys=['img'])\n                ])\n        ]),\n    test=dict(  # Test dataset config, modify the ann_file for test-dev/test submission\n        type='CocoDataset',\n        ann_file='data/coco/annotations/instances_val2017.json',\n        img_prefix='data/coco/val2017/',\n        pipeline=[  # Pipeline is passed by test_pipeline created before\n            dict(type='LoadImageFromFile'),\n            dict(\n                type='MultiScaleFlipAug',\n                img_scale=(1333, 800),\n                flip=False,\n                transforms=[\n                    dict(type='Resize', keep_ratio=True),\n                    dict(type='RandomFlip'),\n                    dict(\n                        type='Normalize',\n                        mean=[123.675, 116.28, 103.53],\n                        std=[58.395, 57.12, 57.375],\n                        to_rgb=True),\n                    dict(type='Pad', size_divisor=32),\n                    dict(type='ImageToTensor', keys=['img']),\n                    dict(type='Collect', keys=['img'])\n                ])\n        ]))\nevaluation = dict(  # The config to build the evaluation hook, refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/evaluation/eval_hooks.py#L7 for more details.\n    interval=1,  # Evaluation interval\n    metric=['bbox', 'segm'])  # Metrics used during evaluation\noptimizer = dict(  # Config used to build optimizer, support all the optimizers in PyTorch whose arguments are also the same as those in PyTorch\n    type='SGD',  # Type of optimizers, refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/optimizer/default_constructor.py#L13 for more details\n    lr=0.02,  # Learning rate of optimizers, see detail usages of the parameters in the documentaion of PyTorch\n    momentum=0.9,  # Momentum\n    weight_decay=0.0001)  # Weight decay of SGD\noptimizer_config = dict(  # Config used to build the optimizer hook, refer to https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/optimizer.py#L8 for implementation details.\n    grad_clip=None)  # Most of the methods do not use gradient clip\nlr_config = dict(  # Learning rate scheduler config used to register LrUpdater hook\n    policy='step',  # The policy of scheduler, also support CosineAnealing, Cyclic, etc. Refer to details of supported LrUpdater from https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9.\n    warmup='linear',  # The warmup policy, also support `exp` and `constant`.\n    warmup_iters=500,  # The number of iterations for warmup\n    warmup_ratio=\n    0.001,  # The ratio of the starting learning rate used for warmup\n    step=[8, 11])  # Steps to decay the learning rate\ntotal_epochs = 12  # Total epochs to train the model\ncheckpoint_config = dict(  # Config to set the checkpoint hook, Refer to https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py for implementation.\n    interval=1)  # The save interval is 1\nlog_config = dict(  # config to register logger hook\n    interval=50,  # Interval to print the log\n    hooks=[\n        # dict(type='TensorboardLoggerHook')  # The Tensorboard logger is also supported\n        dict(type='TextLoggerHook')\n    ])  # The logger used to record the training process.\ndist_params = dict(backend='nccl')  # Parameters to setup distributed training, the port can also be set.\nlog_level = 'INFO'  # The level of logging.\nload_from = None  # load models as a pre-trained model from a given path. This will not resume training.\nresume_from = None  # Resume checkpoints from a given path, the training will be resumed from the epoch when the checkpoint's is saved.\nworkflow = [('train', 1)]  # Workflow for runner. [('train', 1)] means there is only one workflow and the workflow named 'train' is executed once. The workflow trains the model by 12 epochs according to the total_epochs.\nwork_dir = 'work_dir'  # Directory to save the model checkpoints and logs for the current experiments.\n\n```\n\n## FAQ\n\n### Ignore some fields in the base configs\n\nSometimes, you may set `_delete_=True` to ignore some of fields in base configs.\nYou may refer to [mmcv](https://mmcv.readthedocs.io/en/latest/utils.html#inherit-from-base-config-with-ignored-fields) for simple inllustration.\n\nIn MMDetection, for example, to change the backbone of Mask R-CNN with the following config.\n\n```python\nmodel = dict(\n    type='MaskRCNN',\n    pretrained='torchvision://resnet50',\n    backbone=dict(\n        type='ResNet',\n        depth=50,\n        num_stages=4,\n        out_indices=(0, 1, 2, 3),\n        frozen_stages=1,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        norm_eval=True,\n        style='pytorch'),\n    neck=dict(...),\n    rpn_head=dict(...),\n    roi_head=dict(...))\n```\n\n`ResNet` and `HRNet` use different keywords to construct.\n\n```python\n_base_ = '../mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py'\nmodel = dict(\n    pretrained='open-mmlab://msra/hrnetv2_w32',\n    backbone=dict(\n        _delete_=True,\n        type='HRNet',\n        extra=dict(\n            stage1=dict(\n                num_modules=1,\n                num_branches=1,\n                block='BOTTLENECK',\n                num_blocks=(4, ),\n                num_channels=(64, )),\n            stage2=dict(\n                num_modules=1,\n                num_branches=2,\n                block='BASIC',\n                num_blocks=(4, 4),\n                num_channels=(32, 64)),\n            stage3=dict(\n                num_modules=4,\n                num_branches=3,\n                block='BASIC',\n                num_blocks=(4, 4, 4),\n                num_channels=(32, 64, 128)),\n            stage4=dict(\n                num_modules=3,\n                num_branches=4,\n                block='BASIC',\n                num_blocks=(4, 4, 4, 4),\n                num_channels=(32, 64, 128, 256)))),\n    neck=dict(...))\n```\n\nThe `_delete_=True` would replace all old keys in `backbone` field with new keys new keys.\n\n### Use intermediate variables in configs\n\nSome intermediate variables are used in the configs files, like `train_pipeline`/`test_pipeline` in datasets.\nIt's worth noting that when modifying intermediate variables in the children configs, user need to pass the intermediate variables into corresponding fields again.\nFor example, we would like to use multi scale strategy to train a Mask R-CNN. `train_pipeline`/`test_pipeline` are intermediate variable we would like modify.\n```python\n_base_ = './mask_rcnn_r50_fpn_1x_coco.py'\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n    dict(\n        type='Resize',\n        img_scale=[(1333, 640), (1333, 672), (1333, 704), (1333, 736),\n                   (1333, 768), (1333, 800)],\n        multiscale_mode=\"value\",\n        keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\ndata = dict(\n    train=dict(pipeline=train_pipeline),\n    val=dict(pipeline=test_pipeline),\n    test=dict(pipeline=test_pipeline))\n```\nWe first define the new `train_pipeline`/`test_pipeline` and pass them into `data`.\n"
  },
  {
    "path": "code/docs/getting_started.md",
    "content": "# Getting Started\n\nThis page provides basic tutorials about the usage of MMDetection.\nFor installation instructions, please see [install.md](install.md).\n\n## Prepare datasets\n\nIt is recommended to symlink the dataset root to `$MMDETECTION/data`.\nIf your folder structure is different, you may need to change the corresponding paths in config files.\n\n```\nmmdetection\n├── mmdet\n├── tools\n├── configs\n├── data\n│   ├── coco\n│   │   ├── annotations\n│   │   ├── train2017\n│   │   ├── val2017\n│   │   ├── test2017\n│   ├── cityscapes\n│   │   ├── annotations\n│   │   ├── leftImg8bit\n│   │   │   ├── train\n│   │   │   ├── val\n│   │   ├── gtFine\n│   │   │   ├── train\n│   │   │   ├── val\n│   ├── VOCdevkit\n│   │   ├── VOC2007\n│   │   ├── VOC2012\n\n```\n\nThe cityscapes annotations have to be converted into the coco format using `tools/convert_datasets/cityscapes.py`:\n\n```shell\npip install cityscapesscripts\npython tools/convert_datasets/cityscapes.py ./data/cityscapes --nproc 8 --out-dir ./data/cityscapes/annotations\n```\n\nCurrently the config files in `cityscapes` use COCO pre-trained weights to initialize.\nYou could download the pre-trained models in advance if network is unavailable or slow, otherwise it would cause errors at the beginning of training.\n\nFor using custom datasets, please refer to [Tutorials 2: Adding New Dataset](tutorials/new_dataset.md).\n\n## Inference with pretrained models\n\nWe provide testing scripts to evaluate a whole dataset (COCO, PASCAL VOC, Cityscapes, etc.),\nand also some high-level apis for easier integration to other projects.\n\n### Test a dataset\n\n- single GPU\n- single node multiple GPU\n- multiple node\n\nYou can use the following commands to test a dataset.\n\n```shell\n# single-gpu testing\npython tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] [--show]\n\n# multi-gpu testing\n./tools/dist_test.sh ${CONFIG_FILE} ${CHECKPOINT_FILE} ${GPU_NUM} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}]\n```\n\nOptional arguments:\n- `RESULT_FILE`: Filename of the output results in pickle format. If not specified, the results will not be saved to a file.\n- `EVAL_METRICS`: Items to be evaluated on the results. Allowed values depend on the dataset, e.g., `proposal_fast`, `proposal`, `bbox`, `segm` are available for COCO, `mAP`, `recall` for PASCAL VOC. Cityscapes could be evaluated by `cityscapes` as well as all COCO metrics.\n- `--show`: If specified, detection results will be plotted on the images and shown in a new window. It is only applicable to single GPU testing and used for debugging and visualization. Please make sure that GUI is available in your environment, otherwise you may encounter the error like `cannot connect to X server`.\n- `--show-dir`: If specified, detection results will be plotted on the images and saved to the specified directory. It is only applicable to single GPU testing and used for debugging and visualization. You do NOT need a GUI available in your environment for using this option.\n- `--show-score-thr`: If specified, detections with score below this threshold will be removed.\n\n\nExamples:\n\nAssume that you have already downloaded the checkpoints to the directory `checkpoints/`.\n\n1. Test Faster R-CNN and visualize the results. Press any key for the next image.\n\n```shell\npython tools/test.py configs/faster_rcnn_r50_fpn_1x_coco.py \\\n    checkpoints/faster_rcnn_r50_fpn_1x_20181010-3d1b3351.pth \\\n    --show\n```\n\n2. Test Faster R-CNN and save the painted images for latter visualization.\n\n```shell\npython tools/test.py configs/faster_rcnn_r50_fpn_1x.py \\\n    checkpoints/faster_rcnn_r50_fpn_1x_20181010-3d1b3351.pth \\\n    --show-dir faster_rcnn_r50_fpn_1x_results\n```\n\n3. Test Faster R-CNN on PASCAL VOC (without saving the test results) and evaluate the mAP.\n\n```shell\npython tools/test.py configs/pascal_voc/faster_rcnn_r50_fpn_1x_voc.py \\\n    checkpoints/SOME_CHECKPOINT.pth \\\n    --eval mAP\n```\n\n4. Test Mask R-CNN with 8 GPUs, and evaluate the bbox and mask AP.\n\n```shell\n./tools/dist_test.sh configs/mask_rcnn_r50_fpn_1x_coco.py \\\n    checkpoints/mask_rcnn_r50_fpn_1x_20181010-069fa190.pth \\\n    8 --out results.pkl --eval bbox segm\n```\n\n5. Test Mask R-CNN with 8 GPUs, and evaluate the **classwise** bbox and mask AP.\n\n```shell\n./tools/dist_test.sh configs/mask_rcnn_r50_fpn_1x_coco.py \\\n    checkpoints/mask_rcnn_r50_fpn_1x_20181010-069fa190.pth \\\n    8 --out results.pkl --eval bbox segm --options \"classwise=True\"\n```\n\n6. Test Mask R-CNN on COCO test-dev with 8 GPUs, and generate the json file to be submit to the official evaluation server.\n\n```shell\n./tools/dist_test.sh configs/mask_rcnn_r50_fpn_1x_coco.py \\\n    checkpoints/mask_rcnn_r50_fpn_1x_20181010-069fa190.pth \\\n    8 --format-only --options \"jsonfile_prefix=./mask_rcnn_test-dev_results\"\n```\n\nYou will get two json files `mask_rcnn_test-dev_results.bbox.json` and `mask_rcnn_test-dev_results.segm.json`.\n\n7. Test Mask R-CNN on Cityscapes test with 8 GPUs, and generate the txt and png files to be submit to the official evaluation server.\n\n```shell\n./tools/dist_test.sh configs/cityscapes/mask_rcnn_r50_fpn_1x_cityscapes.py \\\n    checkpoints/mask_rcnn_r50_fpn_1x_cityscapes_20200227-afe51d5a.pth \\\n    8  --format-only --options \"txtfile_prefix=./mask_rcnn_cityscapes_test_results\"\n```\n\nThe generated png and txt would be under `./mask_rcnn_cityscapes_test_results` directory.\n\n\n### Image demo\n\nWe provide a demo script to test a single image.\n\n```shell\npython demo/image_demo.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--score-thr ${SCORE_THR}]\n```\n\nExamples:\n\n```shell\npython demo/image_demo.py demo/demo.jpg configs/faster_rcnn_r50_fpn_1x_coco.py \\\n    checkpoints/faster_rcnn_r50_fpn_1x_20181010-3d1b3351.pth --device cpu\n```\n\n### Webcam demo\n\nWe provide a webcam demo to illustrate the results.\n\n```shell\npython demo/webcam_demo.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--camera-id ${CAMERA-ID}] [--score-thr ${SCORE_THR}]\n```\n\nExamples:\n\n```shell\npython demo/webcam_demo.py configs/faster_rcnn_r50_fpn_1x_coco.py \\\n    checkpoints/faster_rcnn_r50_fpn_1x_20181010-3d1b3351.pth\n```\n\n\n### High-level APIs for testing images\n\n#### Synchronous interface\nHere is an example of building the model and test given images.\n\n```python\nfrom mmdet.apis import init_detector, inference_detector\nimport mmcv\n\nconfig_file = 'configs/faster_rcnn_r50_fpn_1x_coco.py'\ncheckpoint_file = 'checkpoints/faster_rcnn_r50_fpn_1x_20181010-3d1b3351.pth'\n\n# build the model from a config file and a checkpoint file\nmodel = init_detector(config_file, checkpoint_file, device='cuda:0')\n\n# test a single image and show the results\nimg = 'test.jpg'  # or img = mmcv.imread(img), which will only load it once\nresult = inference_detector(model, img)\n# visualize the results in a new window\nmodel.show_result(img, result)\n# or save the visualization results to image files\nmodel.show_result(img, result, out_file='result.jpg')\n\n# test a video and show the results\nvideo = mmcv.VideoReader('video.mp4')\nfor frame in video:\n    result = inference_detector(model, frame)\n    model.show_result(frame, result, wait_time=1)\n```\n\nA notebook demo can be found in [demo/inference_demo.ipynb](https://github.com/open-mmlab/mmdetection/blob/master/demo/inference_demo.ipynb).\n\n#### Asynchronous interface - supported for Python 3.7+\n\nAsync interface allows not to block CPU on GPU bound inference code and enables better CPU/GPU utilization for single threaded application. Inference can be done concurrently either between different input data samples or between different models of some inference pipeline.\n\nSee `tests/async_benchmark.py` to compare the speed of synchronous and asynchronous interfaces.\n\n```python\nimport asyncio\nimport torch\nfrom mmdet.apis import init_detector, async_inference_detector\nfrom mmdet.utils.contextmanagers import concurrent\n\nasync def main():\n    config_file = 'configs/faster_rcnn_r50_fpn_1x_coco.py'\n    checkpoint_file = 'checkpoints/faster_rcnn_r50_fpn_1x_20181010-3d1b3351.pth'\n    device = 'cuda:0'\n    model = init_detector(config_file, checkpoint=checkpoint_file, device=device)\n\n    # queue is used for concurrent inference of multiple images\n    streamqueue = asyncio.Queue()\n    # queue size defines concurrency level\n    streamqueue_size = 3\n\n    for _ in range(streamqueue_size):\n        streamqueue.put_nowait(torch.cuda.Stream(device=device))\n\n    # test a single image and show the results\n    img = 'test.jpg'  # or img = mmcv.imread(img), which will only load it once\n\n    async with concurrent(streamqueue):\n        result = await async_inference_detector(model, img)\n\n    # visualize the results in a new window\n    model.show_result(img, result)\n    # or save the visualization results to image files\n    model.show_result(img, result, out_file='result.jpg')\n\n\nasyncio.run(main())\n\n```\n\n\n## Train a model\n\nMMDetection implements distributed training and non-distributed training,\nwhich uses `MMDistributedDataParallel` and `MMDataParallel` respectively.\n\nAll outputs (log files and checkpoints) will be saved to the working directory,\nwhich is specified by `work_dir` in the config file.\n\nBy default we evaluate the model on the validation set after each epoch, you can change the evaluation interval by adding the interval argument in the training config.\n```python\nevaluation = dict(interval=12)  # This evaluate the model per 12 epoch.\n```\n\n**\\*Important\\***: The default learning rate in config files is for 8 GPUs and 2 img/gpu (batch size = 8*2 = 16).\nAccording to the [Linear Scaling Rule](https://arxiv.org/abs/1706.02677), you need to set the learning rate proportional to the batch size if you use different GPUs or images per GPU, e.g., lr=0.01 for 4 GPUs * 2 img/gpu and lr=0.08 for 16 GPUs * 4 img/gpu.\n\n### Train with a single GPU\n\n```shell\npython tools/train.py ${CONFIG_FILE} [optional arguments]\n```\n\nIf you want to specify the working directory in the command, you can add an argument `--work_dir ${YOUR_WORK_DIR}`.\n\n### Train with multiple GPUs\n\n```shell\n./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments]\n```\n\nOptional arguments are:\n\n- `--no-validate` (**not suggested**): By default, the codebase will perform evaluation at every k (default value is 1, which can be modified like [this](https://github.com/open-mmlab/mmdetection/blob/master/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py#L174)) epochs during the training. To disable this behavior, use `--no-validate`.\n- `--work-dir ${WORK_DIR}`: Override the working directory specified in the config file.\n- `--resume-from ${CHECKPOINT_FILE}`: Resume from a previous checkpoint file.\n\nDifference between `resume-from` and `load-from`:\n`resume-from` loads both the model weights and optimizer status, and the epoch is also inherited from the specified checkpoint. It is usually used for resuming the training process that is interrupted accidentally.\n`load-from` only loads the model weights and the training epoch starts from 0. It is usually used for finetuning.\n\n### Train with multiple machines\n\nIf you run MMDetection on a cluster managed with [slurm](https://slurm.schedmd.com/), you can use the script `slurm_train.sh`. (This script also supports single machine training.)\n\n```shell\n[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR}\n```\n\nHere is an example of using 16 GPUs to train Mask R-CNN on the dev partition.\n\n```shell\nGPUS=16 ./tools/slurm_train.sh dev mask_r50_1x configs/mask_rcnn_r50_fpn_1x_coco.py /nfs/xxxx/mask_rcnn_r50_fpn_1x\n```\n\nYou can check [slurm_train.sh](https://github.com/open-mmlab/mmdetection/blob/master/tools/slurm_train.sh) for full arguments and environment variables.\n\nIf you have just multiple machines connected with ethernet, you can refer to\nPyTorch [launch utility](https://pytorch.org/docs/stable/distributed_deprecated.html#launch-utility).\nUsually it is slow if you do not have high speed networking like InfiniBand.\n\n### Launch multiple jobs on a single machine\n\nIf you launch multiple jobs on a single machine, e.g., 2 jobs of 4-GPU training on a machine with 8 GPUs,\nyou need to specify different ports (29500 by default) for each job to avoid communication conflict.\n\nIf you use `dist_train.sh` to launch training jobs, you can set the port in commands.\n\n```shell\nCUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4\nCUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4\n```\n\nIf you use launch training jobs with Slurm, you need to modify the config files (usually the 6th line from the bottom in config files) to set different communication ports.\n\nIn `config1.py`,\n```python\ndist_params = dict(backend='nccl', port=29500)\n```\n\nIn `config2.py`,\n```python\ndist_params = dict(backend='nccl', port=29501)\n```\n\nThen you can launch two jobs with `config1.py` ang `config2.py`.\n\n```shell\nCUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR}\nCUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR}\n```\n\n## Useful tools\n\nWe provide lots of useful tools under `tools/` directory.\n\n### Analyze logs\n\nYou can plot loss/mAP curves given a training log file. Run `pip install seaborn` first to install the dependency.\n\n![loss curve image](../demo/loss_curve.png)\n\n```shell\npython tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}]\n```\n\nExamples:\n\n- Plot the classification loss of some run.\n\n```shell\npython tools/analyze_logs.py plot_curve log.json --keys loss_cls --legend loss_cls\n```\n\n- Plot the classification and regression loss of some run, and save the figure to a pdf.\n\n```shell\npython tools/analyze_logs.py plot_curve log.json --keys loss_cls loss_bbox --out losses.pdf\n```\n\n- Compare the bbox mAP of two runs in the same figure.\n\n```shell\npython tools/analyze_logs.py plot_curve log1.json log2.json --keys bbox_mAP --legend run1 run2\n```\n\nYou can also compute the average training speed.\n\n```shell\npython tools/analyze_logs.py cal_train_time log.json [--include-outliers]\n```\n\nThe output is expected to be like the following.\n\n```\n-----Analyze train time of work_dirs/some_exp/20190611_192040.log.json-----\nslowest epoch 11, average time is 1.2024\nfastest epoch 1, average time is 1.1909\ntime std over epochs is 0.0028\naverage iter time: 1.1959 s/iter\n\n```\n\n### Get the FLOPs and params (experimental)\n\nWe provide a script adapted from [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) to compute the FLOPs and params of a given model.\n\n```shell\npython tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]\n```\n\nYou will get the result like this.\n\n```\n==============================\nInput shape: (3, 1280, 800)\nFlops: 239.32 GFLOPs\nParams: 37.74 M\n==============================\n```\n\n**Note**: This tool is still experimental and we do not guarantee that the number is correct. You may well use the result for simple comparisons, but double check it before you adopt it in technical reports or papers.\n\n(1) FLOPs are related to the input shape while parameters are not. The default input shape is (1, 3, 1280, 800).\n(2) Some operators are not counted into FLOPs like GN and custom operators. Refer to [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/flops_counter.py) for details.\n(3) The FLOPs of two-stage detectors is dependent on the number of proposals.\n\n### Publish a model\n\nBefore you upload a model to AWS, you may want to\n(1) convert model weights to CPU tensors, (2) delete the optimizer states and\n(3) compute the hash of the checkpoint file and append the hash id to the filename.\n\n```shell\npython tools/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}\n```\n\nE.g.,\n\n```shell\npython tools/publish_model.py work_dirs/faster_rcnn/latest.pth faster_rcnn_r50_fpn_1x_20190801.pth\n```\n\nThe final output filename will be `faster_rcnn_r50_fpn_1x_20190801-{hash id}.pth`.\n\n### Test the robustness of detectors\n\nPlease refer to [robustness_benchmarking.md](robustness_benchmarking.md).\n\n### Convert to ONNX (experimental)\n\nWe provide a script to convert model to [ONNX](https://github.com/onnx/onnx) format. The converted model could be visualized by tools like [Netron](https://github.com/lutzroeder/netron).\n\n```shell\npython tools/pytorch2onnx.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --out ${ONNX_FILE} [--shape ${INPUT_SHAPE}]\n```\n\n**Note**: This tool is still experimental. Customized operators are not supported for now. We set `use_torchvision=True` on-the-fly for `RoIPool` and `RoIAlign`.\n\n## Tutorials\n\nCurrently, we provide four tutorials for users to [finetune models](tutorials/finetune.md), [add new dataset](tutorials/new_dataset.md), [design data pipeline](tutorials/data_pipeline.md) and [add new modules](tutorials/new_modules.md).\nWe also provide a full description about the [config system](config.md).\n"
  },
  {
    "path": "code/docs/index.rst",
    "content": "Welcome to MMDetection's documentation!\n=======================================\n\n.. toctree::\n   :maxdepth: 2\n\n   install.md\n   getting_started.md\n   config.md\n   model_zoo.md\n   tutorials/finetune.md\n   tutorials/new_dataset.md\n   tutorials/data_pipeline.md\n   tutorials/new_modules.md\n   compatibility.md\n   changelog.md\n   projects.md\n   api.rst\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`search`\n"
  },
  {
    "path": "code/docs/install.md",
    "content": "## Installation\n\n### Requirements\n\n- Linux or macOS (Windows is not currently officially supported)\n- Python 3.6+\n- PyTorch 1.3+\n- CUDA 9.2+ (If you build PyTorch from source, CUDA 9.0 is also compatible)\n- GCC 5+\n- [mmcv](https://github.com/open-mmlab/mmcv)\n\n\n### Install mmdetection\n\na. Create a conda virtual environment and activate it.\n\n```shell\nconda create -n open-mmlab python=3.7 -y\nconda activate open-mmlab\n```\n\nb. Install PyTorch and torchvision following the [official instructions](https://pytorch.org/), e.g.,\n\n```shell\nconda install pytorch torchvision -c pytorch\n```\n\nNote: Make sure that your compilation CUDA version and runtime CUDA version match.\nYou can check the supported CUDA version for precompiled packages on the [PyTorch website](https://pytorch.org/).\n\n`E.g.1` If you have CUDA 10.1 installed under `/usr/local/cuda` and would like to install\nPyTorch 1.5, you need to install the prebuilt PyTorch with CUDA 10.1.\n\n```python\nconda install pytorch cudatoolkit=10.1 torchvision -c pytorch\n```\n\n`E.g. 2` If you have CUDA 9.2 installed under `/usr/local/cuda` and would like to install\nPyTorch 1.3.1., you need to install the prebuilt PyTorch with CUDA 9.2.\n\n```python\nconda install pytorch=1.3.1 cudatoolkit=9.2 torchvision=0.4.2 -c pytorch\n```\n\nIf you build PyTorch from source instead of installing the prebuilt pacakge,\nyou can use more CUDA versions such as 9.0.\n\nc. Clone the mmdetection repository.\n\n```shell\ngit clone https://github.com/Scalsol/RepPointsV2.git\ncd RepPointsV2\n```\n\nd. Install build requirements and then install mmdetection.\n(We install our forked version of pycocotools via the github repo instead of pypi\nfor better compatibility with our repo.)\n\n```shell\npip install -r requirements/build.txt\npip install \"git+https://github.com/open-mmlab/cocoapi.git#subdirectory=pycocotools\"\npip install -v -e .  # or \"python setup.py develop\"\n```\n\nIf you build mmdetection on macOS, replace the last command with\n\n```\nCC=clang CXX=clang++ CFLAGS='-stdlib=libc++' pip install -e .\n```\n\nNote:\n\n1. The git commit id will be written to the version number with step d, e.g. 0.6.0+2e7045c. The version will also be saved in trained models.\nIt is recommended that you run step d each time you pull some updates from github. If C++/CUDA codes are modified, then this step is compulsory.\n\n    > Important: Be sure to remove the `./build` folder if you reinstall mmdet with a different CUDA/PyTorch version.\n\n    ```\n    pip uninstall mmdet\n    rm -rf ./build\n    find . -name \"*.so\" | xargs rm\n    ```\n\n2. Following the above instructions, mmdetection is installed on `dev` mode, any local modifications made to the code will take effect without the need to reinstall it (unless you submit some commits and want to update the version number).\n\n3. If you would like to use `opencv-python-headless` instead of `opencv-python`,\nyou can install it before installing MMCV.\n\n4. Some dependencies are optional. Simply running `pip install -v -e .` will only install the minimum runtime requirements. To use optional dependencies like `albumentations` and `imagecorruptions` either install them manually with `pip install -r requirements/optional.txt` or specify desired extras when calling `pip` (e.g. `pip install -v -e .[optional]`). Valid keys for the extras field are: `all`, `tests`, `build`, and `optional`.\n\n### Install with CPU only\nThe code can be built for CPU only environment (where CUDA isn't available).\n\nIn CPU mode you can run the demo/webcam_demo.py for example.\nHowever some functionality is gone in this mode:\n\n- Deformable Convolution\n- Deformable ROI pooling\n- CARAFE: Content-Aware ReAssembly of FEatures\n- nms_cuda\n- sigmoid_focal_loss_cuda\n\nSo if you try to run inference with a model containing deformable convolution you will get an error.\nNote: We set `use_torchvision=True` on-the-fly in CPU mode for `RoIPool` and `RoIAlign`\n\n### Another option: Docker Image\n\nWe provide a [Dockerfile](https://github.com/open-mmlab/mmdetection/blob/master/docker/Dockerfile) to build an image.\n\n```shell\n# build an image with PyTorch 1.5, CUDA 10.1\ndocker build -t mmdetection docker/\n```\n\nRun it with\n\n```shell\ndocker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmdetection/data mmdetection\n```\n\n### A from-scratch setup script\n\nHere is a full script for setting up mmdetection with conda.\n\n```shell\nconda create -n open-mmlab python=3.7 -y\nconda activate open-mmlab\n\n# install latest pytorch prebuilt with the default prebuilt CUDA version (usually the latest)\nconda install -c pytorch pytorch torchvision -y\ngit clone https://github.com/open-mmlab/mmdetection.git\ncd mmdetection\npip install -r requirements/build.txt\npip install \"git+https://github.com/open-mmlab/cocoapi.git#subdirectory=pycocotools\"\npip install -v -e .\n```\n\n### Using multiple MMDetection versions\n\nThe train and test scripts already modify the `PYTHONPATH` to ensure the script use the MMDetection in the current directory.\n\nTo use the default MMDetection installed in the environment rather than that you are working with, you can remove the following line in those scripts\n\n```shell\nPYTHONPATH=\"$(dirname $0)/..\":$PYTHONPATH\n```\n"
  },
  {
    "path": "code/docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=.\nset BUILDDIR=_build\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\n\n:end\npopd\n"
  },
  {
    "path": "code/docs/model_zoo.md",
    "content": "# Benchmark and Model Zoo\n\n## Mirror sites\n\nWe use AWS as the main site to host our model zoo, and maintain a mirror on aliyun.\nYou can replace `https://s3.ap-northeast-2.amazonaws.com/open-mmlab` with `https://open-mmlab.oss-cn-beijing.aliyuncs.com` in model urls.\n\n## Common settings\n\n- All models were trained on `coco_2017_train`, and tested on the `coco_2017_val`.\n- We use distributed training.\n- All pytorch-style pretrained backbones on ImageNet are from PyTorch model zoo, caffe-style pretrained backbones are converted from the newly released model from detectron2.\n- For fair comparison with other codebases, we report the GPU memory as the maximum value of `torch.cuda.max_memory_allocated()` for all 8 GPUs. Note that this value is usually less than what `nvidia-smi` shows.\n- We report the inference time as the total time of network forwarding and post-processing, excluding the data loading time. Results are obtained with the script [benchmark.py](https://github.com/open-mmlab/mmdetection/blob/master/tools/benchmark.py) which computes the average time on 2000 images.\n\n\n## Baselines\n\n### RPN\n\nPlease refer to [RPN](https://github.com/open-mmlab/mmdetection/blob/master/configs/rpn) for details.\n\n### Faster R-CNN\n\nPlease refer to [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/faster_rcnn) for details.\n\n### Mask R-CNN\n\nPlease refer to [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/mask_rcnn) for details.\n\n### Fast R-CNN (with pre-computed proposals)\n\nPlease refer to [Fast R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/fast_rcnn) for details.\n\n### RetinaNet\n\nPlease refer to [RetinaNet](https://github.com/open-mmlab/mmdetection/blob/master/configs/retinanet) for details.\n\n### Cascade R-CNN and Cascade Mask R-CNN\n\nPlease refer to [Cascade R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/cascade_rcnn) for details.\n\n### Hybrid Task Cascade (HTC)\n\nPlease refer to [HTC](https://github.com/open-mmlab/mmdetection/blob/master/configs/htc) for details.\n\n### SSD\n\nPlease refer to [SSD](https://github.com/open-mmlab/mmdetection/blob/master/configs/ssd) for details.\n\n### Group Normalization (GN)\n\nPlease refer to [Group Normalization](https://github.com/open-mmlab/mmdetection/blob/master/configs/gn) for details.\n\n### Weight Standardization\n\nPlease refer to [Weight Standardization](https://github.com/open-mmlab/mmdetection/blob/master/configs/gn+ws) for details.\n\n### Deformable Convolution v2\n\nPlease refer to [Deformable Convolutional Networks](https://github.com/open-mmlab/mmdetection/blob/master/configs/dcn) for details.\n\n### CARAFE: Content-Aware ReAssembly of FEatures\nPlease refer to [CARAFE](https://github.com/open-mmlab/mmdetection/blob/master/configs/carafe) for details.\n\n### Instaboost\n\nPlease refer to [Instaboost](https://github.com/open-mmlab/mmdetection/blob/master/configs/instaboost) for details.\n\n### Libra R-CNN\n\nPlease refer to [Libra R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/libra_rcnn) for details.\n\n### Guided Anchoring\n\nPlease refer to [Guided Anchoring](https://github.com/open-mmlab/mmdetection/blob/master/configs/guided_anchoring) for details.\n\n### FCOS\n\nPlease refer to [FCOS](https://github.com/open-mmlab/mmdetection/blob/master/configs/fcos) for details.\n\n### FoveaBox\n\nPlease refer to [FoveaBox](https://github.com/open-mmlab/mmdetection/blob/master/configs/foveabox) for details.\n\n### RepPoints\n\nPlease refer to [RepPoints](https://github.com/open-mmlab/mmdetection/blob/master/configs/reppoints) for details.\n\n### FreeAnchor\n\nPlease refer to [FreeAnchor](https://github.com/open-mmlab/mmdetection/blob/master/configs/free_anchor) for details.\n\n### Grid R-CNN (plus)\n\nPlease refer to [Grid R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/grid_rcnn) for details.\n\n### GHM\n\nPlease refer to [GHM](https://github.com/open-mmlab/mmdetection/blob/master/configs/ghm) for details.\n\n### GCNet\n\nPlease refer to [GCNet](https://github.com/open-mmlab/mmdetection/blob/master/configs/gcnet) for details.\n\n### HRNet\nPlease refer to [HRNet](https://github.com/open-mmlab/mmdetection/blob/master/configs/hrnet) for details.\n\n### Mask Scoring R-CNN\n\nPlease refer to [Mask Scoring R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/ms_rcnn) for details.\n\n### Train from Scratch\n\nPlease refer to [Rethinking ImageNet Pre-training](https://github.com/open-mmlab/mmdetection/blob/master/configs/scratch) for details.\n\n### NAS-FPN\nPlease refer to [NAS-FPN](https://github.com/open-mmlab/mmdetection/blob/master/configs/nas_fpn) for details.\n\n### ATSS\nPlease refer to [ATSS](https://github.com/open-mmlab/mmdetection/blob/master/configs/atss) for details.\n\n### FSAF\nPlease refer to [FSAF](https://github.com/open-mmlab/mmdetection/blob/master/configs/fsaf) for details.\n\n### RegNetX\nPlease refer to [RegNet](https://github.com/open-mmlab/mmdetection/blob/master/configs/regnet) for details.\n\n### Res2Net\nPlease refer to [Res2Net](https://github.com/open-mmlab/mmdetection/blob/master/configs/res2net) for details.\n\n### GRoIE\nPlease refer to [GRoIE](https://github.com/open-mmlab/mmdetection/blob/master/configs/groie) for details.\n\n### Dynamic R-CNN\nPlease refer to [Dynamic R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/dynamic_rcnn) for details.\n\n### PointRend\nPlease refer to [PointRend](https://github.com/open-mmlab/mmdetection/blob/master/configs/point_rend) for details.\n\n### DetectoRS\nPlease refer to [DetectoRS](https://github.com/open-mmlab/mmdetection/blob/master/configs/detectors) for details.\n\n### Generalized Focal Loss\nPlease refer to [Generalized Focal Loss](https://github.com/open-mmlab/mmdetection/blob/master/configs/gfl) for details.\n\n### Other datasets\n\nWe also benchmark some methods on [PASCAL VOC](https://github.com/open-mmlab/mmdetection/blob/master/configs/pascal_voc), [Cityscapes](https://github.com/open-mmlab/mmdetection/blob/master/configs/cityscapes) and [WIDER FACE](https://github.com/open-mmlab/mmdetection/blob/master/configs/wider_face).\n\n### Pre-trained Models\n\nWe also train [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/faster_rcnn) and [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/mask_rcnn) using ResNet-50 and [RegNetX-3.2G](https://github.com/open-mmlab/mmdetection/blob/master/configs/regnet) with multi-scale training and longer schedules. These models serve as strong pre-trained models for downstream tasks for convenience.\n\n## Speed benchmark\nWe compare the training speed of Mask R-CNN with some other popular frameworks (The data is copied from [detectron2](https://github.com/facebookresearch/detectron2/blob/master/docs/notes/benchmarks.md)).\nFor mmdetection, we benchmark with [mask_rcnn_r50_caffe_fpn_poly_1x_coco_v1.py](https://github.com/open-mmlab/mmdetection/blob/master/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_poly_1x_coco_v1.py), which should have the same setting with [mask_rcnn_R_50_FPN_noaug_1x.yaml](https://github.com/facebookresearch/detectron2/blob/master/configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml) of detectron2.\nWe also provide the [checkpoint](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug_compare_20200518-10127928.pth) and [training log](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug_20200518_105755.log.json) for reference. The throughput is computed as the average throughput in iterations 100-500 to skip GPU warmup time.\n\n| Implementation       | Throughput (img/s) |\n|----------------------|--------------------|\n| [Detectron2](https://github.com/facebookresearch/detectron2) | 62 |\n| [MMDetection](https://github.com/open-mmlab/mmdetection) | 61 |\n| [maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark/)   | 53 |\n| [tensorpack](https://github.com/tensorpack/tensorpack/tree/master/examples/FasterRCNN) | 50 |\n| [simpledet](https://github.com/TuSimple/simpledet/) | 39 |\n| [Detectron](https://github.com/facebookresearch/Detectron) | 19 |\n| [matterport/Mask_RCNN](https://github.com/matterport/Mask_RCNN/) | 14 |\n\n## Comparison with Detectron2\n\nWe compare mmdetection with [Detectron2](https://github.com/facebookresearch/detectron2.git) in terms of speed and performance.\nWe use the commit id [185c27e](https://github.com/facebookresearch/detectron2/tree/185c27e4b4d2d4c68b5627b3765420c6d7f5a659)(30/4/2020) of detectron.\nFor fair comparison, we install and run both frameworks on the same machine.\n\n### Hardware\n\n- 8 NVIDIA Tesla V100 (32G) GPUs\n- Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz\n\n### Software environment\n\n- Python 3.7\n- PyTorch 1.4\n- CUDA 10.1\n- CUDNN 7.6.03\n- NCCL 2.4.08\n\n### Performance\n\n| Type         | Lr schd | Detectron2  | mmdetection | Download |\n|--------------|---------|-------------|-------------|-------------|\n| [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco.py) | 1x      | [37.9](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-Detection/faster_rcnn_R_50_FPN_1x.yaml)        | 38.0        | [model](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco-5324cff8.pth) &#124; [log](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco_20200429_234554.log.json) |\n| [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/master/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco.py)   | 1x      | [38.6 & 35.2](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml) | 38.8 & 35.4 | [model](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco-dbecf295.pth) &#124; [log](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco_20200430_054239.log.json) |\n| [Retinanet](https://github.com/open-mmlab/mmdetection/blob/master/configs/retinanet/retinanet_r50_caffe_fpn_mstrain_1x_coco.py)    | 1x      | [36.5](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-Detection/retinanet_R_50_FPN_1x.yaml)        | 37.0        | [model](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/retinanet_r50_caffe_fpn_mstrain_1x_coco/retinanet_r50_caffe_fpn_mstrain_1x_coco-586977a0.pth) &#124; [log](https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmdetection/v2.0/benchmark/retinanet_r50_caffe_fpn_mstrain_1x_coco/retinanet_r50_caffe_fpn_mstrain_1x_coco_20200430_014748.log.json) |\n\n\n### Training Speed\n\nThe training speed is measure with s/iter. The lower, the better.\n\n| Type         | Detectron2 | mmdetection |\n|--------------|------------|-------------|\n| Faster R-CNN | 0.210      | 0.216       |\n| Mask R-CNN   | 0.261      | 0.265       |\n| Retinanet    | 0.200      | 0.205       |\n\n\n### Inference Speed\n\nThe inference speed is measured with fps (img/s) on a single GPU, the higher, the better.\nTo be consistent with Detectron2, we report the pure inference speed (without the time of data loading).\nFor Mask R-CNN, we exclude the time of RLE encoding in post-processing.\nWe also include the officially reported speed in the parentheses, which is slightly higher\nthan the results tested on our server due to differences of hardwares.\n\n| Type         | Detectron2  | mmdetection |\n|--------------|-------------|-------------|\n| Faster R-CNN | 25.6 (26.3) | 22.2        |\n| Mask R-CNN   | 22.5 (23.3) | 19.6        |\n| Retinanet    | 17.8 (18.2) | 20.6        |\n\n### Training memory\n\n| Type         | Detectron2 | mmdetection |\n|--------------|------------|-------------|\n| Faster R-CNN | 3.0        | 3.8         |\n| Mask R-CNN   | 3.4        | 3.9         |\n| Retinanet    | 3.9        | 3.4         |\n"
  },
  {
    "path": "code/docs/projects.md",
    "content": "# Projects\n\nThere are many projects built upon MMDetection.\nSome of them are published in top-tier conferences (CVPR, ICCV, and ECCV), the others are also highly influential.\nWe list some of them as examples of how to extend MMDetection for your own projects.\nPull requests are also welcomed.\n\nTo make this list also a reference for the community to develop and compare new object detection algorithms, we list them following the time order of top-tier conferences.\nMethods already supported and maintained by MMDetection are not listed.\n\n- Overcoming Classifier Imbalance for Long-tail Object Detection with Balanced Group Softmax, CVPR2020. [[paper]](http://openaccess.thecvf.com/content_CVPR_2020/papers/Li_Overcoming_Classifier_Imbalance_for_Long-Tail_Object_Detection_With_Balanced_Group_CVPR_2020_paper.pdf)[[github]](https://github.com/FishYuLi/BalancedGroupSoftmax)\n- Coherent Reconstruction of Multiple Humans from a Single Image, CVPR2020. [[paper]](https://jiangwenpl.github.io/multiperson/)[[github]](https://github.com/JiangWenPL/multiperson)\n- Look-into-Object: Self-supervised Structure Modeling for Object Recognition, CVPR 2020. [[paper]](http://openaccess.thecvf.com/content_CVPR_2020/papers/Zhou_Look-Into-Object_Self-Supervised_Structure_Modeling_for_Object_Recognition_CVPR_2020_paper.pdf)[[github]](https://github.com/JDAI-CV/LIO)\n- Video Panoptic Segmentation, CVPR2020. [[paper]](https://arxiv.org/abs/2006.11339)[[github]](https://github.com/mcahny/vps)\n- D2Det: Towards High Quality Object Detection and Instance Segmentation, CVPR2020. [[paper]](http://openaccess.thecvf.com/content_CVPR_2020/html/Cao_D2Det_Towards_High_Quality_Object_Detection_and_Instance_Segmentation_CVPR_2020_paper.html)[[github]](https://github.com/JialeCao001/D2Det)\n- CentripetalNet: Pursuing High-quality Keypoint Pairs for Object Detection, CVPR2020. [[paper]](https://arxiv.org/abs/2003.09119)[[github]](https://github.com/KiveeDong/CentripetalNet)\n- Learning a Unified Sample Weighting Network for Object Detection, CVPR 2020. [[paper]](http://openaccess.thecvf.com/content_CVPR_2020/html/Cai_Learning_a_Unified_Sample_Weighting_Network_for_Object_Detection_CVPR_2020_paper.html)[[github]](https://github.com/caiqi/sample-weighting-network)\n- Scale-equalizing Pyramid Convolution for Object Detection, CVPR2020. [[paper]](https://arxiv.org/abs/2005.03101) [[github]](https://github.com/jshilong/SEPC)\n- Revisiting the Sibling Head in Object Detector, CVPR2020. [[paper]](https://arxiv.org/abs/2003.07540)[[github]](https://github.com/Sense-X/TSD)\n- PolarMask: Single Shot Instance Segmentation with Polar Representation, CVPR2020. [[paper]](https://arxiv.org/abs/1909.13226)[[github]](https://github.com/xieenze/PolarMask)\n- Hit-Detector: Hierarchical Trinity Architecture Search for Object Detection, CVPR2020. [[paper]](https://arxiv.org/abs/2003.11818)[[github]](https://github.com/ggjy/HitDet.pytorch)\n- ZeroQ: A Novel Zero Shot Quantization Framework, CVPR2020. [[paper]](https://arxiv.org/abs/2001.00281)[[github]](https://github.com/amirgholami/ZeroQ)\n- CBNet: A Novel Composite Backbone Network Architecture for Object Detection, AAAI2020. [[paper]](https://aaai.org/Papers/AAAI/2020GB/AAAI-LiuY.1833.pdf)[[github]](https://github.com/VDIGPKU/CBNet)\n- RDSNet: A New Deep Architecture for Reciprocal Object Detection and Instance Segmentation, AAAI2020. [[paper]](https://arxiv.org/abs/1912.05070)[[github]](https://github.com/wangsr126/RDSNet)\n- Training-Time-Friendly Network for Real-Time Object Detection, AAAI2020. [[paper]](https://arxiv.org/abs/1909.00700)[[github]](https://github.com/ZJULearning/ttfnet)\n- Cascade RPN: Delving into High-Quality Region Proposal Network with Adaptive Convolution, NeurIPS 2019. [[paper]](https://arxiv.org/abs/1909.06720)[[github]](https://github.com/thangvubk/Cascade-RPN)\n- Reasoning R-CNN: Unifying Adaptive Global Reasoning into Large-scale Object Detection, CVPR2019. [[paper]](http://openaccess.thecvf.com/content_CVPR_2019/papers/Xu_Reasoning-RCNN_Unifying_Adaptive_Global_Reasoning_Into_Large-Scale_Object_Detection_CVPR_2019_paper.pdf)[[github]](https://github.com/chanyn/Reasoning-RCNN)\n- Learning RoI Transformer for Oriented Object Detection in Aerial Images, CVPR2019. [[paper]](https://arxiv.org/abs/1812.00155)[[github]](https://github.com/dingjiansw101/AerialDetection)\n- SOLO: Segmenting Objects by Locations. [[paper]](https://arxiv.org/abs/1912.04488)[[github]](https://github.com/WXinlong/SOLO)\n- SOLOv2: Dynamic, Faster and Stronger. [[paper]](https://arxiv.org/abs/2003.10152)[[github]](https://github.com/WXinlong/SOLO)\n- Dense Peppoints: Representing Visual Objects with Dense Point Sets. [[paper]](https://arxiv.org/abs/1912.11473)[[github]](https://github.com/justimyhxu/Dense-RepPoints)\n- IterDet: Iterative Scheme for Object Detection in Crowded Environments. [[paper]](https://arxiv.org/abs/2005.05708)[[github]](https://github.com/saic-vul/iterdet)\n- Cross-Iteration Batch Normalization. [[paper]](https://arxiv.org/abs/2002.05712)[[github]](https://github.com/Howal/Cross-iterationBatchNorm)\n-  DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution. [[paper]](https://arxiv.org/abs/2006.02334)[[github]](https://github.com/joe-siyuan-qiao/DetectoRS)\n-  Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection. [[paper]](https://arxiv.org/abs/2006.04388v1)[[github]](https://github.com/implus/GFocal)\n- Pedestrian Detection: The Elephant In The Room. [[paper]](https://arxiv.org/abs/2003.08799)[[github]](https://github.com/hasanirtiza/Pedestron)\n"
  },
  {
    "path": "code/docs/robustness_benchmarking.md",
    "content": "# Corruption Benchmarking\n\n## Introduction\n\nWe provide tools to test object detection and instance segmentation models on the image corruption benchmark defined in [Benchmarking Robustness in Object Detection: Autonomous Driving when Winter is Coming](https://arxiv.org/abs/1907.07484).\nThis page provides basic tutorials how to use the benchmark.\n\n```\n@article{michaelis2019winter,\n  title={Benchmarking Robustness in Object Detection:\n    Autonomous Driving when Winter is Coming},\n  author={Michaelis, Claudio and Mitzkus, Benjamin and\n    Geirhos, Robert and Rusak, Evgenia and\n    Bringmann, Oliver and Ecker, Alexander S. and\n    Bethge, Matthias and Brendel, Wieland},\n  journal={arXiv:1907.07484},\n  year={2019}\n}\n```\n\n![image corruption example](../demo/corruptions_sev_3.png)\n\n## About the benchmark\n\nTo submit results to the benchmark please visit the [benchmark homepage](https://github.com/bethgelab/robust-detection-benchmark)\n\nThe benchmark is modelled after the [imagenet-c benchmark](https://github.com/hendrycks/robustness) which was originally\npublished in [Benchmarking Neural Network Robustness to Common Corruptions and Perturbations](https://arxiv.org/abs/1903.12261) (ICLR 2019) by Dan Hendrycks and Thomas Dietterich.\n\nThe image corruption functions are included in this library but can be installed separately using:\n\n```shell\npip install imagecorruptions\n```\n\nCompared to imagenet-c a few changes had to be made to handle images of arbitrary size and greyscale images.\nWe also modfied the 'motion blur' and 'snow' corruptions to remove dependency from a linux specific library,\nwhich would have to be installed separately otherwise. For details please refer to the [imagecorruptions repository](https://github.com/bethgelab/imagecorruptions).\n\n## Inference with pretrained models\n\nWe provide a testing script to evaluate a models performance on any combination of the corruptions provided in the benchmark.\n\n### Test a dataset\n\n- [x] single GPU testing\n- [ ] multiple GPU testing\n- [ ] visualize detection results\n\nYou can use the following commands to test a models performance under the 15 corruptions used in the benchmark.\n\n```shell\n# single-gpu testing\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}]\n```\n\nAlternatively different group of corruptions can be selected.\n\n```shell\n# noise\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions noise\n\n# blur\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions blur\n\n# wetaher\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions weather\n\n# digital\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions digital\n```\n\nOr a costom set of corruptions e.g.:\n```shell\n# gaussian noise, zoom blur and snow\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions gaussian_noise zoom_blur snow\n```\n\nFinally the corruption severities to evaluate can be chosen.\nSeverity 0 corresponds to clean data and the effect increases from 1 to 5.\n\n```shell\n# severity 1\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --severities 1\n\n# severities 0,2,4\npython tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --severities 0 2 4\n```\n\n## Results for modelzoo models\n\nThe results on COCO 2017val are shown in the below table.\n\nModel  | Backbone  | Style   | Lr schd | box AP clean | box AP corr. | box % | mask AP clean | mask AP corr. | mask % |\n:-----:|:---------:|:-------:|:-------:|:------------:|:------------:|:-----:|:-------------:|:-------------:|:------:|\nFaster R-CNN | R-50-FPN  | pytorch | 1x      | 36.3   | 18.2         | 50.2  | -             | -             | -      |\nFaster R-CNN | R-101-FPN | pytorch | 1x      | 38.5   | 20.9         | 54.2  | -             | -             | -      |\nFaster R-CNN | X-101-32x4d-FPN | pytorch |1x | 40.1   | 22.3         | 55.5  | -             | -             | -      |\nFaster R-CNN | X-101-64x4d-FPN | pytorch |1x | 41.3   | 23.4         | 56.6  | -             | -             | -      |\nFaster R-CNN | R-50-FPN-DCN | pytorch | 1x   | 40.0   | 22.4         | 56.1  | -             | -             | -      |\nFaster R-CNN | X-101-32x4d-FPN-DCN | pytorch | 1x | 43.4 | 26.7      | 61.6  | -             | -             | -      |\nMask R-CNN   | R-50-FPN  | pytorch | 1x      | 37.3   | 18.7         | 50.1  | 34.2          | 16.8          | 49.1   |\nMask R-CNN   | R-50-FPN-DCN | pytorch | 1x   | 41.1   | 23.3         | 56.7  | 37.2          | 20.7          | 55.7   |\nCascade R-CNN | R-50-FPN  | pytorch | 1x     | 40.4   | 20.1         | 49.7  | -             | -             | -      |\nCascade Mask R-CNN | R-50-FPN  | pytorch | 1x| 41.2   | 20.7         | 50.2  | 35.7          | 17.6          | 49.3   |\nRetinaNet    | R-50-FPN  | pytorch | 1x      | 35.6   | 17.8         | 50.1  | -             | -             | -      |\nHybrid Task Cascade | X-101-64x4d-FPN-DCN | pytorch | 1x | 50.6 | 32.7 | 64.7 | 43.8         | 28.1          | 64.0   |\n\nResults may vary slightly due to the stochastic application of the corruptions.\n"
  },
  {
    "path": "code/docs/tutorials/data_pipeline.md",
    "content": "# Tutorial 3: Custom Data Pipelines\n\n## Design of Data pipelines\n\nFollowing typical conventions, we use `Dataset` and `DataLoader` for data loading\nwith multiple workers. `Dataset` returns a dict of data items corresponding\nthe arguments of models' forward method.\nSince the data in object detection may not be the same size (image size, gt bbox size, etc.),\nwe introduce a new `DataContainer` type in MMCV to help collect and distribute\ndata of different size.\nSee [here](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py) for more details.\n\nThe data preparation pipeline and the dataset is decomposed. Usually a dataset\ndefines how to process the annotations and a data pipeline defines all the steps to prepare a data dict.\nA pipeline consists of a sequence of operations. Each operation takes a dict as input and also output a dict for the next transform.\n\nWe present a classical pipeline in the following figure. The blue blocks are pipeline operations. With the pipeline going on, each operator can add new keys (marked as green) to the result dict or update the existing keys (marked as orange).\n![pipeline figure](../../demo/data_pipeline.png)\n\nThe operations are categorized into data loading, pre-processing, formatting and test-time augmentation.\n\nHere is an pipeline example for Faster R-CNN.\n```python\nimg_norm_cfg = dict(\n    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\ntrain_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(type='LoadAnnotations', with_bbox=True),\n    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n    dict(type='RandomFlip', flip_ratio=0.5),\n    dict(type='Normalize', **img_norm_cfg),\n    dict(type='Pad', size_divisor=32),\n    dict(type='DefaultFormatBundle'),\n    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),\n]\ntest_pipeline = [\n    dict(type='LoadImageFromFile'),\n    dict(\n        type='MultiScaleFlipAug',\n        img_scale=(1333, 800),\n        flip=False,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ])\n]\n```\n\nFor each operation, we list the related dict fields that are added/updated/removed.\n\n### Data loading\n\n`LoadImageFromFile`\n- add: img, img_shape, ori_shape\n\n`LoadAnnotations`\n- add: gt_bboxes, gt_bboxes_ignore, gt_labels, gt_masks, gt_semantic_seg, bbox_fields, mask_fields\n\n`LoadProposals`\n- add: proposals\n\n### Pre-processing\n\n`Resize`\n- add: scale, scale_idx, pad_shape, scale_factor, keep_ratio\n- update: img, img_shape, *bbox_fields, *mask_fields, *seg_fields\n\n`RandomFlip`\n- add: flip\n- update: img, *bbox_fields, *mask_fields, *seg_fields\n\n`Pad`\n- add: pad_fixed_size, pad_size_divisor\n- update: img, pad_shape, *mask_fields, *seg_fields\n\n`RandomCrop`\n- update: img, pad_shape, gt_bboxes, gt_labels, gt_masks, *bbox_fields\n\n`Normalize`\n- add: img_norm_cfg\n- update: img\n\n`SegRescale`\n- update: gt_semantic_seg\n\n`PhotoMetricDistortion`\n- update: img\n\n`Expand`\n- update: img, gt_bboxes\n\n`MinIoURandomCrop`\n- update: img, gt_bboxes, gt_labels\n\n`Corrupt`\n- update: img\n\n### Formatting\n\n`ToTensor`\n- update: specified by `keys`.\n\n`ImageToTensor`\n- update: specified by `keys`.\n\n`Transpose`\n- update: specified by `keys`.\n\n`ToDataContainer`\n- update: specified by `fields`.\n\n`DefaultFormatBundle`\n- update: img, proposals, gt_bboxes, gt_bboxes_ignore, gt_labels, gt_masks, gt_semantic_seg\n\n`Collect`\n- add: img_meta (the keys of img_meta is specified by `meta_keys`)\n- remove: all other keys except for those specified by `keys`\n\n### Test time augmentation\n\n`MultiScaleFlipAug`\n\n## Extend and use custom pipelines\n\n1. Write a new pipeline in any file, e.g., `my_pipeline.py`. It takes a dict as input and return a dict.\n\n    ```python\n    from mmdet.datasets import PIPELINES\n\n    @PIPELINES.register_module()\n    class MyTransform:\n\n        def __call__(self, results):\n            results['dummy'] = True\n            return results\n    ```\n\n2. Import the new class.\n\n    ```python\n    from .my_pipeline import MyTransform\n    ```\n\n3. Use it in config files.\n\n    ```python\n    img_norm_cfg = dict(\n        mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)\n    train_pipeline = [\n        dict(type='LoadImageFromFile'),\n        dict(type='LoadAnnotations', with_bbox=True),\n        dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),\n        dict(type='RandomFlip', flip_ratio=0.5),\n        dict(type='Normalize', **img_norm_cfg),\n        dict(type='Pad', size_divisor=32),\n        dict(type='MyTransform'),\n        dict(type='DefaultFormatBundle'),\n        dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),\n    ]\n    ```\n"
  },
  {
    "path": "code/docs/tutorials/finetune.md",
    "content": "# Tutorial 1: Finetuning Models\n\nDetectors pre-trained on the COCO dataset can serve as a good pre-trained model for other datasets, e.g., CityScapes and KITTI Dataset.\nThis tutorial provides instruction for users to use the models provided in the [Model Zoo](../model_zoo.md) for other datasets to obtain better performance.\n\nThere are two steps to finetune a model on a new dataset.\n- Add support for the new dataset following [Tutorial 2: Adding New Dataset](new_dataset.md).\n- Modify the configs as will be discussed in this tutorial.\n\n\nTake the finetuning process on Cityscapes Dataset as an example, the users need to modify five parts in the config.\n\n## Inherit base configs\nTo release the burden and reduce bugs in writing the whole configs, MMDetection V2.0 support inheriting configs from multiple existing configs. To finetune a Mask RCNN model, the new config needs to inherit\n`_base_/models/mask_rcnn_r50_fpn.py` to build the basic structure of the model. To use the Cityscapes Dataset, the new config can also simply inherit `_base_/datasets/cityscapes_instance.py`. For runtime settings such as training schedules, the new config needs to inherit `_base_/default_runtime.py`. This configs are in the `configs` directory and the users can also choose to write the whole contents rather than use inheritance.\n\n```python\n_base_ = [\n    '../_base_/models/mask_rcnn_r50_fpn.py',\n    '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py'\n]\n```\n\n## Modify head\nThen the new config needs to modify the head according to the class numbers of the new datasets. By only changing `num_classes` in the roi_head, the weights of the pre-trained models are mostly reused except the final prediction head.\n\n```python\nmodel = dict(\n    pretrained=None,\n    roi_head=dict(\n        bbox_head=dict(\n            type='Shared2FCBBoxHead',\n            in_channels=256,\n            fc_out_channels=1024,\n            roi_feat_size=7,\n            num_classes=8,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n            loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)),\n        mask_head=dict(\n            type='FCNMaskHead',\n            num_convs=4,\n            in_channels=256,\n            conv_out_channels=256,\n            num_classes=8,\n            loss_mask=dict(\n                type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))))\n```\n\n## Modify dataset\nThe users may also need to prepare the dataset and write the configs about dataset. MMDetection V2.0 already support VOC, WIDER FACE, COCO and Cityscapes Dataset.\n\n## Modify training schedule\nThe finetuning hyperparameters vary from the default schedule. It usually requires smaller learning rate and less training epochs\n\n```python\n# optimizer\n# lr is set for a batch size of 8\noptimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)\noptimizer_config = dict(grad_clip=None)\n# learning policy\nlr_config = dict(\n    policy='step',\n    warmup='linear',\n    warmup_iters=500,\n    warmup_ratio=0.001,\n    # [7] yields higher performance than [6]\n    step=[7])\ntotal_epochs = 8  # actual epoch = 8 * 8 = 64\nlog_config = dict(interval=100)\n```\n\n## Use pre-trained model\nTo use the pre-trained model, the new config add the link of pre-trained models in the `load_from`. The users might need to download the model weights before training to avoid the download time during training.\n\n```python\nload_from = 'https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection/models/mask_rcnn_r50_fpn_2x_20181010-41d35c05.pth'  # noqa\n\n```\n"
  },
  {
    "path": "code/docs/tutorials/new_dataset.md",
    "content": "# Tutorial 2: Adding New Dataset\n\n## Customize datasets by reorganizing data\n\n### Reorganize dataset to existing format\n\nThe simplest way is to convert your dataset to existing dataset formats (COCO or PASCAL VOC).\n\nThe annotation json files in COCO format has the following necessary keys:\n\n```python\n'images': [\n    {\n        'file_name': 'COCO_val2014_000000001268.jpg',\n        'height': 427,\n        'width': 640,\n        'id': 1268\n    },\n    ...\n],\n\n'annotations': [\n    {\n        'segmentation': [[192.81,\n            247.09,\n            ...\n            219.03,\n            249.06]],  # if you have mask labels\n        'area': 1035.749,\n        'iscrowd': 0,\n        'image_id': 1268,\n        'bbox': [192.81, 224.8, 74.73, 33.43],\n        'category_id': 16,\n        'id': 42986\n    },\n    ...\n],\n\n'categories': [\n    {'id': 0, 'name': 'car'},\n ]\n```\n\nThere are three necessary keys in the json file:\n- `images`: contains a list of images with theire informations like `file_name`, `height`, `width`, and `id`.\n- `annotations`: contains the list of instance annotations.\n- `categories`: contains the list of categories names and their ID.\n\nAfter the data pre-processing, the users need to further modify the config files to use the dataset.\nHere we show an example of using a custom dataset of 5 classes, assuming it is also in COCO format.\n\nIn `configs/my_custom_config.py`:\n\n```python\n...\n# dataset settings\ndataset_type = 'CocoDataset'\nclasses = ('a', 'b', 'c', 'd', 'e')\n...\ndata = dict(\n    samples_per_gpu=2,\n    workers_per_gpu=2,\n    train=dict(\n        type=dataset_type,\n        classes=classes,\n        ann_file='path/to/your/train/data',\n        ...),\n    val=dict(\n        type=dataset_type,\n        classes=classes,\n        ann_file='path/to/your/val/data',\n        ...),\n    test=dict(\n        type=dataset_type,\n        classes=classes,\n        ann_file='path/to/your/test/data',\n        ...))\n...\n```\n\nWe use this way to support CityScapes dataset. The script is in [cityscapes.py](https://github.com/open-mmlab/mmdetection/blob/master/tools/convert_datasets/cityscapes.py) and we also provide the finetuning [configs](https://github.com/open-mmlab/mmdetection/blob/master/configs/cityscapes).\n\n### Reorganize dataset to middle format\n\nIt is also fine if you do not want to convert the annotation format to COCO or PASCAL format.\nActually, we define a simple annotation format and all existing datasets are\nprocessed to be compatible with it, either online or offline.\n\nThe annotation of a dataset is a list of dict, each dict corresponds to an image.\nThere are 3 field `filename` (relative path), `width`, `height` for testing,\nand an additional field `ann` for training. `ann` is also a dict containing at least 2 fields:\n`bboxes` and `labels`, both of which are numpy arrays. Some datasets may provide\nannotations like crowd/difficult/ignored bboxes, we use `bboxes_ignore` and `labels_ignore`\nto cover them.\n\nHere is an example.\n```\n[\n    {\n        'filename': 'a.jpg',\n        'width': 1280,\n        'height': 720,\n        'ann': {\n            'bboxes': <np.ndarray, float32> (n, 4),\n            'labels': <np.ndarray, int64> (n, ),\n            'bboxes_ignore': <np.ndarray, float32> (k, 4),\n            'labels_ignore': <np.ndarray, int64> (k, ) (optional field)\n        }\n    },\n    ...\n]\n```\n\nThere are two ways to work with custom datasets.\n\n- online conversion\n\n  You can write a new Dataset class inherited from `CustomDataset`, and overwrite two methods\n  `load_annotations(self, ann_file)` and `get_ann_info(self, idx)`,\n  like [CocoDataset](https://github.com/open-mmlab/mmdetection/blob/master/mmdet/datasets/coco.py) and [VOCDataset](https://github.com/open-mmlab/mmdetection/blob/master/mmdet/datasets/voc.py).\n\n- offline conversion\n\n  You can convert the annotation format to the expected format above and save it to\n  a pickle or json file, like [pascal_voc.py](https://github.com/open-mmlab/mmdetection/blob/master/tools/convert_datasets/pascal_voc.py).\n  Then you can simply use `CustomDataset`.\n\n### An example of customized dataset\n\nAssume the annotation is in a new format in text files.\nThe bounding boxes annotations are stored in text file `annotation.txt` as the following\n\n```\n#\n000001.jpg\n1280 720\n2\n10 20 40 60 1\n20 40 50 60 2\n#\n000002.jpg\n1280 720\n3\n50 20 40 60 2\n20 40 30 45 2\n30 40 50 60 3\n```\n\nWe can create a new dataset in `mmdet/datasets/my_dataset.py` to load the data.\n\n```python\nimport mmcv\nimport numpy as np\n\nfrom .builder import DATASETS\nfrom .custom import CustomDataset\n\n\n@DATASETS.register_module()\nclass MyDataset(CustomDataset):\n\n    CLASSES = ('person', 'bicycle', 'car', 'motorcycle')\n\n    def load_annotations(self, ann_file):\n        ann_list = mmcv.list_from_file(ann_file)\n\n        data_infos = []\n        for i, ann_line in enumerate(ann_list):\n            if ann_line != '#':\n                continue\n\n            img_shape = ann_list[i + 2].split(' ')\n            width = int(img_shape[0])\n            height = int(img_shape[1])\n            bbox_number = int(ann_list[i + 3])\n\n            anns = ann_line.split(' ')\n            bboxes = []\n            labels = []\n            for anns in ann_list[i + 4:i + 4 + bbox_number]:\n                bboxes.append([float(ann) for ann in anns[:4]])\n                labels.append(int(anns[4]))\n\n            data_infos.append(\n                dict(\n                    filename=ann_list[i + 1],\n                    width=width,\n                    height=height,\n                    ann=dict(\n                        bboxes=np.array(bboxes).astype(np.float32),\n                        labels=np.array(labels).astype(np.int64))\n                ))\n\n        return data_infos\n\n    def get_ann_info(self, idx):\n        return self.data_infos[idx]['ann']\n\n```\n\nThen in the config, to use `MyDataset` you can modify the config as the following\n\n```python\ndataset_A_train = dict(\n    type='MyDataset',\n    ann_file = 'image_list.txt',\n    pipeline=train_pipeline\n)\n```\n\n## Customize datasets by mixing dataset\n\nMMDetection also supports to mix dataset for training.\nCurrently it supports to concat and repeat datasets.\n\n### Repeat dataset\n\nWe use `RepeatDataset` as wrapper to repeat the dataset. For example, suppose the original dataset is `Dataset_A`, to repeat it, the config looks like the following\n```python\ndataset_A_train = dict(\n        type='RepeatDataset',\n        times=N,\n        dataset=dict(  # This is the original config of Dataset_A\n            type='Dataset_A',\n            ...\n            pipeline=train_pipeline\n        )\n    )\n```\n\n### Class balanced dataset\n\nWe use `ClassBalancedDataset` as wrapper to repeat the dataset based on category\nfrequency. The dataset to repeat needs to instantiate function `self.get_cat_ids(idx)`\nto support `ClassBalancedDataset`.\nFor example, to repeat `Dataset_A` with `oversample_thr=1e-3`, the config looks like the following\n```python\ndataset_A_train = dict(\n        type='ClassBalancedDataset',\n        oversample_thr=1e-3,\n        dataset=dict(  # This is the original config of Dataset_A\n            type='Dataset_A',\n            ...\n            pipeline=train_pipeline\n        )\n    )\n```\nYou may refer to [source code](../../mmdet/datasets/dataset_wrappers.py) for details.\n\n### Concatenate dataset\n\nThere two ways to concatenate the dataset.\n\n1. If the datasets you want to concatenate are in the same type with different annotation files, you can concatenate the dataset configs like the following.\n\n    ```python\n    dataset_A_train = dict(\n        type='Dataset_A',\n        ann_file = ['anno_file_1', 'anno_file_2'],\n        pipeline=train_pipeline\n    )\n    ```\n\n2. In case the dataset you want to concatenate is different, you can concatenate the dataset configs like the following.\n\n    ```python\n    dataset_A_train = dict()\n    dataset_B_train = dict()\n\n    data = dict(\n        imgs_per_gpu=2,\n        workers_per_gpu=2,\n        train = [\n            dataset_A_train,\n            dataset_B_train\n        ],\n        val = dataset_A_val,\n        test = dataset_A_test\n        )\n    ```\n\n\nA more complex example that repeats `Dataset_A` and `Dataset_B` by N and M times, respectively, and then concatenates the repeated datasets is as the following.\n\n```python\ndataset_A_train = dict(\n    type='RepeatDataset',\n    times=N,\n    dataset=dict(\n        type='Dataset_A',\n        ...\n        pipeline=train_pipeline\n    )\n)\ndataset_A_val = dict(\n    ...\n    pipeline=test_pipeline\n)\ndataset_A_test = dict(\n    ...\n    pipeline=test_pipeline\n)\ndataset_B_train = dict(\n    type='RepeatDataset',\n    times=M,\n    dataset=dict(\n        type='Dataset_B',\n        ...\n        pipeline=train_pipeline\n    )\n)\ndata = dict(\n    imgs_per_gpu=2,\n    workers_per_gpu=2,\n    train = [\n        dataset_A_train,\n        dataset_B_train\n    ],\n    val = dataset_A_val,\n    test = dataset_A_test\n)\n\n```\n\n### Modify classes of existing dataset\n\nWith existing dataset types, we can modify the class names of them to train subset of the dataset.\nFor example, if you want to train only three classes of the current dataset,\nyou can modify the classes of dataset.\nThe dataset will subtract subset of the data which contains at least one class in the `classes`.\n\n```python\nclasses = ('person', 'bicycle', 'car')\ndata = dict(\n    train=dict(classes=classes),\n    val=dict(classes=classes),\n    test=dict(classes=classes))\n```\n\nMMDetection V2.0 also supports to read the classes from a file, which is common in real applications.\nFor example, assume the `classes.txt` contains the name of classes as the following.\n\n```\nperson\nbicycle\ncar\n```\n\nUsers can set the classes as a file path, the dataset will load it and convert it to a list automatically.\n```python\nclasses = 'path/to/classes.txt'\ndata = dict(\n    train=dict(classes=classes),\n    val=dict(classes=classes),\n    test=dict(classes=classes))\n```\n"
  },
  {
    "path": "code/docs/tutorials/new_modules.md",
    "content": "# Tutorial 4: Adding New Modules\n\n## Customize optimizer\n\nA customized optimizer could be defined as following.\n\nAssume you want to add a optimizer named as `MyOptimizer`, which has arguments `a`, `b`, and `c`.\nYou need to create a new directory named `mmdet/core/optimizer`.\nAnd then implement the new optimizer in a file, e.g., in `mmdet/core/optimizer/my_optimizer.py`:\n\n```python\nfrom .registry import OPTIMIZERS\nfrom torch.optim import Optimizer\n\n\n@OPTIMIZERS.register_module()\nclass MyOptimizer(Optimizer):\n\n    def __init__(self, a, b, c)\n\n```\n\nThen add this module in `mmdet/core/optimizer/__init__.py` thus the registry will\nfind the new module and add it:\n\n```python\nfrom .my_optimizer import MyOptimizer\n```\n\nThen you can use `MyOptimizer` in `optimizer` field of config files.\nIn the configs, the optimizers are defined by the field `optimizer` like the following:\n```python\noptimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)\n```\nTo use your own optimizer, the field can be changed as\n```python\noptimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)\n```\n\nWe already support to use all the optimizers implemented by PyTorch, and the only modification is to change the `optimizer` field of config files.\nFor example, if you want to use `ADAM`, though the performance will drop a lot, the modification could be as the following.\n```python\noptimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)\n```\nThe users can directly set arguments following the [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) of PyTorch.\n\n## Customize optimizer constructor\n\nSome models may have some parameter-specific settings for optimization, e.g. weight decay for BatchNoarm layers.\nThe users can do those fine-grained parameter tuning through customizing optimizer constructor.\n\n```python\nfrom mmcv.utils import build_from_cfg\n\nfrom mmcv.runner.optimizer import OPTIMIZER_BUILDERS, OPTIMIZERS\nfrom mmdet.utils import get_root_logger\nfrom .my_optimizer import MyOptimizer\n\n\n@OPTIMIZER_BUILDERS.register_module()\nclass MyOptimizerConstructor(object):\n\n    def __init__(self, optimizer_cfg, paramwise_cfg=None):\n\n    def __call__(self, model):\n\n        return my_optimizer\n\n```\n\n\n## Develop new components\n\nWe basically categorize model components into 4 types.\n\n- backbone: usually an FCN network to extract feature maps, e.g., ResNet, MobileNet.\n- neck: the component between backbones and heads, e.g., FPN, PAFPN.\n- head: the component for specific tasks, e.g., bbox prediction and mask prediction.\n- roi extractor: the part for extracting RoI features from feature maps, e.g., RoI Align.\n\n### Add new backbones\n\nHere we show how to develop new components with an example of MobileNet.\n\n1. Create a new file `mmdet/models/backbones/mobilenet.py`.\n\n```python\nimport torch.nn as nn\n\nfrom ..registry import BACKBONES\n\n\n@BACKBONES.register_module()\nclass MobileNet(nn.Module):\n\n    def __init__(self, arg1, arg2):\n        pass\n\n    def forward(self, x):  # should return a tuple\n        pass\n\n    def init_weights(self, pretrained=None):\n        pass\n```\n\n2. Import the module in `mmdet/models/backbones/__init__.py`.\n\n```python\nfrom .mobilenet import MobileNet\n```\n\n3. Use it in your config file.\n\n```python\nmodel = dict(\n    ...\n    backbone=dict(\n        type='MobileNet',\n        arg1=xxx,\n        arg2=xxx),\n    ...\n```\n\n### Add new necks\n\nHere we take PAFPN as an example.\n\n1. Create a new file in `mmdet/models/necks/pafpn.py`.\n\n    ```python\n    from ..registry import NECKS\n\n    @NECKS.register\n    class PAFPN(nn.Module):\n\n        def __init__(self,\n                    in_channels,\n                    out_channels,\n                    num_outs,\n                    start_level=0,\n                    end_level=-1,\n                    add_extra_convs=False):\n            pass\n\n        def forward(self, inputs):\n            # implementation is ignored\n            pass\n    ```\n\n2. Import the module in `mmdet/models/necks/__init__.py`.\n\n    ```python\n    from .pafpn import PAFPN\n    ```\n\n3. Modify the config file.\n\n    ```python\n    neck=dict(\n        type='PAFPN',\n        in_channels=[256, 512, 1024, 2048],\n        out_channels=256,\n        num_outs=5)\n    ```\n\n### Add new heads\n\nHere we show how to develop a new head with the example of [Double Head R-CNN](https://arxiv.org/abs/1904.06493) as the following.\n\nFirst, add a new bbox head in `mmdet/models/bbox_heads/double_bbox_head.py`.\nDouble Head R-CNN implements a new bbox head for object detection.\nTo implement a bbox head, basically we need to implement three functions of the new module as the following.\n\n```python\n@HEADS.register_module()\nclass DoubleConvFCBBoxHead(BBoxHead):\n    r\"\"\"Bbox head used in Double-Head R-CNN\n\n                                      /-> cls\n                  /-> shared convs ->\n                                      \\-> reg\n    roi features\n                                      /-> cls\n                  \\-> shared fc    ->\n                                      \\-> reg\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_convs=0,\n                 num_fcs=0,\n                 conv_out_channels=1024,\n                 fc_out_channels=1024,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 **kwargs):\n        kwargs.setdefault('with_avg_pool', True)\n        super(DoubleConvFCBBoxHead, self).__init__(**kwargs)\n\n    def init_weights(self):\n        # conv layers are already initialized by ConvModule\n\n    def forward(self, x_cls, x_reg):\n\n```\n\nSecond, implement a new RoI Head if it is necessary. We plan to inherit the new `DoubleHeadRoIHead` from `StandardRoIHead`. We can find that a `StandardRoIHead` already implements the following functions.\n\n```python\nimport torch\n\nfrom mmdet.core import bbox2result, bbox2roi, build_assigner, build_sampler\nfrom ..builder import HEADS, build_head, build_roi_extractor\nfrom .base_roi_head import BaseRoIHead\nfrom .test_mixins import BBoxTestMixin, MaskTestMixin\n\n\n@HEADS.register_module()\nclass StandardRoIHead(BaseRoIHead, BBoxTestMixin, MaskTestMixin):\n    \"\"\"Simplest base roi head including one bbox head and one mask head.\n    \"\"\"\n\n    def init_assigner_sampler(self):\n\n    def init_bbox_head(self, bbox_roi_extractor, bbox_head):\n\n    def init_mask_head(self, mask_roi_extractor, mask_head):\n\n    def init_weights(self, pretrained):\n\n    def forward_dummy(self, x, proposals):\n\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None):\n\n    def _bbox_forward(self, x, rois):\n\n    def _bbox_forward_train(self, x, sampling_results, gt_bboxes, gt_labels,\n                            img_metas):\n\n    def _mask_forward_train(self, x, sampling_results, bbox_feats, gt_masks,\n                            img_metas):\n\n    def _mask_forward(self, x, rois=None, pos_inds=None, bbox_feats=None):\n\n\n    def simple_test(self,\n                    x,\n                    proposal_list,\n                    img_metas,\n                    proposals=None,\n                    rescale=False):\n        \"\"\"Test without augmentation.\"\"\"\n\n```\n\nDouble Head's modification is mainly in the bbox_forward logic, and it inherits other logics from the `StandardRoIHead`.\nIn the `mmdet/models/roi_heads/double_roi_head.py`, we implement the new RoI Head as the following:\n\n```python\nfrom ..builder import HEADS\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass DoubleHeadRoIHead(StandardRoIHead):\n    \"\"\"RoI head for Double Head RCNN\n\n    https://arxiv.org/abs/1904.06493\n    \"\"\"\n\n    def __init__(self, reg_roi_scale_factor, **kwargs):\n        super(DoubleHeadRoIHead, self).__init__(**kwargs)\n        self.reg_roi_scale_factor = reg_roi_scale_factor\n\n    def _bbox_forward(self, x, rois):\n        bbox_cls_feats = self.bbox_roi_extractor(\n            x[:self.bbox_roi_extractor.num_inputs], rois)\n        bbox_reg_feats = self.bbox_roi_extractor(\n            x[:self.bbox_roi_extractor.num_inputs],\n            rois,\n            roi_scale_factor=self.reg_roi_scale_factor)\n        if self.with_shared_head:\n            bbox_cls_feats = self.shared_head(bbox_cls_feats)\n            bbox_reg_feats = self.shared_head(bbox_reg_feats)\n        cls_score, bbox_pred = self.bbox_head(bbox_cls_feats, bbox_reg_feats)\n\n        bbox_results = dict(\n            cls_score=cls_score,\n            bbox_pred=bbox_pred,\n            bbox_feats=bbox_cls_feats)\n        return bbox_results\n```\n\nLast, the users need to add the module in the `mmdet/models/bbox_heads/__init__.py` and `mmdet/models/roi_heads/__init__.py` thus the corresponding registry could find and load them.\n\nTo config file of Double Head R-CNN is as the following\n\n```python\n_base_ = '../faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'\nmodel = dict(\n    roi_head=dict(\n        type='DoubleHeadRoIHead',\n        reg_roi_scale_factor=1.3,\n        bbox_head=dict(\n            _delete_=True,\n            type='DoubleConvFCBBoxHead',\n            num_convs=4,\n            num_fcs=2,\n            in_channels=256,\n            conv_out_channels=1024,\n            fc_out_channels=1024,\n            roi_feat_size=7,\n            num_classes=80,\n            bbox_coder=dict(\n                type='DeltaXYWHBBoxCoder',\n                target_means=[0., 0., 0., 0.],\n                target_stds=[0.1, 0.1, 0.2, 0.2]),\n            reg_class_agnostic=False,\n            loss_cls=dict(\n                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=2.0),\n            loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=2.0))))\n\n```\n\nSince MMDetection 2.0, the config system support to inherit configs such that the users can focus on the modification.\nThe Double Head R-CNN mainly uses a new DoubleHeadRoIHead and a new\n`DoubleConvFCBBoxHead`, the arguments are set according to the `__init__` function of each module.\n\n\n### Add new loss\n\nAssume you want to add a new loss as `MyLoss`, for bounding box regression.\nTo add a new loss function, the users need implement it in `mmdet/models/losses/my_loss.py`.\nThe decorator `weighted_loss` enable the loss to be weighted for each element.\n\n```python\nimport torch\nimport torch.nn as nn\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n@weighted_loss\ndef my_loss(pred, target):\n    assert pred.size() == target.size() and target.numel() > 0\n    loss = torch.abs(pred - target)\n    return loss\n\n@LOSSES.register_module()\nclass MyLoss(nn.Module):\n\n    def __init__(self, reduction='mean', loss_weight=1.0):\n        super(MyLoss, self).__init__()\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_bbox = self.loss_weight * my_loss(\n            pred, target, weight, reduction=reduction, avg_factor=avg_factor)\n        return loss_bbox\n```\n\nThen the users need to add it in the `mmdet/models/losses/__init__.py`.\n```python\nfrom .my_loss import MyLoss, my_loss\n\n```\n\nTo use it, modify the `loss_xxx` field.\nSince MyLoss is for regrression, you need to modify the `loss_bbox` field in the head.\n```python\nloss_bbox=dict(type='MyLoss', loss_weight=1.0))\n```\n"
  },
  {
    "path": "code/mmcv/.dockerignore",
    "content": ".git\n.gitignore\n*.egg-info\n.eggs/\n.mypy-cache\npip-wheel-metadata\n"
  },
  {
    "path": "code/mmcv/.github/workflows/build.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: build\n\non: [push, pull_request]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [3.6, 3.7, 3.8]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v1\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install linting dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install flake8 isort yapf\n    - name: Lint with flake8\n      run: flake8 --max-complexity 20 .\n    - name: Lint with isort\n      run: isort -rc --check-only --diff mmcv/ tests/ examples/\n    - name: Format with yapf\n      run: yapf -r -d mmcv/ tests/ examples/\n    - name: Build and install\n      run: rm -rf .eggs && pip install -e .\n    - name: Install system dependencies\n      run: sudo apt-get update && sudo apt-get install -y ffmpeg libturbojpeg\n    - name: Install unittest dependencies\n      run: |\n        pip install pytest coverage lmdb PyTurboJPEG\n        pip install torch==1.4.0+cpu torchvision==0.5.0+cpu -f https://download.pytorch.org/whl/torch_stable.html\n    - name: Run unittests and generate coverage report\n      run: |\n        coverage run --branch --source=mmcv -m pytest tests/\n        coverage xml\n        coverage report -m\n    - name: Upload coverage to Codecov\n      uses: codecov/codecov-action@master\n      with:\n        file: ./coverage.xml\n        flags: unittests\n        env_vars: OS,PYTHON\n        name: codecov-umbrella\n        fail_ci_if_error: false\n"
  },
  {
    "path": "code/mmcv/.github/workflows/publish-to-pypi.yml",
    "content": "name: deploy\n\non: push\n\njobs:\n  build-n-publish:\n    runs-on: ubuntu-latest\n    if: startsWith(github.event.ref, 'refs/tags')\n    steps:\n      - uses: actions/checkout@v2\n      - name: Set up Python 3.7\n        uses: actions/setup-python@v1\n        with:\n          python-version: 3.7\n      - name: Build MMCV\n        run: python setup.py sdist\n      - name: Publish distribution to PyPI\n        run: |\n          pip install twine\n          twine upload dist/* -u __token__ -p ${{ secrets.pypi_password }}\n"
  },
  {
    "path": "code/mmcv/.gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n# editors and IDEs\n.idea/\n.vscode/\n\n# custom\n.DS_Store\nmmcv/video/optflow_warp/flow_warp_module.cpp\n"
  },
  {
    "path": "code/mmcv/.pre-commit-config.yaml",
    "content": "exclude: ^tests/data/\nrepos:\n  - repo: https://gitlab.com/pycqa/flake8.git\n    rev: 3.8.0\n    hooks:\n      - id: flake8\n  - repo: https://github.com/asottile/seed-isort-config\n    rev: v2.1.0\n    hooks:\n      - id: seed-isort-config\n  - repo: https://github.com/timothycrosley/isort\n    rev: 4.3.21\n    hooks:\n      - id: isort\n  - repo: https://github.com/pre-commit/mirrors-yapf\n    rev: v0.30.0\n    hooks:\n      - id: yapf\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v2.5.0\n    hooks:\n      - id: trailing-whitespace\n      - id: check-yaml\n      - id: end-of-file-fixer\n      - id: requirements-txt-fixer\n      - id: double-quote-string-fixer\n      - id: fix-encoding-pragma\n        args: [\"--remove\"]\n      - id: mixed-line-ending\n        args: [\"--fix=lf\"]\n"
  },
  {
    "path": "code/mmcv/.readthedocs.yml",
    "content": "version: 2\n\npython:\n  version: 3.7\n  install:\n    - requirements: requirements.txt\n    - requirements: docs/requirements.txt\n"
  },
  {
    "path": "code/mmcv/CONTRIBUTING.md",
    "content": "# Contributing to MMCV\n\nAll kinds of contributions are welcome, including but not limited to the following.\n\n- Fixes (typo, bugs)\n- New features and components\n\n## Workflow\n\n1. fork and pull the latest MMCV\n2. checkout a new branch (do not use master branch for PRs)\n3. commit your changes\n4. create a PR\n\nNote: If you plan to add some new features that involve large changes, it is encouraged to open an issue for discussion first.\n\n## Code style\n\n### Python\nWe adopt [PEP8](https://www.python.org/dev/peps/pep-0008/) as the preferred code style.\n\nWe use the following tools for linting and formatting:\n- [flake8](http://flake8.pycqa.org/en/latest/): linter\n- [yapf](https://github.com/google/yapf): formatter\n- [isort](https://github.com/timothycrosley/isort): sort imports\n\nStyle configurations of yapf and isort can be found in [setup.cfg](./setup.cfg).\n\nWe use [pre-commit hook](https://pre-commit.com/) that checks and formats for `flake8`, `yapf`, `isort`, `trailing whitespaces`,\n fixes `end-of-files`, sorts `requirments.txt` automatically on every commit.\nThe config for a pre-commit hook is stored in [.pre-commit-config](./.pre-commit-config.yaml).\n\nAfter you clone the repository, you will need to install initialize pre-commit hook.\n\n```\npip install -U pre-commit\n```\n\nFrom the repository folder\n```\npre-commit install\n```\n\nAfter this on every commit check code linters and formatter will be enforced.\n\n\n>Before you create a PR, make sure that your code lints and is formatted by yapf.\n\n### C++ and CUDA\nWe follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).\n"
  },
  {
    "path": "code/mmcv/Dockerfile",
    "content": "FROM python:3.7\n\nWORKDIR /mmcv\n\nCOPY . /mmcv\n\nRUN pip install -e .\n"
  },
  {
    "path": "code/mmcv/LICENSE",
    "content": "Copyright (c) Open-MMLab. All rights reserved.\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2018-2020 Open-MMLab. All rights reserved.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "code/mmcv/MANIFEST.in",
    "content": "include mmcv/video/optflow_warp/*.hpp mmcv/video/optflow_warp/*.pyx\ninclude requirements.txt\ninclude mmcv/model_zoo/open_mmlab.json mmcv/model_zoo/deprecated.json\n"
  },
  {
    "path": "code/mmcv/README.rst",
    "content": "MMCV\n====\n\n.. image:: https://img.shields.io/pypi/v/mmcv\n  :target: https://pypi.org/project/mmcv\n\n.. image:: https://github.com/open-mmlab/mmcv/workflows/build/badge.svg\n  :target: https://github.com/open-mmlab/mmcv/actions\n\n.. image:: https://codecov.io/gh/open-mmlab/mmcv/branch/master/graph/badge.svg\n  :target: https://codecov.io/gh/open-mmlab/mmcv\n\n.. image:: \thttps://img.shields.io/github/license/open-mmlab/mmcv.svg\n  :target: https://github.com/open-mmlab/mmcv/blob/master/LICENSE\n\n\nIntroduction\n------------\n\nMMCV is a foundational python library for computer vision research and supports many\nresearch projects in MMLAB, such as `MMDetection <https://github.com/open-mmlab/mmdetection>`_\nand `MMAction <https://github.com/open-mmlab/mmaction>`_.\n\nIt provides the following functionalities.\n\n- Universal IO APIs\n- Image processing\n- Video processing\n- Image and annotation visualization\n- Useful utilities (progress bar, timer, ...)\n- PyTorch runner with hooking mechanism\n- Various CNN architectures\n\nSee the `documentation <http://mmcv.readthedocs.io/en/latest>`_ for more features and usage.\n\nNote: MMCV requires Python 3.6+.\n\n\nInstallation\n------------\n\nTry and start with\n\n.. code::\n\n    pip install mmcv\n\n\nor install from source\n\n.. code::\n\n    git clone https://github.com/open-mmlab/mmcv.git\n    cd mmcv\n    pip install -e .\n\nNote: If you would like to use :code:`opencv-python-headless` instead of :code:`opencv-python`,\ne.g., in a minimum container environment or servers without GUI,\nyou can first install it before installing MMCV to skip the installation of :code:`opencv-python`.\n"
  },
  {
    "path": "code/mmcv/docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "code/mmcv/docs/api.rst",
    "content": "API Documentation\n=================\n\n\nfileio\n-------\n.. automodule:: mmcv.fileio\n    :members:\n\nimage\n------\n.. automodule:: mmcv.image\n    :members:\n\nvideo\n------\n.. automodule:: mmcv.video\n    :members:\n\narraymisc\n---------\n.. automodule:: mmcv.arraymisc\n    :members:\n\nvisualization\n--------------\n.. automodule:: mmcv.visualization\n    :members:\n\nutils\n-----\n.. automodule:: mmcv.utils\n    :members:\n\ncnn\n----\n.. automodule:: mmcv.cnn\n    :members:\n\nrunner\n------\n.. automodule:: mmcv.runner\n    :members:\n"
  },
  {
    "path": "code/mmcv/docs/cnn.md",
    "content": "## CNN\n\nWe provide some building bricks for CNNs, includeing layer building, module bundles and weight initialization.\n\n### Layer building\n\nWe may need to try different layers of the same type when running experiments,\nbut do not want to modify the code from time to time.\nHere we provide some layer building methods to construct layers from a dict,\nwhich can be written in configs or specified via command line arguments.\n\n#### Usage\n\nA simplest example is\n```python\ncfg = dict(type='Conv3d')\nlayer = build_norm_layer(cfg, in_channels=3, out_channels=8, kernel_size=3)\n```\n\n- `build_conv_layer`: Supported types are Conv1d, Conv2d, Conv3d, Conv (alias for Conv2d).\n- `build_norm_layer`: Supported types are BN1d, BN2d, BN3d, BN (alias for BN2d), SyncBN, GN, LN, IN1d, IN2d, IN3d, IN (alias for IN2d).\n- `build_activation_layer`: Supported types are ReLU, LeakyReLU, PReLU, RReLU, ReLU6, ELU, Sigmoid, Tanh.\n- `build_upsample_layer`: Supported types are nearest, bilinear, deconv, pixel_shuffle.\n- `build_padding_layer`: Supported types are zero, reflect, replicate.\n\n#### Extension\n\nWe also allow extending the building methods with custom layers and operators.\n\n1. Write and register your own module.\n\n    ```python\n    from mmcv.cnn import UPSAMPLE_LAYERS\n\n    @UPSAMPLE_LAYERS.register_module()\n    class MyUpsample:\n\n        def __init__(self, scale_factor):\n            pass\n\n        def forward(self, x):\n            pass\n    ```\n\n2. Import `MyUpsample` somewhere (e.g., in `__init__.py`) and then use it.\n\n    ```python\n    cfg = dict(type='MyUpsample', scale_factor=2)\n    layer = build_upsample_layer(cfg)\n    ```\n\n### Module bundles\n\nWe also provide common module bundles to facilitate the network construction.\n`ConvModule` is a bundle of convolution, normalization and activation layers,\nplease refer to the [api](api.html#mmcv.cnn.ConvModule) for details.\n\n```python\n# conv + bn + relu\nconv = ConvModule(3, 8, 2, norm_cfg=dict(type='BN'))\n# conv + gn + relu\nconv = ConvModule(3, 8, 2, norm_cfg=dict(type='GN', num_groups=2))\n# conv + relu\nconv = ConvModule(3, 8, 2)\n# conv\nconv = ConvModule(3, 8, 2, act_cfg=None)\n# conv + leaky relu\nconv = ConvModule(3, 8, 3, padding=1, act_cfg=dict(type='LeakyReLU'))\n# bn + conv + relu\nconv = ConvModule(\n    3, 8, 2, norm_cfg=dict(type='BN'), order=('norm', 'conv', 'act'))\n```\n\n### Weight initialization\n\nWe wrap some initialization methods which accept a module as argument.\n\n- `constant_init`\n- `xavier_init`\n- `normal_init`\n- `uniform_init`\n- `kaiming_init`\n- `caffe2_xavier_init`\n- `bias_init_with_prob`\n\nExamples:\n\n```python\nconv1 = nn.Conv2d(3, 3, 1)\nnormal_init(conv1, std=0.01, bias=0)\nxavier_init(conv1, distribution='uniform')\n```\n"
  },
  {
    "path": "code/mmcv/docs/conf.py",
    "content": "#\n# Configuration file for the Sphinx documentation builder.\n#\n# This file does only contain a selection of the most common options. For a\n# full list see the documentation:\n# http://www.sphinx-doc.org/en/master/config\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath('..'))\n\nversion_file = '../mmcv/version.py'\nwith open(version_file, 'r') as f:\n    exec(compile(f.read(), version_file, 'exec'))\n__version__ = locals()['__version__']\n\n# -- Project information -----------------------------------------------------\n\nproject = 'mmcv'\ncopyright = '2018-2019, Kai Chen'\nauthor = 'Kai Chen'\n\n# The short X.Y version\nversion = __version__\n# The full version, including alpha/beta/rc tags\nrelease = __version__\n\n# -- General configuration ---------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.napoleon',\n    'sphinx.ext.viewcode',\n    'recommonmark',\n]\n\nautodoc_mock_imports = ['cv2', 'mmcv._ext', 'torchvision']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\nsource_suffix = {\n    '.rst': 'restructuredtext',\n    '.md': 'markdown',\n}\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\n# html_theme_options = {}\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\n#\n# The default sidebars (for documents that don't match any pattern) are\n# defined by theme itself.  Builtin themes are using these templates by\n# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',\n# 'searchbox.html']``.\n#\n# html_sidebars = {}\n\n# -- Options for HTMLHelp output ---------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'mmcvdoc'\n\n# -- Options for LaTeX output ------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #\n    # 'papersize': 'letterpaper',\n\n    # The font size ('10pt', '11pt' or '12pt').\n    #\n    # 'pointsize': '10pt',\n\n    # Additional stuff for the LaTeX preamble.\n    #\n    # 'preamble': '',\n\n    # Latex figure (float) alignment\n    #\n    # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (master_doc, 'mmcv.tex', 'mmcv Documentation', 'Kai Chen', 'manual'),\n]\n\n# -- Options for manual page output ------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, 'mmcv', 'mmcv Documentation', [author], 1)]\n\n# -- Options for Texinfo output ----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (master_doc, 'mmcv', 'mmcv Documentation', author, 'mmcv',\n     'One line description of project.', 'Miscellaneous'),\n]\n\n# -- Options for Epub output -------------------------------------------------\n\n# Bibliographic Dublin Core info.\nepub_title = project\n\n# The unique identifier of the text. This can be a ISBN number\n# or the project homepage.\n#\n# epub_identifier = ''\n\n# A unique identification for the text.\n#\n# epub_uid = ''\n\n# A list of files that should not be packed into the epub file.\nepub_exclude_files = ['search.html']\n\n# -- Extension configuration -------------------------------------------------\n"
  },
  {
    "path": "code/mmcv/docs/image.md",
    "content": "## Image\n\nThis module provides some image processing methods, which requires `opencv` to be installed.\n\n### Read/Write/Show\nTo read or write images files, use `imread` or `imwrite`.\n\n```python\nimport mmcv\n\nimg = mmcv.imread('test.jpg')\nimg = mmcv.imread('test.jpg', flag='grayscale')\nimg_ = mmcv.imread(img) # nothing will happen, img_ = img\nmmcv.imwrite(img, 'out.jpg')\n```\n\nTo read images from bytes\n\n```python\nwith open('test.jpg', 'rb') as f:\n    data = f.read()\nimg = mmcv.imfrombytes(data)\n```\n\nTo show an image file or a loaded image\n\n```python\nmmcv.imshow('tests/data/color.jpg')\n# this is equivalent to\n\nfor i in range(10):\n    img = np.random.randint(256, size=(100, 100, 3), dtype=np.uint8)\n    mmcv.imshow(img, win_name='test image', wait_time=200)\n```\n\n### Color space conversion\nSupported conversion methods:\n- bgr2gray\n- gray2bgr\n- bgr2rgb\n- rgb2bgr\n- bgr2hsv\n- hsv2bgr\n\n```python\nimg = mmcv.imread('tests/data/color.jpg')\nimg1 = mmcv.bgr2rgb(img)\nimg2 = mmcv.rgb2gray(img1)\nimg3 = mmcv.bgr2hsv(img)\n```\n\n### Resize\nThere are three resize methods. All `imresize_*` methods have an argument `return_scale`,\nif this argument is `False`, then the return value is merely the resized image, otherwise\nis a tuple `(resized_img, scale)`.\n\n```python\n# resize to a given size\nmmcv.imresize(img, (1000, 600), return_scale=True)\n\n# resize to the same size of another image\nmmcv.imresize_like(img, dst_img, return_scale=False)\n\n# resize by a ratio\nmmcv.imrescale(img, 0.5)\n\n# resize so that the max edge no longer than 1000, short edge no longer than 800\n# without changing the aspect ratio\nmmcv.imrescale(img, (1000, 800))\n```\n\n### Rotate\nTo rotate an image by some angle, use `imrotate`. The center can be specified,\nwhich is the center of original image by default. There are two modes of rotating,\none is to keep the image size unchanged so that some parts of the image will be\ncropped after rotating, the other is to extend the image size to fit the rotated\nimage.\n\n```python\nimg = mmcv.imread('tests/data/color.jpg')\n\n# rotate the image clockwise by 30 degrees.\nimg_ = mmcv.imrotate(img, 30)\n\n# rotate the image counterclockwise by 90 degrees.\nimg_ = mmcv.imrotate(img, -90)\n\n# rotate the image clockwise by 30 degrees, and rescale it by 1.5x at the same time.\nimg_ = mmcv.imrotate(img, 30, scale=1.5)\n\n# rotate the image clockwise by 30 degrees, with (100, 100) as the center.\nimg_ = mmcv.imrotate(img, 30, center=(100, 100))\n\n# rotate the image clockwise by 30 degrees, and extend the image size.\nimg_ = mmcv.imrotate(img, 30, auto_bound=True)\n```\n\n### Flip\nTo flip an image, use `imflip`.\n\n```python\nimg = mmcv.imread('tests/data/color.jpg')\n\n# flip the image horizontally\nmmcv.imflip(img)\n\n# flip the image vertically\nmmcv.imflip(img, direction='vertical')\n```\n\n### Crop\n`imcrop` can crop the image with one or some regions, represented as (x1, y1, x2, y2).\n\n```python\nimport mmcv\nimport numpy as np\n\nimg = mmcv.imread('tests/data/color.jpg')\n\n# crop the region (10, 10, 100, 120)\nbboxes = np.array([10, 10, 100, 120])\npatch = mmcv.imcrop(img, bboxes)\n\n# crop two regions (10, 10, 100, 120) and (0, 0, 50, 50)\nbboxes = np.array([[10, 10, 100, 120], [0, 0, 50, 50]])\npatches = mmcv.imcrop(img, bboxes)\n\n# crop two regions, and rescale the patches by 1.2x\npatches = mmcv.imcrop(img, bboxes, scale_ratio=1.2)\n```\n\n### Padding\nThere are two methods `impad` and `impad_to_multiple` to pad an image to the\nspecific size with given values.\n\n```python\nimg = mmcv.imread('tests/data/color.jpg')\n\n# pad the image to (1000, 1200) with all zeros\nimg_ = mmcv.impad(img, (1000, 1200), pad_val=0)\n\n# pad the image to (1000, 1200) with different values for three channels.\nimg_ = mmcv.impad(img, (1000, 1200), pad_val=[100, 50, 200])\n\n# pad an image so that each edge is a multiple of some value.\nimg_ = mmcv.impad_to_multiple(img, 32)\n```\n"
  },
  {
    "path": "code/mmcv/docs/index.rst",
    "content": ".. include:: ../README.rst\n\nContents\n========\n\n.. toctree::\n   :maxdepth: 2\n\n   io.md\n   image.md\n   video.md\n   visualization.md\n   utils.md\n   runner.md\n   cnn.md\n   model_zoo.md\n   api.rst\n\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`search`\n"
  },
  {
    "path": "code/mmcv/docs/io.md",
    "content": "## File IO\n\nThis module provides two universal API to load and dump files of different formats.\n\n### Load and dump data\n`mmcv` provides a universal api for loading and dumping data, currently\nsupported formats are json, yaml and pickle.\n\n```python\nimport mmcv\n\n# load data from a file\ndata = mmcv.load('test.json')\ndata = mmcv.load('test.yaml')\ndata = mmcv.load('test.pkl')\n# load data from a file-like object\nwith open('test.json', 'r') as f:\n    data = mmcv.load(f)\n\n# dump data to a string\njson_str = mmcv.dump(data, file_format='json')\n\n# dump data to a file with a filename (infer format from file extension)\nmmcv.dump(data, 'out.pkl')\n\n# dump data to a file with a file-like object\nwith open('test.yaml', 'w') as f:\n    data = mmcv.dump(data, f, file_format='yaml')\n```\n\nIt is also very convenient to extend the api to support more file formats.\nAll you need to do is to write a file handler inherited from `BaseFileHandler`\nand register it with one or several file formats.\n\nYou need to implement at least 3 methods.\n\n```python\nimport mmcv\n\n# To register multiple file formats, a list can be used as the argument.\n# @mmcv.register_handler(['txt', 'log'])\n@mmcv.register_handler('txt')\nclass TxtHandler1(mmcv.BaseFileHandler):\n\n    def load_from_fileobj(self, file):\n        return file.read()\n\n    def dump_to_fileobj(self, obj, file):\n        file.write(str(obj))\n\n    def dump_to_str(self, obj, **kwargs):\n        return str(obj)\n```\n\nHere is an example of `PickleHandler`.\n\n```python\nimport pickle\n\nclass PickleHandler(mmcv.BaseFileHandler):\n\n    def load_from_fileobj(self, file, **kwargs):\n        return pickle.load(file, **kwargs)\n\n    def load_from_path(self, filepath, **kwargs):\n        return super(PickleHandler, self).load_from_path(\n            filepath, mode='rb', **kwargs)\n\n    def dump_to_str(self, obj, **kwargs):\n        kwargs.setdefault('protocol', 2)\n        return pickle.dumps(obj, **kwargs)\n\n    def dump_to_fileobj(self, obj, file, **kwargs):\n        kwargs.setdefault('protocol', 2)\n        pickle.dump(obj, file, **kwargs)\n\n    def dump_to_path(self, obj, filepath, **kwargs):\n        super(PickleHandler, self).dump_to_path(\n            obj, filepath, mode='wb', **kwargs)\n```\n\n### Load a text file as a list or dict\n\nFor example `a.txt` is a text file with 5 lines.\n```\na\nb\nc\nd\ne\n```\n\nThen use `list_from_file` to load the list from a.txt.\n\n```python\n>>> mmcv.list_from_file('a.txt')\n['a', 'b', 'c', 'd', 'e']\n>>> mmcv.list_from_file('a.txt', offset=2)\n['c', 'd', 'e']\n>>> mmcv.list_from_file('a.txt', max_num=2)\n['a', 'b']\n>>> mmcv.list_from_file('a.txt', prefix='/mnt/')\n['/mnt/a', '/mnt/b', '/mnt/c', '/mnt/d', '/mnt/e']\n```\n\nFor example `b.txt` is a text file with 5 lines.\n```\n1 cat\n2 dog cow\n3 panda\n```\n\nThen use `dict_from_file` to load the list from a.txt.\n\n```python\n>>> mmcv.dict_from_file('b.txt')\n{'1': 'cat', '2': ['dog', 'cow'], '3': 'panda'}\n>>> mmcv.dict_from_file('b.txt', key_type=int)\n{1: 'cat', 2: ['dog', 'cow'], 3: 'panda'}\n```\n"
  },
  {
    "path": "code/mmcv/docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=.\nset BUILDDIR=_build\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%\n\n:end\npopd\n"
  },
  {
    "path": "code/mmcv/docs/model_zoo.md",
    "content": "## Model Zoo\nBesides torchvision pre-trained models, we also provide pre-trained models of following CNN:\n* VGG Caffe\n* ResNet Caffe\n* ResNeXt\n* ResNet with Group Normalization\n* ResNet with Group Normalization and Weight Standardization\n* HRNetV2\n* Res2Net\n* RegNet\n\n### Model URLs in JSON\nThe model zoo links in MMCV are managed by JSON files.\nThe json file consists of key-value pair of model name and its url or path.\nAn example json file could be like:\n```json\n{\n    \"model_a\": \"https://example.com/models/model_a_9e5bac.pth\",\n    \"model_b\": \"pretrain/model_b_ab3ef2c.pth\"\n}\n```\nThe default links of the pre-trained models hosted on Open-MMLab AWS could be found [here](../mmcv/model_zoo/open_mmlab.json).\n\nYou may override default links by putting `open-mmlab.json` under `MMCV_HOME`. If `MMCV_HOME` is not find in the environment, `~/.cache/mmcv` will be used by default. You may `export MMCV_HOME=/your/path` to use your own path.\n\nThe external json files will be merged into default one. If the same key presents in both external json and default json, the external one will be used.\n\n### Load Checkpoint\nThe following types are supported for `filename` argument of `mmcv.load_checkpoint()`.\n* filepath: The filepath of the checkpoint.\n* `http://xxx` and `https://xxx`: The link to download the checkpoint. The `SHA256` postfix should be contained in the filename.\n* `torchvison://xxx`: The model links in `torchvision.models`.Please refer to [torchvision](https://pytorch.org/docs/stable/torchvision/models.html) for details.\n* `open-mmlab://xxx`: The model links or filepath provided in default and additional json files.\n"
  },
  {
    "path": "code/mmcv/docs/requirements.txt",
    "content": "torch\n"
  },
  {
    "path": "code/mmcv/docs/runner.md",
    "content": "## Runner\n\nThe runner module aims to help users to start training with less code, while stays\nflexible and configurable.\n\nDocumentation and examples are still on going.\n"
  },
  {
    "path": "code/mmcv/docs/utils.md",
    "content": "## Utils\n\n### Config\n\n`Config` class is used for manipulating config and config files. It supports\nloading configs from multiple file formats including **python**, **json** and **yaml**.\nIt provides dict-like apis to get and set values.\n\nHere is an example of the config file `test.py`.\n\n```python\na = 1\nb = dict(b1=[0, 1, 2], b2=None)\nc = (1, 2)\nd = 'string'\n```\n\nTo load and use configs\n\n```python\n>>> cfg = Config.fromfile('test.py')\n>>> print(cfg)\n>>> dict(a=1,\n...      b=dict(b1=[0, 1, 2], b2=None),\n...      c=(1, 2),\n...      d='string')\n```\n\nFor all format configs, inheritance is supported. To reuse fields in other config files,\nspecify `_base_='./config_a.py'` or a list of configs `_base_=['./config_a.py', './config_b.py']`.\nHere are 4 examples of config inheritance.\n\n`config_a.py`\n\n```python\na = 1\nb = dict(b1=[0, 1, 2], b2=None)\n```\n\n#### Inherit from base config without overlaped keys.\n\n`config_b.py`\n\n```python\n_base_ = './config_a.py'\nc = (1, 2)\nd = 'string'\n```\n\n```python\n>>> cfg = Config.fromfile('./config_b.py')\n>>> print(cfg)\n>>> dict(a=1,\n...      b=dict(b1=[0, 1, 2], b2=None),\n...      c=(1, 2),\n...      d='string')\n```\n\nNew fields in `config_b.py` are combined with old fields in `config_a.py`\n\n#### Inherit from base config with overlaped keys.\n\n`config_c.py`\n\n```python\n_base_ = './config_a.py'\nb = dict(b2=1)\nc = (1, 2)\n```\n\n```python\n>>> cfg = Config.fromfile('./config_c.py')\n>>> print(cfg)\n>>> dict(a=1,\n...      b=dict(b1=[0, 1, 2], b2=1),\n...      c=(1, 2))\n```\n\n`b.b2=None` in `config_a` is replaced with `b.b2=1` in `config_c.py`.\n\n#### Inherit from base config with ignored fields.\n\n`config_d.py`\n\n```python\n_base_ = './config_a.py'\nb = dict(_delete_=True, b2=None, b3=0.1)\nc = (1, 2)\n```\n\n```python\n>>> cfg = Config.fromfile('./config_d.py')\n>>> print(cfg)\n>>> dict(a=1,\n...      b=dict(b2=None, b3=0.1),\n...      c=(1, 2))\n```\n\nYou may also set `_delete_=True` to ignore some fields in base configs. All old keys `b1, b2, b3` in `b` are replaced with new keys `b2, b3`.\n\n#### Inherit from multiple base configs (the base configs should not contain the same keys).\n\n`config_e.py`\n\n```python\nc = (1, 2)\nd = 'string'\n```\n\n`config_f.py`\n\n```python\n_base_ = ['./config_a.py', './config_e.py']\n```\n\n```python\n>>> cfg = Config.fromfile('./config_f.py')\n>>> print(cfg)\n>>> dict(a=1,\n...      b=dict(b1=[0, 1, 2], b2=None),\n...      c=(1, 2),\n...      d='string')\n```\n\n### ProgressBar\n\nIf you want to apply a method to a list of items and track the progress, `track_progress`\nis a good choice. It will display a progress bar to tell the progress and ETA.\n\n```python\nimport mmcv\n\ndef func(item):\n    # do something\n    pass\n\ntasks = [item_1, item_2, ..., item_n]\n\nmmcv.track_progress(func, tasks)\n```\n\nThe output is like the following.\n![progress](_static/progress.gif)\n\nThere is another method `track_parallel_progress`, which wraps multiprocessing and\nprogress visualization.\n\n```python\nmmcv.track_parallel_progress(func, tasks, 8)  # 8 workers\n```\n\n![progress](_static/parallel_progress.gif)\n\nIf you want to iterate or enumerate a list of items and track the progress, `track_iter_progress`\nis a good choice. It will display a progress bar to tell the progress and ETA.\n\n```python\nimport mmcv\n\ntasks = [item_1, item_2, ..., item_n]\n\nfor task in mmcv.track_iter_progress(tasks):\n    # do something like print\n    print(task)\n\nfor i, task in enumerate(mmcv.track_iter_progress(tasks)):\n    # do something like print\n    print(i)\n    print(task)\n```\n\n### Timer\n\nIt is convinient to compute the runtime of a code block with `Timer`.\n\n```python\nimport time\n\nwith mmcv.Timer():\n    # simulate some code block\n    time.sleep(1)\n```\n\nor try with `since_start()` and `since_last_check()`. This former can\nreturn the runtime since the timer starts and the latter will return the time\nsince the last time checked.\n\n```python\ntimer = mmcv.Timer()\n# code block 1 here\nprint(timer.since_start())\n# code block 2 here\nprint(timer.since_last_check())\nprint(timer.since_start())\n```\n"
  },
  {
    "path": "code/mmcv/docs/video.md",
    "content": "## Video\n\nThis module provides the following functionalities.\n\n- A `VideoReader` class with friendly apis to read and convert videos.\n- Some methods for editing (cut, concat, resize) videos.\n- Optical flow read/write/warp.\n\n\n### VideoReader\n\nThe `VideoReader` class provides sequence like apis to access video frames.\nIt will internally cache the frames which have been visited.\n\n```python\nvideo = mmcv.VideoReader('test.mp4')\n\n# obtain basic information\nprint(len(video))\nprint(video.width, video.height, video.resolution, video.fps)\n\n# iterate over all frames\nfor frame in video:\n    print(frame.shape)\n\n# read the next frame\nimg = video.read()\n\n# read a frame by index\nimg = video[100]\n\n# read some frames\nimg = video[5:10]\n```\n\nTo convert a video to images or generate a video from a image directory.\n\n```python\n# split a video into frames and save to a folder\nvideo = mmcv.VideoReader('test.mp4')\nvideo.cvt2frames('out_dir')\n\n# generate video from frames\nmmcv.frames2video('out_dir', 'test.avi')\n```\n\n### Editing utils\n\nThere are also some methods for editing videos, which wraps the commands of ffmpeg.\n\n```python\n# cut a video clip\nmmcv.cut_video('test.mp4', 'clip1.mp4', start=3, end=10, vcodec='h264')\n\n# join a list of video clips\nmmcv.concat_video(['clip1.mp4', 'clip2.mp4'], 'joined.mp4', log_level='quiet')\n\n# resize a video with the specified size\nmmcv.resize_video('test.mp4', 'resized1.mp4', (360, 240))\n\n# resize a video with a scaling ratio of 2\nmmcv.resize_video('test.mp4', 'resized2.mp4', ratio=2)\n```\n\n### Optical flow\n\n`mmcv` provides the following methods to operate on optical flows.\n\n- IO\n- Visualization\n- Flow warpping\n\nWe provide two options to dump optical flow files: uncompressed and compressed.\nThe uncompressed way just dumps the floating numbers to a binary file. It is\nlossless but the dumped file has a larger size.\nThe compressed way quantizes the optical flow to 0-255 and dumps it as a\njpeg image. The flow of x-dim and y-dim will be concatenated into a single image.\n\n```python\nflow = np.random.rand(800, 600, 2).astype(np.float32)\n# dump the flow to a flo file (~3.7M)\nmmcv.flowwrite(flow, 'uncompressed.flo')\n# dump the flow to a jpeg file (~230K)\n# the shape of the dumped image is (800, 1200)\nmmcv.flowwrite(flow, 'compressed.jpg', quantize=True, concat_axis=1)\n\n# read the flow file, the shape of loaded flow is (800, 600, 2) for both ways\nflow = mmcv.flowread('uncompressed.flo')\nflow = mmcv.flowread('compressed.jpg', quantize=True, concat_axis=1)\n```\n\nIt is possible to visualize optical flows with `mmcv.flowshow()`.\n\n```python\nmmcv.flowshow(flow)\n```\n\n![progress](_static/flow_visualization.png)\n\n3. Flow warpping\n\n```python\nimg1 = mmcv.imread('img1.jpg')\nflow = mmcv.flowread('flow.flo')\nwarpped_img2 = mmcv.flow_warp(img1, flow)\n```\n\nimg1 (left) and img2 (right)\n\n![raw images](_static/flow_raw_images.png)\n\noptical flow (img2 -> img1)\n\n![optical flow](_static/flow_img2toimg1.png)\n\nwarpped image and difference with ground truth\n\n![warpped image](_static/flow_warp_diff.png)\n"
  },
  {
    "path": "code/mmcv/docs/visualization.md",
    "content": "## Visualization\n\n`mmcv` can show images and annotations (currently supported types include bounding boxes).\n\n```python\n# show an image file\nmmcv.imshow('a.jpg')\n\n# show a loaded image\nimg = np.random.rand(100, 100, 3)\nmmcv.imshow(img)\n\n# show image with bounding boxes\nimg = np.random.rand(100, 100, 3)\nbboxes = np.array([[0, 0, 50, 50], [20, 20, 60, 60]])\nmmcv.imshow_bboxes(img, bboxes)\n```\n\n`mmcv` can also visualize special images such as optical flows.\n\n```python\nflow = mmcv.flowread('test.flo')\nmmcv.flowshow(flow)\n```\n"
  },
  {
    "path": "code/mmcv/examples/config_cifar10.py",
    "content": "# model settings\nmodel = 'resnet18'\n# dataset settings\ndata_root = '/mnt/SSD/dataset/cifar10'\nmean = [0.4914, 0.4822, 0.4465]\nstd = [0.2023, 0.1994, 0.2010]\nbatch_size = 64\n\n# optimizer and learning rate\noptimizer = dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=5e-4)\noptimizer_config = dict(grad_clip=None)\nlr_config = dict(policy='step', step=2)\n\n# runtime settings\nwork_dir = './demo'\ngpus = range(2)\ndist_params = dict(backend='nccl')\ndata_workers = 2  # data workers per gpu\ncheckpoint_config = dict(interval=1)  # save checkpoint at every epoch\nworkflow = [('train', 1), ('val', 1)]\ntotal_epochs = 6\nresume_from = None\nload_from = None\n\n# logging settings\nlog_level = 'INFO'\nlog_config = dict(\n    interval=50,  # log at every 50 iterations\n    hooks=[\n        dict(type='TextLoggerHook'),\n        # dict(type='TensorboardLoggerHook'),\n    ])\n"
  },
  {
    "path": "code/mmcv/examples/dist_train_cifar10.sh",
    "content": "#!/usr/bin/env bash\n\nPYTHON=${PYTHON:-\"python\"}\n\n$PYTHON -m torch.distributed.launch --nproc_per_node=$2 train_cifar10.py $1 --launcher pytorch ${@:3}\n"
  },
  {
    "path": "code/mmcv/examples/resnet_cifar.py",
    "content": "# copied from\n# https://github.com/kuangliu/pytorch-cifar/blob/master/models/resnet.py\n\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\nclass BasicBlock(nn.Module):\n    expansion = 1\n\n    def __init__(self, in_planes, planes, stride=1):\n        super(BasicBlock, self).__init__()\n        self.conv1 = nn.Conv2d(\n            in_planes,\n            planes,\n            kernel_size=3,\n            stride=stride,\n            padding=1,\n            bias=False)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(\n            planes, planes, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(planes)\n\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_planes != self.expansion * planes:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(\n                    in_planes,\n                    self.expansion * planes,\n                    kernel_size=1,\n                    stride=stride,\n                    bias=False), nn.BatchNorm2d(self.expansion * planes))\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out += self.shortcut(x)\n        out = F.relu(out)\n        return out\n\n\nclass Bottleneck(nn.Module):\n    expansion = 4\n\n    def __init__(self, in_planes, planes, stride=1):\n        super(Bottleneck, self).__init__()\n        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(\n            planes,\n            planes,\n            kernel_size=3,\n            stride=stride,\n            padding=1,\n            bias=False)\n        self.bn2 = nn.BatchNorm2d(planes)\n        self.conv3 = nn.Conv2d(\n            planes, self.expansion * planes, kernel_size=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(self.expansion * planes)\n\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_planes != self.expansion * planes:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(\n                    in_planes,\n                    self.expansion * planes,\n                    kernel_size=1,\n                    stride=stride,\n                    bias=False), nn.BatchNorm2d(self.expansion * planes))\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = F.relu(self.bn2(self.conv2(out)))\n        out = self.bn3(self.conv3(out))\n        out += self.shortcut(x)\n        out = F.relu(out)\n        return out\n\n\nclass ResNet(nn.Module):\n\n    def __init__(self, block, num_blocks, num_classes=10):\n        super(ResNet, self).__init__()\n        self.in_planes = 64\n\n        self.conv1 = nn.Conv2d(\n            3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)\n        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)\n        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)\n        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)\n        self.linear = nn.Linear(512 * block.expansion, num_classes)\n\n    def _make_layer(self, block, planes, num_blocks, stride):\n        strides = [stride] + [1] * (num_blocks - 1)\n        layers = []\n        for stride in strides:\n            layers.append(block(self.in_planes, planes, stride))\n            self.in_planes = planes * block.expansion\n        return nn.Sequential(*layers)\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.layer1(out)\n        out = self.layer2(out)\n        out = self.layer3(out)\n        out = self.layer4(out)\n        out = F.avg_pool2d(out, 4)\n        out = out.view(out.size(0), -1)\n        out = self.linear(out)\n        return out\n\n\ndef resnet18():\n    return ResNet(BasicBlock, [2, 2, 2, 2])\n\n\ndef resnet34():\n    return ResNet(BasicBlock, [3, 4, 6, 3])\n\n\ndef resnet50():\n    return ResNet(Bottleneck, [3, 4, 6, 3])\n\n\ndef resnet101():\n    return ResNet(Bottleneck, [3, 4, 23, 3])\n\n\ndef resnet152():\n    return ResNet(Bottleneck, [3, 8, 36, 3])\n"
  },
  {
    "path": "code/mmcv/examples/train_cifar10.py",
    "content": "import logging\nimport os\nfrom argparse import ArgumentParser\nfrom collections import OrderedDict\n\nimport resnet_cifar\nimport torch\nimport torch.distributed as dist\nimport torch.multiprocessing as mp\nimport torch.nn.functional as F\nfrom torch.nn.parallel import DataParallel, DistributedDataParallel\nfrom torch.utils.data import DataLoader\nfrom torch.utils.data.distributed import DistributedSampler\nfrom torchvision import datasets, transforms\n\nfrom mmcv import Config\nfrom mmcv.runner import DistSamplerSeedHook, Runner\n\n\ndef accuracy(output, target, topk=(1, )):\n    \"\"\"Computes the precision@k for the specified values of k\"\"\"\n    with torch.no_grad():\n        maxk = max(topk)\n        batch_size = target.size(0)\n\n        _, pred = output.topk(maxk, 1, True, True)\n        pred = pred.t()\n        correct = pred.eq(target.view(1, -1).expand_as(pred))\n\n        res = []\n        for k in topk:\n            correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)\n            res.append(correct_k.mul_(100.0 / batch_size))\n        return res\n\n\ndef batch_processor(model, data, train_mode):\n    img, label = data\n    label = label.cuda(non_blocking=True)\n    pred = model(img)\n    loss = F.cross_entropy(pred, label)\n    acc_top1, acc_top5 = accuracy(pred, label, topk=(1, 5))\n    log_vars = OrderedDict()\n    log_vars['loss'] = loss.item()\n    log_vars['acc_top1'] = acc_top1.item()\n    log_vars['acc_top5'] = acc_top5.item()\n    outputs = dict(loss=loss, log_vars=log_vars, num_samples=img.size(0))\n    return outputs\n\n\ndef get_logger(log_level):\n    logging.basicConfig(\n        format='%(asctime)s - %(levelname)s - %(message)s', level=log_level)\n    logger = logging.getLogger()\n    return logger\n\n\ndef init_dist(backend='nccl', **kwargs):\n    if mp.get_start_method(allow_none=True) is None:\n        mp.set_start_method('spawn')\n    rank = int(os.environ['RANK'])\n    num_gpus = torch.cuda.device_count()\n    torch.cuda.set_device(rank % num_gpus)\n    dist.init_process_group(backend=backend, **kwargs)\n\n\ndef parse_args():\n    parser = ArgumentParser(description='Train CIFAR-10 classification')\n    parser.add_argument('config', help='train config file path')\n    parser.add_argument(\n        '--launcher',\n        choices=['none', 'pytorch'],\n        default='none',\n        help='job launcher')\n    parser.add_argument('--local_rank', type=int, default=0)\n    return parser.parse_args()\n\n\ndef main():\n    args = parse_args()\n\n    cfg = Config.fromfile(args.config)\n\n    logger = get_logger(cfg.log_level)\n\n    # init distributed environment if necessary\n    if args.launcher == 'none':\n        dist = False\n        logger.info('Disabled distributed training.')\n    else:\n        dist = True\n        init_dist(**cfg.dist_params)\n        world_size = torch.distributed.get_world_size()\n        rank = torch.distributed.get_rank()\n        if rank != 0:\n            logger.setLevel('ERROR')\n        logger.info('Enabled distributed training.')\n\n    # build datasets and dataloaders\n    normalize = transforms.Normalize(mean=cfg.mean, std=cfg.std)\n    train_dataset = datasets.CIFAR10(\n        root=cfg.data_root,\n        train=True,\n        transform=transforms.Compose([\n            transforms.RandomCrop(32, padding=4),\n            transforms.RandomHorizontalFlip(),\n            transforms.ToTensor(),\n            normalize,\n        ]))\n    val_dataset = datasets.CIFAR10(\n        root=cfg.data_root,\n        train=False,\n        transform=transforms.Compose([\n            transforms.ToTensor(),\n            normalize,\n        ]))\n    if dist:\n        num_workers = cfg.data_workers\n        assert cfg.batch_size % world_size == 0\n        batch_size = cfg.batch_size // world_size\n        train_sampler = DistributedSampler(train_dataset, world_size, rank)\n        val_sampler = DistributedSampler(val_dataset, world_size, rank)\n        shuffle = False\n    else:\n        num_workers = cfg.data_workers * len(cfg.gpus)\n        batch_size = cfg.batch_size\n        train_sampler = None\n        val_sampler = None\n        shuffle = True\n    train_loader = DataLoader(\n        train_dataset,\n        batch_size=batch_size,\n        shuffle=shuffle,\n        sampler=train_sampler,\n        num_workers=num_workers)\n    val_loader = DataLoader(\n        val_dataset,\n        batch_size=batch_size,\n        shuffle=False,\n        sampler=val_sampler,\n        num_workers=num_workers)\n\n    # build model\n    model = getattr(resnet_cifar, cfg.model)()\n    if dist:\n        model = DistributedDataParallel(\n            model.cuda(), device_ids=[torch.cuda.current_device()])\n    else:\n        model = DataParallel(model, device_ids=cfg.gpus).cuda()\n\n    # build runner and register hooks\n    runner = Runner(\n        model,\n        batch_processor,\n        cfg.optimizer,\n        cfg.work_dir,\n        log_level=cfg.log_level)\n    runner.register_training_hooks(\n        lr_config=cfg.lr_config,\n        optimizer_config=cfg.optimizer_config,\n        checkpoint_config=cfg.checkpoint_config,\n        log_config=cfg.log_config)\n    if dist:\n        runner.register_hook(DistSamplerSeedHook())\n\n    # load param (if necessary) and run\n    if cfg.get('resume_from') is not None:\n        runner.resume(cfg.resume_from)\n    elif cfg.get('load_from') is not None:\n        runner.load_checkpoint(cfg.load_from)\n\n    runner.run([train_loader, val_loader], cfg.workflow, cfg.total_epochs)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/mmcv/mmcv/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\n# flake8: noqa\nfrom .arraymisc import *\nfrom .fileio import *\nfrom .image import *\nfrom .utils import *\nfrom .version import __version__\nfrom .video import *\nfrom .visualization import *\n\n# The following modules are not imported to this level, so mmcv may be used\n# without PyTorch.\n# - runner\n# - parallel\n"
  },
  {
    "path": "code/mmcv/mmcv/arraymisc/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .quantization import dequantize, quantize\n\n__all__ = ['quantize', 'dequantize']\n"
  },
  {
    "path": "code/mmcv/mmcv/arraymisc/quantization.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numpy as np\n\n\ndef quantize(arr, min_val, max_val, levels, dtype=np.int64):\n    \"\"\"Quantize an array of (-inf, inf) to [0, levels-1].\n\n    Args:\n        arr (ndarray): Input array.\n        min_val (scalar): Minimum value to be clipped.\n        max_val (scalar): Maximum value to be clipped.\n        levels (int): Quantization levels.\n        dtype (np.type): The type of the quantized array.\n\n    Returns:\n        tuple: Quantized array.\n    \"\"\"\n    if not (isinstance(levels, int) and levels > 1):\n        raise ValueError(\n            f'levels must be a positive integer, but got {levels}')\n    if min_val >= max_val:\n        raise ValueError(\n            f'min_val ({min_val}) must be smaller than max_val ({max_val})')\n\n    arr = np.clip(arr, min_val, max_val) - min_val\n    quantized_arr = np.minimum(\n        np.floor(levels * arr / (max_val - min_val)).astype(dtype), levels - 1)\n\n    return quantized_arr\n\n\ndef dequantize(arr, min_val, max_val, levels, dtype=np.float64):\n    \"\"\"Dequantize an array.\n\n    Args:\n        arr (ndarray): Input array.\n        min_val (scalar): Minimum value to be clipped.\n        max_val (scalar): Maximum value to be clipped.\n        levels (int): Quantization levels.\n        dtype (np.type): The type of the dequantized array.\n\n    Returns:\n        tuple: Dequantized array.\n    \"\"\"\n    if not (isinstance(levels, int) and levels > 1):\n        raise ValueError(\n            f'levels must be a positive integer, but got {levels}')\n    if min_val >= max_val:\n        raise ValueError(\n            f'min_val ({min_val}) must be smaller than max_val ({max_val})')\n\n    dequantized_arr = (arr + 0.5).astype(dtype) * (max_val -\n                                                   min_val) / levels + min_val\n\n    return dequantized_arr\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .alexnet import AlexNet\nfrom .bricks import (ACTIVATION_LAYERS, CONV_LAYERS, NORM_LAYERS,\n                     PADDING_LAYERS, UPSAMPLE_LAYERS, ConvModule, NonLocal1d,\n                     NonLocal2d, NonLocal3d, Scale, build_activation_layer,\n                     build_conv_layer, build_norm_layer, build_padding_layer,\n                     build_upsample_layer, is_norm)\nfrom .resnet import ResNet, make_res_layer\nfrom .utils import (bias_init_with_prob, caffe2_xavier_init, constant_init,\n                    get_model_complexity_info, kaiming_init, normal_init,\n                    uniform_init, xavier_init)\nfrom .vgg import VGG, make_vgg_layer\n\n__all__ = [\n    'AlexNet', 'VGG', 'make_vgg_layer', 'ResNet', 'make_res_layer',\n    'constant_init', 'xavier_init', 'normal_init', 'uniform_init',\n    'kaiming_init', 'caffe2_xavier_init', 'bias_init_with_prob', 'ConvModule',\n    'build_activation_layer', 'build_conv_layer', 'build_norm_layer',\n    'build_padding_layer', 'build_upsample_layer', 'is_norm', 'NonLocal1d',\n    'NonLocal2d', 'NonLocal3d', 'ACTIVATION_LAYERS', 'CONV_LAYERS',\n    'NORM_LAYERS', 'PADDING_LAYERS', 'UPSAMPLE_LAYERS', 'Scale',\n    'get_model_complexity_info'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/alexnet.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport logging\n\nimport torch.nn as nn\n\nfrom ..runner import load_checkpoint\n\n\nclass AlexNet(nn.Module):\n    \"\"\"AlexNet backbone.\n\n    Args:\n        num_classes (int): number of classes for classification.\n    \"\"\"\n\n    def __init__(self, num_classes=-1):\n        super(AlexNet, self).__init__()\n        self.num_classes = num_classes\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n            nn.Conv2d(64, 192, kernel_size=5, padding=2),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n            nn.Conv2d(192, 384, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(384, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(256, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n        )\n        if self.num_classes > 0:\n            self.classifier = nn.Sequential(\n                nn.Dropout(),\n                nn.Linear(256 * 6 * 6, 4096),\n                nn.ReLU(inplace=True),\n                nn.Dropout(),\n                nn.Linear(4096, 4096),\n                nn.ReLU(inplace=True),\n                nn.Linear(4096, num_classes),\n            )\n\n    def init_weights(self, pretrained=None):\n        if isinstance(pretrained, str):\n            logger = logging.getLogger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            # use default initializer\n            pass\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n    def forward(self, x):\n\n        x = self.features(x)\n        if self.num_classes > 0:\n            x = x.view(x.size(0), 256 * 6 * 6)\n            x = self.classifier(x)\n\n        return x\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/__init__.py",
    "content": "from .activation import build_activation_layer\nfrom .conv import build_conv_layer\nfrom .conv_module import ConvModule\nfrom .hsigmoid import HSigmoid\nfrom .hswish import HSwish\nfrom .non_local import NonLocal1d, NonLocal2d, NonLocal3d\nfrom .norm import build_norm_layer, is_norm\nfrom .padding import build_padding_layer\nfrom .registry import (ACTIVATION_LAYERS, CONV_LAYERS, NORM_LAYERS,\n                       PADDING_LAYERS, UPSAMPLE_LAYERS)\nfrom .scale import Scale\nfrom .upsample import build_upsample_layer\n\n__all__ = [\n    'ConvModule', 'build_activation_layer', 'build_conv_layer',\n    'build_norm_layer', 'build_padding_layer', 'build_upsample_layer',\n    'is_norm', 'HSigmoid', 'HSwish', 'NonLocal1d', 'NonLocal2d', 'NonLocal3d',\n    'ACTIVATION_LAYERS', 'CONV_LAYERS', 'NORM_LAYERS', 'PADDING_LAYERS',\n    'UPSAMPLE_LAYERS', 'Scale'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/activation.py",
    "content": "import torch.nn as nn\n\nfrom mmcv.utils import build_from_cfg\nfrom .registry import ACTIVATION_LAYERS\n\nfor module in [\n        nn.ReLU, nn.LeakyReLU, nn.PReLU, nn.RReLU, nn.ReLU6, nn.ELU,\n        nn.Sigmoid, nn.Tanh\n]:\n    ACTIVATION_LAYERS.register_module(module=module)\n\n\ndef build_activation_layer(cfg):\n    \"\"\"Build activation layer.\n\n    Args:\n        cfg (dict): The activation layer config, which should contain:\n            - type (str): Layer type.\n            - layer args: Args needed to instantiate an activation layer.\n\n    Returns:\n        nn.Module: Created activation layer.\n    \"\"\"\n    return build_from_cfg(cfg, ACTIVATION_LAYERS)\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/conv.py",
    "content": "from torch import nn\n\nfrom .registry import CONV_LAYERS\n\nCONV_LAYERS.register_module('Conv1d', module=nn.Conv1d)\nCONV_LAYERS.register_module('Conv2d', module=nn.Conv2d)\nCONV_LAYERS.register_module('Conv3d', module=nn.Conv3d)\nCONV_LAYERS.register_module('Conv', module=nn.Conv2d)\n\n\ndef build_conv_layer(cfg, *args, **kwargs):\n    \"\"\"Build convolution layer.\n\n    Args:\n        cfg (None or dict): The conv layer config, which should contain:\n            - type (str): Layer type.\n            - layer args: Args needed to instantiate an activation layer.\n        args (argument list): Arguments passed to the `__init__`\n            method of the corresponding conv layer.\n        kwargs (keyword arguments): Keyword arguments passed to the `__init__`\n            method of the corresponding conv layer.\n\n    Returns:\n        nn.Module: Created conv layer.\n    \"\"\"\n    if cfg is None:\n        cfg_ = dict(type='Conv2d')\n    else:\n        if not isinstance(cfg, dict):\n            raise TypeError('cfg must be a dict')\n        if 'type' not in cfg:\n            raise KeyError('the cfg dict must contain the key \"type\"')\n        cfg_ = cfg.copy()\n\n    layer_type = cfg_.pop('type')\n    if layer_type not in CONV_LAYERS:\n        raise KeyError(f'Unrecognized norm type {layer_type}')\n    else:\n        conv_layer = CONV_LAYERS.get(layer_type)\n\n    layer = conv_layer(*args, **kwargs, **cfg_)\n\n    return layer\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/conv_module.py",
    "content": "import warnings\n\nimport torch.nn as nn\n\nfrom ..utils import constant_init, kaiming_init\nfrom .activation import build_activation_layer\nfrom .conv import build_conv_layer\nfrom .norm import build_norm_layer\nfrom .padding import build_padding_layer\n\n\nclass ConvModule(nn.Module):\n    \"\"\"A conv block that bundles conv/norm/activation layers.\n\n    This block simplifies the usage of convolution layers, which are commonly\n    used with a norm layer (e.g., BatchNorm) and activation layer (e.g., ReLU).\n    It is based upon three build methods: `build_conv_layer()`,\n    `build_norm_layer()` and `build_activation_layer()`.\n\n    Besides, we add some additional features in this module.\n    1. Automatically set `bias` of the conv layer.\n    2. Spectral norm is supported.\n    3. More padding modes are supported. Before PyTorch 1.5, nn.Conv2d only\n    supports zero and circular padding, and we add \"reflect\" padding mode.\n\n    Args:\n        in_channels (int): Same as nn.Conv2d.\n        out_channels (int): Same as nn.Conv2d.\n        kernel_size (int | tuple[int]): Same as nn.Conv2d.\n        stride (int | tuple[int]): Same as nn.Conv2d.\n        padding (int | tuple[int]): Same as nn.Conv2d.\n        dilation (int | tuple[int]): Same as nn.Conv2d.\n        groups (int): Same as nn.Conv2d.\n        bias (bool | str): If specified as `auto`, it will be decided by the\n            norm_cfg. Bias will be set as True if `norm_cfg` is None, otherwise\n            False. Default: \"auto\".\n        conv_cfg (dict): Config dict for convolution layer. Default: None,\n            which means using conv2d.\n        norm_cfg (dict): Config dict for normalization layer. Default: None.\n        act_cfg (dict): Config dict for activation layer.\n            Default: dict(type='ReLU').\n        inplace (bool): Whether to use inplace mode for activation.\n            Default: True.\n        with_spectral_norm (bool): Whether use spectral norm in conv module.\n            Default: False.\n        padding_mode (str): If the `padding_mode` has not been supported by\n            current `Conv2d` in PyTorch, we will use our own padding layer\n            instead. Currently, we support ['zeros', 'circular'] with official\n            implementation and ['reflect'] with our own implementation.\n            Default: 'zeros'.\n        order (tuple[str]): The order of conv/norm/activation layers. It is a\n            sequence of \"conv\", \"norm\" and \"act\". Common examples are\n            (\"conv\", \"norm\", \"act\") and (\"act\", \"conv\", \"norm\").\n            Default: ('conv', 'norm', 'act').\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 bias='auto',\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 act_cfg=dict(type='ReLU'),\n                 inplace=True,\n                 with_spectral_norm=False,\n                 padding_mode='zeros',\n                 order=('conv', 'norm', 'act')):\n        super(ConvModule, self).__init__()\n        assert conv_cfg is None or isinstance(conv_cfg, dict)\n        assert norm_cfg is None or isinstance(norm_cfg, dict)\n        assert act_cfg is None or isinstance(act_cfg, dict)\n        official_padding_mode = ['zeros', 'circular']\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.act_cfg = act_cfg\n        self.inplace = inplace\n        self.with_spectral_norm = with_spectral_norm\n        self.with_explicit_padding = padding_mode not in official_padding_mode\n        self.order = order\n        assert isinstance(self.order, tuple) and len(self.order) == 3\n        assert set(order) == set(['conv', 'norm', 'act'])\n\n        self.with_norm = norm_cfg is not None\n        self.with_activation = act_cfg is not None\n        # if the conv layer is before a norm layer, bias is unnecessary.\n        if bias == 'auto':\n            bias = not self.with_norm\n        self.with_bias = bias\n\n        if self.with_norm and self.with_bias:\n            warnings.warn('ConvModule has norm and bias at the same time')\n\n        if self.with_explicit_padding:\n            pad_cfg = dict(type=padding_mode)\n            self.padding_layer = build_padding_layer(pad_cfg, padding)\n\n        # reset padding to 0 for conv module\n        conv_padding = 0 if self.with_explicit_padding else padding\n        # build convolution layer\n        self.conv = build_conv_layer(\n            conv_cfg,\n            in_channels,\n            out_channels,\n            kernel_size,\n            stride=stride,\n            padding=conv_padding,\n            dilation=dilation,\n            groups=groups,\n            bias=bias)\n        # export the attributes of self.conv to a higher level for convenience\n        self.in_channels = self.conv.in_channels\n        self.out_channels = self.conv.out_channels\n        self.kernel_size = self.conv.kernel_size\n        self.stride = self.conv.stride\n        self.padding = padding\n        self.dilation = self.conv.dilation\n        self.transposed = self.conv.transposed\n        self.output_padding = self.conv.output_padding\n        self.groups = self.conv.groups\n\n        if self.with_spectral_norm:\n            self.conv = nn.utils.spectral_norm(self.conv)\n\n        # build normalization layers\n        if self.with_norm:\n            # norm layer is after conv layer\n            if order.index('norm') > order.index('conv'):\n                norm_channels = out_channels\n            else:\n                norm_channels = in_channels\n            self.norm_name, norm = build_norm_layer(norm_cfg, norm_channels)\n            self.add_module(self.norm_name, norm)\n\n        # build activation layer\n        if self.with_activation:\n            act_cfg_ = act_cfg.copy()\n            # nn.Tanh has no 'inplace' argument\n            if act_cfg_['type'] not in ['Tanh', 'PReLU', 'Sigmoid']:\n                act_cfg_.setdefault('inplace', inplace)\n            self.activate = build_activation_layer(act_cfg_)\n\n        # Use msra init by default\n        self.init_weights()\n\n    @property\n    def norm(self):\n        return getattr(self, self.norm_name)\n\n    def init_weights(self):\n        # 1. It is mainly for customized conv layers with their own\n        #    initialization manners, and we do not want ConvModule to\n        #    overrides the initialization.\n        # 2. For customized conv layers without their own initialization\n        #    manners, they will be initialized by this method with default\n        #    `kaiming_init`.\n        # 3. For PyTorch's conv layers, they will be initialized anyway by\n        #    their own `reset_parameters` methods.\n        if not hasattr(self.conv, 'init_weights'):\n            if self.with_activation and self.act_cfg['type'] == 'LeakyReLU':\n                nonlinearity = 'leaky_relu'\n                a = self.act_cfg.get('negative_slope', 0.01)\n            else:\n                nonlinearity = 'relu'\n                a = 0\n            kaiming_init(self.conv, a=a, nonlinearity=nonlinearity)\n        if self.with_norm:\n            constant_init(self.norm, 1, bias=0)\n\n    def forward(self, x, activate=True, norm=True):\n        for layer in self.order:\n            if layer == 'conv':\n                if self.with_explicit_padding:\n                    x = self.padding_layer(x)\n                x = self.conv(x)\n            elif layer == 'norm' and norm and self.with_norm:\n                x = self.norm(x)\n            elif layer == 'act' and activate and self.with_activation:\n                x = self.activate(x)\n        return x\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/hsigmoid.py",
    "content": "import torch.nn as nn\n\nfrom .registry import ACTIVATION_LAYERS\n\n\n@ACTIVATION_LAYERS.register_module()\nclass HSigmoid(nn.Module):\n    \"\"\"Hard Sigmoid Module. Apply the hard sigmoid function:\n    Hsigmoid(x) = min(max((x + 1) / 2, 0), 1)\n\n    Returns:\n        Tensor: The output tensor.\n    \"\"\"\n\n    def __init__(self):\n        super(HSigmoid, self).__init__()\n\n    def forward(self, x):\n        x = (x + 1) / 2\n\n        return x.clamp_(0, 1)\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/hswish.py",
    "content": "import torch.nn as nn\n\nfrom .registry import ACTIVATION_LAYERS\n\n\n@ACTIVATION_LAYERS.register_module()\nclass HSwish(nn.Module):\n    \"\"\"Hard Swish Module. Apply the hard swish function:\n    Hswish(x) = x * ReLU6(x + 3) / 6\n\n    Args:\n        inplace (bool): can optionally do the operation in-place.\n            Default: False.\n\n    Returns:\n        Tensor: The output tensor.\n    \"\"\"\n\n    def __init__(self, inplace=False):\n        super(HSwish, self).__init__()\n        self.act = nn.ReLU6(inplace)\n\n    def forward(self, x):\n        return x * self.act(x + 3) / 6\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/non_local.py",
    "content": "from abc import ABCMeta\n\nimport torch\nimport torch.nn as nn\n\nfrom ..utils import constant_init, normal_init\nfrom .conv_module import ConvModule\n\n\nclass _NonLocalNd(nn.Module, metaclass=ABCMeta):\n    \"\"\"Basic Non-local module.\n\n    This module is proposed in\n    \"Non-local Neural Networks\"\n    Paper reference: https://arxiv.org/abs/1711.07971\n\n    Args:\n        in_channels (int): Channels of the input feature map.\n        reduction (int): Channel reduction ratio. Default: 2.\n        use_scale (bool): Whether to scale pairwise_weight by\n            `1/sqrt(inter_channels)` when the mode is `embedded_gaussian`.\n            Default: True.\n        conv_cfg (None | dict): The config dict for convolution layers.\n            If not specified, it will use `nn.Conv2d` for convolution layers.\n            Default: None.\n        norm_cfg (None | dict): The config dict for normalization layers.\n            Default: None. (This parameter is only applicable to conv_out.)\n        mode (str): Options are `embedded_gaussian` and `dot_product`.\n            Default: embedded_gaussian.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 reduction=2,\n                 use_scale=True,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 mode='embedded_gaussian',\n                 **kwargs):\n        super(_NonLocalNd, self).__init__()\n        self.in_channels = in_channels\n        self.reduction = reduction\n        self.use_scale = use_scale\n        self.inter_channels = in_channels // reduction\n        self.mode = mode\n\n        if mode not in ['embedded_gaussian', 'dot_product']:\n            raise ValueError(\n                \"Mode should be in 'embedded_gaussian' or 'dot_product', \"\n                f'but got {mode} instead.')\n\n        # g, theta, phi are defaulted as `nn.ConvNd`.\n        # Here we use ConvModule for potential usage.\n        self.g = ConvModule(\n            self.in_channels,\n            self.inter_channels,\n            kernel_size=1,\n            conv_cfg=conv_cfg,\n            act_cfg=None)\n        self.theta = ConvModule(\n            self.in_channels,\n            self.inter_channels,\n            kernel_size=1,\n            conv_cfg=conv_cfg,\n            act_cfg=None)\n        self.phi = ConvModule(\n            self.in_channels,\n            self.inter_channels,\n            kernel_size=1,\n            conv_cfg=conv_cfg,\n            act_cfg=None)\n        self.conv_out = ConvModule(\n            self.inter_channels,\n            self.in_channels,\n            kernel_size=1,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg,\n            act_cfg=None)\n\n        self.init_weights(**kwargs)\n\n    def init_weights(self, std=0.01, zeros_init=True):\n        for m in [self.g, self.theta, self.phi]:\n            normal_init(m.conv, std=std)\n        if zeros_init:\n            if self.conv_out.norm_cfg is None:\n                constant_init(self.conv_out.conv, 0)\n            else:\n                constant_init(self.conv_out.norm, 0)\n        else:\n            if self.conv_out.norm_cfg is None:\n                normal_init(self.conv_out.conv, std=std)\n            else:\n                normal_init(self.conv_out.norm, std=std)\n\n    def embedded_gaussian(self, theta_x, phi_x):\n        # NonLocal1d pairwise_weight: [N, H, H]\n        # NonLocal2d pairwise_weight: [N, HxW, HxW]\n        # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW]\n        pairwise_weight = torch.matmul(theta_x, phi_x)\n        if self.use_scale:\n            # theta_x.shape[-1] is `self.inter_channels`\n            pairwise_weight /= theta_x.shape[-1]**0.5\n        pairwise_weight = pairwise_weight.softmax(dim=-1)\n        return pairwise_weight\n\n    def dot_product(self, theta_x, phi_x):\n        # NonLocal1d pairwise_weight: [N, H, H]\n        # NonLocal2d pairwise_weight: [N, HxW, HxW]\n        # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW]\n        pairwise_weight = torch.matmul(theta_x, phi_x)\n        pairwise_weight /= pairwise_weight.shape[-1]\n        return pairwise_weight\n\n    def forward(self, x):\n        # Assume `reduction = 1`, then `inter_channels = C`\n        # NonLocal1d x: [N, C, H]\n        # NonLocal2d x: [N, C, H, W]\n        # NonLocal3d x: [N, C, T, H, W]\n        n = x.size(0)\n\n        # NonLocal1d g_x: [N, H, C]\n        # NonLocal2d g_x: [N, HxW, C]\n        # NonLocal3d g_x: [N, TxHxW, C]\n        g_x = self.g(x).view(n, self.inter_channels, -1)\n        g_x = g_x.permute(0, 2, 1)\n\n        # NonLocal1d theta_x: [N, H, C]\n        # NonLocal2d theta_x: [N, HxW, C]\n        # NonLocal3d theta_x: [N, TxHxW, C]\n        theta_x = self.theta(x).view(n, self.inter_channels, -1)\n        theta_x = theta_x.permute(0, 2, 1)\n\n        # NonLocal1d phi_x: [N, C, H]\n        # NonLocal2d phi_x: [N, C, HxW]\n        # NonLocal3d phi_x: [N, C, TxHxW]\n        phi_x = self.phi(x).view(n, self.inter_channels, -1)\n\n        pairwise_func = getattr(self, self.mode)\n        # NonLocal1d pairwise_weight: [N, H, H]\n        # NonLocal2d pairwise_weight: [N, HxW, HxW]\n        # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW]\n        pairwise_weight = pairwise_func(theta_x, phi_x)\n\n        # NonLocal1d y: [N, H, C]\n        # NonLocal2d y: [N, HxW, C]\n        # NonLocal3d y: [N, TxHxW, C]\n        y = torch.matmul(pairwise_weight, g_x)\n        # NonLocal1d y: [N, C, H]\n        # NonLocal2d y: [N, C, H, W]\n        # NonLocal3d y: [N, C, T, H, W]\n        y = y.permute(0, 2, 1).contiguous().reshape(n, self.inter_channels,\n                                                    *x.size()[2:])\n\n        output = x + self.conv_out(y)\n\n        return output\n\n\nclass NonLocal1d(_NonLocalNd):\n    \"\"\"1D Non-local module.\n\n    Args:\n        in_channels (int): Same as `NonLocalND`.\n        sub_sample (bool): Whether to apply max pooling after pairwise\n            function (Note that the `sub_sample` is applied on spatial only).\n            Default: False.\n        conv_cfg (None | dict): Same as `NonLocalND`.\n            Default: dict(type='Conv1d').\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 sub_sample=False,\n                 conv_cfg=dict(type='Conv1d'),\n                 **kwargs):\n        super(NonLocal1d, self).__init__(\n            in_channels, conv_cfg=conv_cfg, **kwargs)\n\n        self.sub_sample = sub_sample\n\n        if sub_sample:\n            max_pool_layer = nn.MaxPool1d(kernel_size=2)\n            self.g = nn.Sequential(self.g, max_pool_layer)\n            self.phi = nn.Sequential(self.phi, max_pool_layer)\n\n\nclass NonLocal2d(_NonLocalNd):\n    \"\"\"2D Non-local module.\n\n    Args:\n        in_channels (int): Same as `NonLocalND`.\n        sub_sample (bool): Whether to apply max pooling after pairwise\n            function (Note that the `sub_sample` is applied on spatial only).\n            Default: False.\n        conv_cfg (None | dict): Same as `NonLocalND`.\n            Default: dict(type='Conv2d').\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 sub_sample=False,\n                 conv_cfg=dict(type='Conv2d'),\n                 **kwargs):\n        super(NonLocal2d, self).__init__(\n            in_channels, conv_cfg=conv_cfg, **kwargs)\n\n        self.sub_sample = sub_sample\n\n        if sub_sample:\n            max_pool_layer = nn.MaxPool2d(kernel_size=(2, 2))\n            self.g = nn.Sequential(self.g, max_pool_layer)\n            self.phi = nn.Sequential(self.phi, max_pool_layer)\n\n\nclass NonLocal3d(_NonLocalNd):\n    \"\"\"3D Non-local module.\n\n    Args:\n        in_channels (int): Same as `NonLocalND`.\n        sub_sample (bool): Whether to apply max pooling after pairwise\n            function (Note that the `sub_sample` is applied on spatial only).\n            Default: False.\n        conv_cfg (None | dict): Same as `NonLocalND`.\n            Default: dict(type='Conv3d').\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 sub_sample=False,\n                 conv_cfg=dict(type='Conv3d'),\n                 **kwargs):\n        super(NonLocal3d, self).__init__(\n            in_channels, conv_cfg=conv_cfg, **kwargs)\n        self.sub_sample = sub_sample\n\n        if sub_sample:\n            max_pool_layer = nn.MaxPool3d(kernel_size=(1, 2, 2))\n            self.g = nn.Sequential(self.g, max_pool_layer)\n            self.phi = nn.Sequential(self.phi, max_pool_layer)\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/norm.py",
    "content": "import inspect\n\nimport torch.nn as nn\n\nfrom mmcv.utils import is_tuple_of\nfrom mmcv.utils.parrots_wrapper import SyncBatchNorm, _BatchNorm, _InstanceNorm\nfrom .registry import NORM_LAYERS\n\nNORM_LAYERS.register_module('BN', module=nn.BatchNorm2d)\nNORM_LAYERS.register_module('BN1d', module=nn.BatchNorm1d)\nNORM_LAYERS.register_module('BN2d', module=nn.BatchNorm2d)\nNORM_LAYERS.register_module('BN3d', module=nn.BatchNorm3d)\nNORM_LAYERS.register_module('SyncBN', module=SyncBatchNorm)\nNORM_LAYERS.register_module('GN', module=nn.GroupNorm)\nNORM_LAYERS.register_module('LN', module=nn.LayerNorm)\nNORM_LAYERS.register_module('IN', module=nn.InstanceNorm2d)\nNORM_LAYERS.register_module('IN1d', module=nn.InstanceNorm1d)\nNORM_LAYERS.register_module('IN2d', module=nn.InstanceNorm2d)\nNORM_LAYERS.register_module('IN3d', module=nn.InstanceNorm3d)\n\n\ndef infer_abbr(class_type):\n    \"\"\"Infer abbreviation from the class name.\n\n    When we build a norm layer with `build_norm_layer()`, we want to preserve\n    the norm type in variable names, e.g, self.bn1, self.gn. This method will\n    infer the abbreviation to map class types to abbreviations.\n\n    Rule 1: If the class has the property \"abbr\", return the property.\n    Rule 2: If the parent class is _BatchNorm, GroupNorm, LayerNorm or\n    InstanceNorm, the abbreviation of this layer will be \"bn\", \"gn\", \"ln\" and\n    \"in\" respectively.\n    Rule 3: If the class name contains \"batch\", \"group\", \"layer\" or \"instance\",\n    the abbreviation of this layer will be \"bn\", \"gn\", \"ln\" and \"in\"\n    respectively.\n    Rule 4: Otherwise, the abbreviation falls back to \"norm\".\n\n    Args:\n        class_type (type): The norm layer type.\n\n    Returns:\n        str: The inferred abbreviation.\n    \"\"\"\n    if not inspect.isclass(class_type):\n        raise TypeError(\n            f'class_type must be a type, but got {type(class_type)}')\n    if hasattr(class_type, 'abbr'):\n        return class_type.abbr\n    if issubclass(class_type, _InstanceNorm):  # IN is a subclass of BN\n        return 'in'\n    elif issubclass(class_type, _BatchNorm):\n        return 'bn'\n    elif issubclass(class_type, nn.GroupNorm):\n        return 'gn'\n    elif issubclass(class_type, nn.LayerNorm):\n        return 'ln'\n    else:\n        class_name = class_type.__name__.lower()\n        if 'batch' in class_name:\n            return 'bn'\n        elif 'group' in class_name:\n            return 'gn'\n        elif 'layer' in class_name:\n            return 'ln'\n        elif 'instance' in class_name:\n            return 'in'\n        else:\n            return 'norm'\n\n\ndef build_norm_layer(cfg, num_features, postfix=''):\n    \"\"\"Build normalization layer.\n\n    Args:\n        cfg (dict): The norm layer config, which should contain:\n            - type (str): Layer type.\n            - layer args: Args needed to instantiate a norm layer.\n            - requires_grad (bool, optional): Whether stop gradient updates.\n        num_features (int): Number of input channels.\n        postfix (int | str): The postfix to be appended into norm abbreviation\n            to create named layer.\n\n    Returns:\n        tuple[str, nn.Module]:\n            name (str): The layer name consisting of abbreviation and postfix,\n                e.g., bn1, gn.\n            layer (nn.Module): Created norm layer.\n    \"\"\"\n    if not isinstance(cfg, dict):\n        raise TypeError('cfg must be a dict')\n    if 'type' not in cfg:\n        raise KeyError('the cfg dict must contain the key \"type\"')\n    cfg_ = cfg.copy()\n\n    layer_type = cfg_.pop('type')\n    if layer_type not in NORM_LAYERS:\n        raise KeyError(f'Unrecognized norm type {layer_type}')\n\n    norm_layer = NORM_LAYERS.get(layer_type)\n    abbr = infer_abbr(norm_layer)\n\n    assert isinstance(postfix, (int, str))\n    name = abbr + str(postfix)\n\n    requires_grad = cfg_.pop('requires_grad', True)\n    cfg_.setdefault('eps', 1e-5)\n    if layer_type != 'GN':\n        layer = norm_layer(num_features, **cfg_)\n        if layer_type == 'SyncBN':\n            layer._specify_ddp_gpu_num(1)\n    else:\n        assert 'num_groups' in cfg_\n        layer = norm_layer(num_channels=num_features, **cfg_)\n\n    for param in layer.parameters():\n        param.requires_grad = requires_grad\n\n    return name, layer\n\n\ndef is_norm(layer, exclude=None):\n    \"\"\"Check if a layer is a normalization layer.\n\n    Args:\n        layer (nn.Module): The layer to be checked.\n        exclude (type | tuple[type]): Types to be excluded.\n\n    Returns:\n        bool: Whether the layer is a norm layer.\n    \"\"\"\n    if exclude is not None:\n        if not isinstance(exclude, tuple):\n            exclude = (exclude, )\n        if not is_tuple_of(exclude, type):\n            raise TypeError(\n                f'\"exclude\" must be either None or type or a tuple of types, '\n                f'but got {type(exclude)}: {exclude}')\n\n    if exclude and isinstance(layer, exclude):\n        return False\n\n    all_norm_bases = (_BatchNorm, _InstanceNorm, nn.GroupNorm, nn.LayerNorm)\n    return isinstance(layer, all_norm_bases)\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/padding.py",
    "content": "import torch.nn as nn\n\nfrom .registry import PADDING_LAYERS\n\nPADDING_LAYERS.register_module('zero', module=nn.ZeroPad2d)\nPADDING_LAYERS.register_module('reflect', module=nn.ReflectionPad2d)\nPADDING_LAYERS.register_module('replicate', module=nn.ReplicationPad2d)\n\n\ndef build_padding_layer(cfg, *args, **kwargs):\n    \"\"\"Build padding layer.\n\n    Args:\n        cfg (None or dict): The padding layer config, which should contain:\n            - type (str): Layer type.\n            - layer args: Args needed to instantiate a padding layer.\n\n    Returns:\n        nn.Module: Created padding layer.\n    \"\"\"\n    if not isinstance(cfg, dict):\n        raise TypeError('cfg must be a dict')\n    if 'type' not in cfg:\n        raise KeyError('the cfg dict must contain the key \"type\"')\n\n    cfg_ = cfg.copy()\n    padding_type = cfg_.pop('type')\n    if padding_type not in PADDING_LAYERS:\n        raise KeyError(f'Unrecognized padding type {padding_type}.')\n    else:\n        padding_layer = PADDING_LAYERS.get(padding_type)\n\n    layer = padding_layer(*args, **kwargs, **cfg_)\n\n    return layer\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/registry.py",
    "content": "from mmcv.utils import Registry\n\nCONV_LAYERS = Registry('conv layer')\nNORM_LAYERS = Registry('norm layer')\nACTIVATION_LAYERS = Registry('activation layer')\nPADDING_LAYERS = Registry('padding layer')\nUPSAMPLE_LAYERS = Registry('upsample layer')\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/scale.py",
    "content": "import torch\nimport torch.nn as nn\n\n\nclass Scale(nn.Module):\n    \"\"\"A learnable scale parameter.\n\n    This layer scales the input by a learnable factor. It multiplies a\n    learnable scale parameter of shape (1,) with input of any shape.\n\n    Args:\n        scale (float): Initial value of scale factor. Default: 1.0\n    \"\"\"\n\n    def __init__(self, scale=1.0):\n        super(Scale, self).__init__()\n        self.scale = nn.Parameter(torch.tensor(scale, dtype=torch.float))\n\n    def forward(self, x):\n        return x * self.scale\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/bricks/upsample.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..utils import xavier_init\nfrom .registry import UPSAMPLE_LAYERS\n\nUPSAMPLE_LAYERS.register_module('nearest', module=nn.Upsample)\nUPSAMPLE_LAYERS.register_module('bilinear', module=nn.Upsample)\nUPSAMPLE_LAYERS.register_module('deconv', module=nn.ConvTranspose2d)\n\n\n@UPSAMPLE_LAYERS.register_module(name='pixel_shuffle')\nclass PixelShufflePack(nn.Module):\n    \"\"\"Pixel Shuffle upsample layer.\n\n    This module packs `F.pixel_shuffle()` and a nn.Conv2d module together to\n    achieve a simple upsampling with pixel shuffle.\n\n    Args:\n        in_channels (int): Number of input channels.\n        out_channels (int): Number of output channels.\n        scale_factor (int): Upsample ratio.\n        upsample_kernel (int): Kernel size of the conv layer to expand the\n            channels.\n    \"\"\"\n\n    def __init__(self, in_channels, out_channels, scale_factor,\n                 upsample_kernel):\n        super(PixelShufflePack, self).__init__()\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.scale_factor = scale_factor\n        self.upsample_kernel = upsample_kernel\n        self.upsample_conv = nn.Conv2d(\n            self.in_channels,\n            self.out_channels * scale_factor * scale_factor,\n            self.upsample_kernel,\n            padding=(self.upsample_kernel - 1) // 2)\n        self.init_weights()\n\n    def init_weights(self):\n        xavier_init(self.upsample_conv, distribution='uniform')\n\n    def forward(self, x):\n        x = self.upsample_conv(x)\n        x = F.pixel_shuffle(x, self.scale_factor)\n        return x\n\n\ndef build_upsample_layer(cfg, *args, **kwargs):\n    \"\"\"Build upsample layer.\n\n    Args:\n        cfg (dict): The upsample layer config, which should contain:\n            - type (str): Layer type.\n            - scale_factor (int): Upsample ratio, which is not applicable to\n                deconv.\n            - layer args: Args needed to instantiate a upsample layer.\n        args (argument list): Arguments passed to the `__init__`\n            method of the corresponding conv layer.\n        kwargs (keyword arguments): Keyword arguments passed to the `__init__`\n            method of the corresponding conv layer.\n\n    Returns:\n        nn.Module: Created upsample layer.\n    \"\"\"\n    if not isinstance(cfg, dict):\n        raise TypeError(f'cfg must be a dict, but got {type(cfg)}')\n    if 'type' not in cfg:\n        raise KeyError(\n            f'the cfg dict must contain the key \"type\", but got {cfg}')\n    cfg_ = cfg.copy()\n\n    layer_type = cfg_.pop('type')\n    if layer_type not in UPSAMPLE_LAYERS:\n        raise KeyError(f'Unrecognized upsample type {layer_type}')\n    else:\n        upsample = UPSAMPLE_LAYERS.get(layer_type)\n\n    if upsample is nn.Upsample:\n        cfg_['mode'] = layer_type\n    layer = upsample(*args, **kwargs, **cfg_)\n    return layer\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/resnet.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport logging\n\nimport torch.nn as nn\nimport torch.utils.checkpoint as cp\n\nfrom ..runner import load_checkpoint\nfrom .utils import constant_init, kaiming_init\n\n\ndef conv3x3(in_planes, out_planes, stride=1, dilation=1):\n    \"\"\"3x3 convolution with padding\"\"\"\n    return nn.Conv2d(\n        in_planes,\n        out_planes,\n        kernel_size=3,\n        stride=stride,\n        padding=dilation,\n        dilation=dilation,\n        bias=False)\n\n\nclass BasicBlock(nn.Module):\n    expansion = 1\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 stride=1,\n                 dilation=1,\n                 downsample=None,\n                 style='pytorch',\n                 with_cp=False):\n        super(BasicBlock, self).__init__()\n        assert style in ['pytorch', 'caffe']\n        self.conv1 = conv3x3(inplanes, planes, stride, dilation)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv2 = conv3x3(planes, planes)\n        self.bn2 = nn.BatchNorm2d(planes)\n        self.downsample = downsample\n        self.stride = stride\n        self.dilation = dilation\n        assert not with_cp\n\n    def forward(self, x):\n        residual = x\n\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n\n        out = self.conv2(out)\n        out = self.bn2(out)\n\n        if self.downsample is not None:\n            residual = self.downsample(x)\n\n        out += residual\n        out = self.relu(out)\n\n        return out\n\n\nclass Bottleneck(nn.Module):\n    expansion = 4\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 stride=1,\n                 dilation=1,\n                 downsample=None,\n                 style='pytorch',\n                 with_cp=False):\n        \"\"\"Bottleneck block.\n\n        If style is \"pytorch\", the stride-two layer is the 3x3 conv layer,\n        if it is \"caffe\", the stride-two layer is the first 1x1 conv layer.\n        \"\"\"\n        super(Bottleneck, self).__init__()\n        assert style in ['pytorch', 'caffe']\n        if style == 'pytorch':\n            conv1_stride = 1\n            conv2_stride = stride\n        else:\n            conv1_stride = stride\n            conv2_stride = 1\n        self.conv1 = nn.Conv2d(\n            inplanes, planes, kernel_size=1, stride=conv1_stride, bias=False)\n        self.conv2 = nn.Conv2d(\n            planes,\n            planes,\n            kernel_size=3,\n            stride=conv2_stride,\n            padding=dilation,\n            dilation=dilation,\n            bias=False)\n\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.bn2 = nn.BatchNorm2d(planes)\n        self.conv3 = nn.Conv2d(\n            planes, planes * self.expansion, kernel_size=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(planes * self.expansion)\n        self.relu = nn.ReLU(inplace=True)\n        self.downsample = downsample\n        self.stride = stride\n        self.dilation = dilation\n        self.with_cp = with_cp\n\n    def forward(self, x):\n\n        def _inner_forward(x):\n            residual = x\n\n            out = self.conv1(x)\n            out = self.bn1(out)\n            out = self.relu(out)\n\n            out = self.conv2(out)\n            out = self.bn2(out)\n            out = self.relu(out)\n\n            out = self.conv3(out)\n            out = self.bn3(out)\n\n            if self.downsample is not None:\n                residual = self.downsample(x)\n\n            out += residual\n\n            return out\n\n        if self.with_cp and x.requires_grad:\n            out = cp.checkpoint(_inner_forward, x)\n        else:\n            out = _inner_forward(x)\n\n        out = self.relu(out)\n\n        return out\n\n\ndef make_res_layer(block,\n                   inplanes,\n                   planes,\n                   blocks,\n                   stride=1,\n                   dilation=1,\n                   style='pytorch',\n                   with_cp=False):\n    downsample = None\n    if stride != 1 or inplanes != planes * block.expansion:\n        downsample = nn.Sequential(\n            nn.Conv2d(\n                inplanes,\n                planes * block.expansion,\n                kernel_size=1,\n                stride=stride,\n                bias=False),\n            nn.BatchNorm2d(planes * block.expansion),\n        )\n\n    layers = []\n    layers.append(\n        block(\n            inplanes,\n            planes,\n            stride,\n            dilation,\n            downsample,\n            style=style,\n            with_cp=with_cp))\n    inplanes = planes * block.expansion\n    for _ in range(1, blocks):\n        layers.append(\n            block(inplanes, planes, 1, dilation, style=style, with_cp=with_cp))\n\n    return nn.Sequential(*layers)\n\n\nclass ResNet(nn.Module):\n    \"\"\"ResNet backbone.\n\n    Args:\n        depth (int): Depth of resnet, from {18, 34, 50, 101, 152}.\n        num_stages (int): Resnet stages, normally 4.\n        strides (Sequence[int]): Strides of the first block of each stage.\n        dilations (Sequence[int]): Dilation of each stage.\n        out_indices (Sequence[int]): Output from which stages.\n        style (str): `pytorch` or `caffe`. If set to \"pytorch\", the stride-two\n            layer is the 3x3 conv layer, otherwise the stride-two layer is\n            the first 1x1 conv layer.\n        frozen_stages (int): Stages to be frozen (all param fixed). -1 means\n            not freezing any parameters.\n        bn_eval (bool): Whether to set BN layers as eval mode, namely, freeze\n            running stats (mean and var).\n        bn_frozen (bool): Whether to freeze weight and bias of BN layers.\n        with_cp (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n    \"\"\"\n\n    arch_settings = {\n        18: (BasicBlock, (2, 2, 2, 2)),\n        34: (BasicBlock, (3, 4, 6, 3)),\n        50: (Bottleneck, (3, 4, 6, 3)),\n        101: (Bottleneck, (3, 4, 23, 3)),\n        152: (Bottleneck, (3, 8, 36, 3))\n    }\n\n    def __init__(self,\n                 depth,\n                 num_stages=4,\n                 strides=(1, 2, 2, 2),\n                 dilations=(1, 1, 1, 1),\n                 out_indices=(0, 1, 2, 3),\n                 style='pytorch',\n                 frozen_stages=-1,\n                 bn_eval=True,\n                 bn_frozen=False,\n                 with_cp=False):\n        super(ResNet, self).__init__()\n        if depth not in self.arch_settings:\n            raise KeyError(f'invalid depth {depth} for resnet')\n        assert num_stages >= 1 and num_stages <= 4\n        block, stage_blocks = self.arch_settings[depth]\n        stage_blocks = stage_blocks[:num_stages]\n        assert len(strides) == len(dilations) == num_stages\n        assert max(out_indices) < num_stages\n\n        self.out_indices = out_indices\n        self.style = style\n        self.frozen_stages = frozen_stages\n        self.bn_eval = bn_eval\n        self.bn_frozen = bn_frozen\n        self.with_cp = with_cp\n\n        self.inplanes = 64\n        self.conv1 = nn.Conv2d(\n            3, 64, kernel_size=7, stride=2, padding=3, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        self.relu = nn.ReLU(inplace=True)\n        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n\n        self.res_layers = []\n        for i, num_blocks in enumerate(stage_blocks):\n            stride = strides[i]\n            dilation = dilations[i]\n            planes = 64 * 2**i\n            res_layer = make_res_layer(\n                block,\n                self.inplanes,\n                planes,\n                num_blocks,\n                stride=stride,\n                dilation=dilation,\n                style=self.style,\n                with_cp=with_cp)\n            self.inplanes = planes * block.expansion\n            layer_name = f'layer{i + 1}'\n            self.add_module(layer_name, res_layer)\n            self.res_layers.append(layer_name)\n\n        self.feat_dim = block.expansion * 64 * 2**(len(stage_blocks) - 1)\n\n    def init_weights(self, pretrained=None):\n        if isinstance(pretrained, str):\n            logger = logging.getLogger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, nn.BatchNorm2d):\n                    constant_init(m, 1)\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n    def forward(self, x):\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.maxpool(x)\n        outs = []\n        for i, layer_name in enumerate(self.res_layers):\n            res_layer = getattr(self, layer_name)\n            x = res_layer(x)\n            if i in self.out_indices:\n                outs.append(x)\n        if len(outs) == 1:\n            return outs[0]\n        else:\n            return tuple(outs)\n\n    def train(self, mode=True):\n        super(ResNet, self).train(mode)\n        if self.bn_eval:\n            for m in self.modules():\n                if isinstance(m, nn.BatchNorm2d):\n                    m.eval()\n                    if self.bn_frozen:\n                        for params in m.parameters():\n                            params.requires_grad = False\n        if mode and self.frozen_stages >= 0:\n            for param in self.conv1.parameters():\n                param.requires_grad = False\n            for param in self.bn1.parameters():\n                param.requires_grad = False\n            self.bn1.eval()\n            self.bn1.weight.requires_grad = False\n            self.bn1.bias.requires_grad = False\n            for i in range(1, self.frozen_stages + 1):\n                mod = getattr(self, f'layer{i}')\n                mod.eval()\n                for param in mod.parameters():\n                    param.requires_grad = False\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/utils/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .flops_counter import get_model_complexity_info\nfrom .weight_init import (bias_init_with_prob, caffe2_xavier_init,\n                          constant_init, kaiming_init, normal_init,\n                          uniform_init, xavier_init)\n\n__all__ = [\n    'get_model_complexity_info', 'bias_init_with_prob', 'caffe2_xavier_init',\n    'constant_init', 'kaiming_init', 'normal_init', 'uniform_init',\n    'xavier_init'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/utils/flops_counter.py",
    "content": "# Modified from flops-counter.pytorch by Vladislav Sovrasov\n# original repo: https://github.com/sovrasov/flops-counter.pytorch\n\n# MIT License\n\n# Copyright (c) 2018 Vladislav Sovrasov\n\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport sys\nfrom functools import partial\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\n\n\ndef get_model_complexity_info(model,\n                              input_shape,\n                              print_per_layer_stat=True,\n                              as_strings=True,\n                              input_constructor=None,\n                              flush=False,\n                              ost=sys.stdout):\n    \"\"\"Get complexity information of a model.\n\n    This method can calculate FLOPs and parameter counts of a model with\n    corresponding input shape. It can also print complexity information for\n    each layer in a model.\n\n    Supported layers are listed as below:\n        - Convolutions: `nn.Conv1d`, `nn.Conv2d`, `nn.Conv3d`.\n        - Activations: `nn.ReLU`, `nn.PReLU`, `nn.ELU`, `nn.LeakyReLU`,\n            `nn.ReLU6`.\n        - Poolings: `nn.MaxPool1d`, `nn.MaxPool2d`, `nn.MaxPool3d`,\n            `nn.AvgPool1d`, `nn.AvgPool2d`, `nn.AvgPool3d`,\n            `nn.AdaptiveMaxPool1d`, `nn.AdaptiveMaxPool2d`,\n            `nn.AdaptiveMaxPool3d`, `nn.AdaptiveAvgPool1d`,\n            `nn.AdaptiveAvgPool2d`, `nn.AdaptiveAvgPool3d`.\n        - BatchNorms: `nn.BatchNorm1d`, `nn.BatchNorm2d`, `nn.BatchNorm3d`.\n        - Linear: `nn.Linear`.\n        - Deconvolution: `nn.ConvTranspose2d`.\n        - Upsample: `nn.Upsample`.\n\n    Args:\n        model (nn.Module): The model for complexity calculation.\n        input_shape (tuple): Input shape used for calculation.\n        print_per_layer_stat (bool): Whether to print complexity information\n            for each layer in a model. Default: True.\n        as_strings (bool): Output FLOPs and params counts in a string form.\n            Default: True.\n        input_constructor (None | callable): If specified, it takes a callable\n            method that generates input. otherwise, it will generate a random\n            tensor with input shape to calculate FLOPs. Default: None.\n        flush (bool): same as that in :func:`print`. Default: False.\n        ost (stream): same as `file` param in :func:`print`.\n            Default: sys.stdout.\n\n    Returns:\n        tuple[float | str]: If `as_strings` is set to True, it will return\n            FLOPs and parameter counts in a string format. otherwise, it will\n            return those in a float number format.\n    \"\"\"\n    assert type(input_shape) is tuple\n    assert len(input_shape) >= 1\n    assert isinstance(model, nn.Module)\n    flops_model = add_flops_counting_methods(model)\n    flops_model.eval()\n    flops_model.start_flops_count()\n    if input_constructor:\n        input = input_constructor(input_shape)\n        _ = flops_model(**input)\n    else:\n        try:\n            batch = torch.ones(()).new_empty(\n                (1, *input_shape),\n                dtype=next(flops_model.parameters()).dtype,\n                device=next(flops_model.parameters()).device)\n        except StopIteration:\n            # Avoid StopIteration for models which have no parameters,\n            # like `nn.Relu()`, `nn.AvgPool2d`, etc.\n            batch = torch.ones(()).new_empty((1, *input_shape))\n\n        _ = flops_model(batch)\n\n    flops_count, params_count = flops_model.compute_average_flops_cost()\n    if print_per_layer_stat:\n        print_model_with_flops(\n            flops_model, flops_count, params_count, ost=ost, flush=flush)\n    flops_model.stop_flops_count()\n\n    if as_strings:\n        return flops_to_string(flops_count), params_to_string(params_count)\n\n    return flops_count, params_count\n\n\ndef flops_to_string(flops, units='GFLOPs', precision=2):\n    \"\"\"Convert FLOPs number into a string.\n\n    Note that Here we take a multiply-add counts as one FLOP.\n\n    Args:\n        flops (float): FLOPs number to be converted.\n        units (str | None): Converted FLOPs units. Options are None, 'GFLOPs',\n            'MFLOPs', 'KFLOPs', 'FLOPs'. If set to None, it will automatically\n            choose the most suitable unit for FLOPs. Default: 'GFLOPs'.\n        precision (int): Digit number after the decimal point. Default: 2.\n\n    Returns:\n        str: The converted FLOPs number with units.\n\n    Examples:\n        >>> flops_to_string(1e9)\n        '1.0 GFLOPs'\n        >>> flops_to_string(2e5, 'MFLOPs')\n        '0.2 MFLOPs'\n        >>> flops_to_string(3e-9, None)\n        '3e-09 FLOPs'\n    \"\"\"\n    if units is None:\n        if flops // 10**9 > 0:\n            return str(round(flops / 10.**9, precision)) + ' GFLOPs'\n        elif flops // 10**6 > 0:\n            return str(round(flops / 10.**6, precision)) + ' MFLOPs'\n        elif flops // 10**3 > 0:\n            return str(round(flops / 10.**3, precision)) + ' KFLOPs'\n        else:\n            return str(flops) + ' FLOPs'\n    else:\n        if units == 'GFLOPs':\n            return str(round(flops / 10.**9, precision)) + ' ' + units\n        elif units == 'MFLOPs':\n            return str(round(flops / 10.**6, precision)) + ' ' + units\n        elif units == 'KFLOPs':\n            return str(round(flops / 10.**3, precision)) + ' ' + units\n        else:\n            return str(flops) + ' FLOPs'\n\n\ndef params_to_string(num_params, units=None, precision=2):\n    \"\"\"Convert parameter number into a string.\n\n    Args:\n        num_params (float): Parameter number to be converted.\n        units (str | None): Converted FLOPs units. Options are None, 'M',\n            'K' and ''. If set to None, it will automatically choose the most\n            suitable unit for Parameter number. Default: None.\n        precision (int): Digit number after the decimal point. Default: 2.\n\n    Returns:\n        str: The converted parameter number with units.\n\n    Examples:\n        >>> params_to_string(1e9)\n        '1000.0 M'\n        >>> params_to_string(2e5)\n        '200.0 k'\n        >>> params_to_string(3e-9)\n        '3e-09'\n    \"\"\"\n    if units is None:\n        if num_params // 10**6 > 0:\n            return str(round(num_params / 10**6, precision)) + ' M'\n        elif num_params // 10**3:\n            return str(round(num_params / 10**3, precision)) + ' k'\n        else:\n            return str(num_params)\n    else:\n        if units == 'M':\n            return str(round(num_params / 10.**6, precision)) + ' ' + units\n        elif units == 'K':\n            return str(round(num_params / 10.**3, precision)) + ' ' + units\n        else:\n            return str(num_params)\n\n\ndef print_model_with_flops(model,\n                           total_flops,\n                           total_params,\n                           units='GFLOPs',\n                           precision=3,\n                           ost=sys.stdout,\n                           flush=False):\n    \"\"\"Print a model with FLOPs for each layer.\n\n    Args:\n        model (nn.Module): The model to be printed.\n        total_flops (float): Total FLOPs of the model.\n        total_params (float): Total parameter counts of the model.\n        units (str | None): Converted FLOPs units. Default: 'GFLOPs'.\n        precision (int): Digit number after the decimal point. Default: 3.\n        ost (stream): same as `file` param in :func:`print`.\n            Default: sys.stdout.\n        flush (bool): same as that in :func:`print`. Default: False.\n\n    Example:\n        >>> class ExampleModel(nn.Module):\n\n        >>> def __init__(self):\n        >>>     super().__init__()\n        >>>     self.conv1 = nn.Conv2d(3, 8, 3)\n        >>>     self.conv2 = nn.Conv2d(8, 256, 3)\n        >>>     self.conv3 = nn.Conv2d(256, 8, 3)\n        >>>     self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))\n        >>>     self.flatten = nn.Flatten()\n        >>>     self.fc = nn.Linear(8, 1)\n\n        >>> def forward(self, x):\n        >>>     x = self.conv1(x)\n        >>>     x = self.conv2(x)\n        >>>     x = self.conv3(x)\n        >>>     x = self.avg_pool(x)\n        >>>     x = self.flatten(x)\n        >>>     x = self.fc(x)\n        >>>     return x\n\n        >>> model = ExampleModel()\n        >>> x = (3, 16, 16)\n        to print the complexity inforamtion state for each layer, you can use\n        >>> get_model_complexity_info(model, x)\n        or directly use\n        >>> print_model_with_flops(model, 4579784.0, 37361)\n        ExampleModel(\n          0.037 M, 100.000% Params, 0.005 GFLOPs, 100.000% FLOPs,\n          (conv1): Conv2d(0.0 M, 0.600% Params, 0.0 GFLOPs, 0.959% FLOPs, 3, 8, kernel_size=(3, 3), stride=(1, 1))  # noqa: E501\n          (conv2): Conv2d(0.019 M, 50.020% Params, 0.003 GFLOPs, 58.760% FLOPs, 8, 256, kernel_size=(3, 3), stride=(1, 1))\n          (conv3): Conv2d(0.018 M, 49.356% Params, 0.002 GFLOPs, 40.264% FLOPs, 256, 8, kernel_size=(3, 3), stride=(1, 1))\n          (avg_pool): AdaptiveAvgPool2d(0.0 M, 0.000% Params, 0.0 GFLOPs, 0.017% FLOPs, output_size=(1, 1))\n          (flatten): Flatten(0.0 M, 0.000% Params, 0.0 GFLOPs, 0.000% FLOPs, )\n          (fc): Linear(0.0 M, 0.024% Params, 0.0 GFLOPs, 0.000% FLOPs, in_features=8, out_features=1, bias=True)\n        )\n    \"\"\"\n\n    def accumulate_params(self):\n        if is_supported_instance(self):\n            return self.__params__\n        else:\n            sum = 0\n            for m in self.children():\n                sum += m.accumulate_params()\n            return sum\n\n    def accumulate_flops(self):\n        if is_supported_instance(self):\n            return self.__flops__ / model.__batch_counter__\n        else:\n            sum = 0\n            for m in self.children():\n                sum += m.accumulate_flops()\n            return sum\n\n    def flops_repr(self):\n        accumulated_num_params = self.accumulate_params()\n        accumulated_flops_cost = self.accumulate_flops()\n        return ', '.join([\n            params_to_string(\n                accumulated_num_params, units='M', precision=precision),\n            '{:.3%} Params'.format(accumulated_num_params / total_params),\n            flops_to_string(\n                accumulated_flops_cost, units=units, precision=precision),\n            '{:.3%} FLOPs'.format(accumulated_flops_cost / total_flops),\n            self.original_extra_repr()\n        ])\n\n    def add_extra_repr(m):\n        m.accumulate_flops = accumulate_flops.__get__(m)\n        m.accumulate_params = accumulate_params.__get__(m)\n        flops_extra_repr = flops_repr.__get__(m)\n        if m.extra_repr != flops_extra_repr:\n            m.original_extra_repr = m.extra_repr\n            m.extra_repr = flops_extra_repr\n            assert m.extra_repr != m.original_extra_repr\n\n    def del_extra_repr(m):\n        if hasattr(m, 'original_extra_repr'):\n            m.extra_repr = m.original_extra_repr\n            del m.original_extra_repr\n        if hasattr(m, 'accumulate_flops'):\n            del m.accumulate_flops\n\n    model.apply(add_extra_repr)\n    print(model, file=ost, flush=flush)\n    model.apply(del_extra_repr)\n\n\ndef get_model_parameters_number(model):\n    \"\"\"Calculate parameter number of a model.\n\n    Args:\n        model (nn.module): The model for parameter number calculation.\n\n    Returns:\n        float: Parameter number of the model.\n    \"\"\"\n    num_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n    return num_params\n\n\ndef add_flops_counting_methods(net_main_module):\n    # adding additional methods to the existing module object,\n    # this is done this way so that each function has access to self object\n    net_main_module.start_flops_count = start_flops_count.__get__(\n        net_main_module)\n    net_main_module.stop_flops_count = stop_flops_count.__get__(\n        net_main_module)\n    net_main_module.reset_flops_count = reset_flops_count.__get__(\n        net_main_module)\n    net_main_module.compute_average_flops_cost = compute_average_flops_cost.__get__(  # noqa: E501\n        net_main_module)\n\n    net_main_module.reset_flops_count()\n\n    return net_main_module\n\n\ndef compute_average_flops_cost(self):\n    \"\"\"Compute average FLOPs cost.\n\n    A method to compute average FLOPs cost, which will be available after\n    `add_flops_counting_methods()` is called on a desired net object.\n\n    Returns:\n        float: Current mean flops consumption per image.\n    \"\"\"\n    batches_count = self.__batch_counter__\n    flops_sum = 0\n    for module in self.modules():\n        if is_supported_instance(module):\n            flops_sum += module.__flops__\n    params_sum = get_model_parameters_number(self)\n    return flops_sum / batches_count, params_sum\n\n\ndef start_flops_count(self):\n    \"\"\"Activate the computation of mean flops consumption per image.\n\n    A method to activate the computation of mean flops consumption per image.\n    which will be available after `add_flops_counting_methods()` is called on\n    a desired net object. It should be called before running the network.\n    \"\"\"\n    add_batch_counter_hook_function(self)\n\n    def add_flops_counter_hook_function(module):\n        if is_supported_instance(module):\n            if hasattr(module, '__flops_handle__'):\n                return\n\n            else:\n                handle = module.register_forward_hook(\n                    MODULES_MAPPING[type(module)])\n\n            module.__flops_handle__ = handle\n\n    self.apply(partial(add_flops_counter_hook_function))\n\n\ndef stop_flops_count(self):\n    \"\"\"Stop computing the mean flops consumption per image.\n\n    A method to stop computing the mean flops consumption per image, which\n    will be available after `add_flops_counting_methods()` is called on a\n    desired net object. It can be called to pause the computation whenever.\n    \"\"\"\n    remove_batch_counter_hook_function(self)\n    self.apply(remove_flops_counter_hook_function)\n\n\ndef reset_flops_count(self):\n    \"\"\"Reset statistics computed so far.\n\n    A method to Reset computed statistics, which will be available\n    after `add_flops_counting_methods()` is called on a desired net object.\n    \"\"\"\n    add_batch_counter_variables_or_reset(self)\n    self.apply(add_flops_counter_variable_or_reset)\n\n\n# ---- Internal functions\ndef empty_flops_counter_hook(module, input, output):\n    module.__flops__ += 0\n\n\ndef upsample_flops_counter_hook(module, input, output):\n    output_size = output[0]\n    batch_size = output_size.shape[0]\n    output_elements_count = batch_size\n    for val in output_size.shape[1:]:\n        output_elements_count *= val\n    module.__flops__ += int(output_elements_count)\n\n\ndef relu_flops_counter_hook(module, input, output):\n    active_elements_count = output.numel()\n    module.__flops__ += int(active_elements_count)\n\n\ndef linear_flops_counter_hook(module, input, output):\n    input = input[0]\n    output_last_dim = output.shape[\n        -1]  # pytorch checks dimensions, so here we don't care much\n    module.__flops__ += int(np.prod(input.shape) * output_last_dim)\n\n\ndef pool_flops_counter_hook(module, input, output):\n    input = input[0]\n    module.__flops__ += int(np.prod(input.shape))\n\n\ndef bn_flops_counter_hook(module, input, output):\n    input = input[0]\n\n    batch_flops = np.prod(input.shape)\n    if module.affine:\n        batch_flops *= 2\n    module.__flops__ += int(batch_flops)\n\n\ndef deconv_flops_counter_hook(conv_module, input, output):\n    # Can have multiple inputs, getting the first one\n    input = input[0]\n\n    batch_size = input.shape[0]\n    input_height, input_width = input.shape[2:]\n\n    kernel_height, kernel_width = conv_module.kernel_size\n    in_channels = conv_module.in_channels\n    out_channels = conv_module.out_channels\n    groups = conv_module.groups\n\n    filters_per_channel = out_channels // groups\n    conv_per_position_flops = (\n        kernel_height * kernel_width * in_channels * filters_per_channel)\n\n    active_elements_count = batch_size * input_height * input_width\n    overall_conv_flops = conv_per_position_flops * active_elements_count\n    bias_flops = 0\n    if conv_module.bias is not None:\n        output_height, output_width = output.shape[2:]\n        bias_flops = out_channels * batch_size * output_height * output_height\n    overall_flops = overall_conv_flops + bias_flops\n\n    conv_module.__flops__ += int(overall_flops)\n\n\ndef conv_flops_counter_hook(conv_module, input, output):\n    # Can have multiple inputs, getting the first one\n    input = input[0]\n\n    batch_size = input.shape[0]\n    output_dims = list(output.shape[2:])\n\n    kernel_dims = list(conv_module.kernel_size)\n    in_channels = conv_module.in_channels\n    out_channels = conv_module.out_channels\n    groups = conv_module.groups\n\n    filters_per_channel = out_channels // groups\n    conv_per_position_flops = int(\n        np.prod(kernel_dims)) * in_channels * filters_per_channel\n\n    active_elements_count = batch_size * int(np.prod(output_dims))\n\n    overall_conv_flops = conv_per_position_flops * active_elements_count\n\n    bias_flops = 0\n\n    if conv_module.bias is not None:\n\n        bias_flops = out_channels * active_elements_count\n\n    overall_flops = overall_conv_flops + bias_flops\n\n    conv_module.__flops__ += int(overall_flops)\n\n\ndef batch_counter_hook(module, input, output):\n    batch_size = 1\n    if len(input) > 0:\n        # Can have multiple inputs, getting the first one\n        input = input[0]\n        batch_size = len(input)\n    else:\n        pass\n        print('Warning! No positional inputs found for a module, '\n              'assuming batch size is 1.')\n    module.__batch_counter__ += batch_size\n\n\ndef add_batch_counter_variables_or_reset(module):\n\n    module.__batch_counter__ = 0\n\n\ndef add_batch_counter_hook_function(module):\n    if hasattr(module, '__batch_counter_handle__'):\n        return\n\n    handle = module.register_forward_hook(batch_counter_hook)\n    module.__batch_counter_handle__ = handle\n\n\ndef remove_batch_counter_hook_function(module):\n    if hasattr(module, '__batch_counter_handle__'):\n        module.__batch_counter_handle__.remove()\n        del module.__batch_counter_handle__\n\n\ndef add_flops_counter_variable_or_reset(module):\n    if is_supported_instance(module):\n        if hasattr(module, '__flops__') or hasattr(module, '__params__'):\n            print('Warning: variables __flops__ or __params__ are already '\n                  'defined for the module' + type(module).__name__ +\n                  ' ptflops can affect your code!')\n        module.__flops__ = 0\n        module.__params__ = get_model_parameters_number(module)\n\n\ndef is_supported_instance(module):\n    if type(module) in MODULES_MAPPING:\n        return True\n    return False\n\n\ndef remove_flops_counter_hook_function(module):\n    if is_supported_instance(module):\n        if hasattr(module, '__flops_handle__'):\n            module.__flops_handle__.remove()\n            del module.__flops_handle__\n\n\nMODULES_MAPPING = {\n    # convolutions\n    nn.Conv1d: conv_flops_counter_hook,\n    nn.Conv2d: conv_flops_counter_hook,\n    nn.Conv3d: conv_flops_counter_hook,\n    # activations\n    nn.ReLU: relu_flops_counter_hook,\n    nn.PReLU: relu_flops_counter_hook,\n    nn.ELU: relu_flops_counter_hook,\n    nn.LeakyReLU: relu_flops_counter_hook,\n    nn.ReLU6: relu_flops_counter_hook,\n    # poolings\n    nn.MaxPool1d: pool_flops_counter_hook,\n    nn.AvgPool1d: pool_flops_counter_hook,\n    nn.AvgPool2d: pool_flops_counter_hook,\n    nn.MaxPool2d: pool_flops_counter_hook,\n    nn.MaxPool3d: pool_flops_counter_hook,\n    nn.AvgPool3d: pool_flops_counter_hook,\n    nn.AdaptiveMaxPool1d: pool_flops_counter_hook,\n    nn.AdaptiveAvgPool1d: pool_flops_counter_hook,\n    nn.AdaptiveMaxPool2d: pool_flops_counter_hook,\n    nn.AdaptiveAvgPool2d: pool_flops_counter_hook,\n    nn.AdaptiveMaxPool3d: pool_flops_counter_hook,\n    nn.AdaptiveAvgPool3d: pool_flops_counter_hook,\n    # BNs\n    nn.BatchNorm1d: bn_flops_counter_hook,\n    nn.BatchNorm2d: bn_flops_counter_hook,\n    nn.BatchNorm3d: bn_flops_counter_hook,\n    # FC\n    nn.Linear: linear_flops_counter_hook,\n    # Upscale\n    nn.Upsample: upsample_flops_counter_hook,\n    # Deconvolution\n    nn.ConvTranspose2d: deconv_flops_counter_hook,\n}\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/utils/weight_init.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numpy as np\nimport torch.nn as nn\n\n\ndef constant_init(module, val, bias=0):\n    if hasattr(module, 'weight') and module.weight is not None:\n        nn.init.constant_(module.weight, val)\n    if hasattr(module, 'bias') and module.bias is not None:\n        nn.init.constant_(module.bias, bias)\n\n\ndef xavier_init(module, gain=1, bias=0, distribution='normal'):\n    assert distribution in ['uniform', 'normal']\n    if distribution == 'uniform':\n        nn.init.xavier_uniform_(module.weight, gain=gain)\n    else:\n        nn.init.xavier_normal_(module.weight, gain=gain)\n    if hasattr(module, 'bias') and module.bias is not None:\n        nn.init.constant_(module.bias, bias)\n\n\ndef normal_init(module, mean=0, std=1, bias=0):\n    nn.init.normal_(module.weight, mean, std)\n    if hasattr(module, 'bias') and module.bias is not None:\n        nn.init.constant_(module.bias, bias)\n\n\ndef uniform_init(module, a=0, b=1, bias=0):\n    nn.init.uniform_(module.weight, a, b)\n    if hasattr(module, 'bias') and module.bias is not None:\n        nn.init.constant_(module.bias, bias)\n\n\ndef kaiming_init(module,\n                 a=0,\n                 mode='fan_out',\n                 nonlinearity='relu',\n                 bias=0,\n                 distribution='normal'):\n    assert distribution in ['uniform', 'normal']\n    if distribution == 'uniform':\n        nn.init.kaiming_uniform_(\n            module.weight, a=a, mode=mode, nonlinearity=nonlinearity)\n    else:\n        nn.init.kaiming_normal_(\n            module.weight, a=a, mode=mode, nonlinearity=nonlinearity)\n    if hasattr(module, 'bias') and module.bias is not None:\n        nn.init.constant_(module.bias, bias)\n\n\ndef caffe2_xavier_init(module, bias=0):\n    # `XavierFill` in Caffe2 corresponds to `kaiming_uniform_` in PyTorch\n    # Acknowledgment to FAIR's internal code\n    kaiming_init(\n        module,\n        a=1,\n        mode='fan_in',\n        nonlinearity='leaky_relu',\n        distribution='uniform')\n\n\ndef bias_init_with_prob(prior_prob):\n    \"\"\" initialize conv/fc bias value according to giving probablity\"\"\"\n    bias_init = float(-np.log((1 - prior_prob) / prior_prob))\n    return bias_init\n"
  },
  {
    "path": "code/mmcv/mmcv/cnn/vgg.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport logging\n\nimport torch.nn as nn\n\nfrom ..runner import load_checkpoint\nfrom .utils import constant_init, kaiming_init, normal_init\n\n\ndef conv3x3(in_planes, out_planes, dilation=1):\n    \"\"\"3x3 convolution with padding\"\"\"\n    return nn.Conv2d(\n        in_planes,\n        out_planes,\n        kernel_size=3,\n        padding=dilation,\n        dilation=dilation)\n\n\ndef make_vgg_layer(inplanes,\n                   planes,\n                   num_blocks,\n                   dilation=1,\n                   with_bn=False,\n                   ceil_mode=False):\n    layers = []\n    for _ in range(num_blocks):\n        layers.append(conv3x3(inplanes, planes, dilation))\n        if with_bn:\n            layers.append(nn.BatchNorm2d(planes))\n        layers.append(nn.ReLU(inplace=True))\n        inplanes = planes\n    layers.append(nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=ceil_mode))\n\n    return layers\n\n\nclass VGG(nn.Module):\n    \"\"\"VGG backbone.\n\n    Args:\n        depth (int): Depth of vgg, from {11, 13, 16, 19}.\n        with_bn (bool): Use BatchNorm or not.\n        num_classes (int): number of classes for classification.\n        num_stages (int): VGG stages, normally 5.\n        dilations (Sequence[int]): Dilation of each stage.\n        out_indices (Sequence[int]): Output from which stages.\n        frozen_stages (int): Stages to be frozen (all param fixed). -1 means\n            not freezing any parameters.\n        bn_eval (bool): Whether to set BN layers as eval mode, namely, freeze\n            running stats (mean and var).\n        bn_frozen (bool): Whether to freeze weight and bias of BN layers.\n    \"\"\"\n\n    arch_settings = {\n        11: (1, 1, 2, 2, 2),\n        13: (2, 2, 2, 2, 2),\n        16: (2, 2, 3, 3, 3),\n        19: (2, 2, 4, 4, 4)\n    }\n\n    def __init__(self,\n                 depth,\n                 with_bn=False,\n                 num_classes=-1,\n                 num_stages=5,\n                 dilations=(1, 1, 1, 1, 1),\n                 out_indices=(0, 1, 2, 3, 4),\n                 frozen_stages=-1,\n                 bn_eval=True,\n                 bn_frozen=False,\n                 ceil_mode=False,\n                 with_last_pool=True):\n        super(VGG, self).__init__()\n        if depth not in self.arch_settings:\n            raise KeyError(f'invalid depth {depth} for vgg')\n        assert num_stages >= 1 and num_stages <= 5\n        stage_blocks = self.arch_settings[depth]\n        self.stage_blocks = stage_blocks[:num_stages]\n        assert len(dilations) == num_stages\n        assert max(out_indices) <= num_stages\n\n        self.num_classes = num_classes\n        self.out_indices = out_indices\n        self.frozen_stages = frozen_stages\n        self.bn_eval = bn_eval\n        self.bn_frozen = bn_frozen\n\n        self.inplanes = 3\n        start_idx = 0\n        vgg_layers = []\n        self.range_sub_modules = []\n        for i, num_blocks in enumerate(self.stage_blocks):\n            num_modules = num_blocks * (2 + with_bn) + 1\n            end_idx = start_idx + num_modules\n            dilation = dilations[i]\n            planes = 64 * 2**i if i < 4 else 512\n            vgg_layer = make_vgg_layer(\n                self.inplanes,\n                planes,\n                num_blocks,\n                dilation=dilation,\n                with_bn=with_bn,\n                ceil_mode=ceil_mode)\n            vgg_layers.extend(vgg_layer)\n            self.inplanes = planes\n            self.range_sub_modules.append([start_idx, end_idx])\n            start_idx = end_idx\n        if not with_last_pool:\n            vgg_layers.pop(-1)\n            self.range_sub_modules[-1][1] -= 1\n        self.module_name = 'features'\n        self.add_module(self.module_name, nn.Sequential(*vgg_layers))\n\n        if self.num_classes > 0:\n            self.classifier = nn.Sequential(\n                nn.Linear(512 * 7 * 7, 4096),\n                nn.ReLU(True),\n                nn.Dropout(),\n                nn.Linear(4096, 4096),\n                nn.ReLU(True),\n                nn.Dropout(),\n                nn.Linear(4096, num_classes),\n            )\n\n    def init_weights(self, pretrained=None):\n        if isinstance(pretrained, str):\n            logger = logging.getLogger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, nn.BatchNorm2d):\n                    constant_init(m, 1)\n                elif isinstance(m, nn.Linear):\n                    normal_init(m, std=0.01)\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n    def forward(self, x):\n        outs = []\n        vgg_layers = getattr(self, self.module_name)\n        for i in range(len(self.stage_blocks)):\n            for j in range(*self.range_sub_modules[i]):\n                vgg_layer = vgg_layers[j]\n                x = vgg_layer(x)\n            if i in self.out_indices:\n                outs.append(x)\n        if self.num_classes > 0:\n            x = x.view(x.size(0), -1)\n            x = self.classifier(x)\n            outs.append(x)\n        if len(outs) == 1:\n            return outs[0]\n        else:\n            return tuple(outs)\n\n    def train(self, mode=True):\n        super(VGG, self).train(mode)\n        if self.bn_eval:\n            for m in self.modules():\n                if isinstance(m, nn.BatchNorm2d):\n                    m.eval()\n                    if self.bn_frozen:\n                        for params in m.parameters():\n                            params.requires_grad = False\n        vgg_layers = getattr(self, self.module_name)\n        if mode and self.frozen_stages >= 0:\n            for i in range(self.frozen_stages):\n                for j in range(*self.range_sub_modules[i]):\n                    mod = vgg_layers[j]\n                    mod.eval()\n                    for param in mod.parameters():\n                        param.requires_grad = False\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .file_client import BaseStorageBackend, FileClient\nfrom .handlers import BaseFileHandler, JsonHandler, PickleHandler, YamlHandler\nfrom .io import dump, load, register_handler\nfrom .parse import dict_from_file, list_from_file\n\n__all__ = [\n    'BaseStorageBackend', 'FileClient', 'load', 'dump', 'register_handler',\n    'BaseFileHandler', 'JsonHandler', 'PickleHandler', 'YamlHandler',\n    'list_from_file', 'dict_from_file'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/file_client.py",
    "content": "import inspect\nimport warnings\nfrom abc import ABCMeta, abstractmethod\n\n\nclass BaseStorageBackend(metaclass=ABCMeta):\n    \"\"\"Abstract class of storage backends.\n\n    All backends need to implement two apis: `get()` and `get_text()`.\n    `get()` reads the file as a byte stream and `get_text()` reads the file\n    as texts.\n    \"\"\"\n\n    @abstractmethod\n    def get(self, filepath):\n        pass\n\n    @abstractmethod\n    def get_text(self, filepath):\n        pass\n\n\nclass CephBackend(BaseStorageBackend):\n    \"\"\"Ceph storage backend.\n\n    Args:\n        path_mapping (dict|None): path mapping dict from local path to Petrel\n            path. When `path_mapping={'src': 'dst'}`, `src` in `filepath` will\n            be replaced by `dst`. Default: None.\n    \"\"\"\n\n    def __init__(self, path_mapping=None):\n        try:\n            import ceph\n            warnings.warn('Ceph is deprecate in favor of Petrel.')\n        except ImportError:\n            raise ImportError('Please install ceph to enable CephBackend.')\n\n        self._client = ceph.S3Client()\n        assert isinstance(path_mapping, dict) or path_mapping is None\n        self.path_mapping = path_mapping\n\n    def get(self, filepath):\n        filepath = str(filepath)\n        if self.path_mapping is not None:\n            for k, v in self.path_mapping.items():\n                filepath = filepath.replace(k, v)\n        value = self._client.Get(filepath)\n        value_buf = memoryview(value)\n        return value_buf\n\n    def get_text(self, filepath):\n        raise NotImplementedError\n\n\nclass PetrelBackend(BaseStorageBackend):\n    \"\"\"Petrel storage backend (for internal use).\n\n    Args:\n        path_mapping (dict|None): path mapping dict from local path to Petrel\n            path. When `path_mapping={'src': 'dst'}`, `src` in `filepath` will\n            be replaced by `dst`. Default: None.\n        enable_mc (bool): whether to enable memcached support. Default: True.\n    \"\"\"\n\n    def __init__(self, path_mapping=None, enable_mc=True):\n        try:\n            from petrel_client import client\n        except ImportError:\n            raise ImportError('Please install petrel_client to enable '\n                              'PetrelBackend.')\n\n        self._client = client.Client(enable_mc=enable_mc)\n        assert isinstance(path_mapping, dict) or path_mapping is None\n        self.path_mapping = path_mapping\n\n    def get(self, filepath):\n        filepath = str(filepath)\n        if self.path_mapping is not None:\n            for k, v in self.path_mapping.items():\n                filepath = filepath.replace(k, v)\n        value = self._client.Get(filepath)\n        value_buf = memoryview(value)\n        return value_buf\n\n    def get_text(self, filepath):\n        raise NotImplementedError\n\n\nclass MemcachedBackend(BaseStorageBackend):\n    \"\"\"Memcached storage backend.\n\n    Attributes:\n        server_list_cfg (str): Config file for memcached server list.\n        client_cfg (str): Config file for memcached client.\n        sys_path (str | None): Additional path to be appended to `sys.path`.\n            Default: None.\n    \"\"\"\n\n    def __init__(self, server_list_cfg, client_cfg, sys_path=None):\n        if sys_path is not None:\n            import sys\n            sys.path.append(sys_path)\n        try:\n            import mc\n        except ImportError:\n            raise ImportError(\n                'Please install memcached to enable MemcachedBackend.')\n\n        self.server_list_cfg = server_list_cfg\n        self.client_cfg = client_cfg\n        self._client = mc.MemcachedClient.GetInstance(self.server_list_cfg,\n                                                      self.client_cfg)\n        # mc.pyvector servers as a point which points to a memory cache\n        self._mc_buffer = mc.pyvector()\n\n    def get(self, filepath):\n        filepath = str(filepath)\n        import mc\n        self._client.Get(filepath, self._mc_buffer)\n        value_buf = mc.ConvertBuffer(self._mc_buffer)\n        return value_buf\n\n    def get_text(self, filepath):\n        raise NotImplementedError\n\n\nclass LmdbBackend(BaseStorageBackend):\n    \"\"\"Lmdb storage backend.\n\n    Args:\n        db_path (str): Lmdb database path.\n        readonly (bool, optional): Lmdb environment parameter. If True,\n            disallow any write operations. Default: True.\n        lock (bool, optional): Lmdb environment parameter. If False, when\n            concurrent access occurs, do not lock the database. Default: False.\n        readahead (bool, optional): Lmdb environment parameter. If False,\n            disable the OS filesystem readahead mechanism, which may improve\n            random read performance when a database is larger than RAM.\n            Default: False.\n\n    Attributes:\n        db_path (str): Lmdb database path.\n    \"\"\"\n\n    def __init__(self,\n                 db_path,\n                 readonly=True,\n                 lock=False,\n                 readahead=False,\n                 **kwargs):\n        try:\n            import lmdb\n        except ImportError:\n            raise ImportError('Please install lmdb to enable LmdbBackend.')\n\n        self.db_path = str(db_path)\n        self._client = lmdb.open(\n            self.db_path,\n            readonly=readonly,\n            lock=lock,\n            readahead=readahead,\n            **kwargs)\n\n    def get(self, filepath):\n        \"\"\"Get values according to the filepath.\n\n        Args:\n            filepath (str | obj:`Path`): Here, filepath is the lmdb key.\n        \"\"\"\n        filepath = str(filepath)\n        with self._client.begin(write=False) as txn:\n            value_buf = txn.get(filepath.encode('ascii'))\n        return value_buf\n\n    def get_text(self, filepath):\n        raise NotImplementedError\n\n\nclass HardDiskBackend(BaseStorageBackend):\n    \"\"\"Raw hard disks storage backend.\"\"\"\n\n    def get(self, filepath):\n        filepath = str(filepath)\n        with open(filepath, 'rb') as f:\n            value_buf = f.read()\n        return value_buf\n\n    def get_text(self, filepath):\n        filepath = str(filepath)\n        with open(filepath, 'r') as f:\n            value_buf = f.read()\n        return value_buf\n\n\nclass FileClient:\n    \"\"\"A general file client to access files in different backend.\n\n    The client loads a file or text in a specified backend from its path\n    and return it as a binary file. it can also register other backend\n    accessor with a given name and backend class.\n\n    Attributes:\n        backend (str): The storage backend type. Options are \"disk\", \"ceph\",\n            \"memcached\" and \"lmdb\".\n        client (:obj:`BaseStorageBackend`): The backend object.\n    \"\"\"\n\n    _backends = {\n        'disk': HardDiskBackend,\n        'ceph': CephBackend,\n        'memcached': MemcachedBackend,\n        'lmdb': LmdbBackend,\n        'petrel': PetrelBackend,\n    }\n\n    def __init__(self, backend='disk', **kwargs):\n        if backend not in self._backends:\n            raise ValueError(\n                f'Backend {backend} is not supported. Currently supported ones'\n                f' are {list(self._backends.keys())}')\n        self.backend = backend\n        self.client = self._backends[backend](**kwargs)\n\n    @classmethod\n    def _register_backend(cls, name, backend, force=False):\n        if not isinstance(name, str):\n            raise TypeError('the backend name should be a string, '\n                            f'but got {type(name)}')\n        if not inspect.isclass(backend):\n            raise TypeError(\n                f'backend should be a class but got {type(backend)}')\n        if not issubclass(backend, BaseStorageBackend):\n            raise TypeError(\n                f'backend {backend} is not a subclass of BaseStorageBackend')\n        if not force and name in cls._backends:\n            raise KeyError(\n                f'{name} is already registered as a storage backend, '\n                'add \"force=True\" if you want to override it')\n\n        cls._backends[name] = backend\n\n    @classmethod\n    def register_backend(cls, name, backend=None, force=False):\n        \"\"\"Register a backend to FileClient.\n\n        This method can be used as a normal class method or a decorator.\n\n        .. code-block:: python\n\n            class NewBackend(BaseStorageBackend):\n\n                def get(self, filepath):\n                    return filepath\n\n                def get_text(self, filepath):\n                    return filepath\n\n            FileClient.register_backend('new', NewBackend)\n\n        or\n\n        .. code-block:: python\n\n            @FileClient.register_backend('new')\n            class NewBackend(BaseStorageBackend):\n\n                def get(self, filepath):\n                    return filepath\n\n                def get_text(self, filepath):\n                    return filepath\n\n        Args:\n            name (str): The name of the registered backend.\n            backend (class, optional): The backend class to be registered,\n                which must be a subclass of :class:`BaseStorageBackend`.\n                When this method is used as a decorator, backend is None.\n                Defaults to None.\n            force (bool, optional): Whether to override the backend if the name\n                has already been registered. Defaults to False.\n        \"\"\"\n        if backend is not None:\n            cls._register_backend(name, backend, force=force)\n            return\n\n        def _register(backend_cls):\n            cls._register_backend(name, backend_cls, force=force)\n            return backend_cls\n\n        return _register\n\n    def get(self, filepath):\n        return self.client.get(filepath)\n\n    def get_text(self, filepath):\n        return self.client.get_text(filepath)\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/handlers/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .base import BaseFileHandler\nfrom .json_handler import JsonHandler\nfrom .pickle_handler import PickleHandler\nfrom .yaml_handler import YamlHandler\n\n__all__ = ['BaseFileHandler', 'JsonHandler', 'PickleHandler', 'YamlHandler']\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/handlers/base.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom abc import ABCMeta, abstractmethod\n\n\nclass BaseFileHandler(metaclass=ABCMeta):\n\n    @abstractmethod\n    def load_from_fileobj(self, file, **kwargs):\n        pass\n\n    @abstractmethod\n    def dump_to_fileobj(self, obj, file, **kwargs):\n        pass\n\n    @abstractmethod\n    def dump_to_str(self, obj, **kwargs):\n        pass\n\n    def load_from_path(self, filepath, mode='r', **kwargs):\n        with open(filepath, mode) as f:\n            return self.load_from_fileobj(f, **kwargs)\n\n    def dump_to_path(self, obj, filepath, mode='w', **kwargs):\n        with open(filepath, mode) as f:\n            self.dump_to_fileobj(obj, f, **kwargs)\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/handlers/json_handler.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport json\n\nfrom .base import BaseFileHandler\n\n\nclass JsonHandler(BaseFileHandler):\n\n    def load_from_fileobj(self, file):\n        return json.load(file)\n\n    def dump_to_fileobj(self, obj, file, **kwargs):\n        json.dump(obj, file, **kwargs)\n\n    def dump_to_str(self, obj, **kwargs):\n        return json.dumps(obj, **kwargs)\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/handlers/pickle_handler.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport pickle\n\nfrom .base import BaseFileHandler\n\n\nclass PickleHandler(BaseFileHandler):\n\n    def load_from_fileobj(self, file, **kwargs):\n        return pickle.load(file, **kwargs)\n\n    def load_from_path(self, filepath, **kwargs):\n        return super(PickleHandler, self).load_from_path(\n            filepath, mode='rb', **kwargs)\n\n    def dump_to_str(self, obj, **kwargs):\n        kwargs.setdefault('protocol', 2)\n        return pickle.dumps(obj, **kwargs)\n\n    def dump_to_fileobj(self, obj, file, **kwargs):\n        kwargs.setdefault('protocol', 2)\n        pickle.dump(obj, file, **kwargs)\n\n    def dump_to_path(self, obj, filepath, **kwargs):\n        super(PickleHandler, self).dump_to_path(\n            obj, filepath, mode='wb', **kwargs)\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/handlers/yaml_handler.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport yaml\n\ntry:\n    from yaml import CLoader as Loader, CDumper as Dumper\nexcept ImportError:\n    from yaml import Loader, Dumper\n\nfrom .base import BaseFileHandler  # isort:skip\n\n\nclass YamlHandler(BaseFileHandler):\n\n    def load_from_fileobj(self, file, **kwargs):\n        kwargs.setdefault('Loader', Loader)\n        return yaml.load(file, **kwargs)\n\n    def dump_to_fileobj(self, obj, file, **kwargs):\n        kwargs.setdefault('Dumper', Dumper)\n        yaml.dump(obj, file, **kwargs)\n\n    def dump_to_str(self, obj, **kwargs):\n        kwargs.setdefault('Dumper', Dumper)\n        return yaml.dump(obj, **kwargs)\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/io.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom pathlib import Path\n\nfrom ..utils import is_list_of, is_str\nfrom .handlers import BaseFileHandler, JsonHandler, PickleHandler, YamlHandler\n\nfile_handlers = {\n    'json': JsonHandler(),\n    'yaml': YamlHandler(),\n    'yml': YamlHandler(),\n    'pickle': PickleHandler(),\n    'pkl': PickleHandler()\n}\n\n\ndef load(file, file_format=None, **kwargs):\n    \"\"\"Load data from json/yaml/pickle files.\n\n    This method provides a unified api for loading data from serialized files.\n\n    Args:\n        file (str or :obj:`Path` or file-like object): Filename or a file-like\n            object.\n        file_format (str, optional): If not specified, the file format will be\n            inferred from the file extension, otherwise use the specified one.\n            Currently supported formats include \"json\", \"yaml/yml\" and\n            \"pickle/pkl\".\n\n    Returns:\n        The content from the file.\n    \"\"\"\n    if isinstance(file, Path):\n        file = str(file)\n    if file_format is None and is_str(file):\n        file_format = file.split('.')[-1]\n    if file_format not in file_handlers:\n        raise TypeError(f'Unsupported format: {file_format}')\n\n    handler = file_handlers[file_format]\n    if is_str(file):\n        obj = handler.load_from_path(file, **kwargs)\n    elif hasattr(file, 'read'):\n        obj = handler.load_from_fileobj(file, **kwargs)\n    else:\n        raise TypeError('\"file\" must be a filepath str or a file-object')\n    return obj\n\n\ndef dump(obj, file=None, file_format=None, **kwargs):\n    \"\"\"Dump data to json/yaml/pickle strings or files.\n\n    This method provides a unified api for dumping data as strings or to files,\n    and also supports custom arguments for each file format.\n\n    Args:\n        obj (any): The python object to be dumped.\n        file (str or :obj:`Path` or file-like object, optional): If not\n            specified, then the object is dump to a str, otherwise to a file\n            specified by the filename or file-like object.\n        file_format (str, optional): Same as :func:`load`.\n\n    Returns:\n        bool: True for success, False otherwise.\n    \"\"\"\n    if isinstance(file, Path):\n        file = str(file)\n    if file_format is None:\n        if is_str(file):\n            file_format = file.split('.')[-1]\n        elif file is None:\n            raise ValueError(\n                'file_format must be specified since file is None')\n    if file_format not in file_handlers:\n        raise TypeError(f'Unsupported format: {file_format}')\n\n    handler = file_handlers[file_format]\n    if file is None:\n        return handler.dump_to_str(obj, **kwargs)\n    elif is_str(file):\n        handler.dump_to_path(obj, file, **kwargs)\n    elif hasattr(file, 'write'):\n        handler.dump_to_fileobj(obj, file, **kwargs)\n    else:\n        raise TypeError('\"file\" must be a filename str or a file-object')\n\n\ndef _register_handler(handler, file_formats):\n    \"\"\"Register a handler for some file extensions.\n\n    Args:\n        handler (:obj:`BaseFileHandler`): Handler to be registered.\n        file_formats (str or list[str]): File formats to be handled by this\n            handler.\n    \"\"\"\n    if not isinstance(handler, BaseFileHandler):\n        raise TypeError(\n            f'handler must be a child of BaseFileHandler, not {type(handler)}')\n    if isinstance(file_formats, str):\n        file_formats = [file_formats]\n    if not is_list_of(file_formats, str):\n        raise TypeError('file_formats must be a str or a list of str')\n    for ext in file_formats:\n        file_handlers[ext] = handler\n\n\ndef register_handler(file_formats, **kwargs):\n\n    def wrap(cls):\n        _register_handler(cls(**kwargs), file_formats)\n        return cls\n\n    return wrap\n"
  },
  {
    "path": "code/mmcv/mmcv/fileio/parse.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\ndef list_from_file(filename, prefix='', offset=0, max_num=0):\n    \"\"\"Load a text file and parse the content as a list of strings.\n\n    Args:\n        filename (str): Filename.\n        prefix (str): The prefix to be inserted to the begining of each item.\n        offset (int): The offset of lines.\n        max_num (int): The maximum number of lines to be read,\n            zeros and negatives mean no limitation.\n\n    Returns:\n        list[str]: A list of strings.\n    \"\"\"\n    cnt = 0\n    item_list = []\n    with open(filename, 'r') as f:\n        for _ in range(offset):\n            f.readline()\n        for line in f:\n            if max_num > 0 and cnt >= max_num:\n                break\n            item_list.append(prefix + line.rstrip('\\n'))\n            cnt += 1\n    return item_list\n\n\ndef dict_from_file(filename, key_type=str):\n    \"\"\"Load a text file and parse the content as a dict.\n\n    Each line of the text file will be two or more columns splited by\n    whitespaces or tabs. The first column will be parsed as dict keys, and\n    the following columns will be parsed as dict values.\n\n    Args:\n        filename(str): Filename.\n        key_type(type): Type of the dict's keys. str is user by default and\n            type conversion will be performed if specified.\n\n    Returns:\n        dict: The parsed contents.\n    \"\"\"\n    mapping = {}\n    with open(filename, 'r') as f:\n        for line in f:\n            items = line.rstrip('\\n').split()\n            assert len(items) >= 2\n            key = key_type(items[0])\n            val = items[1:] if len(items) > 2 else items[1]\n            mapping[key] = val\n    return mapping\n"
  },
  {
    "path": "code/mmcv/mmcv/image/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .colorspace import (bgr2gray, bgr2hls, bgr2hsv, bgr2rgb, bgr2ycbcr,\n                         gray2bgr, gray2rgb, hls2bgr, hsv2bgr, imconvert,\n                         rgb2bgr, rgb2gray, rgb2ycbcr, ycbcr2bgr, ycbcr2rgb)\nfrom .geometric import (imcrop, imflip, imflip_, impad, impad_to_multiple,\n                        imrescale, imresize, imresize_like, imrotate,\n                        rescale_size)\nfrom .io import imfrombytes, imread, imwrite, supported_backends, use_backend\nfrom .photometric import (imdenormalize, iminvert, imnormalize, imnormalize_,\n                          posterize, solarize)\n\n__all__ = [\n    'bgr2gray', 'bgr2hls', 'bgr2hsv', 'bgr2rgb', 'gray2bgr', 'gray2rgb',\n    'hls2bgr', 'hsv2bgr', 'imconvert', 'rgb2bgr', 'rgb2gray', 'imrescale',\n    'imresize', 'imresize_like', 'rescale_size', 'imcrop', 'imflip', 'imflip_',\n    'impad', 'impad_to_multiple', 'imrotate', 'imfrombytes', 'imread',\n    'imwrite', 'supported_backends', 'use_backend', 'imdenormalize',\n    'imnormalize', 'imnormalize_', 'iminvert', 'posterize', 'solarize',\n    'rgb2ycbcr', 'bgr2ycbcr', 'ycbcr2rgb', 'ycbcr2bgr'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/image/colorspace.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport cv2\nimport numpy as np\n\n\ndef imconvert(img, src, dst):\n    \"\"\"Convert an image from the src colorspace to dst colorspace.\n\n    Args:\n        img (ndarray): The input image.\n        src (str): The source colorspace, e.g., 'rgb', 'hsv'.\n        dst (str): The destination colorspace, e.g., 'rgb', 'hsv'.\n\n    Returns:\n        ndarray: The converted image.\n    \"\"\"\n    code = getattr(cv2, f'COLOR_{src.upper()}2{dst.upper()}')\n    out_img = cv2.cvtColor(img, code)\n    return out_img\n\n\ndef bgr2gray(img, keepdim=False):\n    \"\"\"Convert a BGR image to grayscale image.\n\n    Args:\n        img (ndarray): The input image.\n        keepdim (bool): If False (by default), then return the grayscale image\n            with 2 dims, otherwise 3 dims.\n\n    Returns:\n        ndarray: The converted grayscale image.\n    \"\"\"\n    out_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    if keepdim:\n        out_img = out_img[..., None]\n    return out_img\n\n\ndef rgb2gray(img, keepdim=False):\n    \"\"\"Convert a RGB image to grayscale image.\n\n    Args:\n        img (ndarray): The input image.\n        keepdim (bool): If False (by default), then return the grayscale image\n            with 2 dims, otherwise 3 dims.\n\n    Returns:\n        ndarray: The converted grayscale image.\n    \"\"\"\n    out_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    if keepdim:\n        out_img = out_img[..., None]\n    return out_img\n\n\ndef gray2bgr(img):\n    \"\"\"Convert a grayscale image to BGR image.\n\n    Args:\n        img (ndarray): The input image.\n\n    Returns:\n        ndarray: The converted BGR image.\n    \"\"\"\n    img = img[..., None] if img.ndim == 2 else img\n    out_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)\n    return out_img\n\n\ndef gray2rgb(img):\n    \"\"\"Convert a grayscale image to RGB image.\n\n    Args:\n        img (ndarray): The input image.\n\n    Returns:\n        ndarray: The converted RGB image.\n    \"\"\"\n    img = img[..., None] if img.ndim == 2 else img\n    out_img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n    return out_img\n\n\ndef _convert_input_type_range(img):\n    \"\"\"Convert the type and range of the input image.\n\n    It converts the input image to np.float32 type and range of [0, 1].\n    It is mainly used for pre-processing the input image in colorspace\n    convertion functions such as rgb2ycbcr and ycbcr2rgb.\n\n    Args:\n        img (ndarray): The input image. It accepts:\n            1. np.uint8 type with range [0, 255];\n            2. np.float32 type with range [0, 1].\n\n    Returns:\n        (ndarray): The converted image with type of np.float32 and range of\n            [0, 1].\n    \"\"\"\n    img_type = img.dtype\n    img = img.astype(np.float32)\n    if img_type == np.float32:\n        pass\n    elif img_type == np.uint8:\n        img /= 255.\n    else:\n        raise TypeError('The img type should be np.float32 or np.uint8, '\n                        f'but got {img_type}')\n    return img\n\n\ndef _convert_output_type_range(img, dst_type):\n    \"\"\"Convert the type and range of the image according to dst_type.\n\n    It converts the image to desired type and range. If `dst_type` is np.uint8,\n    images will be converted to np.uint8 type with range [0, 255]. If\n    `dst_type` is np.float32, it converts the image to np.float32 type with\n    range [0, 1].\n    It is mainly used for post-processing images in colorspace convertion\n    functions such as rgb2ycbcr and ycbcr2rgb.\n\n    Args:\n        img (ndarray): The image to be converted with np.float32 type and\n            range [0, 255].\n        dst_type (np.uint8 | np.float32): If dst_type is np.uint8, it\n            converts the image to np.uint8 type with range [0, 255]. If\n            dst_type is np.float32, it converts the image to np.float32 type\n            with range [0, 1].\n\n    Returns:\n        (ndarray): The converted image with desired type and range.\n    \"\"\"\n    if dst_type not in (np.uint8, np.float32):\n        raise TypeError('The dst_type should be np.float32 or np.uint8, '\n                        f'but got {dst_type}')\n    if dst_type == np.uint8:\n        img = img.round()\n    else:\n        img /= 255.\n    return img.astype(dst_type)\n\n\ndef rgb2ycbcr(img, y_only=False):\n    \"\"\"Convert a RGB image to YCbCr image.\n\n    This function produces the same results as Matlab's `rgb2ycbcr` function.\n    It implements the ITU-R BT.601 conversion for standard-definition\n    television. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.\n\n    It differs from a similar function in cv2.cvtColor: `RGB <-> YCrCb`.\n    In OpenCV, it implements a JPEG conversion. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.\n\n    Args:\n        img (ndarray): The input image. It accepts:\n            1. np.uint8 type with range [0, 255];\n            2. np.float32 type with range [0, 1].\n        y_only (bool): Whether to only return Y channel. Default: False.\n\n    Returns:\n        ndarray: The converted YCbCr image. The output image has the same type\n            and range as input image.\n    \"\"\"\n    img_type = img.dtype\n    img = _convert_input_type_range(img)\n    if y_only:\n        out_img = np.dot(img, [65.481, 128.553, 24.966]) + 16.0\n    else:\n        out_img = np.matmul(\n            img, [[65.481, -37.797, 112.0], [128.553, -74.203, -93.786],\n                  [24.966, 112.0, -18.214]]) + [16, 128, 128]\n    out_img = _convert_output_type_range(out_img, img_type)\n    return out_img\n\n\ndef bgr2ycbcr(img, y_only=False):\n    \"\"\"Convert a BGR image to YCbCr image.\n\n    The bgr version of rgb2ycbcr.\n    It implements the ITU-R BT.601 conversion for standard-definition\n    television. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.\n\n    It differs from a similar function in cv2.cvtColor: `BGR <-> YCrCb`.\n    In OpenCV, it implements a JPEG conversion. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.\n\n    Args:\n        img (ndarray): The input image. It accepts:\n            1. np.uint8 type with range [0, 255];\n            2. np.float32 type with range [0, 1].\n        y_only (bool): Whether to only return Y channel. Default: False.\n\n    Returns:\n        ndarray: The converted YCbCr image. The output image has the same type\n            and range as input image.\n    \"\"\"\n    img_type = img.dtype\n    img = _convert_input_type_range(img)\n    if y_only:\n        out_img = np.dot(img, [24.966, 128.553, 65.481]) + 16.0\n    else:\n        out_img = np.matmul(\n            img, [[24.966, 112.0, -18.214], [128.553, -74.203, -93.786],\n                  [65.481, -37.797, 112.0]]) + [16, 128, 128]\n    out_img = _convert_output_type_range(out_img, img_type)\n    return out_img\n\n\ndef ycbcr2rgb(img):\n    \"\"\"Convert a YCbCr image to RGB image.\n\n    This function produces the same results as Matlab's ycbcr2rgb function.\n    It implements the ITU-R BT.601 conversion for standard-definition\n    television. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.\n\n    It differs from a similar function in cv2.cvtColor: `YCrCb <-> RGB`.\n    In OpenCV, it implements a JPEG conversion. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.\n\n    Args:\n        img (ndarray): The input image. It accepts:\n            1. np.uint8 type with range [0, 255];\n            2. np.float32 type with range [0, 1].\n\n    Returns:\n        ndarray: The converted RGB image. The output image has the same type\n            and range as input image.\n    \"\"\"\n    img_type = img.dtype\n    img = _convert_input_type_range(img) * 255\n    out_img = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621],\n                              [0, -0.00153632, 0.00791071],\n                              [0.00625893, -0.00318811, 0]]) * 255.0 + [\n                                  -222.921, 135.576, -276.836\n                              ]\n    out_img = _convert_output_type_range(out_img, img_type)\n    return out_img\n\n\ndef ycbcr2bgr(img):\n    \"\"\"Convert a YCbCr image to BGR image.\n\n    The bgr version of ycbcr2rgb.\n    It implements the ITU-R BT.601 conversion for standard-definition\n    television. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.\n\n    It differs from a similar function in cv2.cvtColor: `YCrCb <-> BGR`.\n    In OpenCV, it implements a JPEG conversion. See more details in\n    https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.\n\n    Args:\n        img (ndarray): The input image. It accepts:\n            1. np.uint8 type with range [0, 255];\n            2. np.float32 type with range [0, 1].\n\n    Returns:\n        ndarray: The converted BGR image. The output image has the same type\n            and range as input image.\n    \"\"\"\n    img_type = img.dtype\n    img = _convert_input_type_range(img) * 255\n    out_img = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621],\n                              [0.00791071, -0.00153632, 0],\n                              [0, -0.00318811, 0.00625893]]) * 255.0 + [\n                                  -276.836, 135.576, -222.921\n                              ]\n    out_img = _convert_output_type_range(out_img, img_type)\n    return out_img\n\n\ndef convert_color_factory(src, dst):\n\n    code = getattr(cv2, f'COLOR_{src.upper()}2{dst.upper()}')\n\n    def convert_color(img):\n        out_img = cv2.cvtColor(img, code)\n        return out_img\n\n    convert_color.__doc__ = f\"\"\"Convert a {src.upper()} image to {dst.upper()}\n        image.\n\n    Args:\n        img (ndarray or str): The input image.\n\n    Returns:\n        ndarray: The converted {dst.upper()} image.\n    \"\"\"\n\n    return convert_color\n\n\nbgr2rgb = convert_color_factory('bgr', 'rgb')\n\nrgb2bgr = convert_color_factory('rgb', 'bgr')\n\nbgr2hsv = convert_color_factory('bgr', 'hsv')\n\nhsv2bgr = convert_color_factory('hsv', 'bgr')\n\nbgr2hls = convert_color_factory('bgr', 'hls')\n\nhls2bgr = convert_color_factory('hls', 'bgr')\n"
  },
  {
    "path": "code/mmcv/mmcv/image/geometric.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport cv2\nimport numpy as np\n\n\ndef _scale_size(size, scale):\n    \"\"\"Rescale a size by a ratio.\n\n    Args:\n        size (tuple[int]): (w, h).\n        scale (float): Scaling factor.\n\n    Returns:\n        tuple[int]: scaled size.\n    \"\"\"\n    w, h = size\n    return int(w * float(scale) + 0.5), int(h * float(scale) + 0.5)\n\n\ninterp_codes = {\n    'nearest': cv2.INTER_NEAREST,\n    'bilinear': cv2.INTER_LINEAR,\n    'bicubic': cv2.INTER_CUBIC,\n    'area': cv2.INTER_AREA,\n    'lanczos': cv2.INTER_LANCZOS4\n}\n\n\ndef imresize(img,\n             size,\n             return_scale=False,\n             interpolation='bilinear',\n             out=None):\n    \"\"\"Resize image to a given size.\n\n    Args:\n        img (ndarray): The input image.\n        size (tuple[int]): Target size (w, h).\n        return_scale (bool): Whether to return `w_scale` and `h_scale`.\n        interpolation (str): Interpolation method, accepted values are\n            \"nearest\", \"bilinear\", \"bicubic\", \"area\", \"lanczos\".\n        out (ndarray): The output destination.\n\n    Returns:\n        tuple | ndarray: (`resized_img`, `w_scale`, `h_scale`) or\n            `resized_img`.\n    \"\"\"\n    h, w = img.shape[:2]\n    resized_img = cv2.resize(\n        img, size, dst=out, interpolation=interp_codes[interpolation])\n    if not return_scale:\n        return resized_img\n    else:\n        w_scale = size[0] / w\n        h_scale = size[1] / h\n        return resized_img, w_scale, h_scale\n\n\ndef imresize_like(img, dst_img, return_scale=False, interpolation='bilinear'):\n    \"\"\"Resize image to the same size of a given image.\n\n    Args:\n        img (ndarray): The input image.\n        dst_img (ndarray): The target image.\n        return_scale (bool): Whether to return `w_scale` and `h_scale`.\n        interpolation (str): Same as :func:`resize`.\n\n    Returns:\n        tuple or ndarray: (`resized_img`, `w_scale`, `h_scale`) or\n            `resized_img`.\n    \"\"\"\n    h, w = dst_img.shape[:2]\n    return imresize(img, (w, h), return_scale, interpolation)\n\n\ndef rescale_size(old_size, scale, return_scale=False):\n    \"\"\"Calculate the new size to be rescaled to.\n\n    Args:\n        old_size (tuple[int]): The old size (w, h) of image.\n        scale (float | tuple[int]): The scaling factor or maximum size.\n            If it is a float number, then the image will be rescaled by this\n            factor, else if it is a tuple of 2 integers, then the image will\n            be rescaled as large as possible within the scale.\n        return_scale (bool): Whether to return the scaling factor besides the\n            rescaled image size.\n\n    Returns:\n        tuple[int]: The new rescaled image size.\n    \"\"\"\n    w, h = old_size\n    if isinstance(scale, (float, int)):\n        if scale <= 0:\n            raise ValueError(f'Invalid scale {scale}, must be positive.')\n        scale_factor = scale\n    elif isinstance(scale, tuple):\n        max_long_edge = max(scale)\n        max_short_edge = min(scale)\n        scale_factor = min(max_long_edge / max(h, w),\n                           max_short_edge / min(h, w))\n    else:\n        raise TypeError(\n            f'Scale must be a number or tuple of int, but got {type(scale)}')\n\n    new_size = _scale_size((w, h), scale_factor)\n\n    if return_scale:\n        return new_size, scale_factor\n    else:\n        return new_size\n\n\ndef imrescale(img, scale, return_scale=False, interpolation='bilinear'):\n    \"\"\"Resize image while keeping the aspect ratio.\n\n    Args:\n        img (ndarray): The input image.\n        scale (float | tuple[int]): The scaling factor or maximum size.\n            If it is a float number, then the image will be rescaled by this\n            factor, else if it is a tuple of 2 integers, then the image will\n            be rescaled as large as possible within the scale.\n        return_scale (bool): Whether to return the scaling factor besides the\n            rescaled image.\n        interpolation (str): Same as :func:`resize`.\n\n    Returns:\n        ndarray: The rescaled image.\n    \"\"\"\n    h, w = img.shape[:2]\n    new_size, scale_factor = rescale_size((w, h), scale, return_scale=True)\n    rescaled_img = imresize(img, new_size, interpolation=interpolation)\n    if return_scale:\n        return rescaled_img, scale_factor\n    else:\n        return rescaled_img\n\n\ndef imflip(img, direction='horizontal'):\n    \"\"\"Flip an image horizontally or vertically.\n\n    Args:\n        img (ndarray): Image to be flipped.\n        direction (str): The flip direction, either \"horizontal\" or \"vertical\".\n\n    Returns:\n        ndarray: The flipped image.\n    \"\"\"\n    assert direction in ['horizontal', 'vertical']\n    if direction == 'horizontal':\n        return np.flip(img, axis=1)\n    else:\n        return np.flip(img, axis=0)\n\n\ndef imflip_(img, direction='horizontal'):\n    \"\"\"Inplace flip an image horizontally or vertically.\n\n    Args:\n        img (ndarray): Image to be flipped.\n        direction (str): The flip direction, either \"horizontal\" or \"vertical\".\n\n    Returns:\n        ndarray: The flipped image (inplace).\n    \"\"\"\n    assert direction in ['horizontal', 'vertical']\n    if direction == 'horizontal':\n        return cv2.flip(img, 1, img)\n    else:\n        return cv2.flip(img, 0, img)\n\n\ndef imrotate(img,\n             angle,\n             center=None,\n             scale=1.0,\n             border_value=0,\n             auto_bound=False):\n    \"\"\"Rotate an image.\n\n    Args:\n        img (ndarray): Image to be rotated.\n        angle (float): Rotation angle in degrees, positive values mean\n            clockwise rotation.\n        center (tuple[float], optional): Center point (w, h) of the rotation in\n            the source image. If not specified, the center of the image will be\n            used.\n        scale (float): Isotropic scale factor.\n        border_value (int): Border value.\n        auto_bound (bool): Whether to adjust the image size to cover the whole\n            rotated image.\n\n    Returns:\n        ndarray: The rotated image.\n    \"\"\"\n    if center is not None and auto_bound:\n        raise ValueError('`auto_bound` conflicts with `center`')\n    h, w = img.shape[:2]\n    if center is None:\n        center = ((w - 1) * 0.5, (h - 1) * 0.5)\n    assert isinstance(center, tuple)\n\n    matrix = cv2.getRotationMatrix2D(center, -angle, scale)\n    if auto_bound:\n        cos = np.abs(matrix[0, 0])\n        sin = np.abs(matrix[0, 1])\n        new_w = h * sin + w * cos\n        new_h = h * cos + w * sin\n        matrix[0, 2] += (new_w - w) * 0.5\n        matrix[1, 2] += (new_h - h) * 0.5\n        w = int(np.round(new_w))\n        h = int(np.round(new_h))\n    rotated = cv2.warpAffine(img, matrix, (w, h), borderValue=border_value)\n    return rotated\n\n\ndef bbox_clip(bboxes, img_shape):\n    \"\"\"Clip bboxes to fit the image shape.\n\n    Args:\n        bboxes (ndarray): Shape (..., 4*k)\n        img_shape (tuple[int]): (height, width) of the image.\n\n    Returns:\n        ndarray: Clipped bboxes.\n    \"\"\"\n    assert bboxes.shape[-1] % 4 == 0\n    cmin = np.empty(bboxes.shape[-1], dtype=bboxes.dtype)\n    cmin[0::2] = img_shape[1] - 1\n    cmin[1::2] = img_shape[0] - 1\n    clipped_bboxes = np.maximum(np.minimum(bboxes, cmin), 0)\n    return clipped_bboxes\n\n\ndef bbox_scaling(bboxes, scale, clip_shape=None):\n    \"\"\"Scaling bboxes w.r.t the box center.\n\n    Args:\n        bboxes (ndarray): Shape(..., 4).\n        scale (float): Scaling factor.\n        clip_shape (tuple[int], optional): If specified, bboxes that exceed the\n            boundary will be clipped according to the given shape (h, w).\n\n    Returns:\n        ndarray: Scaled bboxes.\n    \"\"\"\n    if float(scale) == 1.0:\n        scaled_bboxes = bboxes.copy()\n    else:\n        w = bboxes[..., 2] - bboxes[..., 0] + 1\n        h = bboxes[..., 3] - bboxes[..., 1] + 1\n        dw = (w * (scale - 1)) * 0.5\n        dh = (h * (scale - 1)) * 0.5\n        scaled_bboxes = bboxes + np.stack((-dw, -dh, dw, dh), axis=-1)\n    if clip_shape is not None:\n        return bbox_clip(scaled_bboxes, clip_shape)\n    else:\n        return scaled_bboxes\n\n\ndef imcrop(img, bboxes, scale=1.0, pad_fill=None):\n    \"\"\"Crop image patches.\n\n    3 steps: scale the bboxes -> clip bboxes -> crop and pad.\n\n    Args:\n        img (ndarray): Image to be cropped.\n        bboxes (ndarray): Shape (k, 4) or (4, ), location of cropped bboxes.\n        scale (float, optional): Scale ratio of bboxes, the default value\n            1.0 means no padding.\n        pad_fill (Number | list[Number]): Value to be filled for padding.\n            Default: None, which means no padding.\n\n    Returns:\n        list[ndarray] | ndarray: The cropped image patches.\n    \"\"\"\n    chn = 1 if img.ndim == 2 else img.shape[2]\n    if pad_fill is not None:\n        if isinstance(pad_fill, (int, float)):\n            pad_fill = [pad_fill for _ in range(chn)]\n        assert len(pad_fill) == chn\n\n    _bboxes = bboxes[None, ...] if bboxes.ndim == 1 else bboxes\n    scaled_bboxes = bbox_scaling(_bboxes, scale).astype(np.int32)\n    clipped_bbox = bbox_clip(scaled_bboxes, img.shape)\n\n    patches = []\n    for i in range(clipped_bbox.shape[0]):\n        x1, y1, x2, y2 = tuple(clipped_bbox[i, :])\n        if pad_fill is None:\n            patch = img[y1:y2 + 1, x1:x2 + 1, ...]\n        else:\n            _x1, _y1, _x2, _y2 = tuple(scaled_bboxes[i, :])\n            if chn == 1:\n                patch_shape = (_y2 - _y1 + 1, _x2 - _x1 + 1)\n            else:\n                patch_shape = (_y2 - _y1 + 1, _x2 - _x1 + 1, chn)\n            patch = np.array(\n                pad_fill, dtype=img.dtype) * np.ones(\n                    patch_shape, dtype=img.dtype)\n            x_start = 0 if _x1 >= 0 else -_x1\n            y_start = 0 if _y1 >= 0 else -_y1\n            w = x2 - x1 + 1\n            h = y2 - y1 + 1\n            patch[y_start:y_start + h, x_start:x_start + w,\n                  ...] = img[y1:y1 + h, x1:x1 + w, ...]\n        patches.append(patch)\n\n    if bboxes.ndim == 1:\n        return patches[0]\n    else:\n        return patches\n\n\ndef impad(img, shape, pad_val=0):\n    \"\"\"Pad an image to a certain shape.\n\n    Args:\n        img (ndarray): Image to be padded.\n        shape (tuple[int]): Expected padding shape (h, w).\n        pad_val (Number | Sequence[Number]): Values to be filled in padding\n            areas. Default: 0.\n\n    Returns:\n        ndarray: The padded image.\n    \"\"\"\n    if not isinstance(pad_val, (int, float)):\n        assert len(pad_val) == img.shape[-1]\n    if len(shape) < len(img.shape):\n        shape = shape + (img.shape[-1], )\n    assert len(shape) == len(img.shape)\n    for s, img_s in zip(shape, img.shape):\n        assert s >= img_s\n    pad = np.empty(shape, dtype=img.dtype)\n    pad[...] = pad_val\n    pad[:img.shape[0], :img.shape[1], ...] = img\n    return pad\n\n\ndef impad_to_multiple(img, divisor, pad_val=0):\n    \"\"\"Pad an image to ensure each edge to be multiple to some number.\n\n    Args:\n        img (ndarray): Image to be padded.\n        divisor (int): Padded image edges will be multiple to divisor.\n        pad_val (Number | Sequence[Number]): Same as :func:`impad`.\n\n    Returns:\n        ndarray: The padded image.\n    \"\"\"\n    pad_h = int(np.ceil(img.shape[0] / divisor)) * divisor\n    pad_w = int(np.ceil(img.shape[1] / divisor)) * divisor\n    return impad(img, (pad_h, pad_w), pad_val)\n"
  },
  {
    "path": "code/mmcv/mmcv/image/io.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport io\nimport os.path as osp\nfrom pathlib import Path\n\nimport cv2\nimport numpy as np\nfrom cv2 import IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_UNCHANGED\n\nfrom mmcv.utils import check_file_exist, is_str, mkdir_or_exist\n\ntry:\n    from turbojpeg import TJCS_RGB, TJPF_BGR, TJPF_GRAY, TurboJPEG\nexcept ImportError:\n    TJCS_RGB = TJPF_GRAY = TJPF_BGR = TurboJPEG = None\n\ntry:\n    from PIL import Image\nexcept ImportError:\n    Image = None\n\njpeg = None\nsupported_backends = ['cv2', 'turbojpeg', 'pillow']\n\nimread_flags = {\n    'color': IMREAD_COLOR,\n    'grayscale': IMREAD_GRAYSCALE,\n    'unchanged': IMREAD_UNCHANGED\n}\n\nimread_backend = 'cv2'\n\n\ndef use_backend(backend):\n    \"\"\"Select a backend for image decoding.\n\n    Args:\n        backend (str): The image decoding backend type. Options are `cv2`,\n        `pillow`, `turbojpeg` (see https://github.com/lilohuang/PyTurboJPEG).\n        `turbojpeg` is faster but it only supports `.jpeg` file format.\n    \"\"\"\n    assert backend in supported_backends\n    global imread_backend\n    imread_backend = backend\n    if imread_backend == 'turbojpeg':\n        if TurboJPEG is None:\n            raise ImportError('`PyTurboJPEG` is not installed')\n        global jpeg\n        if jpeg is None:\n            jpeg = TurboJPEG()\n    elif imread_backend == 'pillow':\n        if Image is None:\n            raise ImportError('`Pillow` is not installed')\n\n\ndef _jpegflag(flag='color', channel_order='bgr'):\n    channel_order = channel_order.lower()\n    if channel_order not in ['rgb', 'bgr']:\n        raise ValueError('channel order must be either \"rgb\" or \"bgr\"')\n\n    if flag == 'color':\n        if channel_order == 'bgr':\n            return TJPF_BGR\n        elif channel_order == 'rgb':\n            return TJCS_RGB\n    elif flag == 'grayscale':\n        return TJPF_GRAY\n    else:\n        raise ValueError('flag must be \"color\" or \"grayscale\"')\n\n\ndef _pillow2array(img, flag='color', channel_order='bgr'):\n    \"\"\"Convert a pillow image to numpy array\n\n    Args:\n        img (:obj:`PIL.Image.Image`): The image loaded using PIL\n        flag (str): Flags specifying the color type of a loaded image,\n            candidates are 'color', 'grayscale' and 'unchanged'.\n            Default to 'color'.\n        channel_order (str): The channel order of the output image array,\n            candidates are 'bgr' and 'rgb'. Default to 'bgr'.\n\n    Returns:\n        np.ndarray: The converted numpy array\n    \"\"\"\n    channel_order = channel_order.lower()\n    if channel_order not in ['rgb', 'bgr']:\n        raise ValueError('channel order must be either \"rgb\" or \"bgr\"')\n\n    if flag == 'unchanged':\n        array = np.array(img)\n        if array.ndim >= 3 and array.shape[2] >= 3:  # color image\n            array[:, :, :3] = array[:, :, (2, 1, 0)]  # RGB to BGR\n    else:\n        # If the image mode is not 'RGB', convert it to 'RGB' first.\n        if img.mode != 'RGB':\n            if img.mode != 'LA':\n                # Most formats except 'LA' can be directly converted to RGB\n                img = img.convert('RGB')\n            else:\n                # When the mode is 'LA', the default conversion will fill in\n                #  the canvas with black, which sometimes shadows black objects\n                #  in the foreground.\n                #\n                # Therefore, a random color (124, 117, 104) is used for canvas\n                img_rgba = img.convert('RGBA')\n                img = Image.new('RGB', img_rgba.size, (124, 117, 104))\n                img.paste(img_rgba, mask=img_rgba.split()[3])  # 3 is alpha\n        if flag == 'color':\n            array = np.array(img)\n            if channel_order != 'rgb':\n                array = array[:, :, ::-1]  # RGB to BGR\n        elif flag == 'grayscale':\n            img = img.convert('L')\n            array = np.array(img)\n        else:\n            raise ValueError(\n                'flag must be \"color\", \"grayscale\" or \"unchanged\", '\n                f'but got {flag}')\n    return array\n\n\ndef imread(img_or_path, flag='color', channel_order='bgr', backend=None):\n    \"\"\"Read an image.\n\n    Args:\n        img_or_path (ndarray or str or Path): Either a numpy array or str or\n            pathlib.Path. If it is a numpy array (loaded image), then\n            it will be returned as is.\n        flag (str): Flags specifying the color type of a loaded image,\n            candidates are `color`, `grayscale` and `unchanged`.\n            Note that the `turbojpeg` backened does not support `unchanged`.\n        channel_order (str): Order of channel, candidates are `bgr` and `rgb`.\n        backend (str|None): The image decoding backend type. Options are `cv2`,\n            `pillow`, `turbojpeg`, `None`. If backend is None, the global\n            imread_backend specified by ``mmcv.use_backend()`` will be used.\n            Default: None.\n\n    Returns:\n        ndarray: Loaded image array.\n    \"\"\"\n\n    if backend is None:\n        backend = imread_backend\n    if backend not in supported_backends:\n        raise ValueError(f'backend: {backend} is not supported. Supported '\n                         \"backends are 'cv2', 'turbojpeg', 'pillow'\")\n    if isinstance(img_or_path, Path):\n        img_or_path = str(img_or_path)\n\n    if isinstance(img_or_path, np.ndarray):\n        return img_or_path\n    elif is_str(img_or_path):\n        check_file_exist(img_or_path,\n                         f'img file does not exist: {img_or_path}')\n        if backend == 'turbojpeg':\n            with open(img_or_path, 'rb') as in_file:\n                img = jpeg.decode(in_file.read(),\n                                  _jpegflag(flag, channel_order))\n                if img.shape[-1] == 1:\n                    img = img[:, :, 0]\n            return img\n        elif backend == 'pillow':\n            img = Image.open(img_or_path)\n            img = _pillow2array(img, flag, channel_order)\n            return img\n        else:\n            flag = imread_flags[flag] if is_str(flag) else flag\n            img = cv2.imread(img_or_path, flag)\n            if flag == IMREAD_COLOR and channel_order == 'rgb':\n                cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)\n            return img\n    else:\n        raise TypeError('\"img\" must be a numpy array or a str or '\n                        'a pathlib.Path object')\n\n\ndef imfrombytes(content, flag='color', channel_order='bgr', backend=None):\n    \"\"\"Read an image from bytes.\n\n    Args:\n        content (bytes): Image bytes got from files or other streams.\n        flag (str): Same as :func:`imread`.\n        backend (str|None): The image decoding backend type. Options are `cv2`,\n            `pillow`, `turbojpeg`, `None`. If backend is None, the global\n            imread_backend specified by ``mmcv.use_backend()`` will be used.\n            Default: None.\n\n    Returns:\n        ndarray: Loaded image array.\n    \"\"\"\n\n    if backend is None:\n        backend = imread_backend\n    if backend not in supported_backends:\n        raise ValueError(f'backend: {backend} is not supported. Supported '\n                         \"backends are 'cv2', 'turbojpeg', 'pillow'\")\n    if backend == 'turbojpeg':\n        img = jpeg.decode(content, _jpegflag(flag, channel_order))\n        if img.shape[-1] == 1:\n            img = img[:, :, 0]\n        return img\n    elif backend == 'pillow':\n        buff = io.BytesIO(content)\n        img = Image.open(buff)\n        img = _pillow2array(img, flag, channel_order)\n        return img\n    else:\n        img_np = np.frombuffer(content, np.uint8)\n        flag = imread_flags[flag] if is_str(flag) else flag\n        img = cv2.imdecode(img_np, flag)\n        if flag == IMREAD_COLOR and channel_order == 'rgb':\n            cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)\n        return img\n\n\ndef imwrite(img, file_path, params=None, auto_mkdir=True):\n    \"\"\"Write image to file\n\n    Args:\n        img (ndarray): Image array to be written.\n        file_path (str): Image file path.\n        params (None or list): Same as opencv's :func:`imwrite` interface.\n        auto_mkdir (bool): If the parent folder of `file_path` does not exist,\n            whether to create it automatically.\n\n    Returns:\n        bool: Successful or not.\n    \"\"\"\n    if auto_mkdir:\n        dir_name = osp.abspath(osp.dirname(file_path))\n        mkdir_or_exist(dir_name)\n    return cv2.imwrite(file_path, img, params)\n"
  },
  {
    "path": "code/mmcv/mmcv/image/photometric.py",
    "content": "import cv2\nimport numpy as np\n\n\ndef imnormalize(img, mean, std, to_rgb=True):\n    \"\"\"Normalize an image with mean and std.\n\n    Args:\n        img (ndarray): Image to be normalized.\n        mean (ndarray): The mean to be used for normalize.\n        std (ndarray): The std to be used for normalize.\n        to_rgb (bool): Whether to convert to rgb.\n\n    Returns:\n        ndarray: The normalized image.\n    \"\"\"\n    img = img.copy().astype(np.float32)\n    return imnormalize_(img, mean, std, to_rgb)\n\n\ndef imnormalize_(img, mean, std, to_rgb=True):\n    \"\"\"Inplace normalize an image with mean and std.\n\n    Args:\n        img (ndarray): Image to be normalized.\n        mean (ndarray): The mean to be used for normalize.\n        std (ndarray): The std to be used for normalize.\n        to_rgb (bool): Whether to convert to rgb.\n\n    Returns:\n        ndarray: The normalized image.\n    \"\"\"\n    # cv2 inplace normalization does not accept uint8\n    assert img.dtype != np.uint8\n    mean = np.float64(mean.reshape(1, -1))\n    stdinv = 1 / np.float64(std.reshape(1, -1))\n    if to_rgb:\n        cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)  # inplace\n    cv2.subtract(img, mean, img)  # inplace\n    cv2.multiply(img, stdinv, img)  # inplace\n    return img\n\n\ndef imdenormalize(img, mean, std, to_bgr=True):\n    assert img.dtype != np.uint8\n    mean = mean.reshape(1, -1).astype(np.float64)\n    std = std.reshape(1, -1).astype(np.float64)\n    img = cv2.multiply(img, std)  # make a copy\n    cv2.add(img, mean, img)  # inplace\n    if to_bgr:\n        cv2.cvtColor(img, cv2.COLOR_RGB2BGR, img)  # inplace\n    return img\n\n\ndef iminvert(img):\n    \"\"\"Invert (negate) an image\n\n    Args:\n        img (ndarray): Image to be inverted.\n\n    Returns:\n        ndarray: The inverted image.\n    \"\"\"\n    return np.full_like(img, 255) - img\n\n\ndef solarize(img, thr=128):\n    \"\"\"Solarize an image (invert all pixel values above a threshold)\n\n    Args:\n        img (ndarray): Image to be solarized.\n        thr (int): Threshold for solarizing (0 - 255).\n\n    Returns:\n        ndarray: The solarized image.\n    \"\"\"\n    img = np.where(img < thr, img, 255 - img)\n    return img\n\n\ndef posterize(img, bits):\n    \"\"\"Posterize an image (reduce the number of bits for each color channel)\n\n    Args:\n        img (ndarray): Image to be posterized.\n        bits (int): Number of bits (1 to 8) to use for posterizing.\n\n    Returns:\n        ndarray: The posterized image.\n    \"\"\"\n    shift = 8 - bits\n    img = np.left_shift(np.right_shift(img, shift), shift)\n    return img\n"
  },
  {
    "path": "code/mmcv/mmcv/model_zoo/deprecated.json",
    "content": "{\n  \"resnet50_caffe\": \"detectron/resnet50_caffe\",\n  \"resnet50_caffe_bgr\": \"detectron2/resnet50_caffe_bgr\",\n  \"resnet101_caffe\": \"detectron/resnet101_caffe\",\n  \"resnet101_caffe_bgr\": \"detectron2/resnet101_caffe_bgr\"\n}\n"
  },
  {
    "path": "code/mmcv/mmcv/model_zoo/open_mmlab.json",
    "content": "{\n  \"vgg16_caffe\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/vgg16_caffe-292e1171.pth\",\n  \"detectron/resnet50_caffe\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet50_caffe-788b5fa3.pth\",\n  \"detectron2/resnet50_caffe\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet50_msra-5891d200.pth\",\n  \"detectron/resnet101_caffe\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet101_caffe-3ad79236.pth\",\n  \"detectron2/resnet101_caffe\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet101_msra-6cc46731.pth\",\n  \"detectron2/resnext101_32x8d\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext101_32x8d-1516f1aa.pth\",\n  \"resnext50_32x4d\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext50-32x4d-0ab1a123.pth\",\n  \"resnext101_32x4d\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext101_32x4d-a5af3160.pth\",\n  \"resnext101_64x4d\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext101_64x4d-ee2c6f71.pth\",\n  \"contrib/resnet50_gn\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet50_gn_thangvubk-ad1730dd.pth\",\n  \"detectron/resnet50_gn\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet50_gn-9186a21c.pth\",\n  \"detectron/resnet101_gn\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet101_gn-cac0ab98.pth\",\n  \"jhu/resnet50_gn_ws\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet50_gn_ws-15beedd8.pth\",\n  \"jhu/resnet101_gn_ws\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet101_gn_ws-3e3c308c.pth\",\n  \"jhu/resnext50_32x4d_gn_ws\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext50_32x4d_gn_ws-0d87ac85.pth\",\n  \"jhu/resnext101_32x4d_gn_ws\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext101_32x4d_gn_ws-34ac1a9e.pth\",\n  \"jhu/resnext50_32x4d_gn\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext50_32x4d_gn-c7e8b754.pth\",\n  \"jhu/resnext101_32x4d_gn\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnext101_32x4d_gn-ac3bb84e.pth\",\n  \"msra/hrnetv2_w18_small\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/hrnetv2_w18_small-b5a04e21.pth\",\n  \"msra/hrnetv2_w18\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/hrnetv2_w18-00eb2006.pth\",\n  \"msra/hrnetv2_w32\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/hrnetv2_w32-dc9eeb4f.pth\",\n  \"msra/hrnetv2_w40\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/hrnetv2_w40-ed0b031c.pth\",\n  \"msra/hrnetv2_w48\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/hrnetv2_w48-d2186c55.pth\",\n  \"bninception_caffe\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/bn_inception_caffe-ed2e8665.pth\",\n  \"kin400/i3d_r50_f32s2_k400\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/i3d_r50_f32s2_k400-2c57e077.pth\",\n  \"kin400/nl3d_r50_f32s2_k400\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/nl3d_r50_f32s2_k400-fa7e7caa.pth\",\n  \"res2net101_v1d_26w_4s\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/res2net101_v1d_26w_4s_mmdetv2-f0a600f9.pth\",\n  \"regnetx_400mf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_400mf-a5b10d96.pth\",\n  \"regnetx_800mf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_800mf-1f4be4c7.pth\",\n  \"regnetx_1.6gf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_1.6gf-5791c176.pth\",\n  \"regnetx_3.2gf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_3.2gf-c2599b0f.pth\",\n  \"regnetx_4.0gf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_4.0gf-a88f671e.pth\",\n  \"regnetx_6.4gf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_6.4gf-006af45d.pth\",\n  \"regnetx_8.0gf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_8.0gf-3c68abe7.pth\",\n  \"regnetx_12gf\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/regnetx_12gf-4c2a3350.pth\",\n  \"resnet50_v1c\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet50_v1c-2cccc1ad.pth\",\n  \"resnet101_v1c\": \"https://open-mmlab.s3.ap-northeast-2.amazonaws.com/pretrain/third_party/resnet101_v1c-e67eebb6.pth\"\n}\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .collate import collate\nfrom .data_container import DataContainer\nfrom .data_parallel import MMDataParallel\nfrom .distributed import MMDistributedDataParallel\nfrom .registry import MODULE_WRAPPERS\nfrom .scatter_gather import scatter, scatter_kwargs\nfrom .utils import is_module_wrapper\n\n__all__ = [\n    'collate', 'DataContainer', 'MMDataParallel', 'MMDistributedDataParallel',\n    'scatter', 'scatter_kwargs', 'is_module_wrapper', 'MODULE_WRAPPERS'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/_functions.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport torch\nfrom torch.nn.parallel._functions import _get_stream\n\n\ndef scatter(input, devices, streams=None):\n    \"\"\"Scatters tensor across multiple GPUs.\n    \"\"\"\n    if streams is None:\n        streams = [None] * len(devices)\n\n    if isinstance(input, list):\n        chunk_size = (len(input) - 1) // len(devices) + 1\n        outputs = [\n            scatter(input[i], [devices[i // chunk_size]],\n                    [streams[i // chunk_size]]) for i in range(len(input))\n        ]\n        return outputs\n    elif isinstance(input, torch.Tensor):\n        output = input.contiguous()\n        # TODO: copy to a pinned buffer first (if copying from CPU)\n        stream = streams[0] if output.numel() > 0 else None\n        with torch.cuda.device(devices[0]), torch.cuda.stream(stream):\n            output = output.cuda(devices[0], non_blocking=True)\n        return output\n    else:\n        raise Exception(f'Unknown type {type(input)}.')\n\n\ndef synchronize_stream(output, devices, streams):\n    if isinstance(output, list):\n        chunk_size = len(output) // len(devices)\n        for i in range(len(devices)):\n            for j in range(chunk_size):\n                synchronize_stream(output[i * chunk_size + j], [devices[i]],\n                                   [streams[i]])\n    elif isinstance(output, torch.Tensor):\n        if output.numel() != 0:\n            with torch.cuda.device(devices[0]):\n                main_stream = torch.cuda.current_stream()\n                main_stream.wait_stream(streams[0])\n                output.record_stream(main_stream)\n    else:\n        raise Exception(f'Unknown type {type(output)}.')\n\n\ndef get_input_device(input):\n    if isinstance(input, list):\n        for item in input:\n            input_device = get_input_device(item)\n            if input_device != -1:\n                return input_device\n        return -1\n    elif isinstance(input, torch.Tensor):\n        return input.get_device() if input.is_cuda else -1\n    else:\n        raise Exception(f'Unknown type {type(input)}.')\n\n\nclass Scatter:\n\n    @staticmethod\n    def forward(target_gpus, input):\n        input_device = get_input_device(input)\n        streams = None\n        if input_device == -1:\n            # Perform CPU to GPU copies in a background stream\n            streams = [_get_stream(device) for device in target_gpus]\n\n        outputs = scatter(input, target_gpus, streams)\n        # Synchronize with the copy stream\n        if streams is not None:\n            synchronize_stream(outputs, target_gpus, streams)\n\n        return tuple(outputs)\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/collate.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom collections.abc import Mapping, Sequence\n\nimport torch\nimport torch.nn.functional as F\nfrom torch.utils.data.dataloader import default_collate\n\nfrom .data_container import DataContainer\n\n\ndef collate(batch, samples_per_gpu=1):\n    \"\"\"Puts each data field into a tensor/DataContainer with outer dimension\n    batch size.\n\n    Extend default_collate to add support for\n    :type:`~mmcv.parallel.DataContainer`. There are 3 cases.\n\n    1. cpu_only = True, e.g., meta data\n    2. cpu_only = False, stack = True, e.g., images tensors\n    3. cpu_only = False, stack = False, e.g., gt bboxes\n    \"\"\"\n\n    if not isinstance(batch, Sequence):\n        raise TypeError(f'{batch.dtype} is not supported.')\n\n    if isinstance(batch[0], DataContainer):\n        assert len(batch) % samples_per_gpu == 0\n        stacked = []\n        if batch[0].cpu_only:\n            for i in range(0, len(batch), samples_per_gpu):\n                stacked.append(\n                    [sample.data for sample in batch[i:i + samples_per_gpu]])\n            return DataContainer(\n                stacked, batch[0].stack, batch[0].padding_value, cpu_only=True)\n        elif batch[0].stack:\n            for i in range(0, len(batch), samples_per_gpu):\n                assert isinstance(batch[i].data, torch.Tensor)\n\n                if batch[i].pad_dims is not None:\n                    ndim = batch[i].dim()\n                    assert ndim > batch[i].pad_dims\n                    max_shape = [0 for _ in range(batch[i].pad_dims)]\n                    for dim in range(1, batch[i].pad_dims + 1):\n                        max_shape[dim - 1] = batch[i].size(-dim)\n                    for sample in batch[i:i + samples_per_gpu]:\n                        for dim in range(0, ndim - batch[i].pad_dims):\n                            assert batch[i].size(dim) == sample.size(dim)\n                        for dim in range(1, batch[i].pad_dims + 1):\n                            max_shape[dim - 1] = max(max_shape[dim - 1],\n                                                     sample.size(-dim))\n                    padded_samples = []\n                    for sample in batch[i:i + samples_per_gpu]:\n                        pad = [0 for _ in range(batch[i].pad_dims * 2)]\n                        for dim in range(1, batch[i].pad_dims + 1):\n                            pad[2 * dim -\n                                1] = max_shape[dim - 1] - sample.size(-dim)\n                        padded_samples.append(\n                            F.pad(\n                                sample.data, pad, value=sample.padding_value))\n                    stacked.append(default_collate(padded_samples))\n                elif batch[i].pad_dims is None:\n                    stacked.append(\n                        default_collate([\n                            sample.data\n                            for sample in batch[i:i + samples_per_gpu]\n                        ]))\n                else:\n                    raise ValueError(\n                        'pad_dims should be either None or integers (1-3)')\n\n        else:\n            for i in range(0, len(batch), samples_per_gpu):\n                stacked.append(\n                    [sample.data for sample in batch[i:i + samples_per_gpu]])\n        return DataContainer(stacked, batch[0].stack, batch[0].padding_value)\n    elif isinstance(batch[0], Sequence):\n        transposed = zip(*batch)\n        return [collate(samples, samples_per_gpu) for samples in transposed]\n    elif isinstance(batch[0], Mapping):\n        return {\n            key: collate([d[key] for d in batch], samples_per_gpu)\n            for key in batch[0]\n        }\n    else:\n        return default_collate(batch)\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/data_container.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport functools\n\nimport torch\n\n\ndef assert_tensor_type(func):\n\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        if not isinstance(args[0].data, torch.Tensor):\n            raise AttributeError(\n                f'{args[0].__class__.__name__} has no attribute '\n                f'{func.__name__} for type {args[0].datatype}')\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\nclass DataContainer:\n    \"\"\"A container for any type of objects.\n\n    Typically tensors will be stacked in the collate function and sliced along\n    some dimension in the scatter function. This behavior has some limitations.\n    1. All tensors have to be the same size.\n    2. Types are limited (numpy array or Tensor).\n\n    We design `DataContainer` and `MMDataParallel` to overcome these\n    limitations. The behavior can be either of the following.\n\n    - copy to GPU, pad all tensors to the same size and stack them\n    - copy to GPU without stacking\n    - leave the objects as is and pass it to the model\n    - pad_dims specifies the number of last few dimensions to do padding\n    \"\"\"\n\n    def __init__(self,\n                 data,\n                 stack=False,\n                 padding_value=0,\n                 cpu_only=False,\n                 pad_dims=2):\n        self._data = data\n        self._cpu_only = cpu_only\n        self._stack = stack\n        self._padding_value = padding_value\n        assert pad_dims in [None, 1, 2, 3]\n        self._pad_dims = pad_dims\n\n    def __repr__(self):\n        return f'{self.__class__.__name__}({repr(self.data)})'\n\n    def __len__(self):\n        return len(self._data)\n\n    @property\n    def data(self):\n        return self._data\n\n    @property\n    def datatype(self):\n        if isinstance(self.data, torch.Tensor):\n            return self.data.type()\n        else:\n            return type(self.data)\n\n    @property\n    def cpu_only(self):\n        return self._cpu_only\n\n    @property\n    def stack(self):\n        return self._stack\n\n    @property\n    def padding_value(self):\n        return self._padding_value\n\n    @property\n    def pad_dims(self):\n        return self._pad_dims\n\n    @assert_tensor_type\n    def size(self, *args, **kwargs):\n        return self.data.size(*args, **kwargs)\n\n    @assert_tensor_type\n    def dim(self):\n        return self.data.dim()\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/data_parallel.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom itertools import chain\n\nfrom torch.nn.parallel import DataParallel\n\nfrom .scatter_gather import scatter_kwargs\n\n\nclass MMDataParallel(DataParallel):\n\n    def scatter(self, inputs, kwargs, device_ids):\n        return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim)\n\n    def train_step(self, *inputs, **kwargs):\n        if not self.device_ids:\n            return self.module.train_step(*inputs, **kwargs)\n\n        assert len(self.device_ids) == 1, \\\n            ('MMDataParallel only supports single GPU training, if you need to'\n             ' train with multiple GPUs, please use MMDistributedDataParallel'\n             'instead.')\n\n        for t in chain(self.module.parameters(), self.module.buffers()):\n            if t.device != self.src_device_obj:\n                raise RuntimeError(\n                    'module must have its parameters and buffers '\n                    f'on device {self.src_device_obj} (device_ids[0]) but '\n                    f'found one of them on device: {t.device}')\n\n        inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)\n        return self.module.train_step(*inputs[0], **kwargs[0])\n\n    def val_step(self, *inputs, **kwargs):\n        if not self.device_ids:\n            return self.module.val_step(*inputs, **kwargs)\n\n        assert len(self.device_ids) == 1, \\\n            ('MMDataParallel only supports single GPU training, if you need to'\n             ' train with multiple GPUs, please use MMDistributedDataParallel'\n             'instead.')\n\n        for t in chain(self.module.parameters(), self.module.buffers()):\n            if t.device != self.src_device_obj:\n                raise RuntimeError(\n                    'module must have its parameters and buffers '\n                    f'on device {self.src_device_obj} (device_ids[0]) but '\n                    f'found one of them on device: {t.device}')\n\n        inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)\n        return self.module.val_step(*inputs[0], **kwargs[0])\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/distributed.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport torch\nfrom torch.nn.parallel.distributed import (DistributedDataParallel,\n                                           _find_tensors)\n\nfrom mmcv.utils import TORCH_VERSION\nfrom .scatter_gather import scatter_kwargs\n\n\nclass MMDistributedDataParallel(DistributedDataParallel):\n    \"\"\"The DDP module that supports DataContainer.\n\n    MMDDP has two main differences with PyTorch DDP:\n\n    - It supports a custom type :class:`DataContainer` which allows more\n      flexible control of input data.\n    - It implement two APIs ``train_step()`` and ``val_step()``.\n    \"\"\"\n\n    def scatter(self, inputs, kwargs, device_ids):\n        return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim)\n\n    def train_step(self, *inputs, **kwargs):\n        \"\"\"train_step() API for module wrapped by DistributedDataParallel.\n\n        This method is basically the same as\n        ``DistributedDataParallel.forward()``, while replacing\n        ``self.module.forward()`` with ``self.module.train_step()``.\n        It is compatible with PyTorch 1.1 - 1.5.\n        \"\"\"\n        if getattr(self, 'require_forward_param_sync', True):\n            self._sync_params()\n        if self.device_ids:\n            inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)\n            if len(self.device_ids) == 1:\n                output = self.module.train_step(*inputs[0], **kwargs[0])\n            else:\n                outputs = self.parallel_apply(\n                    self._module_copies[:len(inputs)], inputs, kwargs)\n                output = self.gather(outputs, self.output_device)\n        else:\n            output = self.module.train_step(*inputs, **kwargs)\n\n        if torch.is_grad_enabled() and getattr(\n                self, 'require_backward_grad_sync', True):\n            if self.find_unused_parameters:\n                self.reducer.prepare_for_backward(list(_find_tensors(output)))\n            else:\n                self.reducer.prepare_for_backward([])\n        else:\n            if TORCH_VERSION > '1.2':\n                self.require_forward_param_sync = False\n        return output\n\n    def val_step(self, *inputs, **kwargs):\n        \"\"\"val_step() API for module wrapped by DistributedDataParallel.\n\n        This method is basically the same as\n        ``DistributedDataParallel.forward()``, while replacing\n        ``self.module.forward()`` with ``self.module.val_step()``.\n        It is compatible with PyTorch 1.1 - 1.5.\n        \"\"\"\n        if getattr(self, 'require_forward_param_sync', True):\n            self._sync_params()\n        if self.device_ids:\n            inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)\n            if len(self.device_ids) == 1:\n                output = self.module.val_step(*inputs[0], **kwargs[0])\n            else:\n                outputs = self.parallel_apply(\n                    self._module_copies[:len(inputs)], inputs, kwargs)\n                output = self.gather(outputs, self.output_device)\n        else:\n            output = self.module.val_step(*inputs, **kwargs)\n\n        if torch.is_grad_enabled() and getattr(\n                self, 'require_backward_grad_sync', True):\n            if self.find_unused_parameters:\n                self.reducer.prepare_for_backward(list(_find_tensors(output)))\n            else:\n                self.reducer.prepare_for_backward([])\n        else:\n            if TORCH_VERSION > '1.2':\n                self.require_forward_param_sync = False\n        return output\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/distributed_deprecated.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport torch\nimport torch.distributed as dist\nimport torch.nn as nn\nfrom torch._utils import (_flatten_dense_tensors, _take_tensors,\n                          _unflatten_dense_tensors)\n\nfrom mmcv.utils import TORCH_VERSION\nfrom .registry import MODULE_WRAPPERS\nfrom .scatter_gather import scatter_kwargs\n\n\n@MODULE_WRAPPERS.register_module()\nclass MMDistributedDataParallel(nn.Module):\n\n    def __init__(self,\n                 module,\n                 dim=0,\n                 broadcast_buffers=True,\n                 bucket_cap_mb=25):\n        super(MMDistributedDataParallel, self).__init__()\n        self.module = module\n        self.dim = dim\n        self.broadcast_buffers = broadcast_buffers\n\n        self.broadcast_bucket_size = bucket_cap_mb * 1024 * 1024\n        self._sync_params()\n\n    def _dist_broadcast_coalesced(self, tensors, buffer_size):\n        for tensors in _take_tensors(tensors, buffer_size):\n            flat_tensors = _flatten_dense_tensors(tensors)\n            dist.broadcast(flat_tensors, 0)\n            for tensor, synced in zip(\n                    tensors, _unflatten_dense_tensors(flat_tensors, tensors)):\n                tensor.copy_(synced)\n\n    def _sync_params(self):\n        module_states = list(self.module.state_dict().values())\n        if len(module_states) > 0:\n            self._dist_broadcast_coalesced(module_states,\n                                           self.broadcast_bucket_size)\n        if self.broadcast_buffers:\n            if TORCH_VERSION < '1.0':\n                buffers = [b.data for b in self.module._all_buffers()]\n            else:\n                buffers = [b.data for b in self.module.buffers()]\n            if len(buffers) > 0:\n                self._dist_broadcast_coalesced(buffers,\n                                               self.broadcast_bucket_size)\n\n    def scatter(self, inputs, kwargs, device_ids):\n        return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim)\n\n    def forward(self, *inputs, **kwargs):\n        inputs, kwargs = self.scatter(inputs, kwargs,\n                                      [torch.cuda.current_device()])\n        return self.module(*inputs[0], **kwargs[0])\n\n    def train_step(self, *inputs, **kwargs):\n        inputs, kwargs = self.scatter(inputs, kwargs,\n                                      [torch.cuda.current_device()])\n        output = self.module.train_step(*inputs[0], **kwargs[0])\n        return output\n\n    def val_step(self, *inputs, **kwargs):\n        inputs, kwargs = self.scatter(inputs, kwargs,\n                                      [torch.cuda.current_device()])\n        output = self.module.val_step(*inputs[0], **kwargs[0])\n        return output\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/registry.py",
    "content": "from torch.nn.parallel import DataParallel, DistributedDataParallel\n\nfrom mmcv.utils import Registry\n\nMODULE_WRAPPERS = Registry('module wrapper')\nMODULE_WRAPPERS.register_module(module=DataParallel)\nMODULE_WRAPPERS.register_module(module=DistributedDataParallel)\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/scatter_gather.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport torch\nfrom torch.nn.parallel._functions import Scatter as OrigScatter\n\nfrom ._functions import Scatter\nfrom .data_container import DataContainer\n\n\ndef scatter(inputs, target_gpus, dim=0):\n    \"\"\"Scatter inputs to target gpus.\n\n    The only difference from original :func:`scatter` is to add support for\n    :type:`~mmcv.parallel.DataContainer`.\n    \"\"\"\n\n    def scatter_map(obj):\n        if isinstance(obj, torch.Tensor):\n            return OrigScatter.apply(target_gpus, None, dim, obj)\n        if isinstance(obj, DataContainer):\n            if obj.cpu_only:\n                return obj.data\n            else:\n                return Scatter.forward(target_gpus, obj.data)\n        if isinstance(obj, tuple) and len(obj) > 0:\n            return list(zip(*map(scatter_map, obj)))\n        if isinstance(obj, list) and len(obj) > 0:\n            out = list(map(list, zip(*map(scatter_map, obj))))\n            return out\n        if isinstance(obj, dict) and len(obj) > 0:\n            out = list(map(type(obj), zip(*map(scatter_map, obj.items()))))\n            return out\n        return [obj for targets in target_gpus]\n\n    # After scatter_map is called, a scatter_map cell will exist. This cell\n    # has a reference to the actual function scatter_map, which has references\n    # to a closure that has a reference to the scatter_map cell (because the\n    # fn is recursive). To avoid this reference cycle, we set the function to\n    # None, clearing the cell\n    try:\n        return scatter_map(inputs)\n    finally:\n        scatter_map = None\n\n\ndef scatter_kwargs(inputs, kwargs, target_gpus, dim=0):\n    \"\"\"Scatter with support for kwargs dictionary\"\"\"\n    inputs = scatter(inputs, target_gpus, dim) if inputs else []\n    kwargs = scatter(kwargs, target_gpus, dim) if kwargs else []\n    if len(inputs) < len(kwargs):\n        inputs.extend([() for _ in range(len(kwargs) - len(inputs))])\n    elif len(kwargs) < len(inputs):\n        kwargs.extend([{} for _ in range(len(inputs) - len(kwargs))])\n    inputs = tuple(inputs)\n    kwargs = tuple(kwargs)\n    return inputs, kwargs\n"
  },
  {
    "path": "code/mmcv/mmcv/parallel/utils.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .registry import MODULE_WRAPPERS\n\n\ndef is_module_wrapper(module):\n    \"\"\"Check if a module is a module wrapper.\n\n    The following 3 modules in MMCV (and their subclasses) are regarded as\n    module wrappers: DataParallel, DistributedDataParallel,\n    MMDistributedDataParallel (the deprecated version). You may add you own\n    module wrapper by registering it to mmcv.parallel.MODULE_WRAPPERS.\n\n    Args:\n        module (nn.Module): The module to be checked.\n\n    Returns:\n        bool: True if the input module is a module wrapper.\n    \"\"\"\n    module_wrappers = tuple(MODULE_WRAPPERS.module_dict.values())\n    return isinstance(module, module_wrappers)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .base_runner import BaseRunner\nfrom .checkpoint import (_load_checkpoint, load_checkpoint, load_state_dict,\n                         save_checkpoint, weights_to_cpu)\nfrom .dist_utils import get_dist_info, init_dist, master_only\nfrom .epoch_based_runner import EpochBasedRunner, Runner\nfrom .hooks import (HOOKS, CheckpointHook, ClosureHook, DistSamplerSeedHook,\n                    Hook, IterTimerHook, LoggerHook, LrUpdaterHook,\n                    MlflowLoggerHook, OptimizerHook, PaviLoggerHook,\n                    TensorboardLoggerHook, TextLoggerHook, WandbLoggerHook)\nfrom .iter_based_runner import IterBasedRunner, IterLoader\nfrom .log_buffer import LogBuffer\nfrom .optimizer import (OPTIMIZER_BUILDERS, OPTIMIZERS,\n                        DefaultOptimizerConstructor, build_optimizer,\n                        build_optimizer_constructor)\nfrom .priority import Priority, get_priority\nfrom .utils import get_host_info, get_time_str, obj_from_dict\n\n__all__ = [\n    'BaseRunner', 'Runner', 'EpochBasedRunner', 'IterBasedRunner', 'LogBuffer',\n    'HOOKS', 'Hook', 'CheckpointHook', 'ClosureHook', 'LrUpdaterHook',\n    'OptimizerHook', 'IterTimerHook', 'DistSamplerSeedHook', 'LoggerHook',\n    'PaviLoggerHook', 'TextLoggerHook', 'TensorboardLoggerHook',\n    'WandbLoggerHook', 'MlflowLoggerHook', '_load_checkpoint',\n    'load_state_dict', 'load_checkpoint', 'weights_to_cpu', 'save_checkpoint',\n    'Priority', 'get_priority', 'get_host_info', 'get_time_str',\n    'obj_from_dict', 'init_dist', 'get_dist_info', 'master_only',\n    'OPTIMIZER_BUILDERS', 'OPTIMIZERS', 'DefaultOptimizerConstructor',\n    'build_optimizer', 'build_optimizer_constructor', 'IterLoader',\n    'IterBasedRunner'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/base_runner.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport logging\nimport os.path as osp\nimport warnings\nfrom abc import ABCMeta, abstractmethod\n\nimport torch\nfrom torch.optim import Optimizer\n\nimport mmcv\nfrom ..parallel import is_module_wrapper\nfrom .checkpoint import load_checkpoint\nfrom .dist_utils import get_dist_info\nfrom .hooks import HOOKS, Hook, IterTimerHook\nfrom .log_buffer import LogBuffer\nfrom .priority import get_priority\nfrom .utils import get_time_str\n\n\nclass BaseRunner(metaclass=ABCMeta):\n    \"\"\"The base class of Runner, a training helper for PyTorch.\n\n    All subclasses should implement the following APIs:\n\n    - ``run()``\n    - ``train()``\n    - ``val()``\n    - ``save_checkpoint()``\n\n    Args:\n        model (:obj:`torch.nn.Module`): The model to be run.\n        batch_processor (callable): A callable method that process a data\n            batch. The interface of this method should be\n            `batch_processor(model, data, train_mode) -> dict`\n        optimizer (dict or :obj:`torch.optim.Optimizer`): It can be either an\n            optimizer (in most cases) or a dict of optimizers (in models that\n            requires more than one optimizer, e.g., GAN).\n        work_dir (str, optional): The working directory to save checkpoints\n            and logs. Defaults to None.\n        logger (:obj:`logging.Logger`): Logger used during training.\n             Defaults to None. (The default value is just for backward\n             compatibility)\n        meta (dict | None): A dict records some import information such as\n            environment info and seed, which will be logged in logger hook.\n            Defaults to None.\n    \"\"\"\n\n    def __init__(self,\n                 model,\n                 batch_processor=None,\n                 optimizer=None,\n                 work_dir=None,\n                 logger=None,\n                 meta=None):\n        if batch_processor is not None:\n            if not callable(batch_processor):\n                raise TypeError('batch_processor must be callable, '\n                                f'but got {type(batch_processor)}')\n            warnings.warn('batch_processor is deprecated, please implement '\n                          'train_step() and val_step() in the model instead.')\n            # raise an error is `batch_processor` is not None and\n            # `model.train_step()` exists.\n            if is_module_wrapper(model):\n                _model = model.module\n            else:\n                _model = model\n            if hasattr(_model, 'train_step') or hasattr(_model, 'val_step'):\n                raise RuntimeError(\n                    'batch_processor and model.train_step()/model.val_step() '\n                    'cannot be both available.')\n        else:\n            assert hasattr(model, 'train_step')\n\n        # check the type of `optimizer`\n        if isinstance(optimizer, dict):\n            for name, optim in optimizer.items():\n                if not isinstance(optim, Optimizer):\n                    raise TypeError(\n                        f'optimizer must be a dict of torch.optim.Optimizers, '\n                        f'but optimizer[\"{name}\"] is a {type(optim)}')\n        elif not isinstance(optimizer, Optimizer) and optimizer is not None:\n            raise TypeError(\n                f'optimizer must be a torch.optim.Optimizer object '\n                f'or dict or None, but got {type(optimizer)}')\n\n        # check the type of `logger`\n        if not isinstance(logger, logging.Logger):\n            raise TypeError(f'logger must be a logging.Logger object, '\n                            f'but got {type(logger)}')\n\n        # check the type of `meta`\n        if meta is not None and not isinstance(meta, dict):\n            raise TypeError(\n                f'meta must be a dict or None, but got {type(meta)}')\n\n        self.model = model\n        self.batch_processor = batch_processor\n        self.optimizer = optimizer\n        self.logger = logger\n        self.meta = meta\n\n        # create work_dir\n        if mmcv.is_str(work_dir):\n            self.work_dir = osp.abspath(work_dir)\n            mmcv.mkdir_or_exist(self.work_dir)\n        elif work_dir is None:\n            self.work_dir = None\n        else:\n            raise TypeError('\"work_dir\" must be a str or None')\n\n        # get model name from the model class\n        if hasattr(self.model, 'module'):\n            self._model_name = self.model.module.__class__.__name__\n        else:\n            self._model_name = self.model.__class__.__name__\n\n        self._rank, self._world_size = get_dist_info()\n        self.timestamp = get_time_str()\n        self.mode = None\n        self._hooks = []\n        self._epoch = 0\n        self._iter = 0\n        self._inner_iter = 0\n        self._max_epochs = 0\n        self._max_iters = 0\n        # TODO: Redesign LogBuffer, it is not flexible and elegant enough\n        self.log_buffer = LogBuffer()\n\n    @property\n    def model_name(self):\n        \"\"\"str: Name of the model, usually the module class name.\"\"\"\n        return self._model_name\n\n    @property\n    def rank(self):\n        \"\"\"int: Rank of current process. (distributed training)\"\"\"\n        return self._rank\n\n    @property\n    def world_size(self):\n        \"\"\"int: Number of processes participating in the job.\n        (distributed training)\"\"\"\n        return self._world_size\n\n    @property\n    def hooks(self):\n        \"\"\"list[:obj:`Hook`]: A list of registered hooks.\"\"\"\n        return self._hooks\n\n    @property\n    def epoch(self):\n        \"\"\"int: Current epoch.\"\"\"\n        return self._epoch\n\n    @property\n    def iter(self):\n        \"\"\"int: Current iteration.\"\"\"\n        return self._iter\n\n    @property\n    def inner_iter(self):\n        \"\"\"int: Iteration in an epoch.\"\"\"\n        return self._inner_iter\n\n    @property\n    def max_epochs(self):\n        \"\"\"int: Maximum training epochs.\"\"\"\n        return self._max_epochs\n\n    @property\n    def max_iters(self):\n        \"\"\"int: Maximum training iterations.\"\"\"\n        return self._max_iters\n\n    @abstractmethod\n    def train(self):\n        pass\n\n    @abstractmethod\n    def val(self):\n        pass\n\n    @abstractmethod\n    def run(self, data_loaders, workflow, **kwargs):\n        pass\n\n    @abstractmethod\n    def save_checkpoint(self,\n                        out_dir,\n                        filename_tmpl,\n                        save_optimizer=True,\n                        meta=None,\n                        create_symlink=True):\n        pass\n\n    def current_lr(self):\n        \"\"\"Get current learning rates.\n\n        Returns:\n            list[float] | dict[str, list[float]]: Current learning rates of all\n                param groups. If the runner has a dict of optimizers, this\n                method will return a dict.\n        \"\"\"\n        if isinstance(self.optimizer, torch.optim.Optimizer):\n            lr = [group['lr'] for group in self.optimizer.param_groups]\n        elif isinstance(self.optimizer, dict):\n            lr = dict()\n            for name, optim in self.optimizer.items():\n                lr[name] = [group['lr'] for group in optim.param_groups]\n        else:\n            raise RuntimeError(\n                'lr is not applicable because optimizer does not exist.')\n        return lr\n\n    def current_momentum(self):\n        \"\"\"Get current momentums.\n\n        Returns:\n            list[float] | dict[str, list[float]]: Current momentums of all\n                param groups. If the runner has a dict of optimizers, this\n                method will return a dict.\n        \"\"\"\n\n        def _get_momentum(optimizer):\n            momentums = []\n            for group in optimizer.param_groups:\n                if 'momentum' in group.keys():\n                    momentums.append(group['momentum'])\n                elif 'betas' in group.keys():\n                    momentums.append(group['betas'][0])\n                else:\n                    momentums.append(0)\n            return momentums\n\n        if self.optimizer is None:\n            raise RuntimeError(\n                'momentum is not applicable because optimizer does not exist.')\n        elif isinstance(self.optimizer, torch.optim.Optimizer):\n            momentums = _get_momentum(self.optimizer)\n        elif isinstance(self.optimizer, dict):\n            momentums = dict()\n            for name, optim in self.optimizer.items():\n                momentums[name] = _get_momentum(optim)\n        return momentums\n\n    def register_hook(self, hook, priority='NORMAL'):\n        \"\"\"Register a hook into the hook list.\n\n        The hook will be inserted into a priority queue, with the specified\n        priority (See :cls:`Priority` for details of priorities).\n        For hooks with the same priority, they will be triggered in the same\n        order as they are registered.\n\n        Args:\n            hook (:obj:`Hook`): The hook to be registered.\n            priority (int or str or :obj:`Priority`): Hook priority.\n                Lower value means higher priority.\n        \"\"\"\n        assert isinstance(hook, Hook)\n        if hasattr(hook, 'priority'):\n            raise ValueError('\"priority\" is a reserved attribute for hooks')\n        priority = get_priority(priority)\n        hook.priority = priority\n        # insert the hook to a sorted list\n        inserted = False\n        for i in range(len(self._hooks) - 1, -1, -1):\n            if priority >= self._hooks[i].priority:\n                self._hooks.insert(i + 1, hook)\n                inserted = True\n                break\n        if not inserted:\n            self._hooks.insert(0, hook)\n\n    def call_hook(self, fn_name):\n        \"\"\"Call all hooks.\n\n        Args:\n            fn_name (str): The function name in each hook to be called, such as\n                \"before_train_epoch\".\n        \"\"\"\n        for hook in self._hooks:\n            getattr(hook, fn_name)(self)\n\n    def load_checkpoint(self, filename, map_location='cpu', strict=False):\n        self.logger.info('load checkpoint from %s', filename)\n        return load_checkpoint(self.model, filename, map_location, strict,\n                               self.logger)\n\n    def resume(self,\n               checkpoint,\n               resume_optimizer=True,\n               map_location='default'):\n        if map_location == 'default':\n            device_id = torch.cuda.current_device()\n            checkpoint = self.load_checkpoint(\n                checkpoint,\n                map_location=lambda storage, loc: storage.cuda(device_id))\n        else:\n            checkpoint = self.load_checkpoint(\n                checkpoint, map_location=map_location)\n\n        self._epoch = checkpoint['meta']['epoch']\n        self._iter = checkpoint['meta']['iter']\n        if 'optimizer' in checkpoint and resume_optimizer:\n            self.optimizer.load_state_dict(checkpoint['optimizer'])\n\n        self.logger.info('resumed epoch %d, iter %d', self.epoch, self.iter)\n\n    def register_lr_hook(self, lr_config):\n        if isinstance(lr_config, dict):\n            assert 'policy' in lr_config\n            policy_type = lr_config.pop('policy')\n            # If the type of policy is all in lower case, e.g., 'cyclic',\n            # then its first letter will be capitalized, e.g., to be 'Cyclic'.\n            # This is for the convenient usage of Lr updater.\n            # Since this is not applicable for `CosineAnealingLrUpdater`,\n            # the string will not be changed if it contains capital letters.\n            if policy_type == policy_type.lower():\n                policy_type = policy_type.title()\n            hook_type = policy_type + 'LrUpdaterHook'\n            lr_config['type'] = hook_type\n            hook = mmcv.build_from_cfg(lr_config, HOOKS)\n        else:\n            hook = lr_config\n        self.register_hook(hook)\n\n    def register_momentum_hook(self, momentum_config):\n        if momentum_config is None:\n            return\n        if isinstance(momentum_config, dict):\n            assert 'policy' in momentum_config\n            policy_type = momentum_config.pop('policy')\n            # If the type of policy is all in lower case, e.g., 'cyclic',\n            # then its first letter will be capitalized, e.g., to be 'Cyclic'.\n            # This is for the convenient usage of momentum updater.\n            # Since this is not applicable for `CosineAnealingMomentumUpdater`,\n            # the string will not be changed if it contains capital letters.\n            if policy_type == policy_type.lower():\n                policy_type = policy_type.title()\n            hook_type = policy_type + 'MomentumUpdaterHook'\n            momentum_config['type'] = hook_type\n            hook = mmcv.build_from_cfg(momentum_config, HOOKS)\n        else:\n            hook = momentum_config\n        self.register_hook(hook)\n\n    def register_optimizer_hook(self, optimizer_config):\n        if optimizer_config is None:\n            return\n        if isinstance(optimizer_config, dict):\n            optimizer_config.setdefault('type', 'OptimizerHook')\n            hook = mmcv.build_from_cfg(optimizer_config, HOOKS)\n        else:\n            hook = optimizer_config\n        self.register_hook(hook)\n\n    def register_checkpoint_hook(self, checkpoint_config):\n        if checkpoint_config is None:\n            return\n        if isinstance(checkpoint_config, dict):\n            checkpoint_config.setdefault('type', 'CheckpointHook')\n            hook = mmcv.build_from_cfg(checkpoint_config, HOOKS)\n        else:\n            hook = checkpoint_config\n        self.register_hook(hook)\n\n    def register_logger_hooks(self, log_config):\n        log_interval = log_config['interval']\n        for info in log_config['hooks']:\n            logger_hook = mmcv.build_from_cfg(\n                info, HOOKS, default_args=dict(interval=log_interval))\n            self.register_hook(logger_hook, priority='VERY_LOW')\n\n    def register_training_hooks(self,\n                                lr_config,\n                                optimizer_config=None,\n                                checkpoint_config=None,\n                                log_config=None,\n                                momentum_config=None):\n        \"\"\"Register default hooks for training.\n\n        Default hooks include:\n\n        - LrUpdaterHook\n        - MomentumUpdaterHook\n        - OptimizerStepperHook\n        - CheckpointSaverHook\n        - IterTimerHook\n        - LoggerHook(s)\n        \"\"\"\n        self.register_lr_hook(lr_config)\n        self.register_momentum_hook(momentum_config)\n        self.register_optimizer_hook(optimizer_config)\n        self.register_checkpoint_hook(checkpoint_config)\n        self.register_hook(IterTimerHook())\n        self.register_logger_hooks(log_config)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/checkpoint.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport pkgutil\nimport time\nimport warnings\nfrom collections import OrderedDict\nfrom importlib import import_module\n\nimport torch\nimport torchvision\nfrom torch.optim import Optimizer\nfrom torch.utils import model_zoo\n\nimport mmcv\nfrom ..fileio import load as load_file\nfrom ..parallel import is_module_wrapper\nfrom ..utils import mkdir_or_exist\nfrom .dist_utils import get_dist_info\n\nENV_MMCV_HOME = 'MMCV_HOME'\nENV_XDG_CACHE_HOME = 'XDG_CACHE_HOME'\nDEFAULT_CACHE_DIR = '~/.cache'\n\n\ndef _get_mmcv_home():\n    mmcv_home = os.path.expanduser(\n        os.getenv(\n            ENV_MMCV_HOME,\n            os.path.join(\n                os.getenv(ENV_XDG_CACHE_HOME, DEFAULT_CACHE_DIR), 'mmcv')))\n\n    mkdir_or_exist(mmcv_home)\n    return mmcv_home\n\n\ndef load_state_dict(module, state_dict, strict=False, logger=None):\n    \"\"\"Load state_dict to a module.\n\n    This method is modified from :meth:`torch.nn.Module.load_state_dict`.\n    Default value for ``strict`` is set to ``False`` and the message for\n    param mismatch will be shown even if strict is False.\n\n    Args:\n        module (Module): Module that receives the state_dict.\n        state_dict (OrderedDict): Weights.\n        strict (bool): whether to strictly enforce that the keys\n            in :attr:`state_dict` match the keys returned by this module's\n            :meth:`~torch.nn.Module.state_dict` function. Default: ``False``.\n        logger (:obj:`logging.Logger`, optional): Logger to log the error\n            message. If not specified, print function will be used.\n    \"\"\"\n    unexpected_keys = []\n    all_missing_keys = []\n    err_msg = []\n\n    metadata = getattr(state_dict, '_metadata', None)\n    state_dict = state_dict.copy()\n    if metadata is not None:\n        state_dict._metadata = metadata\n\n    # use _load_from_state_dict to enable checkpoint version control\n    def load(module, prefix=''):\n        # recursively check parallel module in case that the model has a\n        # complicated structure, e.g., nn.Module(nn.Module(DDP))\n        if is_module_wrapper(module):\n            module = module.module\n        local_metadata = {} if metadata is None else metadata.get(\n            prefix[:-1], {})\n        module._load_from_state_dict(state_dict, prefix, local_metadata, True,\n                                     all_missing_keys, unexpected_keys,\n                                     err_msg)\n        for name, child in module._modules.items():\n            if child is not None:\n                load(child, prefix + name + '.')\n\n    load(module)\n    load = None  # break load->load reference cycle\n\n    # ignore \"num_batches_tracked\" of BN layers\n    missing_keys = [\n        key for key in all_missing_keys if 'num_batches_tracked' not in key\n    ]\n\n    if unexpected_keys:\n        err_msg.append('unexpected key in source '\n                       f'state_dict: {\", \".join(unexpected_keys)}\\n')\n    if missing_keys:\n        err_msg.append(\n            f'missing keys in source state_dict: {\", \".join(missing_keys)}\\n')\n\n    rank, _ = get_dist_info()\n    if len(err_msg) > 0 and rank == 0:\n        err_msg.insert(\n            0, 'The model and loaded state dict do not match exactly\\n')\n        err_msg = '\\n'.join(err_msg)\n        if strict:\n            raise RuntimeError(err_msg)\n        elif logger is not None:\n            logger.warning(err_msg)\n        else:\n            print(err_msg)\n\n\ndef load_url_dist(url, model_dir=None):\n    \"\"\" In distributed setting, this function only download checkpoint at\n    local rank 0 \"\"\"\n    rank, world_size = get_dist_info()\n    rank = int(os.environ.get('LOCAL_RANK', rank))\n    if rank == 0:\n        checkpoint = model_zoo.load_url(url, model_dir=model_dir)\n    if world_size > 1:\n        torch.distributed.barrier()\n        if rank > 0:\n            checkpoint = model_zoo.load_url(url, model_dir=model_dir)\n    return checkpoint\n\n\ndef get_torchvision_models():\n    model_urls = dict()\n    for _, name, ispkg in pkgutil.walk_packages(torchvision.models.__path__):\n        if ispkg:\n            continue\n        _zoo = import_module(f'torchvision.models.{name}')\n        if hasattr(_zoo, 'model_urls'):\n            _urls = getattr(_zoo, 'model_urls')\n            model_urls.update(_urls)\n    return model_urls\n\n\ndef get_external_models():\n    mmcv_home = _get_mmcv_home()\n    default_json_path = osp.join(mmcv.__path__[0], 'model_zoo/open_mmlab.json')\n    default_urls = load_file(default_json_path)\n    assert isinstance(default_urls, dict)\n    external_json_path = osp.join(mmcv_home, 'open_mmlab.json')\n    if osp.exists(external_json_path):\n        external_urls = load_file(external_json_path)\n        assert isinstance(external_urls, dict)\n        default_urls.update(external_urls)\n\n    return default_urls\n\n\ndef get_deprecated_model_names():\n    deprecate_json_path = osp.join(mmcv.__path__[0],\n                                   'model_zoo/deprecated.json')\n    deprecate_urls = load_file(deprecate_json_path)\n    assert isinstance(deprecate_urls, dict)\n\n    return deprecate_urls\n\n\ndef _load_checkpoint(filename, map_location=None):\n    \"\"\"Load checkpoint from somewhere (modelzoo, file, url).\n\n    Args:\n        filename (str): Accept local filepath, URL, ``torchvision://xxx``,\n            ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for\n            details.\n        map_location (str | None): Same as :func:`torch.load`. Default: None.\n\n    Returns:\n        dict | OrderedDict: The loaded checkpoint. It can be either an\n            OrderedDict storing model weights or a dict containing other\n            information, which depends on the checkpoint.\n    \"\"\"\n    if filename.startswith('modelzoo://'):\n        warnings.warn('The URL scheme of \"modelzoo://\" is deprecated, please '\n                      'use \"torchvision://\" instead')\n        model_urls = get_torchvision_models()\n        model_name = filename[11:]\n        checkpoint = load_url_dist(model_urls[model_name])\n    elif filename.startswith('torchvision://'):\n        model_urls = get_torchvision_models()\n        model_name = filename[14:]\n        checkpoint = load_url_dist(model_urls[model_name])\n    elif filename.startswith('open-mmlab://'):\n        model_urls = get_external_models()\n        model_name = filename[13:]\n        deprecated_urls = get_deprecated_model_names()\n        if model_name in deprecated_urls:\n            warnings.warn(f'open-mmlab://{model_name} is deprecated in favor '\n                          f'of open-mmlab://{deprecated_urls[model_name]}')\n            model_name = deprecated_urls[model_name]\n        model_url = model_urls[model_name]\n        # check if is url\n        if model_url.startswith(('http://', 'https://')):\n            checkpoint = load_url_dist(model_url)\n        else:\n            filename = osp.join(_get_mmcv_home(), model_url)\n            if not osp.isfile(filename):\n                raise IOError(f'{filename} is not a checkpoint file')\n            checkpoint = torch.load(filename, map_location=map_location)\n    elif filename.startswith(('http://', 'https://')):\n        checkpoint = load_url_dist(filename)\n    else:\n        if not osp.isfile(filename):\n            raise IOError(f'{filename} is not a checkpoint file')\n        checkpoint = torch.load(filename, map_location=map_location)\n    return checkpoint\n\n\ndef load_checkpoint(model,\n                    filename,\n                    map_location=None,\n                    strict=False,\n                    logger=None):\n    \"\"\"Load checkpoint from a file or URI.\n\n    Args:\n        model (Module): Module to load checkpoint.\n        filename (str): Accept local filepath, URL, ``torchvision://xxx``,\n            ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for\n            details.\n        map_location (str): Same as :func:`torch.load`.\n        strict (bool): Whether to allow different params for the model and\n            checkpoint.\n        logger (:mod:`logging.Logger` or None): The logger for error message.\n\n    Returns:\n        dict or OrderedDict: The loaded checkpoint.\n    \"\"\"\n    checkpoint = _load_checkpoint(filename, map_location)\n    # OrderedDict is a subclass of dict\n    if not isinstance(checkpoint, dict):\n        raise RuntimeError(\n            f'No state_dict found in checkpoint file {filename}')\n    # get state_dict from checkpoint\n    if 'state_dict' in checkpoint:\n        state_dict = checkpoint['state_dict']\n    else:\n        state_dict = checkpoint\n    # strip prefix of state_dict\n    if list(state_dict.keys())[0].startswith('module.'):\n        state_dict = {k[7:]: v for k, v in checkpoint['state_dict'].items()}\n    # load state_dict\n    load_state_dict(model, state_dict, strict, logger)\n    return checkpoint\n\n\ndef weights_to_cpu(state_dict):\n    \"\"\"Copy a model state_dict to cpu.\n\n    Args:\n        state_dict (OrderedDict): Model weights on GPU.\n\n    Returns:\n        OrderedDict: Model weights on GPU.\n    \"\"\"\n    state_dict_cpu = OrderedDict()\n    for key, val in state_dict.items():\n        state_dict_cpu[key] = val.cpu()\n    return state_dict_cpu\n\n\ndef save_checkpoint(model, filename, optimizer=None, meta=None):\n    \"\"\"Save checkpoint to file.\n\n    The checkpoint will have 3 fields: ``meta``, ``state_dict`` and\n    ``optimizer``. By default ``meta`` will contain version and time info.\n\n    Args:\n        model (Module): Module whose params are to be saved.\n        filename (str): Checkpoint filename.\n        optimizer (:obj:`Optimizer`, optional): Optimizer to be saved.\n        meta (dict, optional): Metadata to be saved in checkpoint.\n    \"\"\"\n    if meta is None:\n        meta = {}\n    elif not isinstance(meta, dict):\n        raise TypeError(f'meta must be a dict or None, but got {type(meta)}')\n    meta.update(mmcv_version=mmcv.__version__, time=time.asctime())\n\n    mmcv.mkdir_or_exist(osp.dirname(filename))\n    if is_module_wrapper(model):\n        model = model.module\n\n    checkpoint = {\n        'meta': meta,\n        'state_dict': weights_to_cpu(model.state_dict())\n    }\n    # save optimizer state dict in the checkpoint\n    if isinstance(optimizer, Optimizer):\n        checkpoint['optimizer'] = optimizer.state_dict()\n    elif isinstance(optimizer, dict):\n        checkpoint['optimizer'] = {}\n        for name, optim in optimizer.items():\n            checkpoint['optimizer'][name] = optim.state_dict()\n    # immediately flush buffer\n    with open(filename, 'wb') as f:\n        torch.save(checkpoint, f)\n        f.flush()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/dist_utils.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport functools\nimport os\nimport subprocess\n\nimport torch\nimport torch.distributed as dist\nimport torch.multiprocessing as mp\n\nfrom mmcv.utils import TORCH_VERSION\n\n\ndef init_dist(launcher, backend='nccl', **kwargs):\n    if mp.get_start_method(allow_none=True) is None:\n        mp.set_start_method('spawn')\n    if launcher == 'pytorch':\n        _init_dist_pytorch(backend, **kwargs)\n    elif launcher == 'mpi':\n        _init_dist_mpi(backend, **kwargs)\n    elif launcher == 'slurm':\n        _init_dist_slurm(backend, **kwargs)\n    else:\n        raise ValueError(f'Invalid launcher type: {launcher}')\n\n\ndef _init_dist_pytorch(backend, **kwargs):\n    # TODO: use local_rank instead of rank % num_gpus\n    rank = int(os.environ['RANK'])\n    num_gpus = torch.cuda.device_count()\n    torch.cuda.set_device(rank % num_gpus)\n    dist.init_process_group(backend=backend, **kwargs)\n\n\ndef _init_dist_mpi(backend, **kwargs):\n    raise NotImplementedError\n\n\ndef _init_dist_slurm(backend, port=None):\n    \"\"\"Initialize slurm distributed training environment.\n\n    If argument ``port`` is not specified, then the master port will be system\n    environment variable ``MASTER_PORT``. If ``MASTER_PORT`` is not in system\n    environment variable, then a default port ``29500`` will be used.\n\n    Args:\n        backend (str): Backend of torch.distributed.\n        port (int, optional): Master port. Defaults to None.\n    \"\"\"\n    proc_id = int(os.environ['SLURM_PROCID'])\n    ntasks = int(os.environ['SLURM_NTASKS'])\n    node_list = os.environ['SLURM_NODELIST']\n    num_gpus = torch.cuda.device_count()\n    torch.cuda.set_device(proc_id % num_gpus)\n    addr = subprocess.getoutput(\n        f'scontrol show hostname {node_list} | head -n1')\n    # specify master port\n    if port is not None:\n        os.environ['MASTER_PORT'] = str(port)\n    elif 'MASTER_PORT' in os.environ:\n        pass  # use MASTER_PORT in the environment variable\n    else:\n        # 29500 is torch.distributed default port\n        os.environ['MASTER_PORT'] = '29500'\n    os.environ['MASTER_ADDR'] = addr\n    os.environ['WORLD_SIZE'] = str(ntasks)\n    os.environ['RANK'] = str(proc_id)\n    dist.init_process_group(backend=backend)\n\n\ndef get_dist_info():\n    if TORCH_VERSION < '1.0':\n        initialized = dist._initialized\n    else:\n        if dist.is_available():\n            initialized = dist.is_initialized()\n        else:\n            initialized = False\n    if initialized:\n        rank = dist.get_rank()\n        world_size = dist.get_world_size()\n    else:\n        rank = 0\n        world_size = 1\n    return rank, world_size\n\n\ndef master_only(func):\n\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        rank, _ = get_dist_info()\n        if rank == 0:\n            return func(*args, **kwargs)\n\n    return wrapper\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/epoch_based_runner.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\nimport time\nimport warnings\n\nimport torch\n\nimport mmcv\nfrom .base_runner import BaseRunner\nfrom .checkpoint import save_checkpoint\nfrom .utils import get_host_info\n\n\nclass EpochBasedRunner(BaseRunner):\n    \"\"\"Epoch-based Runner.\n\n    This runner train models epoch by epoch.\n    \"\"\"\n\n    def train(self, data_loader, **kwargs):\n        self.model.train()\n        self.mode = 'train'\n        self.data_loader = data_loader\n        self._max_iters = self._max_epochs * len(data_loader)\n        self.call_hook('before_train_epoch')\n        time.sleep(2)  # Prevent possible deadlock during epoch transition\n        for i, data_batch in enumerate(data_loader):\n            self._inner_iter = i\n            self.call_hook('before_train_iter')\n            if self.batch_processor is None:\n                outputs = self.model.train_step(data_batch, self.optimizer,\n                                                **kwargs)\n            else:\n                outputs = self.batch_processor(\n                    self.model, data_batch, train_mode=True, **kwargs)\n            if not isinstance(outputs, dict):\n                raise TypeError('\"batch_processor()\" or \"model.train_step()\"'\n                                ' must return a dict')\n            if 'log_vars' in outputs:\n                self.log_buffer.update(outputs['log_vars'],\n                                       outputs['num_samples'])\n            self.outputs = outputs\n            self.call_hook('after_train_iter')\n            self._iter += 1\n\n        self.call_hook('after_train_epoch')\n        self._epoch += 1\n\n    def val(self, data_loader, **kwargs):\n        self.model.eval()\n        self.mode = 'val'\n        self.data_loader = data_loader\n        self.call_hook('before_val_epoch')\n        time.sleep(2)  # Prevent possible deadlock during epoch transition\n        for i, data_batch in enumerate(data_loader):\n            self._inner_iter = i\n            self.call_hook('before_val_iter')\n            with torch.no_grad():\n                if self.batch_processor is None:\n                    outputs = self.model.val_step(data_batch, self.optimizer,\n                                                  **kwargs)\n                else:\n                    outputs = self.batch_processor(\n                        self.model, data_batch, train_mode=False, **kwargs)\n            if not isinstance(outputs, dict):\n                raise TypeError('\"batch_processor()\" or \"model.val_step()\"'\n                                ' must return a dict')\n            if 'log_vars' in outputs:\n                self.log_buffer.update(outputs['log_vars'],\n                                       outputs['num_samples'])\n            self.outputs = outputs\n            self.call_hook('after_val_iter')\n\n        self.call_hook('after_val_epoch')\n\n    def run(self, data_loaders, workflow, max_epochs, **kwargs):\n        \"\"\"Start running.\n\n        Args:\n            data_loaders (list[:obj:`DataLoader`]): Dataloaders for training\n                and validation.\n            workflow (list[tuple]): A list of (phase, epochs) to specify the\n                running order and epochs. E.g, [('train', 2), ('val', 1)] means\n                running 2 epochs for training and 1 epoch for validation,\n                iteratively.\n            max_epochs (int): Total training epochs.\n        \"\"\"\n        assert isinstance(data_loaders, list)\n        assert mmcv.is_list_of(workflow, tuple)\n        assert len(data_loaders) == len(workflow)\n\n        self._max_epochs = max_epochs\n        for i, flow in enumerate(workflow):\n            mode, epochs = flow\n            if mode == 'train':\n                self._max_iters = self._max_epochs * len(data_loaders[i])\n                break\n\n        work_dir = self.work_dir if self.work_dir is not None else 'NONE'\n        self.logger.info('Start running, host: %s, work_dir: %s',\n                         get_host_info(), work_dir)\n        self.logger.info('workflow: %s, max: %d epochs', workflow, max_epochs)\n        self.call_hook('before_run')\n\n        while self.epoch < max_epochs:\n            for i, flow in enumerate(workflow):\n                mode, epochs = flow\n                if isinstance(mode, str):  # self.train()\n                    if not hasattr(self, mode):\n                        raise ValueError(\n                            f'runner has no method named \"{mode}\" to run an '\n                            'epoch')\n                    epoch_runner = getattr(self, mode)\n                else:\n                    raise TypeError(\n                        'mode in workflow must be a str, but got {}'.format(\n                            type(mode)))\n\n                for _ in range(epochs):\n                    if mode == 'train' and self.epoch >= max_epochs:\n                        return\n                    epoch_runner(data_loaders[i], **kwargs)\n\n        time.sleep(1)  # wait for some hooks like loggers to finish\n        self.call_hook('after_run')\n\n    def save_checkpoint(self,\n                        out_dir,\n                        filename_tmpl='epoch_{}.pth',\n                        save_optimizer=True,\n                        meta=None,\n                        create_symlink=True):\n        \"\"\"Save the checkpoint.\n\n        Args:\n            out_dir (str): The directory that checkpoints are saved.\n            filename_tmpl (str, optional): The checkpoint filename template,\n                which contains a placeholder for the epoch number.\n                Defaults to 'epoch_{}.pth'.\n            save_optimizer (bool, optional): Whether to save the optimizer to\n                the checkpoint. Defaults to True.\n            meta (dict, optional): The meta information to be saved in the\n                checkpoint. Defaults to None.\n            create_symlink (bool, optional): Whether to create a symlink\n                \"latest.pth\" to point to the latest checkpoint.\n                Defaults to True.\n        \"\"\"\n        if meta is None:\n            meta = dict(epoch=self.epoch + 1, iter=self.iter)\n        else:\n            meta.update(epoch=self.epoch + 1, iter=self.iter)\n\n        filename = filename_tmpl.format(self.epoch + 1)\n        filepath = osp.join(out_dir, filename)\n        optimizer = self.optimizer if save_optimizer else None\n        save_checkpoint(self.model, filepath, optimizer=optimizer, meta=meta)\n        # in some environments, `os.symlink` is not supported, you may need to\n        # set `create_symlink` to False\n        if create_symlink:\n            mmcv.symlink(filename, osp.join(out_dir, 'latest.pth'))\n\n\nclass Runner(EpochBasedRunner):\n    \"\"\"Deprecated name of EpochBasedRunner\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        warnings.warn(\n            'Runner was deprecated, please use EpochBasedRunner instead')\n        super().__init__(*args, **kwargs)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .checkpoint import CheckpointHook\nfrom .closure import ClosureHook\nfrom .hook import HOOKS, Hook\nfrom .iter_timer import IterTimerHook\nfrom .logger import (LoggerHook, MlflowLoggerHook, PaviLoggerHook,\n                     TensorboardLoggerHook, TextLoggerHook, WandbLoggerHook)\nfrom .lr_updater import LrUpdaterHook\nfrom .memory import EmptyCacheHook\nfrom .momentum_updater import MomentumUpdaterHook\nfrom .optimizer import OptimizerHook\nfrom .sampler_seed import DistSamplerSeedHook\n\n__all__ = [\n    'HOOKS', 'Hook', 'CheckpointHook', 'ClosureHook', 'LrUpdaterHook',\n    'OptimizerHook', 'IterTimerHook', 'DistSamplerSeedHook', 'EmptyCacheHook',\n    'LoggerHook', 'MlflowLoggerHook', 'PaviLoggerHook', 'TextLoggerHook',\n    'TensorboardLoggerHook', 'WandbLoggerHook', 'MomentumUpdaterHook'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/checkpoint.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\n\nfrom ..dist_utils import master_only\nfrom .hook import HOOKS, Hook\n\n\n@HOOKS.register_module()\nclass CheckpointHook(Hook):\n    \"\"\"Save checkpoints periodically.\n\n    Args:\n        interval (int): The saving period. If ``by_epoch=True``, interval\n            indicates epochs, otherwise it indicates iterations.\n            Default: -1, which means \"never\".\n        by_epoch (bool): Saving checkpoints by epoch or by iteration.\n            Default: True.\n        save_optimizer (bool): Whether to save optimizer state_dict in the\n            checkpoint. It is usually used for resuming experiments.\n            Default: True.\n        out_dir (str, optional): The directory to save checkpoints. If not\n            specified, ``runner.work_dir`` will be used by default.\n        max_keep_ckpts (int, optional): The maximum checkpoints to keep.\n            In some cases we want only the latest few checkpoints and would\n            like to delete old ones to save the disk space.\n            Default: -1, which means unlimited.\n    \"\"\"\n\n    def __init__(self,\n                 interval=-1,\n                 by_epoch=True,\n                 save_optimizer=True,\n                 out_dir=None,\n                 max_keep_ckpts=-1,\n                 **kwargs):\n        self.interval = interval\n        self.by_epoch = by_epoch\n        self.save_optimizer = save_optimizer\n        self.out_dir = out_dir\n        self.max_keep_ckpts = max_keep_ckpts\n        self.args = kwargs\n\n    @master_only\n    def after_train_epoch(self, runner):\n        if not self.by_epoch or not self.every_n_epochs(runner, self.interval):\n            return\n\n        runner.logger.info(f'Saving checkpoint at {runner.epoch + 1} epochs')\n        if not self.out_dir:\n            self.out_dir = runner.work_dir\n        runner.save_checkpoint(\n            self.out_dir, save_optimizer=self.save_optimizer, **self.args)\n\n        # remove other checkpoints\n        if self.max_keep_ckpts > 0:\n            filename_tmpl = self.args.get('filename_tmpl', 'epoch_{}.pth')\n            current_epoch = runner.epoch + 1\n            for epoch in range(current_epoch - self.max_keep_ckpts, 0, -1):\n                ckpt_path = os.path.join(self.out_dir,\n                                         filename_tmpl.format(epoch))\n                if os.path.exists(ckpt_path):\n                    os.remove(ckpt_path)\n                else:\n                    break\n\n    @master_only\n    def after_train_iter(self, runner):\n        if self.by_epoch or not self.every_n_iters(runner, self.interval):\n            return\n\n        runner.logger.info(\n            f'Saving checkpoint at {runner.iter + 1} iterations')\n        if not self.out_dir:\n            self.out_dir = runner.work_dir\n        runner.save_checkpoint(\n            self.out_dir, save_optimizer=self.save_optimizer, **self.args)\n\n        # remove other checkpoints\n        if self.max_keep_ckpts > 0:\n            filename_tmpl = self.args.get('filename_tmpl', 'iter_{}.pth')\n            current_iter = runner.iter + 1\n            for _iter in range(\n                    current_iter - self.max_keep_ckpts * self.interval, 0,\n                    -self.interval):\n                ckpt_path = os.path.join(self.out_dir,\n                                         filename_tmpl.format(_iter))\n                if os.path.exists(ckpt_path):\n                    os.remove(ckpt_path)\n                else:\n                    break\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/closure.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .hook import HOOKS, Hook\n\n\n@HOOKS.register_module()\nclass ClosureHook(Hook):\n\n    def __init__(self, fn_name, fn):\n        assert hasattr(self, fn_name)\n        assert callable(fn)\n        setattr(self, fn_name, fn)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/hook.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom mmcv.utils import Registry\n\nHOOKS = Registry('hook')\n\n\nclass Hook:\n\n    def before_run(self, runner):\n        pass\n\n    def after_run(self, runner):\n        pass\n\n    def before_epoch(self, runner):\n        pass\n\n    def after_epoch(self, runner):\n        pass\n\n    def before_iter(self, runner):\n        pass\n\n    def after_iter(self, runner):\n        pass\n\n    def before_train_epoch(self, runner):\n        self.before_epoch(runner)\n\n    def before_val_epoch(self, runner):\n        self.before_epoch(runner)\n\n    def after_train_epoch(self, runner):\n        self.after_epoch(runner)\n\n    def after_val_epoch(self, runner):\n        self.after_epoch(runner)\n\n    def before_train_iter(self, runner):\n        self.before_iter(runner)\n\n    def before_val_iter(self, runner):\n        self.before_iter(runner)\n\n    def after_train_iter(self, runner):\n        self.after_iter(runner)\n\n    def after_val_iter(self, runner):\n        self.after_iter(runner)\n\n    def every_n_epochs(self, runner, n):\n        return (runner.epoch + 1) % n == 0 if n > 0 else False\n\n    def every_n_inner_iters(self, runner, n):\n        return (runner.inner_iter + 1) % n == 0 if n > 0 else False\n\n    def every_n_iters(self, runner, n):\n        return (runner.iter + 1) % n == 0 if n > 0 else False\n\n    def end_of_epoch(self, runner):\n        return runner.inner_iter + 1 == len(runner.data_loader)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/iter_timer.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport time\n\nfrom .hook import HOOKS, Hook\n\n\n@HOOKS.register_module()\nclass IterTimerHook(Hook):\n\n    def before_epoch(self, runner):\n        self.t = time.time()\n\n    def before_iter(self, runner):\n        runner.log_buffer.update({'data_time': time.time() - self.t})\n\n    def after_iter(self, runner):\n        runner.log_buffer.update({'time': time.time() - self.t})\n        self.t = time.time()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .base import LoggerHook\nfrom .mlflow import MlflowLoggerHook\nfrom .pavi import PaviLoggerHook\nfrom .tensorboard import TensorboardLoggerHook\nfrom .text import TextLoggerHook\nfrom .wandb import WandbLoggerHook\n\n__all__ = [\n    'LoggerHook', 'MlflowLoggerHook', 'PaviLoggerHook',\n    'TensorboardLoggerHook', 'TextLoggerHook', 'WandbLoggerHook'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/base.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom abc import ABCMeta, abstractmethod\n\nfrom ..hook import Hook\n\n\nclass LoggerHook(Hook):\n    \"\"\"Base class for logger hooks.\n\n    Args:\n        interval (int): Logging interval (every k iterations).\n        ignore_last (bool): Ignore the log of last iterations in each epoch\n            if less than `interval`.\n        reset_flag (bool): Whether to clear the output buffer after logging.\n        by_epoch (bool): Whether EpochBasedRunner is used.\n    \"\"\"\n\n    __metaclass__ = ABCMeta\n\n    def __init__(self,\n                 interval=10,\n                 ignore_last=True,\n                 reset_flag=False,\n                 by_epoch=True):\n        self.interval = interval\n        self.ignore_last = ignore_last\n        self.reset_flag = reset_flag\n        self.by_epoch = by_epoch\n\n    @abstractmethod\n    def log(self, runner):\n        pass\n\n    def before_run(self, runner):\n        for hook in runner.hooks[::-1]:\n            if isinstance(hook, LoggerHook):\n                hook.reset_flag = True\n                break\n\n    def before_epoch(self, runner):\n        runner.log_buffer.clear()  # clear logs of last epoch\n\n    def after_train_iter(self, runner):\n        if self.by_epoch and self.every_n_inner_iters(runner, self.interval):\n            runner.log_buffer.average(self.interval)\n        elif not self.by_epoch and self.every_n_iters(runner, self.interval):\n            runner.log_buffer.average(self.interval)\n        elif self.end_of_epoch(runner) and not self.ignore_last:\n            # not precise but more stable\n            runner.log_buffer.average(self.interval)\n\n        if runner.log_buffer.ready:\n            self.log(runner)\n            if self.reset_flag:\n                runner.log_buffer.clear_output()\n\n    def after_train_epoch(self, runner):\n        if runner.log_buffer.ready:\n            self.log(runner)\n            if self.reset_flag:\n                runner.log_buffer.clear_output()\n\n    def after_val_epoch(self, runner):\n        runner.log_buffer.average()\n        self.log(runner)\n        if self.reset_flag:\n            runner.log_buffer.clear_output()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/mlflow.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numbers\n\nfrom ...dist_utils import master_only\nfrom ..hook import HOOKS\nfrom .base import LoggerHook\n\n\n@HOOKS.register_module()\nclass MlflowLoggerHook(LoggerHook):\n\n    def __init__(self,\n                 exp_name=None,\n                 tags=None,\n                 log_model=True,\n                 interval=10,\n                 ignore_last=True,\n                 reset_flag=True):\n        \"\"\"Class to log metrics and (optionally) a trained model to MLflow.\n\n        It requires `MLflow`_ to be installed.\n\n        Args:\n            exp_name (str, optional): Name of the experiment to be used.\n                Default None.\n                If not None, set the active experiment.\n                If experiment does not exist, an experiment with provided name\n                will be created.\n            tags (dict of str: str, optional): Tags for the current run.\n                Default None.\n                If not None, set tags for the current run.\n            log_model (bool, optional): Wheter to log an MLflow artifact.\n                Default True.\n                If True, log runner.model as an MLflow artifact\n                for the current run.\n\n        .. _MLflow:\n            https://www.mlflow.org/docs/latest/index.html\n        \"\"\"\n        super(MlflowLoggerHook, self).__init__(interval, ignore_last,\n                                               reset_flag)\n        self.import_mlflow()\n        self.exp_name = exp_name\n        self.tags = tags\n        self.log_model = log_model\n\n    def import_mlflow(self):\n        try:\n            import mlflow\n            import mlflow.pytorch as mlflow_pytorch\n        except ImportError:\n            raise ImportError(\n                'Please run \"pip install mlflow\" to install mlflow')\n        self.mlflow = mlflow\n        self.mlflow_pytorch = mlflow_pytorch\n\n    @master_only\n    def before_run(self, runner):\n        if self.exp_name is not None:\n            self.mlflow.set_experiment(self.exp_name)\n        if self.tags is not None:\n            self.mlflow.set_tags(self.tags)\n\n    @master_only\n    def log(self, runner):\n        metrics = {}\n        for var, val in runner.log_buffer.output.items():\n            if var in ['time', 'data_time']:\n                continue\n            tag = f'{var}/{runner.mode}'\n            if isinstance(val, numbers.Number):\n                metrics[tag] = val\n        metrics['learning_rate'] = runner.current_lr()[0]\n        metrics['momentum'] = runner.current_momentum()[0]\n        self.mlflow.log_metrics(metrics, step=runner.iter)\n\n    @master_only\n    def after_run(self, runner):\n        if self.log_model:\n            self.mlflow_pytorch.log_model(runner.model, 'models')\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/pavi.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numbers\nimport os.path as osp\n\nimport numpy as np\nimport torch\n\nfrom ...dist_utils import master_only\nfrom ..hook import HOOKS\nfrom .base import LoggerHook\n\n\ndef is_scalar(val, include_np=True, include_torch=True):\n    \"\"\"Tell the input variable is a scalar or not.\n\n    Args:\n        val: Input variable.\n        include_np (bool): Whether include 0-d np.ndarray as a scalar.\n        include_torch (bool): Whether include 0-d torch.Tensor as a scalar.\n\n    Returns:\n        bool: True or False.\n    \"\"\"\n    if isinstance(val, numbers.Number):\n        return True\n    elif include_np and isinstance(val, np.ndarray) and val.ndim == 0:\n        return True\n    elif include_torch and isinstance(val, torch.Tensor) and len(val) == 1:\n        return True\n    else:\n        return False\n\n\n@HOOKS.register_module()\nclass PaviLoggerHook(LoggerHook):\n\n    def __init__(self,\n                 init_kwargs=None,\n                 add_graph=False,\n                 add_last_ckpt=False,\n                 interval=10,\n                 ignore_last=True,\n                 reset_flag=True,\n                 by_epoch=True):\n        super(PaviLoggerHook, self).__init__(interval, ignore_last, reset_flag,\n                                             by_epoch)\n        self.init_kwargs = init_kwargs\n        self.add_graph = add_graph\n        self.add_last_ckpt = add_last_ckpt\n\n    @master_only\n    def before_run(self, runner):\n        try:\n            from pavi import SummaryWriter\n        except ImportError:\n            raise ImportError('Please run \"pip install pavi\" to install pavi.')\n\n        self.run_name = runner.work_dir.split('/')[-1]\n\n        if not self.init_kwargs:\n            self.init_kwargs = dict()\n        self.init_kwargs['task'] = self.run_name\n        self.init_kwargs['model'] = runner._model_name\n\n        self.writer = SummaryWriter(**self.init_kwargs)\n\n        if self.add_graph:\n            self.writer.add_graph(runner.model)\n\n    @master_only\n    def log(self, runner):\n        tags = {}\n        for tag, val in runner.log_buffer.output.items():\n            if tag not in ['time', 'data_time'] and is_scalar(val):\n                tags[tag] = val\n        # add learning rate\n        lrs = runner.current_lr()\n        if isinstance(lrs, dict):\n            for name, value in lrs.items():\n                tags[f'learning_rate/{name}'] = value[0]\n        else:\n            tags['learning_rate'] = lrs[0]\n\n        # add momentum\n        momentums = runner.current_momentum()\n        if isinstance(momentums, dict):\n            for name, value in momentums.items():\n                tags[f'momentum/{name}'] = value[0]\n        else:\n            tags['momentum'] = momentums[0]\n\n        if tags:\n            self.writer.add_scalars(runner.mode, tags, runner.iter)\n\n    @master_only\n    def after_run(self, runner):\n        if self.add_last_ckpt:\n            ckpt_path = osp.join(runner.work_dir, 'latest.pth')\n            self.writer.add_snapshot_file(\n                tag=self.run_name,\n                snapshot_file_path=ckpt_path,\n                iteration=runner.iter)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/tensorboard.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\n\nfrom mmcv.utils import TORCH_VERSION\nfrom ...dist_utils import master_only\nfrom ..hook import HOOKS\nfrom .base import LoggerHook\n\n\n@HOOKS.register_module()\nclass TensorboardLoggerHook(LoggerHook):\n\n    def __init__(self,\n                 log_dir=None,\n                 interval=10,\n                 ignore_last=True,\n                 reset_flag=True,\n                 by_epoch=True):\n        super(TensorboardLoggerHook, self).__init__(interval, ignore_last,\n                                                    reset_flag, by_epoch)\n        self.log_dir = log_dir\n\n    @master_only\n    def before_run(self, runner):\n        if TORCH_VERSION < '1.1' or TORCH_VERSION == 'parrots':\n            try:\n                from tensorboardX import SummaryWriter\n            except ImportError:\n                raise ImportError('Please install tensorboardX to use '\n                                  'TensorboardLoggerHook.')\n        else:\n            try:\n                from torch.utils.tensorboard import SummaryWriter\n            except ImportError:\n                raise ImportError(\n                    'Please run \"pip install future tensorboard\" to install '\n                    'the dependencies to use torch.utils.tensorboard '\n                    '(applicable to PyTorch 1.1 or higher)')\n\n        if self.log_dir is None:\n            self.log_dir = osp.join(runner.work_dir, 'tf_logs')\n        self.writer = SummaryWriter(self.log_dir)\n\n    @master_only\n    def log(self, runner):\n        for var in runner.log_buffer.output:\n            if var in ['time', 'data_time']:\n                continue\n            tag = f'{var}/{runner.mode}'\n            record = runner.log_buffer.output[var]\n            if isinstance(record, str):\n                self.writer.add_text(tag, record, runner.iter)\n            else:\n                self.writer.add_scalar(tag, runner.log_buffer.output[var],\n                                       runner.iter)\n        # add learning rate\n        lrs = runner.current_lr()\n        if isinstance(lrs, dict):\n            for name, value in lrs.items():\n                self.writer.add_scalar(f'learning_rate/{name}', value[0],\n                                       runner.iter)\n        else:\n            self.writer.add_scalar('learning_rate', lrs[0], runner.iter)\n        # add momentum\n        momentums = runner.current_momentum()\n        if isinstance(momentums, dict):\n            for name, value in momentums.items():\n                self.writer.add_scalar(f'momentum/{name}', value[0],\n                                       runner.iter)\n        else:\n            self.writer.add_scalar('momentum', momentums[0], runner.iter)\n\n    @master_only\n    def after_run(self, runner):\n        self.writer.close()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/text.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport datetime\nimport os.path as osp\nfrom collections import OrderedDict\n\nimport torch\nimport torch.distributed as dist\n\nimport mmcv\nfrom ..hook import HOOKS\nfrom .base import LoggerHook\n\n\n@HOOKS.register_module()\nclass TextLoggerHook(LoggerHook):\n    \"\"\"Logger hook in text.\n\n    In this logger hook, the information will be printed on terminal and\n    saved in json file.\n\n    Args:\n        by_epoch (bool): Whether EpochBasedRunner is used.\n        interval (int): Logging interval (every k iterations).\n        ignore_last (bool): Ignore the log of last iterations in each epoch\n            if less than `interval`.\n        reset_flag (bool): Whether to clear the output buffer after logging.\n        interval_exp_name (int): Logging interval for experiment name. This\n            feature is to help users conveniently get the experiment\n            information from screen or log file. Default: 1000.\n    \"\"\"\n\n    def __init__(self,\n                 by_epoch=True,\n                 interval=10,\n                 ignore_last=True,\n                 reset_flag=False,\n                 interval_exp_name=1000):\n        super(TextLoggerHook, self).__init__(interval, ignore_last, reset_flag,\n                                             by_epoch)\n        self.by_epoch = by_epoch\n        self.time_sec_tot = 0\n        self.interval_exp_name = interval_exp_name\n\n    def before_run(self, runner):\n        super(TextLoggerHook, self).before_run(runner)\n        self.start_iter = runner.iter\n        self.json_log_path = osp.join(runner.work_dir,\n                                      f'{runner.timestamp}.log.json')\n        if runner.meta is not None:\n            self._dump_log(runner.meta, runner)\n\n    def _get_max_memory(self, runner):\n        mem = torch.cuda.max_memory_allocated()\n        mem_mb = torch.tensor([mem / (1024 * 1024)],\n                              dtype=torch.int,\n                              device=torch.device('cuda'))\n        if runner.world_size > 1:\n            dist.reduce(mem_mb, 0, op=dist.ReduceOp.MAX)\n        return mem_mb.item()\n\n    def _log_info(self, log_dict, runner):\n        # print exp name for users to distinguish experiments\n        # at every ``interval_exp_name`` iterations and the end of each epoch\n        if runner.meta is not None and 'exp_name' in runner.meta:\n            if (self.every_n_iters(runner, self.interval_exp_name)) or (\n                    self.by_epoch and self.end_of_epoch(runner)):\n                exp_info = f'Exp name: {runner.meta[\"exp_name\"]}'\n                runner.logger.info(exp_info)\n\n        if runner.mode == 'train':\n            if isinstance(log_dict['lr'], dict):\n                lr_str = []\n                for k, val in log_dict['lr'].items():\n                    lr_str.append(f'lr_{k}: {val:.3e}')\n                lr_str = ' '.join(lr_str)\n            else:\n                lr_str = f'lr: {log_dict[\"lr\"]:.3e}'\n\n            # by epoch: Epoch [4][100/1000]\n            # by iter:  Iter [100/100000]\n            if self.by_epoch:\n                log_str = f'Epoch [{log_dict[\"epoch\"]}]' \\\n                          f'[{log_dict[\"iter\"]}/{len(runner.data_loader)}]\\t'\n            else:\n                log_str = f'Iter [{log_dict[\"iter\"]}/{runner.max_iters}]\\t'\n            log_str += f'{lr_str}, '\n\n            if 'time' in log_dict.keys():\n                self.time_sec_tot += (log_dict['time'] * self.interval)\n                time_sec_avg = self.time_sec_tot / (\n                    runner.iter - self.start_iter + 1)\n                eta_sec = time_sec_avg * (runner.max_iters - runner.iter - 1)\n                eta_str = str(datetime.timedelta(seconds=int(eta_sec)))\n                log_str += f'eta: {eta_str}, '\n                log_str += f'time: {log_dict[\"time\"]:.3f}, ' \\\n                           f'data_time: {log_dict[\"data_time\"]:.3f}, '\n                # statistic memory\n                if torch.cuda.is_available():\n                    log_str += f'memory: {log_dict[\"memory\"]}, '\n        else:\n            if self.by_epoch:\n                log_str = f'Epoch({log_dict[\"mode\"]}) ' \\\n                        f'[{log_dict[\"epoch\"] - 1}][{log_dict[\"iter\"]}]\\t'\n            else:\n                log_str = f'Iter({log_dict[\"mode\"]}) [{log_dict[\"iter\"]}]\\t'\n\n        log_items = []\n        for name, val in log_dict.items():\n            # TODO: resolve this hack\n            # these items have been in log_str\n            if name in [\n                    'mode', 'Epoch', 'iter', 'lr', 'time', 'data_time',\n                    'memory', 'epoch'\n            ]:\n                continue\n            if isinstance(val, float):\n                val = f'{val:.4f}'\n            log_items.append(f'{name}: {val}')\n        log_str += ', '.join(log_items)\n\n        runner.logger.info(log_str)\n\n    def _dump_log(self, log_dict, runner):\n        # dump log in json format\n        json_log = OrderedDict()\n        for k, v in log_dict.items():\n            json_log[k] = self._round_float(v)\n        # only append log at last line\n        if runner.rank == 0:\n            with open(self.json_log_path, 'a+') as f:\n                mmcv.dump(json_log, f, file_format='json')\n                f.write('\\n')\n\n    def _round_float(self, items):\n        if isinstance(items, list):\n            return [self._round_float(item) for item in items]\n        elif isinstance(items, float):\n            return round(items, 5)\n        else:\n            return items\n\n    def log(self, runner):\n        log_dict = OrderedDict()\n        # training mode if the output contains the key \"time\"\n        mode = 'train' if 'time' in runner.log_buffer.output else 'val'\n        log_dict['mode'] = mode\n        log_dict['epoch'] = runner.epoch + 1\n        if self.by_epoch:\n            log_dict['iter'] = runner.inner_iter + 1\n        else:\n            log_dict['iter'] = runner.iter + 1\n        # only record lr of the first param group\n        cur_lr = runner.current_lr()\n        if isinstance(cur_lr, list):\n            log_dict['lr'] = cur_lr[0]\n        else:\n            assert isinstance(cur_lr, dict)\n            log_dict['lr'] = {}\n            for k, lr_ in cur_lr.items():\n                assert isinstance(lr_, list)\n                log_dict['lr'].update({k: lr_[0]})\n\n        if mode == 'train':\n            log_dict['time'] = runner.log_buffer.output['time']\n            log_dict['data_time'] = runner.log_buffer.output['data_time']\n\n            # statistic memory\n            if torch.cuda.is_available():\n                log_dict['memory'] = self._get_max_memory(runner)\n        for name, val in runner.log_buffer.output.items():\n            if name in ['time', 'data_time']:\n                continue\n            log_dict[name] = val\n\n        self._log_info(log_dict, runner)\n        self._dump_log(log_dict, runner)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/logger/wandb.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numbers\n\nfrom ...dist_utils import master_only\nfrom ..hook import HOOKS\nfrom .base import LoggerHook\n\n\n@HOOKS.register_module()\nclass WandbLoggerHook(LoggerHook):\n\n    def __init__(self,\n                 init_kwargs=None,\n                 interval=10,\n                 ignore_last=True,\n                 reset_flag=True):\n        super(WandbLoggerHook, self).__init__(interval, ignore_last,\n                                              reset_flag)\n        self.import_wandb()\n        self.init_kwargs = init_kwargs\n\n    def import_wandb(self):\n        try:\n            import wandb\n        except ImportError:\n            raise ImportError(\n                'Please run \"pip install wandb\" to install wandb')\n        self.wandb = wandb\n\n    @master_only\n    def before_run(self, runner):\n        if self.wandb is None:\n            self.import_wandb()\n        if self.init_kwargs:\n            self.wandb.init(**self.init_kwargs)\n        else:\n            self.wandb.init()\n\n    @master_only\n    def log(self, runner):\n        metrics = {}\n        for var, val in runner.log_buffer.output.items():\n            if var in ['time', 'data_time']:\n                continue\n            tag = f'{var}/{runner.mode}'\n            if isinstance(val, numbers.Number):\n                metrics[tag] = val\n        metrics['learning_rate'] = runner.current_lr()[0]\n        metrics['momentum'] = runner.current_momentum()[0]\n        if metrics:\n            self.wandb.log(metrics, step=runner.iter)\n\n    @master_only\n    def after_run(self, runner):\n        self.wandb.join()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/lr_updater.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom math import cos, pi\n\nfrom .hook import HOOKS, Hook\n\n\nclass LrUpdaterHook(Hook):\n    \"\"\"LR Scheduler in MMCV\n\n    Args:\n        by_epoch (bool): LR changes epoch by epoch\n        warmup (string): Type of warmup used. It can be None(use no warmup),\n            'constant', 'linear' or 'exp'\n        warmup_iters (int): The number of iterations or epochs that warmup\n            lasts\n        warmup_ratio (float): LR used at the beginning of warmup equals to\n            warmup_ratio * initial_lr\n        warmup_by_epoch (bool): When warmup_by_epoch == True, warmup_iters\n            means the number of epochs that warmup lasts, otherwise means the\n            number of iteration that warmup lasts\n    \"\"\"\n\n    def __init__(self,\n                 by_epoch=True,\n                 warmup=None,\n                 warmup_iters=0,\n                 warmup_ratio=0.1,\n                 warmup_by_epoch=False):\n        # validate the \"warmup\" argument\n        if warmup is not None:\n            if warmup not in ['constant', 'linear', 'exp']:\n                raise ValueError(\n                    f'\"{warmup}\" is not a supported type for warming up, valid'\n                    ' types are \"constant\" and \"linear\"')\n        if warmup is not None:\n            assert warmup_iters > 0, \\\n                '\"warmup_iters\" must be a positive integer'\n            assert 0 < warmup_ratio <= 1.0, \\\n                '\"warmup_ratio\" must be in range (0,1]'\n\n        self.by_epoch = by_epoch\n        self.warmup = warmup\n        self.warmup_iters = warmup_iters\n        self.warmup_ratio = warmup_ratio\n        self.warmup_by_epoch = warmup_by_epoch\n\n        if self.warmup_by_epoch:\n            self.warmup_epochs = self.warmup_iters\n            self.warmup_iters = None\n        else:\n            self.warmup_epochs = None\n\n        self.base_lr = []  # initial lr for all param groups\n        self.regular_lr = []  # expected lr if no warming up is performed\n\n    def _set_lr(self, runner, lr_groups):\n        if isinstance(runner.optimizer, dict):\n            for k, optim in runner.optimizer.items():\n                for param_group, lr in zip(optim.param_groups, lr_groups[k]):\n                    param_group['lr'] = lr\n        else:\n            for param_group, lr in zip(runner.optimizer.param_groups,\n                                       lr_groups):\n                param_group['lr'] = lr\n\n    def get_lr(self, runner, base_lr):\n        raise NotImplementedError\n\n    def get_regular_lr(self, runner):\n        if isinstance(runner.optimizer, dict):\n            lr_groups = {}\n            for k in runner.optimizer.keys():\n                _lr_group = [\n                    self.get_lr(runner, _base_lr)\n                    for _base_lr in self.base_lr[k]\n                ]\n                lr_groups.update({k: _lr_group})\n\n            return lr_groups\n        else:\n            return [self.get_lr(runner, _base_lr) for _base_lr in self.base_lr]\n\n    def get_warmup_lr(self, cur_iters):\n        if self.warmup == 'constant':\n            warmup_lr = [_lr * self.warmup_ratio for _lr in self.regular_lr]\n        elif self.warmup == 'linear':\n            k = (1 - cur_iters / self.warmup_iters) * (1 - self.warmup_ratio)\n            warmup_lr = [_lr * (1 - k) for _lr in self.regular_lr]\n        elif self.warmup == 'exp':\n            k = self.warmup_ratio**(1 - cur_iters / self.warmup_iters)\n            warmup_lr = [_lr * k for _lr in self.regular_lr]\n        return warmup_lr\n\n    def before_run(self, runner):\n        # NOTE: when resuming from a checkpoint, if 'initial_lr' is not saved,\n        # it will be set according to the optimizer params\n        if isinstance(runner.optimizer, dict):\n            self.base_lr = {}\n            for k, optim in runner.optimizer.items():\n                for group in optim.param_groups:\n                    group.setdefault('initial_lr', group['lr'])\n                _base_lr = [\n                    group['initial_lr'] for group in optim.param_groups\n                ]\n                self.base_lr.update({k: _base_lr})\n        else:\n            for group in runner.optimizer.param_groups:\n                group.setdefault('initial_lr', group['lr'])\n            self.base_lr = [\n                group['initial_lr'] for group in runner.optimizer.param_groups\n            ]\n\n    def before_train_epoch(self, runner):\n        if not self.by_epoch:\n            return\n        if self.warmup_by_epoch:\n            epoch_len = len(runner.data_loader)\n            self.warmup_iters = self.warmup_epochs * epoch_len\n\n        self.regular_lr = self.get_regular_lr(runner)\n        self._set_lr(runner, self.regular_lr)\n\n    def before_train_iter(self, runner):\n        cur_iter = runner.iter\n        if not self.by_epoch:\n            self.regular_lr = self.get_regular_lr(runner)\n            if self.warmup is None or cur_iter >= self.warmup_iters:\n                self._set_lr(runner, self.regular_lr)\n            else:\n                warmup_lr = self.get_warmup_lr(cur_iter)\n                self._set_lr(runner, warmup_lr)\n        elif self.by_epoch:\n            if self.warmup is None or cur_iter > self.warmup_iters:\n                return\n            elif cur_iter == self.warmup_iters:\n                self._set_lr(runner, self.regular_lr)\n            else:\n                warmup_lr = self.get_warmup_lr(cur_iter)\n                self._set_lr(runner, warmup_lr)\n\n\n@HOOKS.register_module()\nclass FixedLrUpdaterHook(LrUpdaterHook):\n\n    def __init__(self, **kwargs):\n        super(FixedLrUpdaterHook, self).__init__(**kwargs)\n\n    def get_lr(self, runner, base_lr):\n        return base_lr\n\n\n@HOOKS.register_module()\nclass StepLrUpdaterHook(LrUpdaterHook):\n\n    def __init__(self, step, gamma=0.1, **kwargs):\n        assert isinstance(step, (list, int))\n        if isinstance(step, list):\n            for s in step:\n                assert isinstance(s, int) and s > 0\n        elif isinstance(step, int):\n            assert step > 0\n        else:\n            raise TypeError('\"step\" must be a list or integer')\n        self.step = step\n        self.gamma = gamma\n        super(StepLrUpdaterHook, self).__init__(**kwargs)\n\n    def get_lr(self, runner, base_lr):\n        progress = runner.epoch if self.by_epoch else runner.iter\n\n        if isinstance(self.step, int):\n            return base_lr * (self.gamma**(progress // self.step))\n\n        exp = len(self.step)\n        for i, s in enumerate(self.step):\n            if progress < s:\n                exp = i\n                break\n        return base_lr * self.gamma**exp\n\n\n@HOOKS.register_module()\nclass ExpLrUpdaterHook(LrUpdaterHook):\n\n    def __init__(self, gamma, **kwargs):\n        self.gamma = gamma\n        super(ExpLrUpdaterHook, self).__init__(**kwargs)\n\n    def get_lr(self, runner, base_lr):\n        progress = runner.epoch if self.by_epoch else runner.iter\n        return base_lr * self.gamma**progress\n\n\n@HOOKS.register_module()\nclass PolyLrUpdaterHook(LrUpdaterHook):\n\n    def __init__(self, power=1., min_lr=0., **kwargs):\n        self.power = power\n        self.min_lr = min_lr\n        super(PolyLrUpdaterHook, self).__init__(**kwargs)\n\n    def get_lr(self, runner, base_lr):\n        if self.by_epoch:\n            progress = runner.epoch\n            max_progress = runner.max_epochs\n        else:\n            progress = runner.iter\n            max_progress = runner.max_iters\n        coeff = (1 - progress / max_progress)**self.power\n        return (base_lr - self.min_lr) * coeff + self.min_lr\n\n\n@HOOKS.register_module()\nclass InvLrUpdaterHook(LrUpdaterHook):\n\n    def __init__(self, gamma, power=1., **kwargs):\n        self.gamma = gamma\n        self.power = power\n        super(InvLrUpdaterHook, self).__init__(**kwargs)\n\n    def get_lr(self, runner, base_lr):\n        progress = runner.epoch if self.by_epoch else runner.iter\n        return base_lr * (1 + self.gamma * progress)**(-self.power)\n\n\n@HOOKS.register_module()\nclass CosineAnealingLrUpdaterHook(LrUpdaterHook):\n\n    def __init__(self, min_lr=None, min_lr_ratio=None, **kwargs):\n        assert (min_lr is None) ^ (min_lr_ratio is None)\n        self.min_lr = min_lr\n        self.min_lr_ratio = min_lr_ratio\n        super(CosineAnealingLrUpdaterHook, self).__init__(**kwargs)\n\n    def get_lr(self, runner, base_lr):\n        if self.by_epoch:\n            progress = runner.epoch\n            max_progress = runner.max_epochs\n        else:\n            progress = runner.iter\n            max_progress = runner.max_iters\n\n        if self.min_lr_ratio is not None:\n            target_lr = base_lr * self.min_lr_ratio\n        else:\n            target_lr = self.min_lr\n        return annealing_cos(base_lr, target_lr, progress / max_progress)\n\n\n@HOOKS.register_module()\nclass CosineRestartLrUpdaterHook(LrUpdaterHook):\n    \"\"\"Cosine annealing with restarts learning rate scheme.\n\n    Args:\n        periods (list[int]): Periods for each cosine anneling cycle.\n        restart_weights (list[float], optional): Restart weights at each\n            restart iteration. Default: [1].\n        min_lr (float, optional): The minimum lr. Default: None.\n        min_lr_ratio (float, optional): The ratio of minimum lr to the base lr.\n            Either `min_lr` or `min_lr_ratio` should be specified.\n            Default: None.\n    \"\"\"\n\n    def __init__(self,\n                 periods,\n                 restart_weights=[1],\n                 min_lr=None,\n                 min_lr_ratio=None,\n                 **kwargs):\n        assert (min_lr is None) ^ (min_lr_ratio is None)\n        self.periods = periods\n        self.min_lr = min_lr\n        self.min_lr_ratio = min_lr_ratio\n        self.restart_weights = restart_weights\n        assert (len(self.periods) == len(self.restart_weights)\n                ), 'periods and restart_weights should have the same length.'\n        super(CosineRestartLrUpdaterHook, self).__init__(**kwargs)\n\n        self.cumulative_periods = [\n            sum(self.periods[0:i + 1]) for i in range(0, len(self.periods))\n        ]\n\n    def get_lr(self, runner, base_lr):\n        if self.by_epoch:\n            progress = runner.epoch\n        else:\n            progress = runner.iter\n\n        if self.min_lr_ratio is not None:\n            target_lr = base_lr * self.min_lr_ratio\n        else:\n            target_lr = self.min_lr\n\n        idx = get_position_from_periods(progress, self.cumulative_periods)\n        current_weight = self.restart_weights[idx]\n        nearest_restart = 0 if idx == 0 else self.cumulative_periods[idx - 1]\n        current_periods = self.periods[idx]\n\n        alpha = min((progress - nearest_restart) / current_periods, 1)\n        return annealing_cos(base_lr, target_lr, alpha, current_weight)\n\n\ndef get_position_from_periods(iteration, cumulative_periods):\n    \"\"\"Get the position from a period list.\n\n    It will return the index of the right-closest number in the period list.\n    For example, the cumulative_periods = [100, 200, 300, 400],\n    if iteration == 50, return 0;\n    if iteration == 210, return 2;\n    if iteration == 300, return 2.\n\n    Args:\n        iteration (int): Current iteration.\n        cumulative_periods (list[int]): Cumulative period list.\n\n    Returns:\n        int: The position of the right-closest number in the period list.\n    \"\"\"\n    for i, period in enumerate(cumulative_periods):\n        if iteration <= period:\n            return i\n    raise ValueError(f'Current iteration {iteration} exceeds '\n                     f'cumulative_periods {cumulative_periods}')\n\n\n@HOOKS.register_module()\nclass CyclicLrUpdaterHook(LrUpdaterHook):\n    \"\"\"Cyclic LR Scheduler\n\n    Implement the cyclical learning rate policy (CLR) described in\n    https://arxiv.org/pdf/1506.01186.pdf\n\n    Different from the original paper, we use cosine anealing rather than\n    triangular policy inside a cycle. This improves the performance in the\n    3D detection area.\n\n    Attributes:\n        target_ratio (tuple[float]): Relative ratio of the highest LR and the\n            lowest LR to the initial LR.\n        cyclic_times (int): Number of cycles during training\n        step_ratio_up (float): The ratio of the increasing process of LR in\n            the total cycle.\n        by_epoch (bool): Whether to update LR by epoch.\n\n    \"\"\"\n\n    def __init__(self,\n                 by_epoch=False,\n                 target_ratio=(10, 1e-4),\n                 cyclic_times=1,\n                 step_ratio_up=0.4,\n                 **kwargs):\n        if isinstance(target_ratio, float):\n            target_ratio = (target_ratio, target_ratio / 1e5)\n        elif isinstance(target_ratio, tuple):\n            target_ratio = (target_ratio[0], target_ratio[0] / 1e5) \\\n                if len(target_ratio) == 1 else target_ratio\n        else:\n            raise ValueError('target_ratio should be either float '\n                             f'or tuple, got {type(target_ratio)}')\n\n        assert len(target_ratio) == 2, \\\n            '\"target_ratio\" must be list or tuple of two floats'\n        assert 0 <= step_ratio_up < 1.0, \\\n            '\"step_ratio_up\" must be in range [0,1)'\n\n        self.target_ratio = target_ratio\n        self.cyclic_times = cyclic_times\n        self.step_ratio_up = step_ratio_up\n        self.lr_phases = []  # init lr_phases\n\n        assert not by_epoch, \\\n            'currently only support \"by_epoch\" = False'\n        super(CyclicLrUpdaterHook, self).__init__(by_epoch, **kwargs)\n\n    def before_run(self, runner):\n        super(CyclicLrUpdaterHook, self).before_run(runner)\n        # initiate lr_phases\n        # total lr_phases are separated as up and down\n        max_iter_per_phase = runner.max_iters // self.cyclic_times\n        iter_up_phase = int(self.step_ratio_up * max_iter_per_phase)\n        self.lr_phases.append(\n            [0, iter_up_phase, max_iter_per_phase, 1, self.target_ratio[0]])\n        self.lr_phases.append([\n            iter_up_phase, max_iter_per_phase, max_iter_per_phase,\n            self.target_ratio[0], self.target_ratio[1]\n        ])\n\n    def get_lr(self, runner, base_lr):\n        curr_iter = runner.iter\n        for (start_iter, end_iter, max_iter_per_phase, start_ratio,\n             end_ratio) in self.lr_phases:\n            curr_iter %= max_iter_per_phase\n            if start_iter <= curr_iter < end_iter:\n                progress = curr_iter - start_iter\n                return annealing_cos(base_lr * start_ratio,\n                                     base_lr * end_ratio,\n                                     progress / (end_iter - start_iter))\n\n\ndef annealing_cos(start, end, factor, weight=1):\n    \"\"\"Calculate annealing cos learning rate.\n\n    Cosine anneal from `weight * start + (1 - weight) * end` to `end` as\n    percentage goes from 0.0 to 1.0.\n\n    Args:\n        start (float): The starting learning rate of the cosine annealing.\n        end (float): The ending learing rate of the cosine annealing.\n        factor (float): The coefficient of `pi` when calculating the current\n            percentage. Range from 0.0 to 1.0.\n        weight (float, optional): The combination factor of `start` and `end`\n            when calculating the actual starting learning rate. Default to 1.\n    \"\"\"\n    cos_out = cos(pi * factor) + 1\n    return end + 0.5 * weight * (start - end) * cos_out\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/memory.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport torch\n\nfrom .hook import HOOKS, Hook\n\n\n@HOOKS.register_module()\nclass EmptyCacheHook(Hook):\n\n    def __init__(self, before_epoch=False, after_epoch=True, after_iter=False):\n        self._before_epoch = before_epoch\n        self._after_epoch = after_epoch\n        self._after_iter = after_iter\n\n    def after_iter(self, runner):\n        if self._after_iter:\n            torch.cuda.empty_cache()\n\n    def before_epoch(self, runner):\n        if self._before_epoch:\n            torch.cuda.empty_cache()\n\n    def after_epoch(self, runner):\n        if self._after_epoch:\n            torch.cuda.empty_cache()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/momentum_updater.py",
    "content": "from .hook import HOOKS, Hook\nfrom .lr_updater import annealing_cos\n\n\nclass MomentumUpdaterHook(Hook):\n\n    def __init__(self,\n                 by_epoch=True,\n                 warmup=None,\n                 warmup_iters=0,\n                 warmup_ratio=0.9):\n        # validate the \"warmup\" argument\n        if warmup is not None:\n            if warmup not in ['constant', 'linear', 'exp']:\n                raise ValueError(\n                    f'\"{warmup}\" is not a supported type for warming up, valid'\n                    ' types are \"constant\" and \"linear\"')\n        if warmup is not None:\n            assert warmup_iters > 0, \\\n                '\"warmup_iters\" must be a positive integer'\n            assert 0 < warmup_ratio <= 1.0, \\\n                '\"warmup_momentum\" must be in range (0,1]'\n\n        self.by_epoch = by_epoch\n        self.warmup = warmup\n        self.warmup_iters = warmup_iters\n        self.warmup_ratio = warmup_ratio\n\n        self.base_momentum = []  # initial momentum for all param groups\n        self.regular_momentum = [\n        ]  # expected momentum if no warming up is performed\n\n    def _set_momentum(self, runner, momentum_groups):\n        for param_group, mom in zip(runner.optimizer.param_groups,\n                                    momentum_groups):\n            if 'momentum' in param_group.keys():\n                param_group['momentum'] = mom\n            elif 'betas' in param_group.keys():\n                param_group['betas'] = (mom, param_group['betas'][1])\n\n    def get_momentum(self, runner, base_momentum):\n        raise NotImplementedError\n\n    def get_regular_momentum(self, runner):\n        return [\n            self.get_momentum(runner, _base_momentum)\n            for _base_momentum in self.base_momentum\n        ]\n\n    def get_warmup_momentum(self, cur_iters):\n        if self.warmup == 'constant':\n            warmup_momentum = [\n                _momentum / self.warmup_ratio\n                for _momentum in self.regular_momentum\n            ]\n        elif self.warmup == 'linear':\n            k = (1 - cur_iters / self.warmup_iters) * (1 - self.warmup_ratio)\n            warmup_momentum = [\n                _momentum / (1 - k) for _momentum in self.regular_mom\n            ]\n        elif self.warmup == 'exp':\n            k = self.warmup_ratio**(1 - cur_iters / self.warmup_iters)\n            warmup_momentum = [_momentum / k for _momentum in self.regular_mom]\n        return warmup_momentum\n\n    def before_run(self, runner):\n        # NOTE: when resuming from a checkpoint,\n        # if 'initial_momentum' is not saved,\n        # it will be set according to the optimizer params\n        for group in runner.optimizer.param_groups:\n            if 'momentum' in group.keys():\n                group.setdefault('initial_momentum', group['momentum'])\n            else:\n                group.setdefault('initial_momentum', group['betas'][0])\n        self.base_momentum = [\n            group['initial_momentum']\n            for group in runner.optimizer.param_groups\n        ]\n\n    def before_train_epoch(self, runner):\n        if not self.by_epoch:\n            return\n        self.regular_mom = self.get_regular_momentum(runner)\n        self._set_momentum(runner, self.regular_mom)\n\n    def before_train_iter(self, runner):\n        cur_iter = runner.iter\n        if not self.by_epoch:\n            self.regular_mom = self.get_regular_momentum(runner)\n            if self.warmup is None or cur_iter >= self.warmup_iters:\n                self._set_momentum(runner, self.regular_mom)\n            else:\n                warmup_momentum = self.get_warmup_momentum(cur_iter)\n                self._set_momentum(runner, warmup_momentum)\n        elif self.by_epoch:\n            if self.warmup is None or cur_iter > self.warmup_iters:\n                return\n            elif cur_iter == self.warmup_iters:\n                self._set_momentum(runner, self.regular_mom)\n            else:\n                warmup_momentum = self.get_warmup_momentum(cur_iter)\n                self._set_momentum(runner, warmup_momentum)\n\n\n@HOOKS.register_module()\nclass CosineAnealingMomentumUpdaterHook(MomentumUpdaterHook):\n\n    def __init__(self, min_momentum=None, min_momentum_ratio=None, **kwargs):\n        assert (min_momentum is None) ^ (min_momentum_ratio is None)\n        self.min_momentum = min_momentum\n        self.min_momentum_ratio = min_momentum_ratio\n        super(CosineAnealingMomentumUpdaterHook, self).__init__(**kwargs)\n\n    def get_momentum(self, runner, base_momentum):\n        if self.by_epoch:\n            progress = runner.epoch\n            max_progress = runner.max_epochs\n        else:\n            progress = runner.iter\n            max_progress = runner.max_iters\n        if self.min_momentum_ratio is not None:\n            target_momentum = base_momentum * self.min_momentum_ratio\n        else:\n            target_momentum = self.min_momentum\n        return annealing_cos(base_momentum, target_momentum,\n                             progress / max_progress)\n\n\n@HOOKS.register_module()\nclass CyclicMomentumUpdaterHook(MomentumUpdaterHook):\n    \"\"\"Cyclic momentum Scheduler\n\n    Implemet the cyclical momentum scheduler policy described in\n    https://arxiv.org/pdf/1708.07120.pdf\n\n    This momentum scheduler usually used together with the CyclicLRUpdater\n    to improve the performance in the 3D detection area.\n\n    Attributes:\n        target_ratio (tuple[float]): Relative ratio of the lowest momentum and\n            the highest momentum to the initial momentum.\n        cyclic_times (int): Number of cycles during training\n        step_ratio_up (float): The ratio of the increasing process of momentum\n            in  the total cycle.\n        by_epoch (bool): Whether to update momentum by epoch.\n\n    \"\"\"\n\n    def __init__(self,\n                 by_epoch=False,\n                 target_ratio=(0.85 / 0.95, 1),\n                 cyclic_times=1,\n                 step_ratio_up=0.4,\n                 **kwargs):\n        if isinstance(target_ratio, float):\n            target_ratio = (target_ratio, target_ratio / 1e5)\n        elif isinstance(target_ratio, tuple):\n            target_ratio = (target_ratio[0], target_ratio[0] / 1e5) \\\n                if len(target_ratio) == 1 else target_ratio\n        else:\n            raise ValueError('target_ratio should be either float '\n                             f'or tuple, got {type(target_ratio)}')\n\n        assert len(target_ratio) == 2, \\\n            '\"target_ratio\" must be list or tuple of two floats'\n        assert 0 <= step_ratio_up < 1.0, \\\n            '\"step_ratio_up\" must be in range [0,1)'\n\n        self.target_ratio = target_ratio\n        self.cyclic_times = cyclic_times\n        self.step_ratio_up = step_ratio_up\n        self.momentum_phases = []  # init momentum_phases\n        # currently only support by_epoch=False\n        assert not by_epoch, \\\n            'currently only support \"by_epoch\" = False'\n        super(CyclicMomentumUpdaterHook, self).__init__(by_epoch, **kwargs)\n\n    def before_run(self, runner):\n        super(CyclicMomentumUpdaterHook, self).before_run(runner)\n        # initiate momentum_phases\n        # total momentum_phases are separated as up and down\n        max_iter_per_phase = runner.max_iters // self.cyclic_times\n        iter_up_phase = int(self.step_ratio_up * max_iter_per_phase)\n        self.momentum_phases.append(\n            [0, iter_up_phase, max_iter_per_phase, 1, self.target_ratio[0]])\n        self.momentum_phases.append([\n            iter_up_phase, max_iter_per_phase, max_iter_per_phase,\n            self.target_ratio[0], self.target_ratio[1]\n        ])\n\n    def get_momentum(self, runner, base_momentum):\n        curr_iter = runner.iter\n        for (start_iter, end_iter, max_iter_per_phase, start_ratio,\n             end_ratio) in self.momentum_phases:\n            curr_iter %= max_iter_per_phase\n            if start_iter <= curr_iter < end_iter:\n                progress = curr_iter - start_iter\n                return annealing_cos(base_momentum * start_ratio,\n                                     base_momentum * end_ratio,\n                                     progress / (end_iter - start_iter))\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/optimizer.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom torch.nn.utils import clip_grad\n\nfrom .hook import HOOKS, Hook\n\n\n@HOOKS.register_module()\nclass OptimizerHook(Hook):\n\n    def __init__(self, grad_clip=None):\n        self.grad_clip = grad_clip\n\n    def clip_grads(self, params):\n        params = list(\n            filter(lambda p: p.requires_grad and p.grad is not None, params))\n        if len(params) > 0:\n            return clip_grad.clip_grad_norm_(params, **self.grad_clip)\n\n    def after_train_iter(self, runner):\n        runner.optimizer.zero_grad()\n        runner.outputs['loss'].backward()\n        if self.grad_clip is not None:\n            grad_norm = self.clip_grads(runner.model.parameters())\n            if grad_norm is not None:\n                # Add grad norm to the logger\n                runner.log_buffer.update({'grad_norm': float(grad_norm)},\n                                         runner.outputs['num_samples'])\n        runner.optimizer.step()\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/hooks/sampler_seed.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .hook import HOOKS, Hook\n\n\n@HOOKS.register_module()\nclass DistSamplerSeedHook(Hook):\n\n    def before_epoch(self, runner):\n        runner.data_loader.sampler.set_epoch(runner.epoch)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/iter_based_runner.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\nimport time\n\nimport torch\nfrom torch.optim import Optimizer\n\nimport mmcv\nfrom .base_runner import BaseRunner\nfrom .checkpoint import save_checkpoint\nfrom .hooks import IterTimerHook\nfrom .utils import get_host_info\n\n\nclass IterLoader:\n\n    def __init__(self, dataloader):\n        self._dataloader = dataloader\n        self.iter_loader = iter(self._dataloader)\n        self._epoch = 0\n\n    @property\n    def epoch(self):\n        return self._epoch\n\n    def __next__(self):\n        try:\n            data = next(self.iter_loader)\n        except StopIteration:\n            self._epoch += 1\n            if hasattr(self._dataloader.sampler, 'set_epoch'):\n                self._dataloader.sampler.set_epoch(self._epoch)\n            self.iter_loader = iter(self._dataloader)\n            data = next(self.iter_loader)\n\n        return data\n\n    def __len__(self):\n        return len(self._dataloader)\n\n\nclass IterBasedRunner(BaseRunner):\n    \"\"\"Iteration-based Runner.\n\n    This runner train models iteration by iteration.\n    \"\"\"\n\n    def train(self, data_loader, **kwargs):\n        self.model.train()\n        self.mode = 'train'\n        self.data_loader = data_loader\n        self._epoch = data_loader.epoch\n        self.call_hook('before_train_iter')\n        data_batch = next(data_loader)\n        outputs = self.model.train_step(data_batch, self.optimizer, **kwargs)\n        if not isinstance(outputs, dict):\n            raise TypeError('model.train_step() must return a dict')\n        if 'log_vars' in outputs:\n            self.log_buffer.update(outputs['log_vars'], outputs['num_samples'])\n        self.outputs = outputs\n        self.call_hook('after_train_iter')\n        self._inner_iter += 1\n        self._iter += 1\n\n    def val(self, data_loader, **kwargs):\n        self.model.eval()\n        self.mode = 'val'\n        self.data_loader = data_loader\n        self.call_hook('before_val_iter')\n        data_batch = next(data_loader)\n        outputs = self.model.val_step(data_batch, **kwargs)\n        if not isinstance(outputs, dict):\n            raise TypeError('model.val_step() must return a dict')\n        if 'log_vars' in outputs:\n            self.log_buffer.update(outputs['log_vars'], outputs['num_samples'])\n        self.outputs = outputs\n        self.call_hook('after_val_iter')\n        self._inner_iter += 1\n\n    def run(self, data_loaders, workflow, max_iters, **kwargs):\n        \"\"\"Start running.\n\n        Args:\n            data_loaders (list[:obj:`DataLoader`]): Dataloaders for training\n                and validation.\n            workflow (list[tuple]): A list of (phase, iters) to specify the\n                running order and iterations. E.g, [('train', 10000),\n                ('val', 1000)] means running 10000 iterations for training and\n                1000 iterations for validation, iteratively.\n            max_iters (int): Total training iterations.\n        \"\"\"\n        assert isinstance(data_loaders, list)\n        assert mmcv.is_list_of(workflow, tuple)\n        assert len(data_loaders) == len(workflow)\n\n        self._max_iters = max_iters\n        work_dir = self.work_dir if self.work_dir is not None else 'NONE'\n        self.logger.info('Start running, host: %s, work_dir: %s',\n                         get_host_info(), work_dir)\n        self.logger.info('workflow: %s, max: %d iters', workflow, max_iters)\n        self.call_hook('before_run')\n\n        iter_loaders = [IterLoader(x) for x in data_loaders]\n\n        self.call_hook('before_epoch')\n\n        while self.iter < max_iters:\n            for i, flow in enumerate(workflow):\n                self._inner_iter = 0\n                mode, iters = flow\n                if not isinstance(mode, str) or not hasattr(self, mode):\n                    raise ValueError(\n                        'runner has no method named \"{}\" to run a workflow'.\n                        format(mode))\n                iter_runner = getattr(self, mode)\n                for _ in range(iters):\n                    if mode == 'train' and self.iter >= max_iters:\n                        return\n                    iter_runner(iter_loaders[i], **kwargs)\n\n        time.sleep(1)  # wait for some hooks like loggers to finish\n        self.call_hook('after_epoch')\n        self.call_hook('after_run')\n\n    def resume(self,\n               checkpoint,\n               resume_optimizer=True,\n               map_location='default'):\n        \"\"\"Resume model from checkpoint.\n\n        Args:\n            checkpoint (str): Checkpoint to resume from.\n            resume_optimizer (bool, optional): Whether resume the optimizer(s)\n                if the checkpoint file includes optimizer(s). Default to True.\n            map_location (str, optional): Same as :func:`torch.load`.\n                Default to 'default'.\n        \"\"\"\n        if map_location == 'default':\n            device_id = torch.cuda.current_device()\n            checkpoint = self.load_checkpoint(\n                checkpoint,\n                map_location=lambda storage, loc: storage.cuda(device_id))\n        else:\n            checkpoint = self.load_checkpoint(\n                checkpoint, map_location=map_location)\n\n        self._epoch = checkpoint['meta']['epoch']\n        self._iter = checkpoint['meta']['iter']\n        self._inner_iter = checkpoint['meta']['iter']\n        if 'optimizer' in checkpoint and resume_optimizer:\n            if isinstance(self.optimizer, Optimizer):\n                self.optimizer.load_state_dict(checkpoint['optimizer'])\n            elif isinstance(self.optimizer, dict):\n                for k in self.optimizer.keys():\n                    self.optimizer[k].load_state_dict(\n                        checkpoint['optimizer'][k])\n\n        self.logger.info(f'resumed from epoch: {self.epoch}, iter {self.iter}')\n\n    def save_checkpoint(self,\n                        out_dir,\n                        filename_tmpl='iter_{}.pth',\n                        meta=None,\n                        save_optimizer=True,\n                        create_symlink=True):\n        \"\"\"Save checkpoint to file.\n\n        Args:\n            out_dir (str): Directory to save checkpoint files.\n            filename_tmpl (str, optional): Checkpoint file template.\n                Defaults to 'iter_{}.pth'.\n            meta (dict, optional): Metadata to be saved in checkpoint.\n                Defaults to None.\n            save_optimizer (bool, optional): Whether save optimizer.\n                Defaults to True.\n            create_symlink (bool, optional): Whether create symlink to the\n                latest checkpoint file. Defaults to True.\n        \"\"\"\n        if meta is None:\n            meta = dict(iter=self.iter + 1, epoch=self.epoch + 1)\n        elif isinstance(meta, dict):\n            meta.update(iter=self.iter + 1, epoch=self.epoch + 1)\n        else:\n            raise TypeError(\n                f'meta should be a dict or None, but got {type(meta)}')\n        meta.update(self.meta)\n\n        filename = filename_tmpl.format(self.iter + 1)\n        filepath = osp.join(out_dir, filename)\n        optimizer = self.optimizer if save_optimizer else None\n        save_checkpoint(self.model, filepath, optimizer=optimizer, meta=meta)\n        # in some environments, `os.symlink` is not supported, you may need to\n        # set `create_symlink` to False\n        if create_symlink:\n            mmcv.symlink(filename, osp.join(out_dir, 'latest.pth'))\n\n    def register_training_hooks(self,\n                                lr_config,\n                                optimizer_config=None,\n                                checkpoint_config=None,\n                                log_config=None,\n                                momentum_config=None):\n        \"\"\"Register default hooks for iter-based training.\n\n        Default hooks include:\n\n        - LrUpdaterHook\n        - MomentumUpdaterHook\n        - OptimizerStepperHook\n        - CheckpointSaverHook\n        - IterTimerHook\n        - LoggerHook(s)\n        \"\"\"\n        if checkpoint_config is not None:\n            checkpoint_config.setdefault('by_epoch', False)\n        if lr_config is not None:\n            lr_config.setdefault('by_epoch', False)\n        self.register_lr_hook(lr_config)\n        self.register_momentum_hook(momentum_config)\n        self.register_optimizer_hook(optimizer_config)\n        self.register_checkpoint_hook(checkpoint_config)\n        self.register_hook(IterTimerHook())\n        if log_config is not None:\n            log_config.setdefault('by_epoch', False)\n        self.register_logger_hooks(log_config)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/log_buffer.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom collections import OrderedDict\n\nimport numpy as np\n\n\nclass LogBuffer:\n\n    def __init__(self):\n        self.val_history = OrderedDict()\n        self.n_history = OrderedDict()\n        self.output = OrderedDict()\n        self.ready = False\n\n    def clear(self):\n        self.val_history.clear()\n        self.n_history.clear()\n        self.clear_output()\n\n    def clear_output(self):\n        self.output.clear()\n        self.ready = False\n\n    def update(self, vars, count=1):\n        assert isinstance(vars, dict)\n        for key, var in vars.items():\n            if key not in self.val_history:\n                self.val_history[key] = []\n                self.n_history[key] = []\n            self.val_history[key].append(var)\n            self.n_history[key].append(count)\n\n    def average(self, n=0):\n        \"\"\"Average latest n values or all values\"\"\"\n        assert n >= 0\n        for key in self.val_history:\n            values = np.array(self.val_history[key][-n:])\n            nums = np.array(self.n_history[key][-n:])\n            avg = np.sum(values * nums) / np.sum(nums)\n            self.output[key] = avg\n        self.ready = True\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/optimizer/__init__.py",
    "content": "from .builder import (OPTIMIZER_BUILDERS, OPTIMIZERS, build_optimizer,\n                      build_optimizer_constructor)\nfrom .default_constructor import DefaultOptimizerConstructor\n\n__all__ = [\n    'OPTIMIZER_BUILDERS', 'OPTIMIZERS', 'DefaultOptimizerConstructor',\n    'build_optimizer', 'build_optimizer_constructor'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/optimizer/builder.py",
    "content": "import copy\nimport inspect\n\nimport torch\n\nfrom ...utils import Registry, build_from_cfg\n\nOPTIMIZERS = Registry('optimizer')\nOPTIMIZER_BUILDERS = Registry('optimizer builder')\n\n\ndef register_torch_optimizers():\n    torch_optimizers = []\n    for module_name in dir(torch.optim):\n        if module_name.startswith('__'):\n            continue\n        _optim = getattr(torch.optim, module_name)\n        if inspect.isclass(_optim) and issubclass(_optim,\n                                                  torch.optim.Optimizer):\n            OPTIMIZERS.register_module()(_optim)\n            torch_optimizers.append(module_name)\n    return torch_optimizers\n\n\nTORCH_OPTIMIZERS = register_torch_optimizers()\n\n\ndef build_optimizer_constructor(cfg):\n    return build_from_cfg(cfg, OPTIMIZER_BUILDERS)\n\n\ndef build_optimizer(model, cfg):\n    optimizer_cfg = copy.deepcopy(cfg)\n    constructor_type = optimizer_cfg.pop('constructor',\n                                         'DefaultOptimizerConstructor')\n    paramwise_cfg = optimizer_cfg.pop('paramwise_cfg', None)\n    optim_constructor = build_optimizer_constructor(\n        dict(\n            type=constructor_type,\n            optimizer_cfg=optimizer_cfg,\n            paramwise_cfg=paramwise_cfg))\n    optimizer = optim_constructor(model)\n    return optimizer\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/optimizer/default_constructor.py",
    "content": "import warnings\n\nimport torch\nfrom torch.nn import GroupNorm, LayerNorm\n\nfrom mmcv.utils import _BatchNorm, _InstanceNorm, build_from_cfg, is_list_of\nfrom .builder import OPTIMIZER_BUILDERS, OPTIMIZERS\n\n\n@OPTIMIZER_BUILDERS.register_module()\nclass DefaultOptimizerConstructor:\n    \"\"\"Default constructor for optimizers.\n\n    By default each parameter share the same optimizer settings, and we\n    provide an argument ``paramwise_cfg`` to specify parameter-wise settings.\n    It is a dict and may contain the following fields:\n\n    - ``custom_keys`` (dict): Specified parameters-wise settings by keys. If\n      one of the keys in ``custom_keys`` is a substring of the name of one\n      parameter, then the setting of the parameter will be specified by\n      ``custom_keys[key]`` and other setting like ``bias_lr_mult`` etc. will\n      be ignored. It should be noted that the aforementioned ``key`` is the\n      longest key that is a substring of the name of the parameter. If there\n      are multiple matched keys with the same length, then the key with lower\n      alphabet order will be chosen.\n      ``custom_keys[key]`` should be a dict and may contain fields ``lr_mult``\n      and ``decay_mult``. See Example 2 below.\n    - ``bias_lr_mult`` (float): It will be multiplied to the learning\n      rate for all bias parameters (except for those in normalization\n      layers).\n    - ``bias_decay_mult`` (float): It will be multiplied to the weight\n      decay for all bias parameters (except for those in\n      normalization layers and depthwise conv layers).\n    - ``norm_decay_mult`` (float): It will be multiplied to the weight\n      decay for all weight and bias parameters of normalization\n      layers.\n    - ``dwconv_decay_mult`` (float): It will be multiplied to the weight\n      decay for all weight and bias parameters of depthwise conv\n      layers.\n    - ``bypass_duplicate`` (bool): If true, the duplicate parameters\n      would not be added into optimizer. Default: False.\n\n    Args:\n        model (:obj:`nn.Module`): The model with parameters to be optimized.\n        optimizer_cfg (dict): The config dict of the optimizer.\n            Positional fields are\n                - `type`: class name of the optimizer.\n            Optional fields are\n                - any arguments of the corresponding optimizer type, e.g.,\n                  lr, weight_decay, momentum, etc.\n        paramwise_cfg (dict, optional): Parameter-wise options.\n\n    Example 1:\n        >>> model = torch.nn.modules.Conv1d(1, 1, 1)\n        >>> optimizer_cfg = dict(type='SGD', lr=0.01, momentum=0.9,\n        >>>                      weight_decay=0.0001)\n        >>> paramwise_cfg = dict(norm_decay_mult=0.)\n        >>> optim_builder = DefaultOptimizerConstructor(\n        >>>     optimizer_cfg, paramwise_cfg)\n        >>> optimizer = optim_builder(model)\n\n    Example 2:\n        >>> # assume model have attribute model.backbone and model.cls_head\n        >>> optimizer_cfg = dict(type='SGD', lr=0.01, weight_decay=0.95)\n        >>> paramwise_cfg = dict(custom_keys={\n                '.backbone': dict(lr_mult=0.1, decay_mult=0.9)})\n        >>> optim_builder = DefaultOptimizerConstructor(\n        >>>     optimizer_cfg, paramwise_cfg)\n        >>> optimizer = optim_builder(model)\n        >>> # Then the `lr` and `weight_decay` for model.backbone is\n        >>> # (0.01 * 0.1, 0.95 * 0.9). `lr` and `weight_decay` for\n        >>> # model.cls_head is (0.01, 0.95).\n    \"\"\"\n\n    def __init__(self, optimizer_cfg, paramwise_cfg=None):\n        if not isinstance(optimizer_cfg, dict):\n            raise TypeError('optimizer_cfg should be a dict',\n                            f'but got {type(optimizer_cfg)}')\n        self.optimizer_cfg = optimizer_cfg\n        self.paramwise_cfg = {} if paramwise_cfg is None else paramwise_cfg\n        self.base_lr = optimizer_cfg.get('lr', None)\n        self.base_wd = optimizer_cfg.get('weight_decay', None)\n        self._validate_cfg()\n\n    def _validate_cfg(self):\n        if not isinstance(self.paramwise_cfg, dict):\n            raise TypeError('paramwise_cfg should be None or a dict, '\n                            f'but got {type(self.paramwise_cfg)}')\n\n        if 'custom_keys' in self.paramwise_cfg:\n            if not isinstance(self.paramwise_cfg['custom_keys'], dict):\n                raise TypeError(\n                    'If specified, custom_keys must be a dict, '\n                    f'but got {type(self.paramwise_cfg[\"custom_keys\"])}')\n            if self.base_wd is None:\n                for key in self.paramwise_cfg['custom_keys']:\n                    if 'decay_mult' in self.paramwise_cfg['custom_keys'][key]:\n                        raise ValueError('base_wd should not be None')\n\n        # get base lr and weight decay\n        # weight_decay must be explicitly specified if mult is specified\n        if ('bias_decay_mult' in self.paramwise_cfg\n                or 'norm_decay_mult' in self.paramwise_cfg\n                or 'dwconv_decay_mult' in self.paramwise_cfg):\n            if self.base_wd is None:\n                raise ValueError('base_wd should not be None')\n\n    def _is_in(self, param_group, param_group_list):\n        assert is_list_of(param_group_list, dict)\n        param = set(param_group['params'])\n        param_set = set()\n        for group in param_group_list:\n            param_set.update(set(group['params']))\n\n        return not param.isdisjoint(param_set)\n\n    def add_params(self, params, module, prefix=''):\n        \"\"\"Add all parameters of module to the params list.\n\n        The parameters of the given module will be added to the list of param\n        groups, with specific rules defined by paramwise_cfg.\n\n        Args:\n            params (list[dict]): A list of param groups, it will be modified\n                in place.\n            module (nn.Module): The module to be added.\n            prefix (str): The prefix of the module\n        \"\"\"\n        # get param-wise options\n        custom_keys = self.paramwise_cfg.get('custom_keys', {})\n        # first sort with alphabet order and then sort with reversed len of str\n        sorted_keys = sorted(sorted(custom_keys.keys()), key=len, reverse=True)\n\n        bias_lr_mult = self.paramwise_cfg.get('bias_lr_mult', 1.)\n        bias_decay_mult = self.paramwise_cfg.get('bias_decay_mult', 1.)\n        norm_decay_mult = self.paramwise_cfg.get('norm_decay_mult', 1.)\n        dwconv_decay_mult = self.paramwise_cfg.get('dwconv_decay_mult', 1.)\n        bypass_duplicate = self.paramwise_cfg.get('bypass_duplicate', False)\n\n        # special rules for norm layers and depth-wise conv layers\n        is_norm = isinstance(module,\n                             (_BatchNorm, _InstanceNorm, GroupNorm, LayerNorm))\n        is_dwconv = (\n            isinstance(module, torch.nn.Conv2d)\n            and module.in_channels == module.groups)\n\n        for name, param in module.named_parameters(recurse=False):\n            param_group = {'params': [param]}\n            if not param.requires_grad:\n                params.append(param_group)\n                continue\n            if bypass_duplicate and self._is_in(param_group, params):\n                warnings.warn(f'{prefix} is duplicate. It is skipped since '\n                              f'bypass_duplicate={bypass_duplicate}')\n                continue\n            # if the parameter match one of the custom keys, ignore other rules\n            is_custom = False\n            for key in sorted_keys:\n                if key in f'{prefix}.{name}':\n                    is_custom = True\n                    lr_mult = custom_keys[key].get('lr_mult', 1.)\n                    param_group['lr'] = self.base_lr * lr_mult\n                    if self.base_wd is not None:\n                        decay_mult = custom_keys[key].get('decay_mult', 1.)\n                        param_group['weight_decay'] = self.base_wd * decay_mult\n                    break\n            if not is_custom:\n                # bias_lr_mult affects all bias parameters except for norm.bias\n                if name == 'bias' and not is_norm:\n                    param_group['lr'] = self.base_lr * bias_lr_mult\n                # apply weight decay policies\n                if self.base_wd is not None:\n                    # norm decay\n                    if is_norm:\n                        param_group[\n                            'weight_decay'] = self.base_wd * norm_decay_mult\n                    # depth-wise conv\n                    elif is_dwconv:\n                        param_group[\n                            'weight_decay'] = self.base_wd * dwconv_decay_mult\n                    # bias lr and decay\n                    elif name == 'bias':\n                        param_group[\n                            'weight_decay'] = self.base_wd * bias_decay_mult\n            params.append(param_group)\n\n        for child_name, child_mod in module.named_children():\n            child_prefix = f'{prefix}.{child_name}' if prefix else child_name\n            self.add_params(params, child_mod, prefix=child_prefix)\n\n    def __call__(self, model):\n        if hasattr(model, 'module'):\n            model = model.module\n\n        optimizer_cfg = self.optimizer_cfg.copy()\n        # if no paramwise option is specified, just use the global setting\n        if not self.paramwise_cfg:\n            optimizer_cfg['params'] = model.parameters()\n            return build_from_cfg(optimizer_cfg, OPTIMIZERS)\n\n        # set param-wise lr and weight decay recursively\n        params = []\n        self.add_params(params, model)\n        optimizer_cfg['params'] = params\n\n        return build_from_cfg(optimizer_cfg, OPTIMIZERS)\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/priority.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom enum import Enum\n\n\nclass Priority(Enum):\n    \"\"\"Hook priority levels.\n\n    +------------+------------+\n    | Level      | Value      |\n    +============+============+\n    | HIGHEST    | 0          |\n    +------------+------------+\n    | VERY_HIGH  | 10         |\n    +------------+------------+\n    | HIGH       | 30         |\n    +------------+------------+\n    | NORMAL     | 50         |\n    +------------+------------+\n    | LOW        | 70         |\n    +------------+------------+\n    | VERY_LOW   | 90         |\n    +------------+------------+\n    | LOWEST     | 100        |\n    +------------+------------+\n    \"\"\"\n\n    HIGHEST = 0\n    VERY_HIGH = 10\n    HIGH = 30\n    NORMAL = 50\n    LOW = 70\n    VERY_LOW = 90\n    LOWEST = 100\n\n\ndef get_priority(priority):\n    \"\"\"Get priority value.\n\n    Args:\n        priority (int or str or :obj:`Priority`): Priority.\n\n    Returns:\n        int: The priority value.\n    \"\"\"\n    if isinstance(priority, int):\n        if priority < 0 or priority > 100:\n            raise ValueError('priority must be between 0 and 100')\n        return priority\n    elif isinstance(priority, Priority):\n        return priority.value\n    elif isinstance(priority, str):\n        return Priority[priority.upper()].value\n    else:\n        raise TypeError('priority must be an integer or Priority enum value')\n"
  },
  {
    "path": "code/mmcv/mmcv/runner/utils.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport sys\nimport time\nfrom getpass import getuser\nfrom socket import gethostname\n\nimport mmcv\n\n\ndef get_host_info():\n    return f'{getuser()}@{gethostname()}'\n\n\ndef get_time_str():\n    return time.strftime('%Y%m%d_%H%M%S', time.localtime())\n\n\ndef obj_from_dict(info, parent=None, default_args=None):\n    \"\"\"Initialize an object from dict.\n\n    The dict must contain the key \"type\", which indicates the object type, it\n    can be either a string or type, such as \"list\" or ``list``. Remaining\n    fields are treated as the arguments for constructing the object.\n\n    Args:\n        info (dict): Object types and arguments.\n        parent (:class:`module`): Module which may containing expected object\n            classes.\n        default_args (dict, optional): Default arguments for initializing the\n            object.\n\n    Returns:\n        any type: Object built from the dict.\n    \"\"\"\n    assert isinstance(info, dict) and 'type' in info\n    assert isinstance(default_args, dict) or default_args is None\n    args = info.copy()\n    obj_type = args.pop('type')\n    if mmcv.is_str(obj_type):\n        if parent is not None:\n            obj_type = getattr(parent, obj_type)\n        else:\n            obj_type = sys.modules[obj_type]\n    elif not isinstance(obj_type, type):\n        raise TypeError('type must be a str or valid type, but '\n                        f'got {type(obj_type)}')\n    if default_args is not None:\n        for name, value in default_args.items():\n            args.setdefault(name, value)\n    return obj_type(**args)\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/__init__.py",
    "content": "# flake8: noqa\n# Copyright (c) Open-MMLab. All rights reserved.\nfrom .config import Config, ConfigDict, DictAction\nfrom .misc import (check_prerequisites, concat_list, is_list_of, is_seq_of,\n                   is_str, is_tuple_of, iter_cast, list_cast,\n                   requires_executable, requires_package, slice_list,\n                   tuple_cast)\nfrom .path import (check_file_exist, fopen, is_filepath, mkdir_or_exist,\n                   scandir, symlink)\nfrom .progressbar import (ProgressBar, track_iter_progress,\n                          track_parallel_progress, track_progress)\nfrom .timer import Timer, TimerError, check_time\n\ntry:\n    import torch\nexcept ImportError:\n    __all__ = [\n        'Config', 'ConfigDict', 'DictAction', 'is_str', 'iter_cast',\n        'list_cast', 'tuple_cast', 'is_seq_of', 'is_list_of', 'is_tuple_of',\n        'slice_list', 'concat_list', 'check_prerequisites', 'requires_package',\n        'requires_executable', 'is_filepath', 'fopen', 'check_file_exist',\n        'mkdir_or_exist', 'symlink', 'scandir', 'ProgressBar',\n        'track_progress', 'track_iter_progress', 'track_parallel_progress',\n        'Timer', 'TimerError', 'check_time'\n    ]\nelse:\n    from .env import TORCH_VERSION\n    from .logging import get_logger, print_log\n    from .parrots_wrapper import (CUDA_HOME, BuildExtension, CppExtension,\n                                  CUDAExtension, DataLoader, PoolDataLoader,\n                                  SyncBatchNorm, _AdaptiveAvgPoolNd,\n                                  _AdaptiveMaxPoolNd, _AvgPoolNd, _BatchNorm,\n                                  _ConvNd, _ConvTransposeMixin, _InstanceNorm,\n                                  _MaxPoolNd, get_build_config)\n    from .registry import Registry, build_from_cfg\n    __all__ = [\n        'Config', 'ConfigDict', 'DictAction', 'get_logger', 'print_log',\n        'is_str', 'iter_cast', 'list_cast', 'tuple_cast', 'is_seq_of',\n        'is_list_of', 'is_tuple_of', 'slice_list', 'concat_list',\n        'check_prerequisites', 'requires_package', 'requires_executable',\n        'is_filepath', 'fopen', 'check_file_exist', 'mkdir_or_exist',\n        'symlink', 'scandir', 'ProgressBar', 'track_progress',\n        'track_iter_progress', 'track_parallel_progress', 'Registry',\n        'build_from_cfg', 'Timer', 'TimerError', 'check_time', 'CUDA_HOME',\n        'SyncBatchNorm', '_AdaptiveAvgPoolNd', '_AdaptiveMaxPoolNd',\n        '_AvgPoolNd', '_BatchNorm', '_ConvNd', '_ConvTransposeMixin',\n        '_InstanceNorm', '_MaxPoolNd', 'get_build_config', 'BuildExtension',\n        'CppExtension', 'CUDAExtension', 'DataLoader', 'PoolDataLoader',\n        'TORCH_VERSION'\n    ]\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/config.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport ast\nimport os.path as osp\nimport shutil\nimport sys\nimport tempfile\nfrom argparse import Action, ArgumentParser\nfrom collections import abc\nfrom importlib import import_module\n\nfrom addict import Dict\nfrom yapf.yapflib.yapf_api import FormatCode\n\nfrom .path import check_file_exist\n\nBASE_KEY = '_base_'\nDELETE_KEY = '_delete_'\nRESERVED_KEYS = ['filename', 'text', 'pretty_text']\n\n\nclass ConfigDict(Dict):\n\n    def __missing__(self, name):\n        raise KeyError(name)\n\n    def __getattr__(self, name):\n        try:\n            value = super(ConfigDict, self).__getattr__(name)\n        except KeyError:\n            ex = AttributeError(f\"'{self.__class__.__name__}' object has no \"\n                                f\"attribute '{name}'\")\n        except Exception as e:\n            ex = e\n        else:\n            return value\n        raise ex\n\n\ndef add_args(parser, cfg, prefix=''):\n    for k, v in cfg.items():\n        if isinstance(v, str):\n            parser.add_argument('--' + prefix + k)\n        elif isinstance(v, int):\n            parser.add_argument('--' + prefix + k, type=int)\n        elif isinstance(v, float):\n            parser.add_argument('--' + prefix + k, type=float)\n        elif isinstance(v, bool):\n            parser.add_argument('--' + prefix + k, action='store_true')\n        elif isinstance(v, dict):\n            add_args(parser, v, prefix + k + '.')\n        elif isinstance(v, abc.Iterable):\n            parser.add_argument('--' + prefix + k, type=type(v[0]), nargs='+')\n        else:\n            print(f'cannot parse key {prefix + k} of type {type(v)}')\n    return parser\n\n\nclass Config:\n    \"\"\"A facility for config and config files.\n\n    It supports common file formats as configs: python/json/yaml. The interface\n    is the same as a dict object and also allows access config values as\n    attributes.\n\n    Example:\n        >>> cfg = Config(dict(a=1, b=dict(b1=[0, 1])))\n        >>> cfg.a\n        1\n        >>> cfg.b\n        {'b1': [0, 1]}\n        >>> cfg.b.b1\n        [0, 1]\n        >>> cfg = Config.fromfile('tests/data/config/a.py')\n        >>> cfg.filename\n        \"/home/kchen/projects/mmcv/tests/data/config/a.py\"\n        >>> cfg.item4\n        'test'\n        >>> cfg\n        \"Config [path: /home/kchen/projects/mmcv/tests/data/config/a.py]: \"\n        \"{'item1': [1, 2], 'item2': {'a': 0}, 'item3': True, 'item4': 'test'}\"\n\n    \"\"\"\n\n    @staticmethod\n    def _validate_py_syntax(filename):\n        with open(filename) as f:\n            content = f.read()\n        try:\n            ast.parse(content)\n        except SyntaxError:\n            raise SyntaxError('There are syntax errors in config '\n                              f'file {filename}')\n\n    @staticmethod\n    def _file2dict(filename):\n        filename = osp.abspath(osp.expanduser(filename))\n        check_file_exist(filename)\n        if filename.endswith('.py'):\n            with tempfile.TemporaryDirectory() as temp_config_dir:\n                temp_config_file = tempfile.NamedTemporaryFile(\n                    dir=temp_config_dir, suffix='.py')\n                temp_config_name = osp.basename(temp_config_file.name)\n                shutil.copyfile(filename,\n                                osp.join(temp_config_dir, temp_config_name))\n                temp_module_name = osp.splitext(temp_config_name)[0]\n                sys.path.insert(0, temp_config_dir)\n                Config._validate_py_syntax(filename)\n                mod = import_module(temp_module_name)\n                sys.path.pop(0)\n                cfg_dict = {\n                    name: value\n                    for name, value in mod.__dict__.items()\n                    if not name.startswith('__')\n                }\n                # delete imported module\n                del sys.modules[temp_module_name]\n                # close temp file\n                temp_config_file.close()\n        elif filename.endswith(('.yml', '.yaml', '.json')):\n            import mmcv\n            cfg_dict = mmcv.load(filename)\n        else:\n            raise IOError('Only py/yml/yaml/json type are supported now!')\n\n        cfg_text = filename + '\\n'\n        with open(filename, 'r') as f:\n            cfg_text += f.read()\n\n        if BASE_KEY in cfg_dict:\n            cfg_dir = osp.dirname(filename)\n            base_filename = cfg_dict.pop(BASE_KEY)\n            base_filename = base_filename if isinstance(\n                base_filename, list) else [base_filename]\n\n            cfg_dict_list = list()\n            cfg_text_list = list()\n            for f in base_filename:\n                _cfg_dict, _cfg_text = Config._file2dict(osp.join(cfg_dir, f))\n                cfg_dict_list.append(_cfg_dict)\n                cfg_text_list.append(_cfg_text)\n\n            base_cfg_dict = dict()\n            for c in cfg_dict_list:\n                if len(base_cfg_dict.keys() & c.keys()) > 0:\n                    raise KeyError('Duplicate key is not allowed among bases')\n                base_cfg_dict.update(c)\n\n            base_cfg_dict = Config._merge_a_into_b(cfg_dict, base_cfg_dict)\n            cfg_dict = base_cfg_dict\n\n            # merge cfg_text\n            cfg_text_list.append(cfg_text)\n            cfg_text = '\\n'.join(cfg_text_list)\n\n        return cfg_dict, cfg_text\n\n    @staticmethod\n    def _merge_a_into_b(a, b):\n        # merge dict `a` into dict `b` (non-inplace). values in `a` will\n        # overwrite `b`.\n        # copy first to avoid inplace modification\n        b = b.copy()\n        for k, v in a.items():\n            if isinstance(v, dict) and k in b and not v.pop(DELETE_KEY, False):\n                if not isinstance(b[k], dict):\n                    raise TypeError(\n                        f'{k}={v} in child config cannot inherit from base '\n                        f'because {k} is a dict in the child config but is of '\n                        f'type {type(b[k])} in base config. You may set '\n                        f'`{DELETE_KEY}=True` to ignore the base config')\n                b[k] = Config._merge_a_into_b(v, b[k])\n            else:\n                b[k] = v\n        return b\n\n    @staticmethod\n    def fromfile(filename):\n        cfg_dict, cfg_text = Config._file2dict(filename)\n        return Config(cfg_dict, cfg_text=cfg_text, filename=filename)\n\n    @staticmethod\n    def auto_argparser(description=None):\n        \"\"\"Generate argparser from config file automatically (experimental)\n        \"\"\"\n        partial_parser = ArgumentParser(description=description)\n        partial_parser.add_argument('config', help='config file path')\n        cfg_file = partial_parser.parse_known_args()[0].config\n        cfg = Config.fromfile(cfg_file)\n        parser = ArgumentParser(description=description)\n        parser.add_argument('config', help='config file path')\n        add_args(parser, cfg)\n        return parser, cfg\n\n    def __init__(self, cfg_dict=None, cfg_text=None, filename=None):\n        if cfg_dict is None:\n            cfg_dict = dict()\n        elif not isinstance(cfg_dict, dict):\n            raise TypeError('cfg_dict must be a dict, but '\n                            f'got {type(cfg_dict)}')\n        for key in cfg_dict:\n            if key in RESERVED_KEYS:\n                raise KeyError(f'{key} is reserved for config file')\n\n        super(Config, self).__setattr__('_cfg_dict', ConfigDict(cfg_dict))\n        super(Config, self).__setattr__('_filename', filename)\n        if cfg_text:\n            text = cfg_text\n        elif filename:\n            with open(filename, 'r') as f:\n                text = f.read()\n        else:\n            text = ''\n        super(Config, self).__setattr__('_text', text)\n\n    @property\n    def filename(self):\n        return self._filename\n\n    @property\n    def text(self):\n        return self._text\n\n    @property\n    def pretty_text(self):\n\n        indent = 4\n\n        def _indent(s_, num_spaces):\n            s = s_.split('\\n')\n            if len(s) == 1:\n                return s_\n            first = s.pop(0)\n            s = [(num_spaces * ' ') + line for line in s]\n            s = '\\n'.join(s)\n            s = first + '\\n' + s\n            return s\n\n        def _format_basic_types(k, v, use_mapping=False):\n            if isinstance(v, str):\n                v_str = f\"'{v}'\"\n            else:\n                v_str = str(v)\n\n            if use_mapping:\n                k_str = f\"'{k}'\" if isinstance(k, str) else str(k)\n                attr_str = f'{k_str}: {v_str}'\n            else:\n                attr_str = f'{str(k)}={v_str}'\n            attr_str = _indent(attr_str, indent)\n\n            return attr_str\n\n        def _format_list(k, v, use_mapping=False):\n            # check if all items in the list are dict\n            if all(isinstance(_, dict) for _ in v):\n                v_str = '[\\n'\n                v_str += '\\n'.join(\n                    f'dict({_indent(_format_dict(v_), indent)}),'\n                    for v_ in v).rstrip(',')\n                if use_mapping:\n                    k_str = f\"'{k}'\" if isinstance(k, str) else str(k)\n                    attr_str = f'{k_str}: {v_str}'\n                else:\n                    attr_str = f'{str(k)}={v_str}'\n                attr_str = _indent(attr_str, indent) + ']'\n            else:\n                attr_str = _format_basic_types(k, v, use_mapping)\n            return attr_str\n\n        def _contain_invalid_identifier(dict_str):\n            contain_invalid_identifier = False\n            for key_name in dict_str:\n                contain_invalid_identifier |= \\\n                    (not str(key_name).isidentifier())\n            return contain_invalid_identifier\n\n        def _format_dict(input_dict, outest_level=False):\n            r = ''\n            s = []\n\n            use_mapping = _contain_invalid_identifier(input_dict)\n            if use_mapping:\n                r += '{'\n            for idx, (k, v) in enumerate(input_dict.items()):\n                is_last = idx >= len(input_dict) - 1\n                end = '' if outest_level or is_last else ','\n                if isinstance(v, dict):\n                    v_str = '\\n' + _format_dict(v)\n                    if use_mapping:\n                        k_str = f\"'{k}'\" if isinstance(k, str) else str(k)\n                        attr_str = f'{k_str}: dict({v_str}'\n                    else:\n                        attr_str = f'{str(k)}=dict({v_str}'\n                    attr_str = _indent(attr_str, indent) + ')' + end\n                elif isinstance(v, list):\n                    attr_str = _format_list(k, v, use_mapping) + end\n                else:\n                    attr_str = _format_basic_types(k, v, use_mapping) + end\n\n                s.append(attr_str)\n            r += '\\n'.join(s)\n            if use_mapping:\n                r += '}'\n            return r\n\n        cfg_dict = self._cfg_dict.to_dict()\n        text = _format_dict(cfg_dict, outest_level=True)\n        # copied from setup.cfg\n        yapf_style = dict(\n            based_on_style='pep8',\n            blank_line_before_nested_class_or_def=True,\n            split_before_expression_after_opening_paren=True)\n        text, _ = FormatCode(text, style_config=yapf_style, verify=True)\n\n        return text\n\n    def __repr__(self):\n        return f'Config (path: {self.filename}): {self._cfg_dict.__repr__()}'\n\n    def __len__(self):\n        return len(self._cfg_dict)\n\n    def __getattr__(self, name):\n        return getattr(self._cfg_dict, name)\n\n    def __getitem__(self, name):\n        return self._cfg_dict.__getitem__(name)\n\n    def __setattr__(self, name, value):\n        if isinstance(value, dict):\n            value = ConfigDict(value)\n        self._cfg_dict.__setattr__(name, value)\n\n    def __setitem__(self, name, value):\n        if isinstance(value, dict):\n            value = ConfigDict(value)\n        self._cfg_dict.__setitem__(name, value)\n\n    def __iter__(self):\n        return iter(self._cfg_dict)\n\n    def dump(self, file=None):\n        cfg_dict = super(Config, self).__getattribute__('_cfg_dict').to_dict()\n        if self.filename.endswith('.py'):\n            if file is None:\n                return self.pretty_text\n            else:\n                with open(file, 'w') as f:\n                    f.write(self.pretty_text)\n        else:\n            import mmcv\n            if file is None:\n                file_format = self.filename.split('.')[-1]\n                return mmcv.dump(cfg_dict, file_format=file_format)\n            else:\n                mmcv.dump(cfg_dict, file)\n\n    def merge_from_dict(self, options):\n        \"\"\"Merge list into cfg_dict\n\n        Merge the dict parsed by MultipleKVAction into this cfg.\n\n        Examples:\n            >>> options = {'model.backbone.depth': 50,\n            ...            'model.backbone.with_cp':True}\n            >>> cfg = Config(dict(model=dict(backbone=dict(type='ResNet'))))\n            >>> cfg.merge_from_dict(options)\n            >>> cfg_dict = super(Config, self).__getattribute__('_cfg_dict')\n            >>> assert cfg_dict == dict(\n            ...     model=dict(backbone=dict(depth=50, with_cp=True)))\n\n        Args:\n            options (dict): dict of configs to merge from.\n        \"\"\"\n        option_cfg_dict = {}\n        for full_key, v in options.items():\n            d = option_cfg_dict\n            key_list = full_key.split('.')\n            for subkey in key_list[:-1]:\n                d.setdefault(subkey, ConfigDict())\n                d = d[subkey]\n            subkey = key_list[-1]\n            d[subkey] = v\n\n        cfg_dict = super(Config, self).__getattribute__('_cfg_dict')\n        super(Config, self).__setattr__(\n            '_cfg_dict', Config._merge_a_into_b(option_cfg_dict, cfg_dict))\n\n\nclass DictAction(Action):\n    \"\"\"\n    argparse action to split an argument into KEY=VALUE form\n    on the first = and append to a dictionary. List options should\n    be passed as comma separated values, i.e KEY=V1,V2,V3\n    \"\"\"\n\n    @staticmethod\n    def _parse_int_float_bool(val):\n        try:\n            return int(val)\n        except ValueError:\n            pass\n        try:\n            return float(val)\n        except ValueError:\n            pass\n        if val.lower() in ['true', 'false']:\n            return True if val.lower() == 'true' else False\n        return val\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        options = {}\n        for kv in values:\n            key, val = kv.split('=', maxsplit=1)\n            val = [self._parse_int_float_bool(v) for v in val.split(',')]\n            if len(val) == 1:\n                val = val[0]\n            options[key] = val\n        setattr(namespace, self.dest, options)\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/env.py",
    "content": "# This file holding some environment constant for sharing by other files\nimport torch\n\nTORCH_VERSION = torch.__version__\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/logging.py",
    "content": "import logging\n\nimport torch.distributed as dist\n\nlogger_initialized = {}\n\n\ndef get_logger(name, log_file=None, log_level=logging.INFO):\n    \"\"\"Initialize and get a logger by name.\n\n    If the logger has not been initialized, this method will initialize the\n    logger by adding one or two handlers, otherwise the initialized logger will\n    be directly returned. During initialization, a StreamHandler will always be\n    added. If `log_file` is specified and the process rank is 0, a FileHandler\n    will also be added.\n\n    Args:\n        name (str): Logger name.\n        log_file (str | None): The log filename. If specified, a FileHandler\n            will be added to the logger.\n        log_level (int): The logger level. Note that only the process of\n            rank 0 is affected, and other processes will set the level to\n            \"Error\" thus be silent most of the time.\n\n    Returns:\n        logging.Logger: The expected logger.\n    \"\"\"\n    logger = logging.getLogger(name)\n    if name in logger_initialized:\n        return logger\n    # handle hierarchical names\n    # e.g., logger \"a\" is initialized, then logger \"a.b\" will skip the\n    # initialization since it is a child of \"a\".\n    for logger_name in logger_initialized:\n        if name.startswith(logger_name):\n            return logger\n\n    stream_handler = logging.StreamHandler()\n    handlers = [stream_handler]\n\n    if dist.is_available() and dist.is_initialized():\n        rank = dist.get_rank()\n    else:\n        rank = 0\n\n    # only rank 0 will add a FileHandler\n    if rank == 0 and log_file is not None:\n        file_handler = logging.FileHandler(log_file, 'w')\n        handlers.append(file_handler)\n\n    formatter = logging.Formatter(\n        '%(asctime)s - %(name)s - %(levelname)s - %(message)s')\n    for handler in handlers:\n        handler.setFormatter(formatter)\n        handler.setLevel(log_level)\n        logger.addHandler(handler)\n\n    if rank == 0:\n        logger.setLevel(log_level)\n    else:\n        logger.setLevel(logging.ERROR)\n\n    logger_initialized[name] = True\n\n    return logger\n\n\ndef print_log(msg, logger=None, level=logging.INFO):\n    \"\"\"Print a log message.\n\n    Args:\n        msg (str): The message to be logged.\n        logger (logging.Logger | str | None): The logger to be used.\n            Some special loggers are:\n            - \"silent\": no message will be printed.\n            - other str: the logger obtained with `get_root_logger(logger)`.\n            - None: The `print()` method will be used to print log messages.\n        level (int): Logging level. Only available when `logger` is a Logger\n            object or \"root\".\n    \"\"\"\n    if logger is None:\n        print(msg)\n    elif isinstance(logger, logging.Logger):\n        logger.log(level, msg)\n    elif logger == 'silent':\n        pass\n    elif isinstance(logger, str):\n        _logger = get_logger(logger)\n        _logger.log(level, msg)\n    else:\n        raise TypeError(\n            'logger should be either a logging.Logger object, str, '\n            f'\"silent\" or None, but got {type(logger)}')\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/misc.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport functools\nimport itertools\nimport subprocess\nfrom collections import abc\nfrom importlib import import_module\n\n\ndef is_str(x):\n    \"\"\"Whether the input is an string instance.\n\n    Note: This method is deprecated since python 2 is no longer supported.\n    \"\"\"\n    return isinstance(x, str)\n\n\ndef iter_cast(inputs, dst_type, return_type=None):\n    \"\"\"Cast elements of an iterable object into some type.\n\n    Args:\n        inputs (Iterable): The input object.\n        dst_type (type): Destination type.\n        return_type (type, optional): If specified, the output object will be\n            converted to this type, otherwise an iterator.\n\n    Returns:\n        iterator or specified type: The converted object.\n    \"\"\"\n    if not isinstance(inputs, abc.Iterable):\n        raise TypeError('inputs must be an iterable object')\n    if not isinstance(dst_type, type):\n        raise TypeError('\"dst_type\" must be a valid type')\n\n    out_iterable = map(dst_type, inputs)\n\n    if return_type is None:\n        return out_iterable\n    else:\n        return return_type(out_iterable)\n\n\ndef list_cast(inputs, dst_type):\n    \"\"\"Cast elements of an iterable object into a list of some type.\n\n    A partial method of :func:`iter_cast`.\n    \"\"\"\n    return iter_cast(inputs, dst_type, return_type=list)\n\n\ndef tuple_cast(inputs, dst_type):\n    \"\"\"Cast elements of an iterable object into a tuple of some type.\n\n    A partial method of :func:`iter_cast`.\n    \"\"\"\n    return iter_cast(inputs, dst_type, return_type=tuple)\n\n\ndef is_seq_of(seq, expected_type, seq_type=None):\n    \"\"\"Check whether it is a sequence of some type.\n\n    Args:\n        seq (Sequence): The sequence to be checked.\n        expected_type (type): Expected type of sequence items.\n        seq_type (type, optional): Expected sequence type.\n\n    Returns:\n        bool: Whether the sequence is valid.\n    \"\"\"\n    if seq_type is None:\n        exp_seq_type = abc.Sequence\n    else:\n        assert isinstance(seq_type, type)\n        exp_seq_type = seq_type\n    if not isinstance(seq, exp_seq_type):\n        return False\n    for item in seq:\n        if not isinstance(item, expected_type):\n            return False\n    return True\n\n\ndef is_list_of(seq, expected_type):\n    \"\"\"Check whether it is a list of some type.\n\n    A partial method of :func:`is_seq_of`.\n    \"\"\"\n    return is_seq_of(seq, expected_type, seq_type=list)\n\n\ndef is_tuple_of(seq, expected_type):\n    \"\"\"Check whether it is a tuple of some type.\n\n    A partial method of :func:`is_seq_of`.\n    \"\"\"\n    return is_seq_of(seq, expected_type, seq_type=tuple)\n\n\ndef slice_list(in_list, lens):\n    \"\"\"Slice a list into several sub lists by a list of given length.\n\n    Args:\n        in_list (list): The list to be sliced.\n        lens(int or list): The expected length of each out list.\n\n    Returns:\n        list: A list of sliced list.\n    \"\"\"\n    if isinstance(lens, int):\n        assert len(in_list) % lens == 0\n        lens = [lens] * int(len(in_list) / lens)\n    if not isinstance(lens, list):\n        raise TypeError('\"indices\" must be an integer or a list of integers')\n    elif sum(lens) != len(in_list):\n        raise ValueError('sum of lens and list length does not '\n                         f'match: {sum(lens)} != {len(in_list)}')\n    out_list = []\n    idx = 0\n    for i in range(len(lens)):\n        out_list.append(in_list[idx:idx + lens[i]])\n        idx += lens[i]\n    return out_list\n\n\ndef concat_list(in_list):\n    \"\"\"Concatenate a list of list into a single list.\n\n    Args:\n        in_list (list): The list of list to be merged.\n\n    Returns:\n        list: The concatenated flat list.\n    \"\"\"\n    return list(itertools.chain(*in_list))\n\n\ndef check_prerequisites(\n        prerequisites,\n        checker,\n        msg_tmpl='Prerequisites \"{}\" are required in method \"{}\" but not '\n        'found, please install them first.'):  # yapf: disable\n    \"\"\"A decorator factory to check if prerequisites are satisfied.\n\n    Args:\n        prerequisites (str of list[str]): Prerequisites to be checked.\n        checker (callable): The checker method that returns True if a\n            prerequisite is meet, False otherwise.\n        msg_tmpl (str): The message template with two variables.\n\n    Returns:\n        decorator: A specific decorator.\n    \"\"\"\n\n    def wrap(func):\n\n        @functools.wraps(func)\n        def wrapped_func(*args, **kwargs):\n            requirements = [prerequisites] if isinstance(\n                prerequisites, str) else prerequisites\n            missing = []\n            for item in requirements:\n                if not checker(item):\n                    missing.append(item)\n            if missing:\n                print(msg_tmpl.format(', '.join(missing), func.__name__))\n                raise RuntimeError('Prerequisites not meet.')\n            else:\n                return func(*args, **kwargs)\n\n        return wrapped_func\n\n    return wrap\n\n\ndef _check_py_package(package):\n    try:\n        import_module(package)\n    except ImportError:\n        return False\n    else:\n        return True\n\n\ndef _check_executable(cmd):\n    if subprocess.call(f'which {cmd}', shell=True) != 0:\n        return False\n    else:\n        return True\n\n\ndef requires_package(prerequisites):\n    \"\"\"A decorator to check if some python packages are installed.\n\n    Example:\n        >>> @requires_package('numpy')\n        >>> func(arg1, args):\n        >>>     return numpy.zeros(1)\n        array([0.])\n        >>> @requires_package(['numpy', 'non_package'])\n        >>> func(arg1, args):\n        >>>     return numpy.zeros(1)\n        ImportError\n    \"\"\"\n    return check_prerequisites(prerequisites, checker=_check_py_package)\n\n\ndef requires_executable(prerequisites):\n    \"\"\"A decorator to check if some executable files are installed.\n\n    Example:\n        >>> @requires_executable('ffmpeg')\n        >>> func(arg1, args):\n        >>>     print(1)\n        1\n    \"\"\"\n    return check_prerequisites(prerequisites, checker=_check_executable)\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/parrots_wrapper.py",
    "content": "from functools import partial\n\nimport torch\n\nfrom .env import TORCH_VERSION\n\n\ndef _get_cuda_home():\n    if TORCH_VERSION == 'parrots':\n        from parrots.utils.build_extension import CUDA_HOME\n    else:\n        from torch.utils.cpp_extension import CUDA_HOME\n    return CUDA_HOME\n\n\ndef get_build_config():\n    if TORCH_VERSION == 'parrots':\n        from parrots.config import get_build_info\n        return get_build_info()\n    else:\n        return torch.__config__.show()\n\n\ndef _get_conv():\n    if TORCH_VERSION == 'parrots':\n        from parrots.nn.modules.conv import _ConvNd, _ConvTransposeMixin\n    else:\n        from torch.nn.modules.conv import _ConvNd, _ConvTransposeMixin\n    return _ConvNd, _ConvTransposeMixin\n\n\ndef _get_dataloader():\n    if TORCH_VERSION == 'parrots':\n        from torch.utils.data import DataLoader, PoolDataLoader\n    else:\n        from torch.utils.data import DataLoader\n        PoolDataLoader = DataLoader\n    return DataLoader, PoolDataLoader\n\n\ndef _get_extension():\n    if TORCH_VERSION == 'parrots':\n        from parrots.utils.build_extension import BuildExtension, Extension\n        CppExtension = partial(Extension, cuda=False)\n        CUDAExtension = partial(Extension, cuda=True)\n    else:\n        from torch.utils.cpp_extension import (BuildExtension, CppExtension,\n                                               CUDAExtension)\n    return BuildExtension, CppExtension, CUDAExtension\n\n\ndef _get_pool():\n    if TORCH_VERSION == 'parrots':\n        from parrots.nn.modules.pool import (_AdaptiveAvgPoolNd,\n                                             _AdaptiveMaxPoolNd, _AvgPoolNd,\n                                             _MaxPoolNd)\n    else:\n        from torch.nn.modules.pooling import (_AdaptiveAvgPoolNd,\n                                              _AdaptiveMaxPoolNd, _AvgPoolNd,\n                                              _MaxPoolNd)\n    return _AdaptiveAvgPoolNd, _AdaptiveMaxPoolNd, _AvgPoolNd, _MaxPoolNd\n\n\ndef _get_norm():\n    if TORCH_VERSION == 'parrots':\n        from parrots.nn.modules.batchnorm import _BatchNorm, _InstanceNorm\n        SyncBatchNorm_ = torch.nn.SyncBatchNorm2d\n    else:\n        from torch.nn.modules.instancenorm import _InstanceNorm\n        from torch.nn.modules.batchnorm import _BatchNorm\n        SyncBatchNorm_ = torch.nn.SyncBatchNorm\n    return _BatchNorm, _InstanceNorm, SyncBatchNorm_\n\n\nCUDA_HOME = _get_cuda_home()\n_ConvNd, _ConvTransposeMixin = _get_conv()\nDataLoader, PoolDataLoader = _get_dataloader()\nBuildExtension, CppExtension, CUDAExtension = _get_extension()\n_BatchNorm, _InstanceNorm, SyncBatchNorm_ = _get_norm()\n_AdaptiveAvgPoolNd, _AdaptiveMaxPoolNd, _AvgPoolNd, _MaxPoolNd = _get_pool()\n\n\nclass SyncBatchNorm(SyncBatchNorm_):\n\n    def _specify_ddp_gpu_num(self, gpu_size):\n        if TORCH_VERSION != 'parrots':\n            super()._specify_ddp_gpu_num(gpu_size)\n\n    def _check_input_dim(self, input):\n        if TORCH_VERSION == 'parrots':\n            if input.dim() < 2:\n                raise ValueError(\n                    f'expected at least 2D input (got {input.dim()}D input)')\n        else:\n            super()._check_input_dim(input)\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/path.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nfrom pathlib import Path\n\nfrom .misc import is_str\n\n\ndef is_filepath(x):\n    return is_str(x) or isinstance(x, Path)\n\n\ndef fopen(filepath, *args, **kwargs):\n    if is_str(filepath):\n        return open(filepath, *args, **kwargs)\n    elif isinstance(filepath, Path):\n        return filepath.open(*args, **kwargs)\n    raise ValueError('`filepath` should be a string or a Path')\n\n\ndef check_file_exist(filename, msg_tmpl='file \"{}\" does not exist'):\n    if not osp.isfile(filename):\n        raise FileNotFoundError(msg_tmpl.format(filename))\n\n\ndef mkdir_or_exist(dir_name, mode=0o777):\n    if dir_name == '':\n        return\n    dir_name = osp.expanduser(dir_name)\n    os.makedirs(dir_name, mode=mode, exist_ok=True)\n\n\ndef symlink(src, dst, overwrite=True, **kwargs):\n    if os.path.lexists(dst) and overwrite:\n        os.remove(dst)\n    os.symlink(src, dst, **kwargs)\n\n\ndef scandir(dir_path, suffix=None, recursive=False):\n    \"\"\"Scan a directory to find the interested files.\n\n    Args:\n        dir_path (str | obj:`Path`): Path of the directory.\n        suffix (str | tuple(str), optional): File suffix that we are\n            interested in. Default: None.\n        recursive (bool, optional): If set to True, recursively scan the\n            directory. Default: False.\n\n    Returns:\n        A generator for all the interested files with relative pathes.\n    \"\"\"\n    if isinstance(dir_path, (str, Path)):\n        dir_path = str(dir_path)\n    else:\n        raise TypeError('\"dir_path\" must be a string or Path object')\n\n    if (suffix is not None) and not isinstance(suffix, (str, tuple)):\n        raise TypeError('\"suffix\" must be a string or tuple of strings')\n\n    root = dir_path\n\n    def _scandir(dir_path, suffix, recursive):\n        for entry in os.scandir(dir_path):\n            if not entry.name.startswith('.') and entry.is_file():\n                rel_path = osp.relpath(entry.path, root)\n                if suffix is None:\n                    yield rel_path\n                elif rel_path.endswith(suffix):\n                    yield rel_path\n            else:\n                if recursive:\n                    yield from _scandir(\n                        entry.path, suffix=suffix, recursive=recursive)\n                else:\n                    continue\n\n    return _scandir(dir_path, suffix=suffix, recursive=recursive)\n\n\ndef find_vcs_root(path, markers=('.git', )):\n    \"\"\"Finds the root directory (including itself) of specified markers.\n\n    Args:\n        path (str): Path of directory or file.\n        markers (list[str], optional): List of file or directory names.\n\n    Returns:\n        The directory contained one of the markers or None if not found.\n    \"\"\"\n    if osp.isfile(path):\n        path = osp.dirname(path)\n\n    prev, cur = None, osp.abspath(osp.expanduser(path))\n    while cur != prev:\n        if any(osp.exists(osp.join(cur, marker)) for marker in markers):\n            return cur\n        prev, cur = cur, osp.split(cur)[0]\n    return None\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/progressbar.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport sys\nfrom collections.abc import Iterable\nfrom multiprocessing import Pool\nfrom shutil import get_terminal_size\n\nfrom .timer import Timer\n\n\nclass ProgressBar:\n    \"\"\"A progress bar which can print the progress\"\"\"\n\n    def __init__(self, task_num=0, bar_width=50, start=True, file=sys.stdout):\n        self.task_num = task_num\n        self.bar_width = bar_width\n        self.completed = 0\n        self.file = file\n        if start:\n            self.start()\n\n    @property\n    def terminal_width(self):\n        width, _ = get_terminal_size()\n        return width\n\n    def start(self):\n        if self.task_num > 0:\n            self.file.write(f'[{\" \" * self.bar_width}] 0/{self.task_num}, '\n                            'elapsed: 0s, ETA:')\n        else:\n            self.file.write('completed: 0, elapsed: 0s')\n        self.file.flush()\n        self.timer = Timer()\n\n    def update(self):\n        self.completed += 1\n        elapsed = self.timer.since_start()\n        if elapsed > 0:\n            fps = self.completed / elapsed\n        else:\n            fps = float('inf')\n        if self.task_num > 0:\n            percentage = self.completed / float(self.task_num)\n            eta = int(elapsed * (1 - percentage) / percentage + 0.5)\n            msg = f'\\r[{{}}] {self.completed}/{self.task_num}, ' \\\n                  f'{fps:.1f} task/s, elapsed: {int(elapsed + 0.5)}s, ' \\\n                  f'ETA: {eta:5}s'\n\n            bar_width = min(self.bar_width,\n                            int(self.terminal_width - len(msg)) + 2,\n                            int(self.terminal_width * 0.6))\n            bar_width = max(2, bar_width)\n            mark_width = int(bar_width * percentage)\n            bar_chars = '>' * mark_width + ' ' * (bar_width - mark_width)\n            self.file.write(msg.format(bar_chars))\n        else:\n            self.file.write(\n                f'completed: {self.completed}, elapsed: {int(elapsed + 0.5)}s,'\n                f' {fps:.1f} tasks/s')\n        self.file.flush()\n\n\ndef track_progress(func, tasks, bar_width=50, file=sys.stdout, **kwargs):\n    \"\"\"Track the progress of tasks execution with a progress bar.\n\n    Tasks are done with a simple for-loop.\n\n    Args:\n        func (callable): The function to be applied to each task.\n        tasks (list or tuple[Iterable, int]): A list of tasks or\n            (tasks, total num).\n        bar_width (int): Width of progress bar.\n\n    Returns:\n        list: The task results.\n    \"\"\"\n    if isinstance(tasks, tuple):\n        assert len(tasks) == 2\n        assert isinstance(tasks[0], Iterable)\n        assert isinstance(tasks[1], int)\n        task_num = tasks[1]\n        tasks = tasks[0]\n    elif isinstance(tasks, Iterable):\n        task_num = len(tasks)\n    else:\n        raise TypeError(\n            '\"tasks\" must be an iterable object or a (iterator, int) tuple')\n    prog_bar = ProgressBar(task_num, bar_width, file=file)\n    results = []\n    for task in tasks:\n        results.append(func(task, **kwargs))\n        prog_bar.update()\n    prog_bar.file.write('\\n')\n    return results\n\n\ndef init_pool(process_num, initializer=None, initargs=None):\n    if initializer is None:\n        return Pool(process_num)\n    elif initargs is None:\n        return Pool(process_num, initializer)\n    else:\n        if not isinstance(initargs, tuple):\n            raise TypeError('\"initargs\" must be a tuple')\n        return Pool(process_num, initializer, initargs)\n\n\ndef track_parallel_progress(func,\n                            tasks,\n                            nproc,\n                            initializer=None,\n                            initargs=None,\n                            bar_width=50,\n                            chunksize=1,\n                            skip_first=False,\n                            keep_order=True,\n                            file=sys.stdout):\n    \"\"\"Track the progress of parallel task execution with a progress bar.\n\n    The built-in :mod:`multiprocessing` module is used for process pools and\n    tasks are done with :func:`Pool.map` or :func:`Pool.imap_unordered`.\n\n    Args:\n        func (callable): The function to be applied to each task.\n        tasks (list or tuple[Iterable, int]): A list of tasks or\n            (tasks, total num).\n        nproc (int): Process (worker) number.\n        initializer (None or callable): Refer to :class:`multiprocessing.Pool`\n            for details.\n        initargs (None or tuple): Refer to :class:`multiprocessing.Pool` for\n            details.\n        chunksize (int): Refer to :class:`multiprocessing.Pool` for details.\n        bar_width (int): Width of progress bar.\n        skip_first (bool): Whether to skip the first sample for each worker\n            when estimating fps, since the initialization step may takes\n            longer.\n        keep_order (bool): If True, :func:`Pool.imap` is used, otherwise\n            :func:`Pool.imap_unordered` is used.\n\n    Returns:\n        list: The task results.\n    \"\"\"\n    if isinstance(tasks, tuple):\n        assert len(tasks) == 2\n        assert isinstance(tasks[0], Iterable)\n        assert isinstance(tasks[1], int)\n        task_num = tasks[1]\n        tasks = tasks[0]\n    elif isinstance(tasks, Iterable):\n        task_num = len(tasks)\n    else:\n        raise TypeError(\n            '\"tasks\" must be an iterable object or a (iterator, int) tuple')\n    pool = init_pool(nproc, initializer, initargs)\n    start = not skip_first\n    task_num -= nproc * chunksize * int(skip_first)\n    prog_bar = ProgressBar(task_num, bar_width, start, file=file)\n    results = []\n    if keep_order:\n        gen = pool.imap(func, tasks, chunksize)\n    else:\n        gen = pool.imap_unordered(func, tasks, chunksize)\n    for result in gen:\n        results.append(result)\n        if skip_first:\n            if len(results) < nproc * chunksize:\n                continue\n            elif len(results) == nproc * chunksize:\n                prog_bar.start()\n                continue\n        prog_bar.update()\n    prog_bar.file.write('\\n')\n    pool.close()\n    pool.join()\n    return results\n\n\ndef track_iter_progress(tasks, bar_width=50, file=sys.stdout):\n    \"\"\"Track the progress of tasks iteration or enumeration with a progress bar.\n\n    Tasks are yielded with a simple for-loop.\n\n    Args:\n        tasks (list or tuple[Iterable, int]): A list of tasks or\n            (tasks, total num).\n        bar_width (int): Width of progress bar.\n\n    Yields:\n        list: The task results.\n    \"\"\"\n    if isinstance(tasks, tuple):\n        assert len(tasks) == 2\n        assert isinstance(tasks[0], Iterable)\n        assert isinstance(tasks[1], int)\n        task_num = tasks[1]\n        tasks = tasks[0]\n    elif isinstance(tasks, Iterable):\n        task_num = len(tasks)\n    else:\n        raise TypeError(\n            '\"tasks\" must be an iterable object or a (iterator, int) tuple')\n    prog_bar = ProgressBar(task_num, bar_width, file=file)\n    for task in tasks:\n        yield task\n        prog_bar.update()\n    prog_bar.file.write('\\n')\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/registry.py",
    "content": "import inspect\nimport warnings\nfrom functools import partial\n\nfrom .misc import is_str\n\n\nclass Registry:\n    \"\"\"A registry to map strings to classes.\n\n    Args:\n        name (str): Registry name.\n    \"\"\"\n\n    def __init__(self, name):\n        self._name = name\n        self._module_dict = dict()\n\n    def __len__(self):\n        return len(self._module_dict)\n\n    def __contains__(self, key):\n        return self.get(key) is not None\n\n    def __repr__(self):\n        format_str = self.__class__.__name__ + \\\n                     f'(name={self._name}, ' \\\n                     f'items={self._module_dict})'\n        return format_str\n\n    @property\n    def name(self):\n        return self._name\n\n    @property\n    def module_dict(self):\n        return self._module_dict\n\n    def get(self, key):\n        \"\"\"Get the registry record.\n\n        Args:\n            key (str): The class name in string format.\n\n        Returns:\n            class: The corresponding class.\n        \"\"\"\n        return self._module_dict.get(key, None)\n\n    def _register_module(self, module_class, module_name=None, force=False):\n        if not inspect.isclass(module_class):\n            raise TypeError('module must be a class, '\n                            f'but got {type(module_class)}')\n\n        if module_name is None:\n            module_name = module_class.__name__\n        if not force and module_name in self._module_dict:\n            raise KeyError(f'{module_name} is already registered '\n                           f'in {self.name}')\n        self._module_dict[module_name] = module_class\n\n    def deprecated_register_module(self, cls=None, force=False):\n        warnings.warn(\n            'The old API of register_module(module, force=False) '\n            'is deprecated and will be removed, please use the new API '\n            'register_module(name=None, force=False, module=None) instead.')\n        if cls is None:\n            return partial(self.deprecated_register_module, force=force)\n        self._register_module(cls, force=force)\n        return cls\n\n    def register_module(self, name=None, force=False, module=None):\n        \"\"\"Register a module.\n\n        A record will be added to `self._module_dict`, whose key is the class\n        name or the specified name, and value is the class itself.\n        It can be used as a decorator or a normal function.\n\n        Example:\n            >>> backbones = Registry('backbone')\n            >>> @backbones.register_module()\n            >>> class ResNet:\n            >>>     pass\n\n            >>> backbones = Registry('backbone')\n            >>> @backbones.register_module(name='mnet')\n            >>> class MobileNet:\n            >>>     pass\n\n            >>> backbones = Registry('backbone')\n            >>> class ResNet:\n            >>>     pass\n            >>> backbones.register_module(ResNet)\n\n        Args:\n            name (str | None): The module name to be registered. If not\n                specified, the class name will be used.\n            force (bool, optional): Whether to override an existing class with\n                the same name. Default: False.\n            module (type): Module class to be registered.\n        \"\"\"\n        if not isinstance(force, bool):\n            raise TypeError(f'force must be a boolean, but got {type(force)}')\n        # NOTE: This is a walkaround to be compatible with the old api,\n        # while it may introduce unexpected bugs.\n        if isinstance(name, type):\n            return self.deprecated_register_module(name, force=force)\n\n        # use it as a normal method: x.register_module(module=SomeClass)\n        if module is not None:\n            self._register_module(\n                module_class=module, module_name=name, force=force)\n            return module\n\n        # raise the error ahead of time\n        if not (name is None or isinstance(name, str)):\n            raise TypeError(f'name must be a str, but got {type(name)}')\n\n        # use it as a decorator: @x.register_module()\n        def _register(cls):\n            self._register_module(\n                module_class=cls, module_name=name, force=force)\n            return cls\n\n        return _register\n\n\ndef build_from_cfg(cfg, registry, default_args=None):\n    \"\"\"Build a module from config dict.\n\n    Args:\n        cfg (dict): Config dict. It should at least contain the key \"type\".\n        registry (:obj:`Registry`): The registry to search the type from.\n        default_args (dict, optional): Default initialization arguments.\n\n    Returns:\n        object: The constructed object.\n    \"\"\"\n    if not isinstance(cfg, dict):\n        raise TypeError(f'cfg must be a dict, but got {type(cfg)}')\n    if 'type' not in cfg:\n        raise KeyError(\n            f'the cfg dict must contain the key \"type\", but got {cfg}')\n    if not isinstance(registry, Registry):\n        raise TypeError('registry must be an mmcv.Registry object, '\n                        f'but got {type(registry)}')\n    if not (isinstance(default_args, dict) or default_args is None):\n        raise TypeError('default_args must be a dict or None, '\n                        f'but got {type(default_args)}')\n\n    args = cfg.copy()\n    obj_type = args.pop('type')\n    if is_str(obj_type):\n        obj_cls = registry.get(obj_type)\n        if obj_cls is None:\n            raise KeyError(\n                f'{obj_type} is not in the {registry.name} registry')\n    elif inspect.isclass(obj_type):\n        obj_cls = obj_type\n    else:\n        raise TypeError(\n            f'type must be a str or valid type, but got {type(obj_type)}')\n\n    if default_args is not None:\n        for name, value in default_args.items():\n            args.setdefault(name, value)\n    return obj_cls(**args)\n"
  },
  {
    "path": "code/mmcv/mmcv/utils/timer.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom time import time\n\n\nclass TimerError(Exception):\n\n    def __init__(self, message):\n        self.message = message\n        super(TimerError, self).__init__(message)\n\n\nclass Timer:\n    \"\"\"A flexible Timer class.\n\n    :Example:\n\n    >>> import time\n    >>> import mmcv\n    >>> with mmcv.Timer():\n    >>>     # simulate a code block that will run for 1s\n    >>>     time.sleep(1)\n    1.000\n    >>> with mmcv.Timer(print_tmpl='it takes {:.1f} seconds'):\n    >>>     # simulate a code block that will run for 1s\n    >>>     time.sleep(1)\n    it takes 1.0 seconds\n    >>> timer = mmcv.Timer()\n    >>> time.sleep(0.5)\n    >>> print(timer.since_start())\n    0.500\n    >>> time.sleep(0.5)\n    >>> print(timer.since_last_check())\n    0.500\n    >>> print(timer.since_start())\n    1.000\n    \"\"\"\n\n    def __init__(self, start=True, print_tmpl=None):\n        self._is_running = False\n        self.print_tmpl = print_tmpl if print_tmpl else '{:.3f}'\n        if start:\n            self.start()\n\n    @property\n    def is_running(self):\n        \"\"\"bool: indicate whether the timer is running\"\"\"\n        return self._is_running\n\n    def __enter__(self):\n        self.start()\n        return self\n\n    def __exit__(self, type, value, traceback):\n        print(self.print_tmpl.format(self.since_last_check()))\n        self._is_running = False\n\n    def start(self):\n        \"\"\"Start the timer.\"\"\"\n        if not self._is_running:\n            self._t_start = time()\n            self._is_running = True\n        self._t_last = time()\n\n    def since_start(self):\n        \"\"\"Total time since the timer is started.\n\n        Returns (float): Time in seconds.\n        \"\"\"\n        if not self._is_running:\n            raise TimerError('timer is not running')\n        self._t_last = time()\n        return self._t_last - self._t_start\n\n    def since_last_check(self):\n        \"\"\"Time since the last checking.\n\n        Either :func:`since_start` or :func:`since_last_check` is a checking\n        operation.\n\n        Returns (float): Time in seconds.\n        \"\"\"\n        if not self._is_running:\n            raise TimerError('timer is not running')\n        dur = time() - self._t_last\n        self._t_last = time()\n        return dur\n\n\n_g_timers = {}  # global timers\n\n\ndef check_time(timer_id):\n    \"\"\"Add check points in a single line.\n\n    This method is suitable for running a task on a list of items. A timer will\n    be registered when the method is called for the first time.\n\n    :Example:\n\n    >>> import time\n    >>> import mmcv\n    >>> for i in range(1, 6):\n    >>>     # simulate a code block\n    >>>     time.sleep(i)\n    >>>     mmcv.check_time('task1')\n    2.000\n    3.000\n    4.000\n    5.000\n\n    Args:\n        timer_id (str): Timer identifier.\n    \"\"\"\n    if timer_id not in _g_timers:\n        _g_timers[timer_id] = Timer()\n        return 0\n    else:\n        return _g_timers[timer_id].since_last_check()\n"
  },
  {
    "path": "code/mmcv/mmcv/version.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\n__version__ = '0.6.2'\n"
  },
  {
    "path": "code/mmcv/mmcv/video/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .io import Cache, VideoReader, frames2video\nfrom .optflow import (dequantize_flow, flow_warp, flowread, flowwrite,\n                      quantize_flow)\nfrom .processing import concat_video, convert_video, cut_video, resize_video\n\n__all__ = [\n    'Cache', 'VideoReader', 'frames2video', 'convert_video', 'resize_video',\n    'cut_video', 'concat_video', 'flowread', 'flowwrite', 'quantize_flow',\n    'dequantize_flow', 'flow_warp'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/video/io.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\nfrom collections import OrderedDict\n\nimport cv2\nfrom cv2 import (CAP_PROP_FOURCC, CAP_PROP_FPS, CAP_PROP_FRAME_COUNT,\n                 CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH,\n                 CAP_PROP_POS_FRAMES, VideoWriter_fourcc)\n\nfrom mmcv.utils import (check_file_exist, mkdir_or_exist, scandir,\n                        track_progress)\n\n\nclass Cache:\n\n    def __init__(self, capacity):\n        self._cache = OrderedDict()\n        self._capacity = int(capacity)\n        if capacity <= 0:\n            raise ValueError('capacity must be a positive integer')\n\n    @property\n    def capacity(self):\n        return self._capacity\n\n    @property\n    def size(self):\n        return len(self._cache)\n\n    def put(self, key, val):\n        if key in self._cache:\n            return\n        if len(self._cache) >= self.capacity:\n            self._cache.popitem(last=False)\n        self._cache[key] = val\n\n    def get(self, key, default=None):\n        val = self._cache[key] if key in self._cache else default\n        return val\n\n\nclass VideoReader:\n    \"\"\"Video class with similar usage to a list object.\n\n    This video warpper class provides convenient apis to access frames.\n    There exists an issue of OpenCV's VideoCapture class that jumping to a\n    certain frame may be inaccurate. It is fixed in this class by checking\n    the position after jumping each time.\n    Cache is used when decoding videos. So if the same frame is visited for\n    the second time, there is no need to decode again if it is stored in the\n    cache.\n\n    :Example:\n\n    >>> import mmcv\n    >>> v = mmcv.VideoReader('sample.mp4')\n    >>> len(v)  # get the total frame number with `len()`\n    120\n    >>> for img in v:  # v is iterable\n    >>>     mmcv.imshow(img)\n    >>> v[5]  # get the 6th frame\n    \"\"\"\n\n    def __init__(self, filename, cache_capacity=10):\n        check_file_exist(filename, 'Video file not found: ' + filename)\n        self._vcap = cv2.VideoCapture(filename)\n        assert cache_capacity > 0\n        self._cache = Cache(cache_capacity)\n        self._position = 0\n        # get basic info\n        self._width = int(self._vcap.get(CAP_PROP_FRAME_WIDTH))\n        self._height = int(self._vcap.get(CAP_PROP_FRAME_HEIGHT))\n        self._fps = self._vcap.get(CAP_PROP_FPS)\n        self._frame_cnt = int(self._vcap.get(CAP_PROP_FRAME_COUNT))\n        self._fourcc = self._vcap.get(CAP_PROP_FOURCC)\n\n    @property\n    def vcap(self):\n        \"\"\":obj:`cv2.VideoCapture`: The raw VideoCapture object.\"\"\"\n        return self._vcap\n\n    @property\n    def opened(self):\n        \"\"\"bool: Indicate whether the video is opened.\"\"\"\n        return self._vcap.isOpened()\n\n    @property\n    def width(self):\n        \"\"\"int: Width of video frames.\"\"\"\n        return self._width\n\n    @property\n    def height(self):\n        \"\"\"int: Height of video frames.\"\"\"\n        return self._height\n\n    @property\n    def resolution(self):\n        \"\"\"tuple: Video resolution (width, height).\"\"\"\n        return (self._width, self._height)\n\n    @property\n    def fps(self):\n        \"\"\"float: FPS of the video.\"\"\"\n        return self._fps\n\n    @property\n    def frame_cnt(self):\n        \"\"\"int: Total frames of the video.\"\"\"\n        return self._frame_cnt\n\n    @property\n    def fourcc(self):\n        \"\"\"str: \"Four character code\" of the video.\"\"\"\n        return self._fourcc\n\n    @property\n    def position(self):\n        \"\"\"int: Current cursor position, indicating frame decoded.\"\"\"\n        return self._position\n\n    def _get_real_position(self):\n        return int(round(self._vcap.get(CAP_PROP_POS_FRAMES)))\n\n    def _set_real_position(self, frame_id):\n        self._vcap.set(CAP_PROP_POS_FRAMES, frame_id)\n        pos = self._get_real_position()\n        for _ in range(frame_id - pos):\n            self._vcap.read()\n        self._position = frame_id\n\n    def read(self):\n        \"\"\"Read the next frame.\n\n        If the next frame have been decoded before and in the cache, then\n        return it directly, otherwise decode, cache and return it.\n\n        Returns:\n            ndarray or None: Return the frame if successful, otherwise None.\n        \"\"\"\n        # pos = self._position\n        if self._cache:\n            img = self._cache.get(self._position)\n            if img is not None:\n                ret = True\n            else:\n                if self._position != self._get_real_position():\n                    self._set_real_position(self._position)\n                ret, img = self._vcap.read()\n                if ret:\n                    self._cache.put(self._position, img)\n        else:\n            ret, img = self._vcap.read()\n        if ret:\n            self._position += 1\n        return img\n\n    def get_frame(self, frame_id):\n        \"\"\"Get frame by index.\n\n        Args:\n            frame_id (int): Index of the expected frame, 0-based.\n\n        Returns:\n            ndarray or None: Return the frame if successful, otherwise None.\n        \"\"\"\n        if frame_id < 0 or frame_id >= self._frame_cnt:\n            raise IndexError(\n                f'\"frame_id\" must be between 0 and {self._frame_cnt - 1}')\n        if frame_id == self._position:\n            return self.read()\n        if self._cache:\n            img = self._cache.get(frame_id)\n            if img is not None:\n                self._position = frame_id + 1\n                return img\n        self._set_real_position(frame_id)\n        ret, img = self._vcap.read()\n        if ret:\n            if self._cache:\n                self._cache.put(self._position, img)\n            self._position += 1\n        return img\n\n    def current_frame(self):\n        \"\"\"Get the current frame (frame that is just visited).\n\n        Returns:\n            ndarray or None: If the video is fresh, return None, otherwise\n                return the frame.\n        \"\"\"\n        if self._position == 0:\n            return None\n        return self._cache.get(self._position - 1)\n\n    def cvt2frames(self,\n                   frame_dir,\n                   file_start=0,\n                   filename_tmpl='{:06d}.jpg',\n                   start=0,\n                   max_num=0,\n                   show_progress=True):\n        \"\"\"Convert a video to frame images\n\n        Args:\n            frame_dir (str): Output directory to store all the frame images.\n            file_start (int): Filenames will start from the specified number.\n            filename_tmpl (str): Filename template with the index as the\n                placeholder.\n            start (int): The starting frame index.\n            max_num (int): Maximum number of frames to be written.\n            show_progress (bool): Whether to show a progress bar.\n        \"\"\"\n        mkdir_or_exist(frame_dir)\n        if max_num == 0:\n            task_num = self.frame_cnt - start\n        else:\n            task_num = min(self.frame_cnt - start, max_num)\n        if task_num <= 0:\n            raise ValueError('start must be less than total frame number')\n        if start > 0:\n            self._set_real_position(start)\n\n        def write_frame(file_idx):\n            img = self.read()\n            filename = osp.join(frame_dir, filename_tmpl.format(file_idx))\n            cv2.imwrite(filename, img)\n\n        if show_progress:\n            track_progress(write_frame, range(file_start,\n                                              file_start + task_num))\n        else:\n            for i in range(task_num):\n                img = self.read()\n                if img is None:\n                    break\n                filename = osp.join(frame_dir,\n                                    filename_tmpl.format(i + file_start))\n                cv2.imwrite(filename, img)\n\n    def __len__(self):\n        return self.frame_cnt\n\n    def __getitem__(self, index):\n        if isinstance(index, slice):\n            return [\n                self.get_frame(i)\n                for i in range(*index.indices(self.frame_cnt))\n            ]\n        # support negative indexing\n        if index < 0:\n            index += self.frame_cnt\n            if index < 0:\n                raise IndexError('index out of range')\n        return self.get_frame(index)\n\n    def __iter__(self):\n        self._set_real_position(0)\n        return self\n\n    def __next__(self):\n        img = self.read()\n        if img is not None:\n            return img\n        else:\n            raise StopIteration\n\n    next = __next__\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        self._vcap.release()\n\n\ndef frames2video(frame_dir,\n                 video_file,\n                 fps=30,\n                 fourcc='XVID',\n                 filename_tmpl='{:06d}.jpg',\n                 start=0,\n                 end=0,\n                 show_progress=True):\n    \"\"\"Read the frame images from a directory and join them as a video\n\n    Args:\n        frame_dir (str): The directory containing video frames.\n        video_file (str): Output filename.\n        fps (float): FPS of the output video.\n        fourcc (str): Fourcc of the output video, this should be compatible\n            with the output file type.\n        filename_tmpl (str): Filename template with the index as the variable.\n        start (int): Starting frame index.\n        end (int): Ending frame index.\n        show_progress (bool): Whether to show a progress bar.\n    \"\"\"\n    if end == 0:\n        ext = filename_tmpl.split('.')[-1]\n        end = len([name for name in scandir(frame_dir, ext)])\n    first_file = osp.join(frame_dir, filename_tmpl.format(start))\n    check_file_exist(first_file, 'The start frame not found: ' + first_file)\n    img = cv2.imread(first_file)\n    height, width = img.shape[:2]\n    resolution = (width, height)\n    vwriter = cv2.VideoWriter(video_file, VideoWriter_fourcc(*fourcc), fps,\n                              resolution)\n\n    def write_frame(file_idx):\n        filename = osp.join(frame_dir, filename_tmpl.format(file_idx))\n        img = cv2.imread(filename)\n        vwriter.write(img)\n\n    if show_progress:\n        track_progress(write_frame, range(start, end))\n    else:\n        for i in range(start, end):\n            filename = osp.join(frame_dir, filename_tmpl.format(i))\n            img = cv2.imread(filename)\n            vwriter.write(img)\n    vwriter.release()\n"
  },
  {
    "path": "code/mmcv/mmcv/video/optflow.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numpy as np\n\nfrom mmcv._ext import flow_warp_c\nfrom mmcv.arraymisc import dequantize, quantize\nfrom mmcv.image import imread, imwrite\nfrom mmcv.utils import is_str\n\n\ndef flowread(flow_or_path, quantize=False, concat_axis=0, *args, **kwargs):\n    \"\"\"Read an optical flow map.\n\n    Args:\n        flow_or_path (ndarray or str): A flow map or filepath.\n        quantize (bool): whether to read quantized pair, if set to True,\n            remaining args will be passed to :func:`dequantize_flow`.\n        concat_axis (int): The axis that dx and dy are concatenated,\n            can be either 0 or 1. Ignored if quantize is False.\n\n    Returns:\n        ndarray: Optical flow represented as a (h, w, 2) numpy array\n    \"\"\"\n    if isinstance(flow_or_path, np.ndarray):\n        if (flow_or_path.ndim != 3) or (flow_or_path.shape[-1] != 2):\n            raise ValueError(f'Invalid flow with shape {flow_or_path.shape}')\n        return flow_or_path\n    elif not is_str(flow_or_path):\n        raise TypeError(f'\"flow_or_path\" must be a filename or numpy array, '\n                        f'not {type(flow_or_path)}')\n\n    if not quantize:\n        with open(flow_or_path, 'rb') as f:\n            try:\n                header = f.read(4).decode('utf-8')\n            except Exception:\n                raise IOError(f'Invalid flow file: {flow_or_path}')\n            else:\n                if header != 'PIEH':\n                    raise IOError(f'Invalid flow file: {flow_or_path}, '\n                                  'header does not contain PIEH')\n\n            w = np.fromfile(f, np.int32, 1).squeeze()\n            h = np.fromfile(f, np.int32, 1).squeeze()\n            flow = np.fromfile(f, np.float32, w * h * 2).reshape((h, w, 2))\n    else:\n        assert concat_axis in [0, 1]\n        cat_flow = imread(flow_or_path, flag='unchanged')\n        if cat_flow.ndim != 2:\n            raise IOError(\n                f'{flow_or_path} is not a valid quantized flow file, '\n                f'its dimension is {cat_flow.ndim}.')\n        assert cat_flow.shape[concat_axis] % 2 == 0\n        dx, dy = np.split(cat_flow, 2, axis=concat_axis)\n        flow = dequantize_flow(dx, dy, *args, **kwargs)\n\n    return flow.astype(np.float32)\n\n\ndef flowwrite(flow, filename, quantize=False, concat_axis=0, *args, **kwargs):\n    \"\"\"Write optical flow to file.\n\n    If the flow is not quantized, it will be saved as a .flo file losslessly,\n    otherwise a jpeg image which is lossy but of much smaller size. (dx and dy\n    will be concatenated horizontally into a single image if quantize is True.)\n\n    Args:\n        flow (ndarray): (h, w, 2) array of optical flow.\n        filename (str): Output filepath.\n        quantize (bool): Whether to quantize the flow and save it to 2 jpeg\n            images. If set to True, remaining args will be passed to\n            :func:`quantize_flow`.\n        concat_axis (int): The axis that dx and dy are concatenated,\n            can be either 0 or 1. Ignored if quantize is False.\n    \"\"\"\n    if not quantize:\n        with open(filename, 'wb') as f:\n            f.write('PIEH'.encode('utf-8'))\n            np.array([flow.shape[1], flow.shape[0]], dtype=np.int32).tofile(f)\n            flow = flow.astype(np.float32)\n            flow.tofile(f)\n            f.flush()\n    else:\n        assert concat_axis in [0, 1]\n        dx, dy = quantize_flow(flow, *args, **kwargs)\n        dxdy = np.concatenate((dx, dy), axis=concat_axis)\n        imwrite(dxdy, filename)\n\n\ndef quantize_flow(flow, max_val=0.02, norm=True):\n    \"\"\"Quantize flow to [0, 255].\n\n    After this step, the size of flow will be much smaller, and can be\n    dumped as jpeg images.\n\n    Args:\n        flow (ndarray): (h, w, 2) array of optical flow.\n        max_val (float): Maximum value of flow, values beyond\n                        [-max_val, max_val] will be truncated.\n        norm (bool): Whether to divide flow values by image width/height.\n\n    Returns:\n        tuple[ndarray]: Quantized dx and dy.\n    \"\"\"\n    h, w, _ = flow.shape\n    dx = flow[..., 0]\n    dy = flow[..., 1]\n    if norm:\n        dx = dx / w  # avoid inplace operations\n        dy = dy / h\n    # use 255 levels instead of 256 to make sure 0 is 0 after dequantization.\n    flow_comps = [\n        quantize(d, -max_val, max_val, 255, np.uint8) for d in [dx, dy]\n    ]\n    return tuple(flow_comps)\n\n\ndef dequantize_flow(dx, dy, max_val=0.02, denorm=True):\n    \"\"\"Recover from quantized flow.\n\n    Args:\n        dx (ndarray): Quantized dx.\n        dy (ndarray): Quantized dy.\n        max_val (float): Maximum value used when quantizing.\n        denorm (bool): Whether to multiply flow values with width/height.\n\n    Returns:\n        ndarray: Dequantized flow.\n    \"\"\"\n    assert dx.shape == dy.shape\n    assert dx.ndim == 2 or (dx.ndim == 3 and dx.shape[-1] == 1)\n\n    dx, dy = [dequantize(d, -max_val, max_val, 255) for d in [dx, dy]]\n\n    if denorm:\n        dx *= dx.shape[1]\n        dy *= dx.shape[0]\n    flow = np.dstack((dx, dy))\n    return flow\n\n\ndef flow_warp(img, flow, filling_value=0, interpolate_mode='nearest'):\n    \"\"\"Use flow to warp img\n\n    Args:\n        img (ndarray, float or uint8): Image to be warped.\n        flow (ndarray, float): Optical Flow.\n        filling_value (int): The missing pixels will be set with filling_value.\n        interpolate_mode (str): bilinear -> Bilinear Interpolation;\n                                nearest -> Nearest Neighbor.\n\n    Returns:\n        ndarray: Warped image with the same shape of img\n    \"\"\"\n    interpolate_mode_dict = {'bilinear': 0, 'nearest': 1}\n    assert len(img.shape) == 3\n    assert len(flow.shape) == 3 and flow.shape[2] == 2\n    assert flow.shape[:2] == img.shape[:2]\n    assert interpolate_mode in interpolate_mode_dict.keys()\n\n    interpolate_mode = interpolate_mode_dict[interpolate_mode]\n    img_float = img.astype(np.float64)\n\n    out = flow_warp_c(\n        img_float,\n        flow.astype(np.float64),\n        filling_value=filling_value,\n        interpolate_mode=interpolate_mode)\n\n    return out\n"
  },
  {
    "path": "code/mmcv/mmcv/video/optflow_warp/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\n"
  },
  {
    "path": "code/mmcv/mmcv/video/optflow_warp/flow_warp.cpp",
    "content": "// Copyright (c) Open-MMLab. All rights reserved.\n#include \"flow_warp.hpp\"\n\nvoid FlowWarp(double* img, double* flow, double* out, const int height,\n              const int width, const int channels, const int filling_value = 0,\n              const int interpolateMode = 0) {\n  for (int h = 0; h < height; h++) {\n    for (int w = 0; w < width; w++) {\n      int offset_cur = h * width + w;\n      int offset_img = offset_cur * channels;\n      int offset_flow = offset_cur * 2;\n      double x, y;\n      x = h + flow[offset_flow + 1];\n      y = w + flow[offset_flow];\n\n      if (x < 0 || x >= height - 1 || y < 0 || y >= width - 1) {\n        for (int k = 0; k < channels; k++) {\n          out[offset_img + k] = filling_value;\n        }\n        continue;\n      }\n\n      if (interpolateMode == 0)\n        BilinearInterpolate(img, width, height, channels, x, y,\n                            out + offset_img);\n      else if (interpolateMode == 1)\n        NNInterpolate(img, width, height, channels, x, y, out + offset_img);\n      else\n        throw \"Not Implemented Interpolation Method\";\n    }\n  }\n}\n\nvoid BilinearInterpolate(const double* img, int width, int height, int channels,\n                         double x, double y, double* out) {\n  int xx, yy, m, n, u, v, offset, offset_img, l;\n  xx = x;\n  yy = y;\n\n  double dx, dy, s;\n\n  dx = __max__(__min__(x - xx, double(1)), double(0));\n  dy = __max__(__min__(y - yy, double(1)), double(0));\n\n  for (m = 0; m <= 1; m++)\n    for (n = 0; n <= 1; n++) {\n      u = EnforceRange(yy + n, width);\n      v = EnforceRange(xx + m, height);\n      offset = v * width + u;\n      offset_img = offset * channels;\n      s = fabs(1 - m - dx) * fabs(1 - n - dy);\n      for (l = 0; l < channels; l++) out[l] += img[offset_img + l] * s;\n    }\n}\n\nvoid NNInterpolate(const double* img, int width, int height, int channels,\n                   double x, double y, double* out) {\n  int xx, yy, m, n, u, v, offset, offset_img, l;\n  xx = x;\n  yy = y;\n\n  double dx, dy;\n\n  dx = __max__(__min__(x - xx, double(1)), double(0));\n  dy = __max__(__min__(y - yy, double(1)), double(0));\n\n  m = (dx < 0.5) ? 0 : 1;\n  n = (dy < 0.5) ? 0 : 1;\n\n  u = EnforceRange(yy + n, width);\n  v = EnforceRange(xx + m, height);\n  offset = v * width + u;\n  offset_img = offset * channels;\n\n  for (l = 0; l < channels; l++) out[l] = img[offset_img + l];\n}\n"
  },
  {
    "path": "code/mmcv/mmcv/video/optflow_warp/flow_warp.hpp",
    "content": "// Copyright (c) Open-MMLab. All rights reserved.\n#include <math.h>\n#include <string.h>\n\nusing namespace std;\n\nvoid FlowWarp(double* img, double* flow1, double* out, const int height,\n              const int width, const int channels, const int filling_value,\n              const int interpolateMode);\n\nvoid BilinearInterpolate(const double* img, int width, int height, int channels,\n                         double x, double y, double* out);\n\nvoid NNInterpolate(const double* img, int width, int height, int channels,\n                   double x, double y, double* out);\n\ntemplate <typename T>\ninline T __min__(T a, T b) {\n  return a > b ? b : a;\n}\n\ntemplate <typename T>\ninline T __max__(T a, T b) {\n  return (a < b) ? b : a;\n}\n\ntemplate <typename T>\ninline T EnforceRange(const T x, const int MaxValue) {\n  return __min__(__max__(x, 0), MaxValue);\n}\n"
  },
  {
    "path": "code/mmcv/mmcv/video/optflow_warp/flow_warp_module.pyx",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nSTUFF = \"Hi\"\n\nimport numpy as np\ncimport numpy as np\n\nnp.import_array()\n\ncdef extern from \"flow_warp.hpp\":\n    void FlowWarp(double* img, double* flow1, double* out, const int height, const int width, const int channels, const int filling_value, const int interpolateMode)\n\ndef flow_warp_c(np.ndarray[double, ndim=3, mode=\"c\"] img_array not None,\n                np.ndarray[double, ndim=3, mode=\"c\"] flow_array not None,\n                int filling_value=0,\n                int interpolate_mode=1):\n\n    out_array = np.zeros_like(img_array)\n\n    FlowWarp(<double*> np.PyArray_DATA(img_array),\n             <double*> np.PyArray_DATA(flow_array),\n             <double*> np.PyArray_DATA(out_array),\n             out_array.shape[0],\n             out_array.shape[1],\n             out_array.shape[2],\n             filling_value,\n             interpolate_mode)\n\n    return out_array\n"
  },
  {
    "path": "code/mmcv/mmcv/video/processing.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport subprocess\nimport tempfile\n\nfrom mmcv.utils import requires_executable\n\n\n@requires_executable('ffmpeg')\ndef convert_video(in_file,\n                  out_file,\n                  print_cmd=False,\n                  pre_options='',\n                  **kwargs):\n    \"\"\"Convert a video with ffmpeg.\n\n    This provides a general api to ffmpeg, the executed command is::\n\n        `ffmpeg -y <pre_options> -i <in_file> <options> <out_file>`\n\n    Options(kwargs) are mapped to ffmpeg commands with the following rules:\n\n    - key=val: \"-key val\"\n    - key=True: \"-key\"\n    - key=False: \"\"\n\n    Args:\n        in_file (str): Input video filename.\n        out_file (str): Output video filename.\n        pre_options (str): Options appears before \"-i <in_file>\".\n        print_cmd (bool): Whether to print the final ffmpeg command.\n    \"\"\"\n    options = []\n    for k, v in kwargs.items():\n        if isinstance(v, bool):\n            if v:\n                options.append(f'-{k}')\n        elif k == 'log_level':\n            assert v in [\n                'quiet', 'panic', 'fatal', 'error', 'warning', 'info',\n                'verbose', 'debug', 'trace'\n            ]\n            options.append(f'-loglevel {v}')\n        else:\n            options.append(f'-{k} {v}')\n    cmd = f'ffmpeg -y {pre_options} -i {in_file} {\" \".join(options)} ' \\\n          f'{out_file}'\n    if print_cmd:\n        print(cmd)\n    subprocess.call(cmd, shell=True)\n\n\n@requires_executable('ffmpeg')\ndef resize_video(in_file,\n                 out_file,\n                 size=None,\n                 ratio=None,\n                 keep_ar=False,\n                 log_level='info',\n                 print_cmd=False):\n    \"\"\"Resize a video.\n\n    Args:\n        in_file (str): Input video filename.\n        out_file (str): Output video filename.\n        size (tuple): Expected size (w, h), eg, (320, 240) or (320, -1).\n        ratio (tuple or float): Expected resize ratio, (2, 0.5) means\n            (w*2, h*0.5).\n        keep_ar (bool): Whether to keep original aspect ratio.\n        log_level (str): Logging level of ffmpeg.\n        print_cmd (bool): Whether to print the final ffmpeg command.\n    \"\"\"\n    if size is None and ratio is None:\n        raise ValueError('expected size or ratio must be specified')\n    if size is not None and ratio is not None:\n        raise ValueError('size and ratio cannot be specified at the same time')\n    options = {'log_level': log_level}\n    if size:\n        if not keep_ar:\n            options['vf'] = f'scale={size[0]}:{size[1]}'\n        else:\n            options['vf'] = f'scale=w={size[0]}:h={size[1]}:' \\\n                            'force_original_aspect_ratio=decrease'\n    else:\n        if not isinstance(ratio, tuple):\n            ratio = (ratio, ratio)\n        options['vf'] = f'scale=\"trunc(iw*{ratio[0]}):trunc(ih*{ratio[1]})\"'\n    convert_video(in_file, out_file, print_cmd, **options)\n\n\n@requires_executable('ffmpeg')\ndef cut_video(in_file,\n              out_file,\n              start=None,\n              end=None,\n              vcodec=None,\n              acodec=None,\n              log_level='info',\n              print_cmd=False):\n    \"\"\"Cut a clip from a video.\n\n    Args:\n        in_file (str): Input video filename.\n        out_file (str): Output video filename.\n        start (None or float): Start time (in seconds).\n        end (None or float): End time (in seconds).\n        vcodec (None or str): Output video codec, None for unchanged.\n        acodec (None or str): Output audio codec, None for unchanged.\n        log_level (str): Logging level of ffmpeg.\n        print_cmd (bool): Whether to print the final ffmpeg command.\n    \"\"\"\n    options = {'log_level': log_level}\n    if vcodec is None:\n        options['vcodec'] = 'copy'\n    if acodec is None:\n        options['acodec'] = 'copy'\n    if start:\n        options['ss'] = start\n    else:\n        start = 0\n    if end:\n        options['t'] = end - start\n    convert_video(in_file, out_file, print_cmd, **options)\n\n\n@requires_executable('ffmpeg')\ndef concat_video(video_list,\n                 out_file,\n                 vcodec=None,\n                 acodec=None,\n                 log_level='info',\n                 print_cmd=False):\n    \"\"\"Concatenate multiple videos into a single one.\n\n    Args:\n        video_list (list): A list of video filenames\n        out_file (str): Output video filename\n        vcodec (None or str): Output video codec, None for unchanged\n        acodec (None or str): Output audio codec, None for unchanged\n        log_level (str): Logging level of ffmpeg.\n        print_cmd (bool): Whether to print the final ffmpeg command.\n    \"\"\"\n    _, tmp_filename = tempfile.mkstemp(suffix='.txt', text=True)\n    with open(tmp_filename, 'w') as f:\n        for filename in video_list:\n            f.write(f'file {osp.abspath(filename)}\\n')\n    options = {'log_level': log_level}\n    if vcodec is None:\n        options['vcodec'] = 'copy'\n    if acodec is None:\n        options['acodec'] = 'copy'\n    convert_video(\n        tmp_filename,\n        out_file,\n        print_cmd,\n        pre_options='-f concat -safe 0',\n        **options)\n    os.remove(tmp_filename)\n"
  },
  {
    "path": "code/mmcv/mmcv/visualization/__init__.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom .color import Color, color_val\nfrom .image import (imshow, imshow_bboxes, imshow_det_bboxes, \n                    imshow_extremes, imshow_polygons, imshow_pose)\nfrom .optflow import flow2rgb, flowshow, make_color_wheel\n\n__all__ = [\n    'Color', 'color_val', 'imshow', 'imshow_bboxes', 'imshow_det_bboxes',\n    'flowshow', 'flow2rgb', 'make_color_wheel', 'imshow_extremes', 'imshow_polygons', \n    'imshow_pose'\n]\n"
  },
  {
    "path": "code/mmcv/mmcv/visualization/color.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom enum import Enum\n\nimport numpy as np\n\nfrom mmcv.utils import is_str\n\n\nclass Color(Enum):\n    \"\"\"An enum that defines common colors.\n\n    Contains red, green, blue, cyan, yellow, magenta, white and black.\n    \"\"\"\n    red = (0, 0, 255)\n    green = (0, 255, 0)\n    blue = (255, 0, 0)\n    cyan = (255, 255, 0)\n    yellow = (0, 255, 255)\n    magenta = (255, 0, 255)\n    white = (255, 255, 255)\n    black = (0, 0, 0)\n\n\ndef color_val(color):\n    \"\"\"Convert various input to color tuples.\n\n    Args:\n        color (:obj:`Color`/str/tuple/int/ndarray): Color inputs\n\n    Returns:\n        tuple[int]: A tuple of 3 integers indicating BGR channels.\n    \"\"\"\n    if is_str(color):\n        return Color[color].value\n    elif isinstance(color, Color):\n        return color.value\n    elif isinstance(color, tuple):\n        assert len(color) == 3\n        for channel in color:\n            assert 0 <= channel <= 255\n        return color\n    elif isinstance(color, int):\n        assert 0 <= color <= 255\n        return color, color, color\n    elif isinstance(color, np.ndarray):\n        assert color.ndim == 1 and color.size == 3\n        assert np.all((color >= 0) & (color <= 255))\n        color = color.astype(np.uint8)\n        return tuple(color)\n    else:\n        raise TypeError(f'Invalid type for color: {type(color)}')\n"
  },
  {
    "path": "code/mmcv/mmcv/visualization/image.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport pdb \nimport cv2 \nimport numpy as np \nimport matplotlib \nmatplotlib.use(\"Agg\") \nimport matplotlib.pyplot as plt \n\nfrom mmcv.image import imread, imwrite \nfrom .color import color_val \n\ncolors_hp = [(255, 0, 255), (255, 0, 0), (0, 0, 255), \n             (255, 0, 0), (0, 0, 255), (255, 0, 0), (0, 0, 255), \n             (255, 0, 0), (0, 0, 255), (255, 0, 0), (0, 0, 255), \n             (255, 0, 0), (0, 0, 255), (255, 0, 0), (0, 0, 255), \n             (255, 0, 0), (0, 0, 255)] \n             \nedges = [[0, 1], [0, 2], [1, 3], [2, 4], \n         [3, 5], [4, 6], [5, 6], [5, 7], \n         [7, 9], [6, 8], [8, 10], [5, 11], \n         [6, 12], [11, 12], [11, 13], \n         [13, 15], [12, 14], [14, 16]] \n         \nec = [(255, 0, 0), (0, 0, 255), (255, 0, 0), (0, 0, 255), \n      (255, 0, 0), (0, 0, 255), (255, 0, 255), \n      (255, 0, 0), (255, 0, 0), (0, 0, 255), (0, 0, 255), \n      (255, 0, 0), (0, 0, 255), (255, 0, 255),\n      (255, 0, 0), (255, 0, 0), (0, 0, 255), (0, 0, 255)] \n\ndef imshow(img, win_name='', wait_time=0):\n    \"\"\"Show an image.\n\n    Args:\n        img (str or ndarray): The image to be displayed.\n        win_name (str): The window name.\n        wait_time (int): Value of waitKey param.\n    \"\"\"\n    cv2.imshow(win_name, imread(img))\n    if wait_time == 0:  # prevent from hangning if windows was closed\n        while True:\n            ret = cv2.waitKey(1)\n\n            closed = cv2.getWindowProperty(win_name, cv2.WND_PROP_VISIBLE) < 1\n            # if user closed window or if some key pressed\n            if closed or ret != -1:\n                break\n    else:\n        ret = cv2.waitKey(wait_time)\n\n\ndef imshow_bboxes(img,\n                  bboxes,\n                  colors='green',\n                  top_k=-1,\n                  thickness=1,\n                  show=True,\n                  win_name='',\n                  wait_time=0,\n                  out_file=None):\n    \"\"\"Draw bboxes on an image.\n\n    Args:\n        img (str or ndarray): The image to be displayed.\n        bboxes (list or ndarray): A list of ndarray of shape (k, 4).\n        colors (list[str or tuple or Color]): A list of colors.\n        top_k (int): Plot the first k bboxes only if set positive.\n        thickness (int): Thickness of lines.\n        show (bool): Whether to show the image.\n        win_name (str): The window name.\n        wait_time (int): Value of waitKey param.\n        out_file (str, optional): The filename to write the image.\n    \"\"\"\n    img = imread(img)\n\n    if isinstance(bboxes, np.ndarray):\n        bboxes = [bboxes]\n    if not isinstance(colors, list):\n        colors = [colors for _ in range(len(bboxes))]\n    colors = [color_val(c) for c in colors]\n    assert len(bboxes) == len(colors)\n\n    for i, _bboxes in enumerate(bboxes):\n        _bboxes = _bboxes.astype(np.int32)\n        if top_k <= 0:\n            _top_k = _bboxes.shape[0]\n        else:\n            _top_k = min(top_k, _bboxes.shape[0])\n        for j in range(_top_k):\n            left_top = (_bboxes[j, 0], _bboxes[j, 1])\n            right_bottom = (_bboxes[j, 2], _bboxes[j, 3])\n            cv2.rectangle(\n                img, left_top, right_bottom, colors[i], thickness=thickness)\n\n    if show:\n        imshow(img, win_name, wait_time)\n    if out_file is not None:\n        imwrite(img, out_file)\n\ndef imshow_det_bboxes(img,\n                      bboxes,\n                      labels,\n                      class_names=None,\n                      score_thr=0,\n                      bbox_color='green',\n                      text_color='green',\n                      thickness=1,\n                      font_scale=0.5,\n                      show=True,\n                      win_name='',\n                      wait_time=0,\n                      out_file=None):\n    \"\"\"Draw bboxes and class labels (with scores) on an image.\n\n    Args:\n        img (str or ndarray): The image to be displayed.\n        bboxes (ndarray): Bounding boxes (with scores), shaped (n, 4) or\n            (n, 5).\n        labels (ndarray): Labels of bboxes.\n        class_names (list[str]): Names of each classes.\n        score_thr (float): Minimum score of bboxes to be shown.\n        bbox_color (str or tuple or :obj:`Color`): Color of bbox lines.\n        text_color (str or tuple or :obj:`Color`): Color of texts.\n        thickness (int): Thickness of lines.\n        font_scale (float): Font scales of texts.\n        show (bool): Whether to show the image.\n        win_name (str): The window name.\n        wait_time (int): Value of waitKey param.\n        out_file (str or None): The filename to write the image.\n    \"\"\"\n    assert bboxes.ndim == 2\n    assert labels.ndim == 1\n    assert bboxes.shape[0] == labels.shape[0]\n    assert bboxes.shape[1] == 4 or bboxes.shape[1] == 5\n    img = imread(img)\n\n    if score_thr > 0:\n        assert bboxes.shape[1] == 5\n        scores = bboxes[:, -1]\n        inds = scores > score_thr\n        bboxes = bboxes[inds, :]\n        labels = labels[inds]\n\n    bbox_color = color_val(bbox_color)\n    text_color = color_val(text_color)\n\n    for bbox, label in zip(bboxes, labels):\n        bbox_int = bbox.astype(np.int32)\n        left_top = (bbox_int[0], bbox_int[1])\n        right_bottom = (bbox_int[2], bbox_int[3])\n        cv2.rectangle(\n            img, left_top, right_bottom, bbox_color, thickness=thickness)\n        label_text = class_names[\n            label] if class_names is not None else f'cls {label}'\n        if len(bbox) > 4:\n            label_text += f'|{bbox[-1]:.02f}'\n        cv2.putText(img, label_text, (bbox_int[0], bbox_int[1] - 2),\n                    cv2.FONT_HERSHEY_COMPLEX, font_scale, text_color)\n\n    if show:\n        imshow(img, win_name, wait_time)\n    if out_file is not None:\n        imwrite(img, out_file)\n\ndef imshow_extremes(img, \n                    bboxes, \n                    extremes, \n                    labels, \n                    class_names=None, \n                    score_thr=0, \n                    out_file=None): \n    assert bboxes.ndim == 2 \n    assert labels.ndim == 1 \n    assert bboxes.shape[0] == labels.shape[0] \n    assert bboxes.shape[1] == 4 or bboxes.shape[1] == 5 \n    img = imread(img) \n    \n    im      = img[:, :, (2, 1, 0)] \n    fig, ax = plt.subplots(figsize=(12, 12)) \n    fig     = ax.imshow(im, aspect='equal') \n    plt.axis('off') \n    fig.axes.get_xaxis().set_visible(False) \n    fig.axes.get_yaxis().set_visible(False) \n\n    if score_thr > 0: \n        assert bboxes.shape[1] == 5 \n        scores = bboxes[:, -1] \n        inds = scores > score_thr \n        bboxes = bboxes[inds, :] \n        extremes = extremes[inds, :] \n        labels = labels[inds]\n   \n    for bbox, label, extreme in zip(bboxes, labels, extremes): \n        bbox_int = bbox.astype(np.int32)\n        left_top = (bbox_int[0], bbox_int[1]) \n        right_bottom = (bbox_int[2], bbox_int[3]) \n        poly = np.array([[extreme[0],extreme[1]],[extreme[2],extreme[3]], \\\n                         [extreme[4],extreme[5]],[extreme[6],extreme[7]]], np.int32) \n        poly = poly.reshape((-1,1,2))\n        \n        ax.add_patch(plt.Rectangle((bbox_int[0], bbox_int[1]), \n                                    bbox_int[2]-bbox_int[0], bbox_int[3]-bbox_int[1], \n                                    fill=False, edgecolor= 'g', linewidth=2.0))\n\n        poly = np.array([[extreme[0], extreme[1]], [extreme[2], extreme[3]], \\\n                         [extreme[4], extreme[5]], [extreme[6], extreme[7]]], np.int32)\n\n        color = np.random.rand(3)\n        ax.add_patch(plt.Polygon(poly, fill=True, color=color, alpha=0.5, edgecolor=None))\n        ax.add_patch(plt.Polygon(poly, fill=False, edgecolor='w', linewidth=1.0))\n\n        label_text = class_names[\n              label] if class_names is not None else f'cls {label}'\n        if len(bbox) > 4:\n            label_text += f'|{bbox[-1]:.02f}'     \n\n        ax.text(bbox_int[0]+1, bbox_int[1]-3, label_text, bbox=dict(facecolor='g', ec='g',\n                                                                    lw=0, alpha=0.5),\n                                                                    fontsize=10, color='white', weight='bold')    \n\n    if  out_file is not None:\n        plt.savefig(out_file)\n        plt.savefig(out_file.replace('jpg', 'pdf'))\n        plt.cla()\n        plt.close('all')     \n\ndef imshow_polygons(img, \n                    bboxes, \n                    polygons, \n                    labels, \n                    class_names=None, \n                    score_thr=0, \n                    out_file=None): \n    assert bboxes.ndim == 2 \n    assert labels.ndim == 1 \n    assert bboxes.shape[0] == labels.shape[0] \n    assert bboxes.shape[1] == 4 or bboxes.shape[1] == 5 \n    img = imread(img) \n    \n    im      = img[:, :, (2, 1, 0)] \n    fig, ax = plt.subplots(figsize=(12, 12)) \n    fig     = ax.imshow(im, aspect='equal') \n    plt.axis('off') \n    fig.axes.get_xaxis().set_visible(False) \n    fig.axes.get_yaxis().set_visible(False) \n\n    if score_thr > 0: \n        assert bboxes.shape[1] == 5 \n        scores = bboxes[:, -1] \n        inds = scores > score_thr \n        bboxes = bboxes[inds, :] \n        polygons = polygons[inds, :] \n        labels = labels[inds]\n   \n    for bbox, label, polygon in zip(bboxes, labels, polygons): \n        bbox_int = bbox.astype(np.int32)\n        left_top = (bbox_int[0], bbox_int[1]) \n        right_bottom = (bbox_int[2], bbox_int[3]) \n        poly = polygon.reshape(-1, 2).astype(np.int32)\n        \n        color = np.random.rand(3)\n        ax.add_patch(plt.Polygon(poly, fill=True, color=color, alpha=0.5, edgecolor=None))\n        ax.add_patch(plt.Polygon(poly, fill=False, edgecolor='w', linewidth=1.0))\n\n        label_text = class_names[\n              label] if class_names is not None else f'cls {label}'\n        if len(bbox) > 4:\n            label_text += f'|{bbox[-1]:.02f}'       \n\n    if out_file is not None:\n       plt.savefig(out_file)\n       plt.savefig(out_file.replace('jpg', 'pdf'))\n       plt.cla()\n       plt.close('all')     \n\n\ndef imshow_pose(img, \n                bboxes, \n                kps, \n                labels, \n                class_names=None, \n                score_thr=0, \n                out_file=None): \n    assert bboxes.ndim == 2 \n    assert labels.ndim == 1 \n    assert bboxes.shape[0] == labels.shape[0] \n    assert bboxes.shape[1] == 4 or bboxes.shape[1] == 5 \n    img = imread(img) \n    \n    im      = img[:, :, (2, 1, 0)] \n    fig, ax = plt.subplots(figsize=(12, 12)) \n    fig     = ax.imshow(im, aspect='equal') \n    plt.axis('off') \n    fig.axes.get_xaxis().set_visible(False) \n    fig.axes.get_yaxis().set_visible(False) \n\n    if score_thr > 0: \n        assert bboxes.shape[1] == 5 \n        scores = bboxes[:, -1] \n        inds = scores > score_thr \n        bboxes = bboxes[inds, :] \n        kps = kps[inds, :] \n        labels = labels[inds]\n   \n    for bbox, label, kp in zip(bboxes, labels, kps): \n        bbox_int = bbox.astype(np.int32)\n        left_top = (bbox_int[0], bbox_int[1]) \n        right_bottom = (bbox_int[2], bbox_int[3]) \n        kp = kp.reshape(-1, 2)\n\n        ax.add_patch(plt.Rectangle((bbox_int[0], bbox_int[1]), \n                                    bbox_int[2]-bbox_int[0], bbox_int[3]-bbox_int[1], \n                                    fill=False, edgecolor= 'mediumslateblue', linewidth=2.0))\n        \n        for i in range(kp.shape[0]):\n            kp_x = kp[i, 0]\n            kp_y = kp[i, 1]\n\n            if colors_hp[i] == (255,0,255):\n                color = 'magenta'\n            elif colors_hp[i] == (255,0,0):\n                color = 'blue'\n            else: #(0, 0, 255)\n                color = 'red'\n            \n            plt.scatter(kp_x, kp_y, color = color, s = 40)\n\n            for j, e in enumerate(edges):\n                if kp[e].min() > 0:\n                    if ec[j] == (255, 0, 255):\n                        color = 'magneta'\n                    elif ec[j] == (255, 0, 0):\n                        color = 'blue'\n                    else: #(0, 0, 255)\n                        color = 'red'\n                    \n                    plt.plot([kp[e[0], 0], kp[e[1], 0]],\n                             [kp[e[0], 1], kp[e[1], 1]], color=color, linewidth=4)\n\n            label_text = 'person'\n            label_text += f'|{bbox[-1]:.02f}'\n            ax.text(bbox_int[0], bbox_int[1]-2, label_text, bbox=dict(facecolor='mediumslateblue', \n                                                                      ec='mediumslateblue',\n                                                                      lw=0, alpha=0.5),\n                                                                      fontsize=10, color='white', \n                                                                      weight='bold')\n\n    if out_file is not None:\n       plt.savefig(out_file)\n       plt.savefig(out_file.replace('jpg', 'pdf'))\n       plt.cla()\n       plt.close('all')     \n"
  },
  {
    "path": "code/mmcv/mmcv/visualization/optflow.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom __future__ import division\n\nimport numpy as np\n\nfrom mmcv.image import rgb2bgr\nfrom mmcv.video import flowread\nfrom .image import imshow\n\n\ndef flowshow(flow, win_name='', wait_time=0):\n    \"\"\"Show optical flow.\n\n    Args:\n        flow (ndarray or str): The optical flow to be displayed.\n        win_name (str): The window name.\n        wait_time (int): Value of waitKey param.\n    \"\"\"\n    flow = flowread(flow)\n    flow_img = flow2rgb(flow)\n    imshow(rgb2bgr(flow_img), win_name, wait_time)\n\n\ndef flow2rgb(flow, color_wheel=None, unknown_thr=1e6):\n    \"\"\"Convert flow map to RGB image.\n\n    Args:\n        flow (ndarray): Array of optical flow.\n        color_wheel (ndarray or None): Color wheel used to map flow field to\n            RGB colorspace. Default color wheel will be used if not specified.\n        unknown_thr (str): Values above this threshold will be marked as\n            unknown and thus ignored.\n\n    Returns:\n        ndarray: RGB image that can be visualized.\n    \"\"\"\n    assert flow.ndim == 3 and flow.shape[-1] == 2\n    if color_wheel is None:\n        color_wheel = make_color_wheel()\n    assert color_wheel.ndim == 2 and color_wheel.shape[1] == 3\n    num_bins = color_wheel.shape[0]\n\n    dx = flow[:, :, 0].copy()\n    dy = flow[:, :, 1].copy()\n\n    ignore_inds = (\n        np.isnan(dx) | np.isnan(dy) | (np.abs(dx) > unknown_thr) |\n        (np.abs(dy) > unknown_thr))\n    dx[ignore_inds] = 0\n    dy[ignore_inds] = 0\n\n    rad = np.sqrt(dx**2 + dy**2)\n    if np.any(rad > np.finfo(float).eps):\n        max_rad = np.max(rad)\n        dx /= max_rad\n        dy /= max_rad\n\n    rad = np.sqrt(dx**2 + dy**2)\n    angle = np.arctan2(-dy, -dx) / np.pi\n\n    bin_real = (angle + 1) / 2 * (num_bins - 1)\n    bin_left = np.floor(bin_real).astype(int)\n    bin_right = (bin_left + 1) % num_bins\n    w = (bin_real - bin_left.astype(np.float32))[..., None]\n    flow_img = (1 -\n                w) * color_wheel[bin_left, :] + w * color_wheel[bin_right, :]\n    small_ind = rad <= 1\n    flow_img[small_ind] = 1 - rad[small_ind, None] * (1 - flow_img[small_ind])\n    flow_img[np.logical_not(small_ind)] *= 0.75\n\n    flow_img[ignore_inds, :] = 0\n\n    return flow_img\n\n\ndef make_color_wheel(bins=None):\n    \"\"\"Build a color wheel.\n\n    Args:\n        bins(list or tuple, optional): Specify the number of bins for each\n            color range, corresponding to six ranges: red -> yellow,\n            yellow -> green, green -> cyan, cyan -> blue, blue -> magenta,\n            magenta -> red. [15, 6, 4, 11, 13, 6] is used for default\n            (see Middlebury).\n\n    Returns:\n        ndarray: Color wheel of shape (total_bins, 3).\n    \"\"\"\n    if bins is None:\n        bins = [15, 6, 4, 11, 13, 6]\n    assert len(bins) == 6\n\n    RY, YG, GC, CB, BM, MR = tuple(bins)\n\n    ry = [1, np.arange(RY) / RY, 0]\n    yg = [1 - np.arange(YG) / YG, 1, 0]\n    gc = [0, 1, np.arange(GC) / GC]\n    cb = [0, 1 - np.arange(CB) / CB, 1]\n    bm = [np.arange(BM) / BM, 0, 1]\n    mr = [1, 0, 1 - np.arange(MR) / MR]\n\n    num_bins = RY + YG + GC + CB + BM + MR\n\n    color_wheel = np.zeros((3, num_bins), dtype=np.float32)\n\n    col = 0\n    for i, color in enumerate([ry, yg, gc, cb, bm, mr]):\n        for j in range(3):\n            color_wheel[j, col:col + bins[i]] = color[j]\n        col += bins[i]\n\n    return color_wheel.T\n"
  },
  {
    "path": "code/mmcv/requirements.txt",
    "content": "addict\nnumpy\npyyaml\nyapf\n"
  },
  {
    "path": "code/mmcv/setup.cfg",
    "content": "[bdist_wheel]\nuniversal=1\n\n[aliases]\ntest=pytest\n\n[tool:pytest]\naddopts=tests/\n\n[yapf]\nbased_on_style = pep8\nblank_line_before_nested_class_or_def = true\nsplit_before_expression_after_opening_paren = true\n\n[isort]\nline_length = 79\nmulti_line_output = 0\nknown_standard_library = pkg_resources,setuptools\nknown_first_party = mmcv\nknown_third_party = Cython,addict,cv2,numpy,pytest,resnet_cifar,torch,torchvision,yaml,yapf\nno_lines_before = STDLIB,LOCALFOLDER\ndefault_section = THIRDPARTY\n"
  },
  {
    "path": "code/mmcv/setup.py",
    "content": "import platform\nimport re\nfrom pkg_resources import DistributionNotFound, get_distribution\nfrom setuptools import Extension, dist, find_packages, setup\n\ndist.Distribution().fetch_build_eggs(['Cython', 'numpy>=1.11.1'])\n\nimport numpy  # NOQA: E402  # isort:skip\nfrom Cython.Distutils import build_ext  # NOQA: E402  # isort:skip\n\n\ndef choose_requirement(primary, secondary):\n    \"\"\"If some version of primary requirement installed, return primary,\n    else return secondary.\n    \"\"\"\n    try:\n        name = re.split(r'[!<>=]', primary)[0]\n        get_distribution(name)\n    except DistributionNotFound:\n        return secondary\n\n    return str(primary)\n\n\ndef readme():\n    with open('README.rst', encoding='utf-8') as f:\n        content = f.read()\n    return content\n\n\ndef get_version():\n    version_file = 'mmcv/version.py'\n    with open(version_file, 'r', encoding='utf-8') as f:\n        exec(compile(f.read(), version_file, 'exec'))\n    return locals()['__version__']\n\n\ndef parse_requirements(fname='requirements.txt', with_version=True):\n    \"\"\"\n    Parse the package dependencies listed in a requirements file but strips\n    specific versioning information.\n\n    Args:\n        fname (str): path to requirements file\n        with_version (bool, default=False): if True include version specs\n\n    Returns:\n        List[str]: list of requirements items\n\n    CommandLine:\n        python -c \"import setup; print(setup.parse_requirements())\"\n    \"\"\"\n    import sys\n    from os.path import exists\n    import re\n    require_fpath = fname\n\n    def parse_line(line):\n        \"\"\"\n        Parse information from a line in a requirements text file\n        \"\"\"\n        if line.startswith('-r '):\n            # Allow specifying requirements in other files\n            target = line.split(' ')[1]\n            for info in parse_require_file(target):\n                yield info\n        else:\n            info = {'line': line}\n            if line.startswith('-e '):\n                info['package'] = line.split('#egg=')[1]\n            else:\n                # Remove versioning from the package\n                pat = '(' + '|'.join(['>=', '==', '>']) + ')'\n                parts = re.split(pat, line, maxsplit=1)\n                parts = [p.strip() for p in parts]\n\n                info['package'] = parts[0]\n                if len(parts) > 1:\n                    op, rest = parts[1:]\n                    if ';' in rest:\n                        # Handle platform specific dependencies\n                        # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies\n                        version, platform_deps = map(str.strip,\n                                                     rest.split(';'))\n                        info['platform_deps'] = platform_deps\n                    else:\n                        version = rest  # NOQA\n                    info['version'] = (op, version)\n            yield info\n\n    def parse_require_file(fpath):\n        with open(fpath, 'r') as f:\n            for line in f.readlines():\n                line = line.strip()\n                if line and not line.startswith('#'):\n                    for info in parse_line(line):\n                        yield info\n\n    def gen_packages_items():\n        if exists(require_fpath):\n            for info in parse_require_file(require_fpath):\n                parts = [info['package']]\n                if with_version and 'version' in info:\n                    parts.extend(info['version'])\n                if not sys.version.startswith('3.4'):\n                    # apparently package_deps are broken in 3.4\n                    platform_deps = info.get('platform_deps')\n                    if platform_deps is not None:\n                        parts.append(';' + platform_deps)\n                item = ''.join(parts)\n                yield item\n\n    packages = list(gen_packages_items())\n    return packages\n\n\n# If first not installed install second package\nCHOOSE_INSTALL_REQUIRES = [('opencv-python-headless>=3', 'opencv-python>=3')]\n\ninstall_requires = parse_requirements()\nfor main, secondary in CHOOSE_INSTALL_REQUIRES:\n    install_requires.append(choose_requirement(main, secondary))\n\nif platform.system() == 'Darwin':\n    extra_compile_args = ['-stdlib=libc++']\n    extra_link_args = ['-stdlib=libc++']\nelse:\n    extra_compile_args = []\n    extra_link_args = []\n\nEXT_MODULES = [\n    Extension(\n        name='mmcv._ext',\n        sources=[\n            './mmcv/video/optflow_warp/flow_warp.cpp',\n            './mmcv/video/optflow_warp/flow_warp_module.pyx'\n        ],\n        include_dirs=[numpy.get_include()],\n        language='c++',\n        extra_compile_args=extra_compile_args,\n        extra_link_args=extra_link_args,\n    ),\n]\n\nsetup(\n    name='mmcv',\n    version=get_version(),\n    description='Open MMLab Computer Vision Foundation',\n    long_description=readme(),\n    keywords='computer vision',\n    packages=find_packages(),\n    include_package_data=True,\n    classifiers=[\n        'Development Status :: 4 - Beta',\n        'License :: OSI Approved :: Apache Software License',\n        'Operating System :: OS Independent',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.5',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Topic :: Utilities',\n    ],\n    url='https://github.com/open-mmlab/mmcv',\n    author='Kai Chen',\n    author_email='chenkaidev@gmail.com',\n    setup_requires=['pytest-runner'],\n    tests_require=['pytest'],\n    install_requires=install_requires,\n    ext_modules=EXT_MODULES,\n    cmdclass={'build_ext': build_ext},\n    zip_safe=False)\n"
  },
  {
    "path": "code/mmcv/tests/data/config/a.b.py",
    "content": "item1 = [1, 2]\nitem2 = {'a': 0}\nitem3 = True\nitem4 = 'test'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/a.py",
    "content": "item1 = [1, 2]\nitem2 = {'a': 0}\nitem3 = True\nitem4 = 'test'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/b.json",
    "content": "{\n    \"item1\": [1, 2],\n    \"item2\": {\n        \"a\": 0\n    },\n    \"item3\": true,\n    \"item4\": \"test\"\n}"
  },
  {
    "path": "code/mmcv/tests/data/config/base.py",
    "content": "item1 = [1, 2]\nitem2 = {'a': 0}\nitem3 = True\nitem4 = 'test'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/c.yaml",
    "content": "item1: [1, 2]\nitem2: {'a': 0}\nitem3: True\nitem4: 'test'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/code.py",
    "content": "from mmcv import Config  # isort:skip\ncfg = Config.fromfile('./tests/data/config/a.py')\nitem5 = cfg.item1[0] + cfg.item2.a\n"
  },
  {
    "path": "code/mmcv/tests/data/config/d.py",
    "content": "_base_ = './base.py'\nitem1 = [2, 3]\nitem2 = {'a': 1}\nitem3 = False\nitem4 = 'test_base'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/delete.py",
    "content": "_base_ = './base.py'\nitem2 = {'b': 0, '_delete_': True}\n"
  },
  {
    "path": "code/mmcv/tests/data/config/e.py",
    "content": "_base_ = './base.py'\nitem3 = {'a': 1}\n"
  },
  {
    "path": "code/mmcv/tests/data/config/f.py",
    "content": "_base_ = './d.py'\nitem4 = 'test_recursive_bases'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/g.py",
    "content": "filename = 'reserved.py'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/i_base.py",
    "content": "item1 = [1, 2]\nitem2 = {'a': 0}\nitem3 = True\nitem4 = 'test'\nitem_cfg = {'b': 1}\nitem5 = {'cfg': item_cfg}\nitem6 = {'cfg': item_cfg}\n"
  },
  {
    "path": "code/mmcv/tests/data/config/i_child.py",
    "content": "_base_ = './i_base.py'\nitem_cfg = {'b': 2}\nitem6 = {'cfg': item_cfg}\n"
  },
  {
    "path": "code/mmcv/tests/data/config/l.py",
    "content": "_base_ = ['./l1.py', './l2.yaml', './l3.json', './l4.py']\nitem3 = False\nitem4 = 'test'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/l1.py",
    "content": "item1 = [1, 2]\n"
  },
  {
    "path": "code/mmcv/tests/data/config/l2.yaml",
    "content": "item2: {'a': 0}\n"
  },
  {
    "path": "code/mmcv/tests/data/config/l3.json",
    "content": "{\n  \"item3\": true\n}\n"
  },
  {
    "path": "code/mmcv/tests/data/config/l4.py",
    "content": "item5 = dict(a=0, b=1)\nitem6 = [dict(a=0), dict(b=1)]\nitem7 = dict(a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))\n"
  },
  {
    "path": "code/mmcv/tests/data/config/m.py",
    "content": "_base_ = ['./l1.py', './l2.yaml', './l3.json', 'a.py']\nitem3 = False\nitem4 = 'test'\n"
  },
  {
    "path": "code/mmcv/tests/data/config/n.py",
    "content": "test_item1 = [1, 2]\nbool_item2 = True\nstr_item3 = 'test'\ndict_item4 = dict(\n    a={\n        'c/d': 'path/d',\n        'f': 's3//f',\n        6: '2333',\n        '2333': 'number'\n    },\n    b={'8': 543},\n    c={9: 678},\n    d={'a': 0},\n    f=dict(a='69'))\ndict_item5 = {'x/x': {'a.0': 233}}\ndict_list_item6 = {'x/x': [{'a.0': 1., 'b.0': 2.}, {'c/3': 3.}]}\n"
  },
  {
    "path": "code/mmcv/tests/data/filelist.txt",
    "content": "1.jpg\n2.jpg\n3.jpg\n4.jpg\n5.jpg"
  },
  {
    "path": "code/mmcv/tests/data/for_scan/1.json",
    "content": ""
  },
  {
    "path": "code/mmcv/tests/data/for_scan/1.txt",
    "content": ""
  },
  {
    "path": "code/mmcv/tests/data/for_scan/2.json",
    "content": ""
  },
  {
    "path": "code/mmcv/tests/data/for_scan/2.txt",
    "content": ""
  },
  {
    "path": "code/mmcv/tests/data/for_scan/sub/1.json",
    "content": ""
  },
  {
    "path": "code/mmcv/tests/data/for_scan/sub/1.txt",
    "content": ""
  },
  {
    "path": "code/mmcv/tests/data/mapping.txt",
    "content": "1 cat\n2 dog cow\n3 panda"
  },
  {
    "path": "code/mmcv/tests/data/model_zoo/deprecated.json",
    "content": "{\n  \"train_old\": \"train\",\n  \"test_old\": \"test\"\n}"
  },
  {
    "path": "code/mmcv/tests/data/model_zoo/mmcv_home/open_mmlab.json",
    "content": "{\n  \"test\": \"test.pth\",\n  \"val\": \"val.pth\",\n  \"train_empty\": \"train.pth\"\n}"
  },
  {
    "path": "code/mmcv/tests/data/model_zoo/open_mmlab.json",
    "content": "{\n  \"train\": \"https://localhost/train.pth\",\n  \"test\": \"https://localhost/test.pth\"\n}"
  },
  {
    "path": "code/mmcv/tests/test_arraymisc.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nfrom __future__ import division\n\nimport numpy as np\nimport pytest\n\nimport mmcv\n\n\ndef test_quantize():\n    arr = np.random.randn(10, 10)\n    levels = 20\n\n    qarr = mmcv.quantize(arr, -1, 1, levels)\n    assert qarr.shape == arr.shape\n    assert qarr.dtype == np.dtype('int64')\n    for i in range(arr.shape[0]):\n        for j in range(arr.shape[1]):\n            ref = min(levels - 1,\n                      int(np.floor(10 * (1 + max(min(arr[i, j], 1), -1)))))\n            assert qarr[i, j] == ref\n\n    qarr = mmcv.quantize(arr, -1, 1, 20, dtype=np.uint8)\n    assert qarr.shape == arr.shape\n    assert qarr.dtype == np.dtype('uint8')\n\n    with pytest.raises(ValueError):\n        mmcv.quantize(arr, -1, 1, levels=0)\n    with pytest.raises(ValueError):\n        mmcv.quantize(arr, -1, 1, levels=10.0)\n    with pytest.raises(ValueError):\n        mmcv.quantize(arr, 2, 1, levels)\n\n\ndef test_dequantize():\n    levels = 20\n    qarr = np.random.randint(levels, size=(10, 10))\n\n    arr = mmcv.dequantize(qarr, -1, 1, levels)\n    assert arr.shape == qarr.shape\n    assert arr.dtype == np.dtype('float64')\n    for i in range(qarr.shape[0]):\n        for j in range(qarr.shape[1]):\n            assert arr[i, j] == (qarr[i, j] + 0.5) / 10 - 1\n\n    arr = mmcv.dequantize(qarr, -1, 1, levels, dtype=np.float32)\n    assert arr.shape == qarr.shape\n    assert arr.dtype == np.dtype('float32')\n\n    with pytest.raises(ValueError):\n        mmcv.dequantize(arr, -1, 1, levels=0)\n    with pytest.raises(ValueError):\n        mmcv.dequantize(arr, -1, 1, levels=10.0)\n    with pytest.raises(ValueError):\n        mmcv.dequantize(arr, 2, 1, levels)\n\n\ndef test_joint():\n    arr = np.random.randn(100, 100)\n    levels = 1000\n    qarr = mmcv.quantize(arr, -1, 1, levels)\n    recover = mmcv.dequantize(qarr, -1, 1, levels)\n    assert np.abs(recover[arr < -1] + 0.999).max() < 1e-6\n    assert np.abs(recover[arr > 1] - 0.999).max() < 1e-6\n    assert np.abs((recover - arr)[(arr >= -1) & (arr <= 1)]).max() <= 1e-3\n\n    arr = np.clip(np.random.randn(100) / 1000, -0.01, 0.01)\n    levels = 99\n    qarr = mmcv.quantize(arr, -1, 1, levels)\n    recover = mmcv.dequantize(qarr, -1, 1, levels)\n    assert np.all(recover == 0)\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_build_layers.py",
    "content": "import pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmcv.cnn.bricks import (ACTIVATION_LAYERS, CONV_LAYERS, NORM_LAYERS,\n                             PADDING_LAYERS, build_activation_layer,\n                             build_conv_layer, build_norm_layer,\n                             build_padding_layer, build_upsample_layer,\n                             is_norm)\nfrom mmcv.cnn.bricks.norm import infer_abbr\nfrom mmcv.cnn.bricks.upsample import PixelShufflePack\nfrom mmcv.utils.parrots_wrapper import _BatchNorm\n\n\ndef test_build_conv_layer():\n    with pytest.raises(TypeError):\n        # cfg must be a dict\n        cfg = 'Conv2d'\n        build_conv_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # `type` must be in cfg\n        cfg = dict(kernel_size=3)\n        build_conv_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # unsupported conv type\n        cfg = dict(type='FancyConv')\n        build_conv_layer(cfg)\n\n    kwargs = dict(\n        in_channels=4, out_channels=8, kernel_size=3, groups=2, dilation=2)\n    cfg = None\n    layer = build_conv_layer(cfg, **kwargs)\n    assert isinstance(layer, nn.Conv2d)\n    assert layer.in_channels == kwargs['in_channels']\n    assert layer.out_channels == kwargs['out_channels']\n    assert layer.kernel_size == (kwargs['kernel_size'], kwargs['kernel_size'])\n    assert layer.groups == kwargs['groups']\n    assert layer.dilation == (kwargs['dilation'], kwargs['dilation'])\n\n    cfg = dict(type='Conv')\n    layer = build_conv_layer(cfg, **kwargs)\n    assert isinstance(layer, nn.Conv2d)\n    assert layer.in_channels == kwargs['in_channels']\n    assert layer.out_channels == kwargs['out_channels']\n    assert layer.kernel_size == (kwargs['kernel_size'], kwargs['kernel_size'])\n    assert layer.groups == kwargs['groups']\n    assert layer.dilation == (kwargs['dilation'], kwargs['dilation'])\n\n    for type_name, module in CONV_LAYERS.module_dict.items():\n        cfg = dict(type=type_name)\n        layer = build_conv_layer(cfg, **kwargs)\n        assert isinstance(layer, module)\n        assert layer.in_channels == kwargs['in_channels']\n        assert layer.out_channels == kwargs['out_channels']\n\n\ndef test_infer_abbr():\n    with pytest.raises(TypeError):\n        # class_type must be a class\n        infer_abbr(0)\n\n    class MyNorm:\n\n        abbr = 'mn'\n\n    assert infer_abbr(MyNorm) == 'mn'\n\n    class FancyBatchNorm:\n        pass\n\n    assert infer_abbr(FancyBatchNorm) == 'bn'\n\n    class FancyInstanceNorm:\n        pass\n\n    assert infer_abbr(FancyInstanceNorm) == 'in'\n\n    class FancyLayerNorm:\n        pass\n\n    assert infer_abbr(FancyLayerNorm) == 'ln'\n\n    class FancyGroupNorm:\n        pass\n\n    assert infer_abbr(FancyGroupNorm) == 'gn'\n\n    class FancyNorm:\n        pass\n\n    assert infer_abbr(FancyNorm) == 'norm'\n\n\ndef test_build_norm_layer():\n    with pytest.raises(TypeError):\n        # cfg must be a dict\n        cfg = 'BN'\n        build_norm_layer(cfg, 3)\n\n    with pytest.raises(KeyError):\n        # `type` must be in cfg\n        cfg = dict()\n        build_norm_layer(cfg, 3)\n\n    with pytest.raises(KeyError):\n        # unsupported norm type\n        cfg = dict(type='FancyNorm')\n        build_norm_layer(cfg, 3)\n\n    with pytest.raises(AssertionError):\n        # postfix must be int or str\n        cfg = dict(type='BN')\n        build_norm_layer(cfg, 3, postfix=[1, 2])\n\n    with pytest.raises(AssertionError):\n        # `num_groups` must be in cfg when using 'GN'\n        cfg = dict(type='GN')\n        build_norm_layer(cfg, 3)\n\n    # test each type of norm layer in norm_cfg\n    abbr_mapping = {\n        'BN': 'bn',\n        'BN1d': 'bn',\n        'BN2d': 'bn',\n        'BN3d': 'bn',\n        'SyncBN': 'bn',\n        'GN': 'gn',\n        'LN': 'ln',\n        'IN': 'in',\n        'IN1d': 'in',\n        'IN2d': 'in',\n        'IN3d': 'in',\n    }\n    for type_name, module in NORM_LAYERS.module_dict.items():\n        for postfix in ['_test', 1]:\n            cfg = dict(type=type_name)\n            if type_name == 'GN':\n                cfg['num_groups'] = 2\n            name, layer = build_norm_layer(cfg, 3, postfix=postfix)\n            assert name == abbr_mapping[type_name] + str(postfix)\n            assert isinstance(layer, module)\n            if type_name == 'GN':\n                assert layer.num_channels == 3\n                assert layer.num_groups == cfg['num_groups']\n            elif type_name != 'LN':\n                assert layer.num_features == 3\n\n\ndef test_build_activation_layer():\n    with pytest.raises(TypeError):\n        # cfg must be a dict\n        cfg = 'ReLU'\n        build_activation_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # `type` must be in cfg\n        cfg = dict()\n        build_activation_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # unsupported activation type\n        cfg = dict(type='FancyReLU')\n        build_activation_layer(cfg)\n\n    # test each type of activation layer in activation_cfg\n    for type_name, module in ACTIVATION_LAYERS.module_dict.items():\n        cfg['type'] = type_name\n        layer = build_activation_layer(cfg)\n        assert isinstance(layer, module)\n\n\ndef test_build_padding_layer():\n    with pytest.raises(TypeError):\n        # cfg must be a dict\n        cfg = 'reflect'\n        build_padding_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # `type` must be in cfg\n        cfg = dict()\n        build_padding_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # unsupported activation type\n        cfg = dict(type='FancyPad')\n        build_padding_layer(cfg)\n\n    for type_name, module in PADDING_LAYERS.module_dict.items():\n        cfg['type'] = type_name\n        layer = build_padding_layer(cfg, 2)\n        assert isinstance(layer, module)\n\n    input_x = torch.randn(1, 2, 5, 5)\n    cfg = dict(type='reflect')\n    padding_layer = build_padding_layer(cfg, 2)\n    res = padding_layer(input_x)\n    assert res.shape == (1, 2, 9, 9)\n\n\ndef test_upsample_layer():\n    with pytest.raises(TypeError):\n        # cfg must be a dict\n        cfg = 'bilinear'\n        build_upsample_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # `type` must be in cfg\n        cfg = dict()\n        build_upsample_layer(cfg)\n\n    with pytest.raises(KeyError):\n        # unsupported activation type\n        cfg = dict(type='FancyUpsample')\n        build_upsample_layer(cfg)\n\n    for type_name in ['nearest', 'bilinear']:\n        cfg['type'] = type_name\n        layer = build_upsample_layer(cfg)\n        assert isinstance(layer, nn.Upsample)\n        assert layer.mode == type_name\n\n    cfg = dict(\n        type='deconv', in_channels=3, out_channels=3, kernel_size=3, stride=2)\n    layer = build_upsample_layer(cfg)\n    assert isinstance(layer, nn.ConvTranspose2d)\n\n    cfg = dict(type='deconv')\n    kwargs = dict(in_channels=3, out_channels=3, kernel_size=3, stride=2)\n    layer = build_upsample_layer(cfg, **kwargs)\n    assert isinstance(layer, nn.ConvTranspose2d)\n    assert layer.in_channels == kwargs['in_channels']\n    assert layer.out_channels == kwargs['out_channels']\n    assert layer.kernel_size == (kwargs['kernel_size'], kwargs['kernel_size'])\n    assert layer.stride == (kwargs['stride'], kwargs['stride'])\n\n    layer = build_upsample_layer(cfg, 3, 3, 3, 2)\n    assert isinstance(layer, nn.ConvTranspose2d)\n    assert layer.in_channels == kwargs['in_channels']\n    assert layer.out_channels == kwargs['out_channels']\n    assert layer.kernel_size == (kwargs['kernel_size'], kwargs['kernel_size'])\n    assert layer.stride == (kwargs['stride'], kwargs['stride'])\n\n    cfg = dict(\n        type='pixel_shuffle',\n        in_channels=3,\n        out_channels=3,\n        scale_factor=2,\n        upsample_kernel=3)\n    layer = build_upsample_layer(cfg)\n\n    assert isinstance(layer, PixelShufflePack)\n    assert layer.scale_factor == 2\n    assert layer.upsample_kernel == 3\n\n\ndef test_pixel_shuffle_pack():\n    x_in = torch.rand(2, 3, 10, 10)\n    pixel_shuffle = PixelShufflePack(3, 3, scale_factor=2, upsample_kernel=3)\n    assert pixel_shuffle.upsample_conv.kernel_size == (3, 3)\n    x_out = pixel_shuffle(x_in)\n    assert x_out.shape == (2, 3, 20, 20)\n\n\ndef test_is_norm():\n    norm_set1 = [\n        nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.InstanceNorm1d,\n        nn.InstanceNorm2d, nn.InstanceNorm3d, nn.LayerNorm\n    ]\n    norm_set2 = [nn.GroupNorm]\n    for norm_type in norm_set1:\n        layer = norm_type(3)\n        assert is_norm(layer)\n        assert not is_norm(layer, exclude=(norm_type, ))\n    for norm_type in norm_set2:\n        layer = norm_type(3, 6)\n        assert is_norm(layer)\n        assert not is_norm(layer, exclude=(norm_type, ))\n\n    class MyNorm(nn.BatchNorm2d):\n        pass\n\n    layer = MyNorm(3)\n    assert is_norm(layer)\n    assert not is_norm(layer, exclude=_BatchNorm)\n    assert not is_norm(layer, exclude=(_BatchNorm, ))\n\n    layer = nn.Conv2d(3, 8, 1)\n    assert not is_norm(layer)\n\n    with pytest.raises(TypeError):\n        layer = nn.BatchNorm1d(3)\n        is_norm(layer, exclude='BN')\n\n    with pytest.raises(TypeError):\n        layer = nn.BatchNorm1d(3)\n        is_norm(layer, exclude=('BN', ))\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_conv_module.py",
    "content": "from unittest.mock import patch\n\nimport pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmcv.cnn.bricks import CONV_LAYERS, ConvModule\n\n\n@CONV_LAYERS.register_module()\nclass ExampleConv(nn.Module):\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 bias=True,\n                 norm_cfg=None):\n        super(ExampleConv, self).__init__()\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.kernel_size = kernel_size\n        self.stride = stride\n        self.padding = padding\n        self.dilation = dilation\n        self.groups = groups\n        self.bias = bias\n        self.norm_cfg = norm_cfg\n        self.output_padding = (0, 0, 0)\n        self.transposed = False\n\n        self.conv0 = nn.Conv2d(in_channels, out_channels, kernel_size)\n        self.init_weights()\n\n    def forward(self, x):\n        x = self.conv0(x)\n        return x\n\n    def init_weights(self):\n        nn.init.constant_(self.conv0.weight, 0)\n\n\ndef test_conv_module():\n    with pytest.raises(AssertionError):\n        # conv_cfg must be a dict or None\n        conv_cfg = 'conv'\n        ConvModule(3, 8, 2, conv_cfg=conv_cfg)\n\n    with pytest.raises(AssertionError):\n        # norm_cfg must be a dict or None\n        norm_cfg = 'norm'\n        ConvModule(3, 8, 2, norm_cfg=norm_cfg)\n\n    with pytest.raises(KeyError):\n        # softmax is not supported\n        act_cfg = dict(type='softmax')\n        ConvModule(3, 8, 2, act_cfg=act_cfg)\n\n    # conv + norm + act\n    conv = ConvModule(3, 8, 2, norm_cfg=dict(type='BN'))\n    assert conv.with_activation\n    assert hasattr(conv, 'activate')\n    assert conv.with_norm\n    assert hasattr(conv, 'norm')\n    x = torch.rand(1, 3, 256, 256)\n    output = conv(x)\n    assert output.shape == (1, 8, 255, 255)\n\n    # conv + act\n    conv = ConvModule(3, 8, 2)\n    assert conv.with_activation\n    assert hasattr(conv, 'activate')\n    assert not conv.with_norm\n    assert not hasattr(conv, 'norm')\n    x = torch.rand(1, 3, 256, 256)\n    output = conv(x)\n    assert output.shape == (1, 8, 255, 255)\n\n    # conv\n    conv = ConvModule(3, 8, 2, act_cfg=None)\n    assert not conv.with_norm\n    assert not hasattr(conv, 'norm')\n    assert not conv.with_activation\n    assert not hasattr(conv, 'activate')\n    x = torch.rand(1, 3, 256, 256)\n    output = conv(x)\n    assert output.shape == (1, 8, 255, 255)\n\n    # conv with its own `init_weights` method\n    conv_module = ConvModule(\n        3, 8, 2, conv_cfg=dict(type='ExampleConv'), act_cfg=None)\n    assert torch.equal(conv_module.conv.conv0.weight, torch.zeros(8, 3, 2, 2))\n\n    # with_spectral_norm=True\n    conv = ConvModule(3, 8, 3, padding=1, with_spectral_norm=True)\n    assert hasattr(conv.conv, 'weight_orig')\n    output = conv(x)\n    assert output.shape == (1, 8, 256, 256)\n\n    # padding_mode='reflect'\n    conv = ConvModule(3, 8, 3, padding=1, padding_mode='reflect')\n    assert isinstance(conv.padding_layer, nn.ReflectionPad2d)\n    output = conv(x)\n    assert output.shape == (1, 8, 256, 256)\n\n    # non-existing padding mode\n    with pytest.raises(KeyError):\n        conv = ConvModule(3, 8, 3, padding=1, padding_mode='non_exists')\n\n    # leaky relu\n    conv = ConvModule(3, 8, 3, padding=1, act_cfg=dict(type='LeakyReLU'))\n    assert isinstance(conv.activate, nn.LeakyReLU)\n    output = conv(x)\n    assert output.shape == (1, 8, 256, 256)\n\n    # tanh\n    conv = ConvModule(3, 8, 3, padding=1, act_cfg=dict(type='Tanh'))\n    assert isinstance(conv.activate, nn.Tanh)\n    output = conv(x)\n    assert output.shape == (1, 8, 256, 256)\n\n    # Sigmoid\n    conv = ConvModule(3, 8, 3, padding=1, act_cfg=dict(type='Sigmoid'))\n    assert isinstance(conv.activate, nn.Sigmoid)\n    output = conv(x)\n    assert output.shape == (1, 8, 256, 256)\n\n    # PReLU\n    conv = ConvModule(3, 8, 3, padding=1, act_cfg=dict(type='PReLU'))\n    assert isinstance(conv.activate, nn.PReLU)\n    output = conv(x)\n    assert output.shape == (1, 8, 256, 256)\n\n\ndef test_bias():\n    # bias: auto, without norm\n    conv = ConvModule(3, 8, 2)\n    assert conv.conv.bias is not None\n\n    # bias: auto, with norm\n    conv = ConvModule(3, 8, 2, norm_cfg=dict(type='BN'))\n    assert conv.conv.bias is None\n\n    # bias: False, without norm\n    conv = ConvModule(3, 8, 2, bias=False)\n    assert conv.conv.bias is None\n\n    # bias: True, with norm\n    with pytest.warns(UserWarning) as record:\n        ConvModule(3, 8, 2, bias=True, norm_cfg=dict(type='BN'))\n    assert len(record) == 1\n    assert record[0].message.args[\n        0] == 'ConvModule has norm and bias at the same time'\n\n\ndef conv_forward(self, x):\n    return x + '_conv'\n\n\ndef bn_forward(self, x):\n    return x + '_bn'\n\n\ndef relu_forward(self, x):\n    return x + '_relu'\n\n\n@patch('torch.nn.ReLU.forward', relu_forward)\n@patch('torch.nn.BatchNorm2d.forward', bn_forward)\n@patch('torch.nn.Conv2d.forward', conv_forward)\ndef test_order():\n\n    with pytest.raises(AssertionError):\n        # order must be a tuple\n        order = ['conv', 'norm', 'act']\n        ConvModule(3, 8, 2, order=order)\n\n    with pytest.raises(AssertionError):\n        # length of order must be 3\n        order = ('conv', 'norm')\n        ConvModule(3, 8, 2, order=order)\n\n    with pytest.raises(AssertionError):\n        # order must be an order of 'conv', 'norm', 'act'\n        order = ('conv', 'norm', 'norm')\n        ConvModule(3, 8, 2, order=order)\n\n    with pytest.raises(AssertionError):\n        # order must be an order of 'conv', 'norm', 'act'\n        order = ('conv', 'norm', 'something')\n        ConvModule(3, 8, 2, order=order)\n\n    # ('conv', 'norm', 'act')\n    conv = ConvModule(3, 8, 2, norm_cfg=dict(type='BN'))\n    out = conv('input')\n    assert out == 'input_conv_bn_relu'\n\n    # ('norm', 'conv', 'act')\n    conv = ConvModule(\n        3, 8, 2, norm_cfg=dict(type='BN'), order=('norm', 'conv', 'act'))\n    out = conv('input')\n    assert out == 'input_bn_conv_relu'\n\n    # ('conv', 'norm', 'act'), activate=False\n    conv = ConvModule(3, 8, 2, norm_cfg=dict(type='BN'))\n    out = conv('input', activate=False)\n    assert out == 'input_conv_bn'\n\n    # ('conv', 'norm', 'act'), activate=False\n    conv = ConvModule(3, 8, 2, norm_cfg=dict(type='BN'))\n    out = conv('input', norm=False)\n    assert out == 'input_conv_relu'\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_flops_counter.py",
    "content": "import pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmcv.cnn import get_model_complexity_info\nfrom mmcv.cnn.utils.flops_counter import flops_to_string, params_to_string\n\ntry:\n    from StringIO import StringIO\nexcept ImportError:\n    from io import StringIO\n\n# yapf: disable\ngt_results = [\n    {'model': nn.Conv1d(3, 8, 3), 'input': (3, 16), 'flops': 1120.0, 'params': 80.0},  # noqa: E501\n    {'model': nn.Conv2d(3, 8, 3), 'input': (3, 16, 16), 'flops': 43904.0, 'params': 224.0},  # noqa: E501\n    {'model': nn.Conv3d(3, 8, 3), 'input': (3, 3, 16, 16), 'flops': 128576.0, 'params': 656.0},  # noqa: E501\n    {'model': nn.ReLU(), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.PReLU(), 'input': (3, 16, 16), 'flops': 768.0, 'params': 1},  # noqa: E501\n    {'model': nn.ELU(), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.LeakyReLU(), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.ReLU6(), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.MaxPool1d(2), 'input': (3, 16), 'flops': 48.0, 'params': 0},  # noqa: E501\n    {'model': nn.MaxPool2d(2), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.MaxPool3d(2), 'input': (3, 3, 16, 16), 'flops': 2304.0, 'params': 0},  # noqa: E501\n    {'model': nn.AvgPool1d(2), 'input': (3, 16), 'flops': 48.0, 'params': 0},  # noqa: E501\n    {'model': nn.AvgPool2d(2), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.AvgPool3d(2), 'input': (3, 3, 16, 16), 'flops': 2304.0, 'params': 0},  # noqa: E501\n    {'model': nn.AdaptiveMaxPool1d(2), 'input': (3, 16), 'flops': 48.0, 'params': 0},  # noqa: E501\n    {'model': nn.AdaptiveMaxPool2d(2), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.AdaptiveMaxPool3d(2), 'input': (3, 3, 16, 16), 'flops': 2304.0, 'params': 0},  # noqa: E501\n    {'model': nn.AdaptiveAvgPool1d(2), 'input': (3, 16), 'flops': 48.0, 'params': 0},  # noqa: E501\n    {'model': nn.AdaptiveAvgPool2d(2), 'input': (3, 16, 16), 'flops': 768.0, 'params': 0},  # noqa: E501\n    {'model': nn.AdaptiveAvgPool3d(2), 'input': (3, 3, 16, 16), 'flops': 2304.0, 'params': 0},  # noqa: E501\n    {'model': nn.BatchNorm1d(3, 8), 'input': (3, 16), 'flops': 96.0, 'params': 6.0},  # noqa: E501\n    {'model': nn.BatchNorm2d(3, 8), 'input': (3, 16, 16), 'flops': 1536.0, 'params': 6.0},  # noqa: E501\n    {'model': nn.BatchNorm3d(3, 8), 'input': (3, 3, 16, 16), 'flops': 4608.0, 'params': 6.0},  # noqa: E501\n    {'model': nn.Linear(1024, 2), 'input': (1024, ), 'flops': 2048.0, 'params': 2050.0},  # noqa: E501\n    {'model': nn.ConvTranspose2d(3, 8, 3), 'input': (3, 16, 16), 'flops': 57888, 'params': 224.0},  # noqa: E501\n    {'model': nn.Upsample((32, 32)), 'input': (3, 16, 16), 'flops': 3072.0, 'params': 0}  # noqa: E501\n]\n# yapf: enable\n\n\nclass ExampleModel(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.conv2d = nn.Conv2d(3, 8, 3)\n\n    def forward(self, imgs):\n        x = torch.randn((1, *imgs))\n        return self.conv2d(x)\n\n\ndef input_constructor(x):\n    return dict(imgs=x)\n\n\ndef test_flops_counter():\n    with pytest.raises(AssertionError):\n        # input_res should be a tuple\n        model = nn.Conv2d(3, 8, 3)\n        input_res = [1, 3, 16, 16]\n        get_model_complexity_info(model, input_res)\n\n    with pytest.raises(AssertionError):\n        # len(input_res) >= 2\n        model = nn.Conv2d(3, 8, 3)\n        input_res = tuple()\n        get_model_complexity_info(model, input_res)\n\n    # test common layers\n    for item in gt_results:\n        model = item['model']\n        input = item['input']\n        flops, params = get_model_complexity_info(\n            model, input, as_strings=False, print_per_layer_stat=False)\n        assert flops == item['flops'] and params == item['params']\n\n    # test input constructor\n    model = ExampleModel()\n    x = (3, 16, 16)\n    flops, params = get_model_complexity_info(\n        model,\n        x,\n        as_strings=False,\n        print_per_layer_stat=False,\n        input_constructor=input_constructor)\n    assert flops == 43904.0 and params == 224.0\n\n    # test output string\n    model = nn.Conv3d(3, 8, 3)\n    x = (3, 3, 512, 512)\n    flops, params = get_model_complexity_info(\n        model, x, print_per_layer_stat=False)\n    assert flops == '0.17 GFLOPs' and params == str(656)\n\n    # test print per layer status\n    model = nn.Conv1d(3, 8, 3)\n    x = (3, 16)\n    out = StringIO()\n    get_model_complexity_info(model, x, ost=out)\n    assert out.getvalue() == \\\n        'Conv1d(0.0 M, 100.000% Params, 0.0 GFLOPs, 100.000% FLOPs, 3, 8, kernel_size=(3,), stride=(1,))\\n'  # noqa: E501\n\n    # test when model is not a common instance\n    model = nn.Sequential(nn.Conv2d(3, 8, 3), nn.Flatten(), nn.Linear(1568, 2))\n    x = (3, 16, 16)\n    flops, params = get_model_complexity_info(\n        model, x, as_strings=False, print_per_layer_stat=True)\n    assert flops == 47040.0 and params == 3362\n\n\ndef test_flops_to_string():\n    flops = 6.54321 * 10.**9\n    assert flops_to_string(flops) == '6.54 GFLOPs'\n    assert flops_to_string(flops, 'MFLOPs') == '6543.21 MFLOPs'\n    assert flops_to_string(flops, 'KFLOPs') == '6543210.0 KFLOPs'\n    assert flops_to_string(flops, 'FLOPs') == '6543210000.0 FLOPs'\n    assert flops_to_string(flops, precision=4) == '6.5432 GFLOPs'\n\n    flops = 6.54321 * 10.**9\n    assert flops_to_string(flops, None) == '6.54 GFLOPs'\n    flops = 3.21 * 10.**7\n    assert flops_to_string(flops, None) == '32.1 MFLOPs'\n    flops = 5.4 * 10.**3\n    assert flops_to_string(flops, None) == '5.4 KFLOPs'\n    flops = 987\n    assert flops_to_string(flops, None) == '987 FLOPs'\n\n\ndef test_params_to_string():\n    num_params = 3.21 * 10.**7\n    assert params_to_string(num_params) == '32.1 M'\n    num_params = 4.56 * 10.**5\n    assert params_to_string(num_params) == '456.0 k'\n    num_params = 7.89 * 10.**2\n    assert params_to_string(num_params) == '789.0'\n\n    num_params = 6.54321 * 10.**7\n    assert params_to_string(num_params, 'M') == '65.43 M'\n    assert params_to_string(num_params, 'K') == '65432.1 K'\n    assert params_to_string(num_params, '') == '65432100.0'\n    assert params_to_string(num_params, precision=4) == '65.4321 M'\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_hsigmoid.py",
    "content": "import torch\n\nfrom mmcv.cnn.bricks import HSigmoid\n\n\ndef test_hsigmoid():\n    act = HSigmoid()\n    input_shape = torch.Size([1, 3, 64, 64])\n    input = torch.randn(input_shape)\n    output = act(input)\n    expected_output = torch.min(\n        torch.max((input + 1) / 2, torch.zeros(input_shape)),\n        torch.ones(input_shape))\n    # test output shape\n    assert output.shape == expected_output.shape\n    # test output value\n    assert torch.equal(output, expected_output)\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_hswish.py",
    "content": "import torch\nfrom torch.nn.functional import relu6\n\nfrom mmcv.cnn.bricks import HSwish\n\n\ndef test_hswish():\n    # test inplace\n    act = HSwish(inplace=True)\n    assert act.act.inplace\n    act = HSwish()\n    assert not act.act.inplace\n\n    input = torch.randn(1, 3, 64, 64)\n    expected_output = input * relu6(input + 3) / 6\n    output = act(input)\n    # test output shape\n    assert output.shape == expected_output.shape\n    # test output value\n    assert torch.equal(output, expected_output)\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_non_local.py",
    "content": "import pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmcv.cnn import NonLocal1d, NonLocal2d, NonLocal3d\nfrom mmcv.cnn.bricks.non_local import _NonLocalNd\n\n\ndef test_nonlocal():\n    with pytest.raises(ValueError):\n        # mode should be in ['embedded_gaussian', 'dot_product']\n        _NonLocalNd(3, mode='unsupport_mode')\n\n    # _NonLocalNd\n    _NonLocalNd(3, norm_cfg=dict(type='BN'))\n    # Not Zero initialization\n    _NonLocalNd(3, norm_cfg=dict(type='BN'), zeros_init=True)\n\n    # NonLocal3d\n    imgs = torch.randn(2, 3, 10, 20, 20)\n    nonlocal_3d = NonLocal3d(3)\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            # NonLocal is only implemented on gpu in parrots\n            imgs = imgs.cuda()\n            nonlocal_3d.cuda()\n    out = nonlocal_3d(imgs)\n    assert out.shape == imgs.shape\n\n    nonlocal_3d = NonLocal3d(3, mode='dot_product')\n    assert nonlocal_3d.mode == 'dot_product'\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            nonlocal_3d.cuda()\n    out = nonlocal_3d(imgs)\n    assert out.shape == imgs.shape\n\n    nonlocal_3d = NonLocal3d(3, mode='dot_product', sub_sample=True)\n    for m in [nonlocal_3d.g, nonlocal_3d.phi]:\n        assert isinstance(m, nn.Sequential) and len(m) == 2\n        assert isinstance(m[1], nn.MaxPool3d)\n        assert m[1].kernel_size == (1, 2, 2)\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            nonlocal_3d.cuda()\n    out = nonlocal_3d(imgs)\n    assert out.shape == imgs.shape\n\n    # NonLocal2d\n    imgs = torch.randn(2, 3, 20, 20)\n    nonlocal_2d = NonLocal2d(3)\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            imgs = imgs.cuda()\n            nonlocal_2d.cuda()\n    out = nonlocal_2d(imgs)\n    assert out.shape == imgs.shape\n\n    nonlocal_2d = NonLocal2d(3, mode='dot_product', sub_sample=True)\n    for m in [nonlocal_2d.g, nonlocal_2d.phi]:\n        assert isinstance(m, nn.Sequential) and len(m) == 2\n        assert isinstance(m[1], nn.MaxPool2d)\n        assert m[1].kernel_size == (2, 2)\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            nonlocal_2d.cuda()\n    out = nonlocal_2d(imgs)\n    assert out.shape == imgs.shape\n\n    # NonLocal1d\n    imgs = torch.randn(2, 3, 20)\n    nonlocal_1d = NonLocal1d(3)\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            imgs = imgs.cuda()\n            nonlocal_1d.cuda()\n    out = nonlocal_1d(imgs)\n    assert out.shape == imgs.shape\n\n    nonlocal_1d = NonLocal1d(3, mode='dot_product', sub_sample=True)\n    for m in [nonlocal_1d.g, nonlocal_1d.phi]:\n        assert isinstance(m, nn.Sequential) and len(m) == 2\n        assert isinstance(m[1], nn.MaxPool1d)\n        assert m[1].kernel_size == 2\n    if torch.__version__ == 'parrots':\n        if torch.cuda.is_available():\n            nonlocal_1d.cuda()\n    out = nonlocal_1d(imgs)\n    assert out.shape == imgs.shape\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_scale.py",
    "content": "import torch\n\nfrom mmcv.cnn.bricks import Scale\n\n\ndef test_scale():\n    # test default scale\n    scale = Scale()\n    assert scale.scale.data == 1.\n    assert scale.scale.dtype == torch.float\n    x = torch.rand(1, 3, 64, 64)\n    output = scale(x)\n    assert output.shape == (1, 3, 64, 64)\n\n    # test given scale\n    scale = Scale(10.)\n    assert scale.scale.data == 10.\n    assert scale.scale.dtype == torch.float\n    x = torch.rand(1, 3, 64, 64)\n    output = scale(x)\n    assert output.shape == (1, 3, 64, 64)\n"
  },
  {
    "path": "code/mmcv/tests/test_cnn/test_weight_init.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numpy as np\nimport pytest\nimport torch\nfrom torch import nn\n\nfrom mmcv.cnn import (bias_init_with_prob, caffe2_xavier_init, constant_init,\n                      kaiming_init, normal_init, uniform_init, xavier_init)\n\n\ndef test_constant_init():\n    conv_module = nn.Conv2d(3, 16, 3)\n    constant_init(conv_module, 0.1)\n    assert conv_module.weight.allclose(\n        torch.full_like(conv_module.weight, 0.1))\n    assert conv_module.bias.allclose(torch.zeros_like(conv_module.bias))\n    conv_module_no_bias = nn.Conv2d(3, 16, 3, bias=False)\n    constant_init(conv_module_no_bias, 0.1)\n    assert conv_module.weight.allclose(\n        torch.full_like(conv_module.weight, 0.1))\n\n\ndef test_xavier_init():\n    conv_module = nn.Conv2d(3, 16, 3)\n    xavier_init(conv_module, bias=0.1)\n    assert conv_module.bias.allclose(torch.full_like(conv_module.bias, 0.1))\n    xavier_init(conv_module, distribution='uniform')\n    # TODO: sanity check of weight distribution, e.g. mean, std\n    with pytest.raises(AssertionError):\n        xavier_init(conv_module, distribution='student-t')\n    conv_module_no_bias = nn.Conv2d(3, 16, 3, bias=False)\n    xavier_init(conv_module_no_bias)\n\n\ndef test_normal_init():\n    conv_module = nn.Conv2d(3, 16, 3)\n    normal_init(conv_module, bias=0.1)\n    # TODO: sanity check of weight distribution, e.g. mean, std\n    assert conv_module.bias.allclose(torch.full_like(conv_module.bias, 0.1))\n    conv_module_no_bias = nn.Conv2d(3, 16, 3, bias=False)\n    normal_init(conv_module_no_bias)\n    # TODO: sanity check distribution, e.g. mean, std\n\n\ndef test_uniform_init():\n    conv_module = nn.Conv2d(3, 16, 3)\n    uniform_init(conv_module, bias=0.1)\n    # TODO: sanity check of weight distribution, e.g. mean, std\n    assert conv_module.bias.allclose(torch.full_like(conv_module.bias, 0.1))\n    conv_module_no_bias = nn.Conv2d(3, 16, 3, bias=False)\n    uniform_init(conv_module_no_bias)\n\n\ndef test_kaiming_init():\n    conv_module = nn.Conv2d(3, 16, 3)\n    kaiming_init(conv_module, bias=0.1)\n    # TODO: sanity check of weight distribution, e.g. mean, std\n    assert conv_module.bias.allclose(torch.full_like(conv_module.bias, 0.1))\n    kaiming_init(conv_module, distribution='uniform')\n    with pytest.raises(AssertionError):\n        kaiming_init(conv_module, distribution='student-t')\n    conv_module_no_bias = nn.Conv2d(3, 16, 3, bias=False)\n    kaiming_init(conv_module_no_bias)\n\n\ndef test_caffe_xavier_init():\n    conv_module = nn.Conv2d(3, 16, 3)\n    caffe2_xavier_init(conv_module)\n\n\ndef test_bias_init_with_prob():\n    conv_module = nn.Conv2d(3, 16, 3)\n    prior_prob = 0.1\n    normal_init(conv_module, bias=bias_init_with_prob(0.1))\n    # TODO: sanity check of weight distribution, e.g. mean, std\n    bias = float(-np.log((1 - prior_prob) / prior_prob))\n    assert conv_module.bias.allclose(torch.full_like(conv_module.bias, bias))\n"
  },
  {
    "path": "code/mmcv/tests/test_config.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport argparse\nimport json\nimport os.path as osp\nimport tempfile\n\nimport pytest\nimport yaml\n\nfrom mmcv import Config, DictAction\n\n\ndef test_construct():\n    cfg = Config()\n    assert cfg.filename is None\n    assert cfg.text == ''\n    assert len(cfg) == 0\n    assert cfg._cfg_dict == {}\n\n    with pytest.raises(TypeError):\n        Config([0, 1])\n\n    cfg_dict = dict(item1=[1, 2], item2=dict(a=0), item3=True, item4='test')\n    # test a.py\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/a.py')\n    cfg = Config(cfg_dict, filename=cfg_file)\n    assert isinstance(cfg, Config)\n    assert cfg.filename == cfg_file\n    assert cfg.text == open(cfg_file, 'r').read()\n    assert cfg.dump() == cfg.pretty_text\n    with tempfile.TemporaryDirectory() as temp_config_dir:\n        dump_file = osp.join(temp_config_dir, 'a.py')\n        cfg.dump(dump_file)\n        assert cfg.dump() == open(dump_file, 'r').read()\n        assert Config.fromfile(dump_file)\n\n    # test b.json\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/b.json')\n    cfg = Config(cfg_dict, filename=cfg_file)\n    assert isinstance(cfg, Config)\n    assert cfg.filename == cfg_file\n    assert cfg.text == open(cfg_file, 'r').read()\n    assert cfg.dump() == json.dumps(cfg_dict)\n    with tempfile.TemporaryDirectory() as temp_config_dir:\n        dump_file = osp.join(temp_config_dir, 'b.json')\n        cfg.dump(dump_file)\n        assert cfg.dump() == open(dump_file, 'r').read()\n        assert Config.fromfile(dump_file)\n\n    # test c.yaml\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/c.yaml')\n    cfg = Config(cfg_dict, filename=cfg_file)\n    assert isinstance(cfg, Config)\n    assert cfg.filename == cfg_file\n    assert cfg.text == open(cfg_file, 'r').read()\n    assert cfg.dump() == yaml.dump(cfg_dict)\n    with tempfile.TemporaryDirectory() as temp_config_dir:\n        dump_file = osp.join(temp_config_dir, 'c.yaml')\n        cfg.dump(dump_file)\n        assert cfg.dump() == open(dump_file, 'r').read()\n        assert Config.fromfile(dump_file)\n\n\ndef test_fromfile():\n    for filename in ['a.py', 'a.b.py', 'b.json', 'c.yaml']:\n        cfg_file = osp.join(osp.dirname(__file__), 'data/config', filename)\n        cfg = Config.fromfile(cfg_file)\n        assert isinstance(cfg, Config)\n        assert cfg.filename == cfg_file\n        assert cfg.text == osp.abspath(osp.expanduser(cfg_file)) + '\\n' + \\\n            open(cfg_file, 'r').read()\n\n    with pytest.raises(FileNotFoundError):\n        Config.fromfile('no_such_file.py')\n    with pytest.raises(IOError):\n        Config.fromfile(osp.join(osp.dirname(__file__), 'data/color.jpg'))\n\n\ndef test_merge_from_base():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/d.py')\n    cfg = Config.fromfile(cfg_file)\n    assert isinstance(cfg, Config)\n    assert cfg.filename == cfg_file\n    base_cfg_file = osp.join(osp.dirname(__file__), 'data/config/base.py')\n    merge_text = osp.abspath(osp.expanduser(base_cfg_file)) + '\\n' + \\\n        open(base_cfg_file, 'r').read()\n    merge_text += '\\n' + osp.abspath(osp.expanduser(cfg_file)) + '\\n' + \\\n                  open(cfg_file, 'r').read()\n    assert cfg.text == merge_text\n    assert cfg.item1 == [2, 3]\n    assert cfg.item2.a == 1\n    assert cfg.item3 is False\n    assert cfg.item4 == 'test_base'\n\n    with pytest.raises(TypeError):\n        Config.fromfile(osp.join(osp.dirname(__file__), 'data/config/e.py'))\n\n\ndef test_merge_from_multiple_bases():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/l.py')\n    cfg = Config.fromfile(cfg_file)\n    assert isinstance(cfg, Config)\n    assert cfg.filename == cfg_file\n    # cfg.field\n    assert cfg.item1 == [1, 2]\n    assert cfg.item2.a == 0\n    assert cfg.item3 is False\n    assert cfg.item4 == 'test'\n    assert cfg.item5 == dict(a=0, b=1)\n    assert cfg.item6 == [dict(a=0), dict(b=1)]\n    assert cfg.item7 == dict(a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))\n\n    with pytest.raises(KeyError):\n        Config.fromfile(osp.join(osp.dirname(__file__), 'data/config/m.py'))\n\n\ndef test_merge_recursive_bases():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/f.py')\n    cfg = Config.fromfile(cfg_file)\n    assert isinstance(cfg, Config)\n    assert cfg.filename == cfg_file\n    # cfg.field\n    assert cfg.item1 == [2, 3]\n    assert cfg.item2.a == 1\n    assert cfg.item3 is False\n    assert cfg.item4 == 'test_recursive_bases'\n\n\ndef test_merge_from_dict():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/a.py')\n    cfg = Config.fromfile(cfg_file)\n    input_options = {'item2.a': 1, 'item2.b': 0.1, 'item3': False}\n    cfg.merge_from_dict(input_options)\n    assert cfg.item2 == dict(a=1, b=0.1)\n    assert cfg.item3 is False\n\n\ndef test_merge_delete():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/delete.py')\n    cfg = Config.fromfile(cfg_file)\n    # cfg.field\n    assert cfg.item1 == [1, 2]\n    assert cfg.item2 == dict(b=0)\n    assert cfg.item3 is True\n    assert cfg.item4 == 'test'\n    assert '_delete_' not in cfg.item2\n\n\ndef test_merge_intermediate_variable():\n\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/i_child.py')\n    cfg = Config.fromfile(cfg_file)\n    # cfg.field\n    assert cfg.item1 == [1, 2]\n    assert cfg.item2 == dict(a=0)\n    assert cfg.item3 is True\n    assert cfg.item4 == 'test'\n    assert cfg.item_cfg == dict(b=2)\n    assert cfg.item5 == dict(cfg=dict(b=1))\n    assert cfg.item6 == dict(cfg=dict(b=2))\n\n\ndef test_fromfile_in_config():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/code.py')\n    cfg = Config.fromfile(cfg_file)\n    # cfg.field\n    assert cfg.cfg.item1 == [1, 2]\n    assert cfg.cfg.item2 == dict(a=0)\n    assert cfg.cfg.item3 is True\n    assert cfg.cfg.item4 == 'test'\n    assert cfg.item5 == 1\n\n\ndef test_dict():\n    cfg_dict = dict(item1=[1, 2], item2=dict(a=0), item3=True, item4='test')\n\n    for filename in ['a.py', 'b.json', 'c.yaml']:\n        cfg_file = osp.join(osp.dirname(__file__), 'data/config', filename)\n        cfg = Config.fromfile(cfg_file)\n\n        # len(cfg)\n        assert len(cfg) == 4\n        # cfg.keys()\n        assert set(cfg.keys()) == set(cfg_dict.keys())\n        assert set(cfg._cfg_dict.keys()) == set(cfg_dict.keys())\n        # cfg.values()\n        for value in cfg.values():\n            assert value in cfg_dict.values()\n        # cfg.items()\n        for name, value in cfg.items():\n            assert name in cfg_dict\n            assert value in cfg_dict.values()\n        # cfg.field\n        assert cfg.item1 == cfg_dict['item1']\n        assert cfg.item2 == cfg_dict['item2']\n        assert cfg.item2.a == 0\n        assert cfg.item3 == cfg_dict['item3']\n        assert cfg.item4 == cfg_dict['item4']\n        with pytest.raises(AttributeError):\n            cfg.not_exist\n        # field in cfg, cfg[field], cfg.get()\n        for name in ['item1', 'item2', 'item3', 'item4']:\n            assert name in cfg\n            assert cfg[name] == cfg_dict[name]\n            assert cfg.get(name) == cfg_dict[name]\n            assert cfg.get('not_exist') is None\n            assert cfg.get('not_exist', 0) == 0\n            with pytest.raises(KeyError):\n                cfg['not_exist']\n        assert 'item1' in cfg\n        assert 'not_exist' not in cfg\n        # cfg.update()\n        cfg.update(dict(item1=0))\n        assert cfg.item1 == 0\n        cfg.update(dict(item2=dict(a=1)))\n        assert cfg.item2.a == 1\n\n\ndef test_setattr():\n    cfg = Config()\n    cfg.item1 = [1, 2]\n    cfg.item2 = {'a': 0}\n    cfg['item5'] = {'a': {'b': None}}\n    assert cfg._cfg_dict['item1'] == [1, 2]\n    assert cfg.item1 == [1, 2]\n    assert cfg._cfg_dict['item2'] == {'a': 0}\n    assert cfg.item2.a == 0\n    assert cfg._cfg_dict['item5'] == {'a': {'b': None}}\n    assert cfg.item5.a.b is None\n\n\ndef test_pretty_text():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/l.py')\n    cfg = Config.fromfile(cfg_file)\n    with tempfile.TemporaryDirectory() as temp_config_dir:\n        text_cfg_filename = osp.join(temp_config_dir, '_text_config.py')\n        with open(text_cfg_filename, 'w') as f:\n            f.write(cfg.pretty_text)\n        text_cfg = Config.fromfile(text_cfg_filename)\n    assert text_cfg._cfg_dict == cfg._cfg_dict\n\n\ndef test_dict_action():\n    parser = argparse.ArgumentParser(description='Train a detector')\n    parser.add_argument(\n        '--options', nargs='+', action=DictAction, help='custom options')\n    args = parser.parse_args(\n        ['--options', 'item2.a=1', 'item2.b=0.1', 'item2.c=x', 'item3=false'])\n    out_dict = {'item2.a': 1, 'item2.b': 0.1, 'item2.c': 'x', 'item3': False}\n    assert args.options == out_dict\n\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/a.py')\n    cfg = Config.fromfile(cfg_file)\n    cfg.merge_from_dict(args.options)\n    assert cfg.item2 == dict(a=1, b=0.1, c='x')\n    assert cfg.item3 is False\n\n\ndef test_dump_mapping():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/n.py')\n    cfg = Config.fromfile(cfg_file)\n\n    with tempfile.TemporaryDirectory() as temp_config_dir:\n        text_cfg_filename = osp.join(temp_config_dir, '_text_config.py')\n        cfg.dump(text_cfg_filename)\n        text_cfg = Config.fromfile(text_cfg_filename)\n\n    assert text_cfg._cfg_dict == cfg._cfg_dict\n\n\ndef test_reserved_key():\n    cfg_file = osp.join(osp.dirname(__file__), 'data/config/g.py')\n    with pytest.raises(KeyError):\n        Config.fromfile(cfg_file)\n\n\ndef test_syntax_error():\n    temp_cfg_file = tempfile.NamedTemporaryFile(suffix='.py')\n    temp_cfg_path = temp_cfg_file.name\n    # write a file with syntax error\n    with open(temp_cfg_path, 'w') as f:\n        f.write('a=0b=dict(c=1)')\n    with pytest.raises(\n            SyntaxError,\n            match='There are syntax errors in config '\n            f'file {temp_cfg_path}'):\n        Config.fromfile(temp_cfg_path)\n    temp_cfg_file.close()\n"
  },
  {
    "path": "code/mmcv/tests/test_fileclient.py",
    "content": "import sys\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\n\nimport pytest\n\nimport mmcv\nfrom mmcv import BaseStorageBackend, FileClient\n\nsys.modules['ceph'] = MagicMock()\nsys.modules['petrel_client'] = MagicMock()\nsys.modules['petrel_client.client'] = MagicMock()\nsys.modules['mc'] = MagicMock()\n\n\nclass MockS3Client:\n\n    def __init__(self, enable_mc=True):\n        self.enable_mc = enable_mc\n\n    def Get(self, filepath):\n        with open(filepath, 'rb') as f:\n            content = f.read()\n        return content\n\n\nclass MockMemcachedClient:\n\n    def __init__(self, server_list_cfg, client_cfg):\n        pass\n\n    def Get(self, filepath, buffer):\n        with open(filepath, 'rb') as f:\n            buffer.content = f.read()\n\n\nclass TestFileClient:\n\n    @classmethod\n    def setup_class(cls):\n        cls.test_data_dir = Path(__file__).parent / 'data'\n        cls.img_path = cls.test_data_dir / 'color.jpg'\n        cls.img_shape = (300, 400, 3)\n        cls.text_path = cls.test_data_dir / 'filelist.txt'\n\n    def test_error(self):\n        with pytest.raises(ValueError):\n            FileClient('hadoop')\n\n    def test_disk_backend(self):\n        disk_backend = FileClient('disk')\n\n        # input path is Path object\n        img_bytes = disk_backend.get(self.img_path)\n        img = mmcv.imfrombytes(img_bytes)\n        assert self.img_path.open('rb').read() == img_bytes\n        assert img.shape == self.img_shape\n        # input path is str\n        img_bytes = disk_backend.get(str(self.img_path))\n        img = mmcv.imfrombytes(img_bytes)\n        assert self.img_path.open('rb').read() == img_bytes\n        assert img.shape == self.img_shape\n\n        # input path is Path object\n        value_buf = disk_backend.get_text(self.text_path)\n        assert self.text_path.open('r').read() == value_buf\n        # input path is str\n        value_buf = disk_backend.get_text(str(self.text_path))\n        assert self.text_path.open('r').read() == value_buf\n\n    @patch('ceph.S3Client', MockS3Client)\n    def test_ceph_backend(self):\n        with pytest.warns(\n                Warning, match='Ceph is deprecate in favor of Petrel.'):\n            FileClient('ceph')\n        ceph_backend = FileClient('ceph')\n\n        # input path is Path object\n        with pytest.raises(NotImplementedError):\n            ceph_backend.get_text(self.text_path)\n        # input path is str\n        with pytest.raises(NotImplementedError):\n            ceph_backend.get_text(str(self.text_path))\n\n        # input path is Path object\n        img_bytes = ceph_backend.get(self.img_path)\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n        # input path is str\n        img_bytes = ceph_backend.get(str(self.img_path))\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n\n        # `path_mapping` is either None or dict\n        with pytest.raises(AssertionError):\n            FileClient('ceph', path_mapping=1)\n        # test `path_mapping`\n        ceph_path = 's3://user/data'\n        ceph_backend = FileClient(\n            'ceph', path_mapping={str(self.test_data_dir): ceph_path})\n        ceph_backend.client._client.Get = MagicMock(\n            return_value=ceph_backend.client._client.Get(self.img_path))\n        img_bytes = ceph_backend.get(self.img_path)\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n        ceph_backend.client._client.Get.assert_called_with(\n            str(self.img_path).replace(str(self.test_data_dir), ceph_path))\n\n    @patch('petrel_client.client.Client', MockS3Client)\n    def test_petrel_backend(self):\n        petrel_backend = FileClient('petrel')\n\n        # input path is Path object\n        with pytest.raises(NotImplementedError):\n            petrel_backend.get_text(self.text_path)\n        # input path is str\n        with pytest.raises(NotImplementedError):\n            petrel_backend.get_text(str(self.text_path))\n\n        # input path is Path object\n        img_bytes = petrel_backend.get(self.img_path)\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n        # input path is str\n        img_bytes = petrel_backend.get(str(self.img_path))\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n\n        # `path_mapping` is either None or dict\n        with pytest.raises(AssertionError):\n            FileClient('petrel', path_mapping=1)\n        # test `path_mapping`\n        petrel_path = 's3://user/data'\n        petrel_backend = FileClient(\n            'petrel', path_mapping={str(self.test_data_dir): petrel_path})\n        petrel_backend.client._client.Get = MagicMock(\n            return_value=petrel_backend.client._client.Get(self.img_path))\n        img_bytes = petrel_backend.get(self.img_path)\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n        petrel_backend.client._client.Get.assert_called_with(\n            str(self.img_path).replace(str(self.test_data_dir), petrel_path))\n\n    @patch('mc.MemcachedClient.GetInstance', MockMemcachedClient)\n    @patch('mc.pyvector', MagicMock)\n    @patch('mc.ConvertBuffer', lambda x: x.content)\n    def test_memcached_backend(self):\n        mc_cfg = dict(server_list_cfg='', client_cfg='', sys_path=None)\n        mc_backend = FileClient('memcached', **mc_cfg)\n\n        # input path is Path object\n        with pytest.raises(NotImplementedError):\n            mc_backend.get_text(self.text_path)\n        # input path is str\n        with pytest.raises(NotImplementedError):\n            mc_backend.get_text(str(self.text_path))\n\n        # input path is Path object\n        img_bytes = mc_backend.get(self.img_path)\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n        # input path is str\n        img_bytes = mc_backend.get(str(self.img_path))\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == self.img_shape\n\n    def test_lmdb_backend(self):\n        lmdb_path = self.test_data_dir / 'demo.lmdb'\n\n        # db_path is Path object\n        lmdb_backend = FileClient('lmdb', db_path=lmdb_path)\n\n        with pytest.raises(NotImplementedError):\n            lmdb_backend.get_text(self.text_path)\n\n        img_bytes = lmdb_backend.get('baboon')\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == (120, 125, 3)\n\n        # db_path is str\n        lmdb_backend = FileClient('lmdb', db_path=str(lmdb_path))\n        with pytest.raises(NotImplementedError):\n            lmdb_backend.get_text(str(self.text_path))\n        img_bytes = lmdb_backend.get('baboon')\n        img = mmcv.imfrombytes(img_bytes)\n        assert img.shape == (120, 125, 3)\n\n    def test_register_backend(self):\n\n        # name must be a string\n        with pytest.raises(TypeError):\n\n            class TestClass1:\n                pass\n\n            FileClient.register_backend(1, TestClass1)\n\n        # module must be a class\n        with pytest.raises(TypeError):\n            FileClient.register_backend('int', 0)\n\n        # module must be a subclass of BaseStorageBackend\n        with pytest.raises(TypeError):\n\n            class TestClass1:\n                pass\n\n            FileClient.register_backend('TestClass1', TestClass1)\n\n        class ExampleBackend(BaseStorageBackend):\n\n            def get(self, filepath):\n                return filepath\n\n            def get_text(self, filepath):\n                return filepath\n\n        FileClient.register_backend('example', ExampleBackend)\n        example_backend = FileClient('example')\n        assert example_backend.get(self.img_path) == self.img_path\n        assert example_backend.get_text(self.text_path) == self.text_path\n        assert 'example' in FileClient._backends\n\n        class Example2Backend(BaseStorageBackend):\n\n            def get(self, filepath):\n                return 'bytes2'\n\n            def get_text(self, filepath):\n                return 'text2'\n\n        # force=False\n        with pytest.raises(KeyError):\n            FileClient.register_backend('example', Example2Backend)\n\n        FileClient.register_backend('example', Example2Backend, force=True)\n        example_backend = FileClient('example')\n        assert example_backend.get(self.img_path) == 'bytes2'\n        assert example_backend.get_text(self.text_path) == 'text2'\n\n        @FileClient.register_backend(name='example3')\n        class Example3Backend(BaseStorageBackend):\n\n            def get(self, filepath):\n                return 'bytes3'\n\n            def get_text(self, filepath):\n                return 'text3'\n\n        example_backend = FileClient('example3')\n        assert example_backend.get(self.img_path) == 'bytes3'\n        assert example_backend.get_text(self.text_path) == 'text3'\n        assert 'example3' in FileClient._backends\n\n        # force=False\n        with pytest.raises(KeyError):\n\n            @FileClient.register_backend(name='example3')\n            class Example4Backend(BaseStorageBackend):\n\n                def get(self, filepath):\n                    return 'bytes4'\n\n                def get_text(self, filepath):\n                    return 'text4'\n\n        @FileClient.register_backend(name='example3', force=True)\n        class Example5Backend(BaseStorageBackend):\n\n            def get(self, filepath):\n                return 'bytes5'\n\n            def get_text(self, filepath):\n                return 'text5'\n\n        example_backend = FileClient('example3')\n        assert example_backend.get(self.img_path) == 'bytes5'\n        assert example_backend.get_text(self.text_path) == 'text5'\n"
  },
  {
    "path": "code/mmcv/tests/test_fileio.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport tempfile\n\nimport pytest\n\nimport mmcv\n\n\ndef _test_handler(file_format, test_obj, str_checker, mode='r+'):\n    # dump to a string\n    dump_str = mmcv.dump(test_obj, file_format=file_format)\n    str_checker(dump_str)\n\n    # load/dump with filenames\n    tmp_filename = osp.join(tempfile.gettempdir(), 'mmcv_test_dump')\n    mmcv.dump(test_obj, tmp_filename, file_format=file_format)\n    assert osp.isfile(tmp_filename)\n    load_obj = mmcv.load(tmp_filename, file_format=file_format)\n    assert load_obj == test_obj\n    os.remove(tmp_filename)\n\n    # json load/dump with a file-like object\n    with tempfile.NamedTemporaryFile(mode, delete=False) as f:\n        tmp_filename = f.name\n        mmcv.dump(test_obj, f, file_format=file_format)\n    assert osp.isfile(tmp_filename)\n    with open(tmp_filename, mode) as f:\n        load_obj = mmcv.load(f, file_format=file_format)\n    assert load_obj == test_obj\n    os.remove(tmp_filename)\n\n    # automatically inference the file format from the given filename\n    tmp_filename = osp.join(tempfile.gettempdir(),\n                            'mmcv_test_dump.' + file_format)\n    mmcv.dump(test_obj, tmp_filename)\n    assert osp.isfile(tmp_filename)\n    load_obj = mmcv.load(tmp_filename)\n    assert load_obj == test_obj\n    os.remove(tmp_filename)\n\n\nobj_for_test = [{'a': 'abc', 'b': 1}, 2, 'c']\n\n\ndef test_json():\n\n    def json_checker(dump_str):\n        assert dump_str in [\n            '[{\"a\": \"abc\", \"b\": 1}, 2, \"c\"]', '[{\"b\": 1, \"a\": \"abc\"}, 2, \"c\"]'\n        ]\n\n    _test_handler('json', obj_for_test, json_checker)\n\n\ndef test_yaml():\n\n    def yaml_checker(dump_str):\n        assert dump_str in [\n            '- {a: abc, b: 1}\\n- 2\\n- c\\n', '- {b: 1, a: abc}\\n- 2\\n- c\\n',\n            '- a: abc\\n  b: 1\\n- 2\\n- c\\n', '- b: 1\\n  a: abc\\n- 2\\n- c\\n'\n        ]\n\n    _test_handler('yaml', obj_for_test, yaml_checker)\n\n\ndef test_pickle():\n\n    def pickle_checker(dump_str):\n        import pickle\n        assert pickle.loads(dump_str) == obj_for_test\n\n    _test_handler('pickle', obj_for_test, pickle_checker, mode='rb+')\n\n\ndef test_exception():\n    test_obj = [{'a': 'abc', 'b': 1}, 2, 'c']\n\n    with pytest.raises(ValueError):\n        mmcv.dump(test_obj)\n\n    with pytest.raises(TypeError):\n        mmcv.dump(test_obj, 'tmp.txt')\n\n\ndef test_register_handler():\n\n    @mmcv.register_handler('txt')\n    class TxtHandler1(mmcv.BaseFileHandler):\n\n        def load_from_fileobj(self, file):\n            return file.read()\n\n        def dump_to_fileobj(self, obj, file):\n            file.write(str(obj))\n\n        def dump_to_str(self, obj, **kwargs):\n            return str(obj)\n\n    @mmcv.register_handler(['txt1', 'txt2'])\n    class TxtHandler2(mmcv.BaseFileHandler):\n\n        def load_from_fileobj(self, file):\n            return file.read()\n\n        def dump_to_fileobj(self, obj, file):\n            file.write('\\n')\n            file.write(str(obj))\n\n        def dump_to_str(self, obj, **kwargs):\n            return str(obj)\n\n    content = mmcv.load(osp.join(osp.dirname(__file__), 'data/filelist.txt'))\n    assert content == '1.jpg\\n2.jpg\\n3.jpg\\n4.jpg\\n5.jpg'\n    tmp_filename = osp.join(tempfile.gettempdir(), 'mmcv_test.txt2')\n    mmcv.dump(content, tmp_filename)\n    with open(tmp_filename, 'r') as f:\n        written = f.read()\n    os.remove(tmp_filename)\n    assert written == '\\n' + content\n\n\ndef test_list_from_file():\n    filename = osp.join(osp.dirname(__file__), 'data/filelist.txt')\n    filelist = mmcv.list_from_file(filename)\n    assert filelist == ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg']\n    filelist = mmcv.list_from_file(filename, prefix='a/')\n    assert filelist == ['a/1.jpg', 'a/2.jpg', 'a/3.jpg', 'a/4.jpg', 'a/5.jpg']\n    filelist = mmcv.list_from_file(filename, offset=2)\n    assert filelist == ['3.jpg', '4.jpg', '5.jpg']\n    filelist = mmcv.list_from_file(filename, max_num=2)\n    assert filelist == ['1.jpg', '2.jpg']\n    filelist = mmcv.list_from_file(filename, offset=3, max_num=3)\n    assert filelist == ['4.jpg', '5.jpg']\n\n\ndef test_dict_from_file():\n    filename = osp.join(osp.dirname(__file__), 'data/mapping.txt')\n    mapping = mmcv.dict_from_file(filename)\n    assert mapping == {'1': 'cat', '2': ['dog', 'cow'], '3': 'panda'}\n    mapping = mmcv.dict_from_file(filename, key_type=int)\n    assert mapping == {1: 'cat', 2: ['dog', 'cow'], 3: 'panda'}\n"
  },
  {
    "path": "code/mmcv/tests/test_image/test_colorspace.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport cv2\nimport numpy as np\nimport pytest\nfrom numpy.testing import assert_array_almost_equal, assert_array_equal\n\nimport mmcv\nfrom mmcv.image.colorspace import (_convert_input_type_range,\n                                   _convert_output_type_range)\n\n\ndef test_bgr2gray():\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.bgr2gray(in_img)\n    computed_gray = (\n        in_img[:, :, 0] * 0.114 + in_img[:, :, 1] * 0.587 +\n        in_img[:, :, 2] * 0.299)\n    assert_array_almost_equal(out_img, computed_gray, decimal=4)\n    out_img_3d = mmcv.bgr2gray(in_img, True)\n    assert out_img_3d.shape == (10, 10, 1)\n    assert_array_almost_equal(out_img_3d[..., 0], out_img, decimal=4)\n\n\ndef test_rgb2gray():\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.rgb2gray(in_img)\n    computed_gray = (\n        in_img[:, :, 0] * 0.299 + in_img[:, :, 1] * 0.587 +\n        in_img[:, :, 2] * 0.114)\n    assert_array_almost_equal(out_img, computed_gray, decimal=4)\n    out_img_3d = mmcv.rgb2gray(in_img, True)\n    assert out_img_3d.shape == (10, 10, 1)\n    assert_array_almost_equal(out_img_3d[..., 0], out_img, decimal=4)\n\n\ndef test_gray2bgr():\n    in_img = np.random.rand(10, 10).astype(np.float32)\n    out_img = mmcv.gray2bgr(in_img)\n    assert out_img.shape == (10, 10, 3)\n    for i in range(3):\n        assert_array_almost_equal(out_img[..., i], in_img, decimal=4)\n\n\ndef test_gray2rgb():\n    in_img = np.random.rand(10, 10).astype(np.float32)\n    out_img = mmcv.gray2rgb(in_img)\n    assert out_img.shape == (10, 10, 3)\n    for i in range(3):\n        assert_array_almost_equal(out_img[..., i], in_img, decimal=4)\n\n\ndef test_bgr2rgb():\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.bgr2rgb(in_img)\n    assert out_img.shape == in_img.shape\n    assert_array_equal(out_img[..., 0], in_img[..., 2])\n    assert_array_equal(out_img[..., 1], in_img[..., 1])\n    assert_array_equal(out_img[..., 2], in_img[..., 0])\n\n\ndef test_rgb2bgr():\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.rgb2bgr(in_img)\n    assert out_img.shape == in_img.shape\n    assert_array_equal(out_img[..., 0], in_img[..., 2])\n    assert_array_equal(out_img[..., 1], in_img[..., 1])\n    assert_array_equal(out_img[..., 2], in_img[..., 0])\n\n\ndef test_bgr2hsv():\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.bgr2hsv(in_img)\n    argmax = in_img.argmax(axis=2)\n    computed_hsv = np.empty_like(in_img)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            b, g, r = in_img[i, j]\n            v = max(r, g, b)\n            s = (v - min(r, g, b)) / v if v != 0 else 0\n            if argmax[i, j] == 0:\n                h = 240 + 60 * (r - g) / (v - min(r, g, b))\n            elif argmax[i, j] == 1:\n                h = 120 + 60 * (b - r) / (v - min(r, g, b))\n            else:\n                h = 60 * (g - b) / (v - min(r, g, b))\n            if h < 0:\n                h += 360\n            computed_hsv[i, j, :] = [h, s, v]\n    assert_array_almost_equal(out_img, computed_hsv, decimal=2)\n\n\ndef test_convert_input_type_range():\n    with pytest.raises(TypeError):\n        # The img type should be np.float32 or np.uint8\n        in_img = np.random.rand(10, 10, 3).astype(np.uint64)\n        _convert_input_type_range(in_img)\n    # np.float32\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = _convert_input_type_range(in_img)\n    assert out_img.dtype == np.float32\n    assert np.absolute(out_img).mean() < 1\n    # np.uint8\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = _convert_input_type_range(in_img)\n    assert out_img.dtype == np.float32\n    assert np.absolute(out_img).mean() < 1\n\n\ndef test_convert_output_type_range():\n    with pytest.raises(TypeError):\n        # The dst_type should be np.float32 or np.uint8\n        in_img = np.random.rand(10, 10, 3).astype(np.float32)\n        _convert_output_type_range(in_img, np.uint64)\n    # np.float32\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.float32)\n    out_img = _convert_output_type_range(in_img, np.float32)\n    assert out_img.dtype == np.float32\n    assert np.absolute(out_img).mean() < 1\n    # np.uint8\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.float32)\n    out_img = _convert_output_type_range(in_img, np.uint8)\n    assert out_img.dtype == np.uint8\n    assert np.absolute(out_img).mean() > 1\n\n\ndef test_rgb2ycbcr():\n    with pytest.raises(TypeError):\n        # The img type should be np.float32 or np.uint8\n        in_img = np.random.rand(10, 10, 3).astype(np.uint64)\n        mmcv.rgb2ycbcr(in_img)\n\n    # float32\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.rgb2ycbcr(in_img)\n    computed_ycbcr = np.empty_like(in_img)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            r, g, b = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            cb = 128 - r * 37.797 - g * 74.203 + b * 112.0\n            cr = 128 + r * 112.0 - g * 93.786 - b * 18.214\n            computed_ycbcr[i, j, :] = [y, cb, cr]\n    computed_ycbcr /= 255.\n    assert_array_almost_equal(out_img, computed_ycbcr, decimal=2)\n    # y_only=True\n    out_img = mmcv.rgb2ycbcr(in_img, y_only=True)\n    computed_y = np.empty_like(out_img, dtype=out_img.dtype)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            r, g, b = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            computed_y[i, j] = y\n    computed_y /= 255.\n    assert_array_almost_equal(out_img, computed_y, decimal=2)\n\n    # uint8\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = mmcv.rgb2ycbcr(in_img)\n    computed_ycbcr = np.empty_like(in_img)\n    in_img = in_img / 255.\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            r, g, b = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            cb = 128 - r * 37.797 - g * 74.203 + b * 112.0\n            cr = 128 + r * 112.0 - g * 93.786 - b * 18.214\n            y, cb, cr = y.round(), cb.round(), cr.round()\n            computed_ycbcr[i, j, :] = [y, cb, cr]\n    assert_array_almost_equal(out_img, computed_ycbcr, decimal=2)\n    # y_only=True\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = mmcv.rgb2ycbcr(in_img, y_only=True)\n    computed_y = np.empty_like(out_img, dtype=out_img.dtype)\n    in_img = in_img / 255.\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            r, g, b = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            y = y.round()\n            computed_y[i, j] = y\n    assert_array_almost_equal(out_img, computed_y, decimal=2)\n\n\ndef test_bgr2ycbcr():\n    # float32\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.bgr2ycbcr(in_img)\n    computed_ycbcr = np.empty_like(in_img)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            b, g, r = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            cb = 128 - r * 37.797 - g * 74.203 + b * 112.0\n            cr = 128 + r * 112.0 - g * 93.786 - b * 18.214\n            computed_ycbcr[i, j, :] = [y, cb, cr]\n    computed_ycbcr /= 255.\n    assert_array_almost_equal(out_img, computed_ycbcr, decimal=2)\n    # y_only=True\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.bgr2ycbcr(in_img, y_only=True)\n    computed_y = np.empty_like(out_img, dtype=out_img.dtype)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            b, g, r = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            computed_y[i, j] = y\n    computed_y /= 255.\n    assert_array_almost_equal(out_img, computed_y, decimal=2)\n\n    # uint8\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = mmcv.bgr2ycbcr(in_img)\n    computed_ycbcr = np.empty_like(in_img)\n    in_img = in_img / 255.\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            b, g, r = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            cb = 128 - r * 37.797 - g * 74.203 + b * 112.0\n            cr = 128 + r * 112.0 - g * 93.786 - b * 18.214\n            y, cb, cr = y.round(), cb.round(), cr.round()\n            computed_ycbcr[i, j, :] = [y, cb, cr]\n    assert_array_almost_equal(out_img, computed_ycbcr, decimal=2)\n    # y_only = True\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = mmcv.bgr2ycbcr(in_img, y_only=True)\n    computed_y = np.empty_like(out_img, dtype=out_img.dtype)\n    in_img = in_img / 255.\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            b, g, r = in_img[i, j]\n            y = 16 + r * 65.481 + g * 128.553 + b * 24.966\n            y = y.round()\n            computed_y[i, j] = y\n    assert_array_almost_equal(out_img, computed_y, decimal=2)\n\n\ndef test_ycbcr2rgb():\n    with pytest.raises(TypeError):\n        # The img type should be np.float32 or np.uint8\n        in_img = np.random.rand(10, 10, 3).astype(np.uint64)\n        mmcv.ycbcr2rgb(in_img)\n\n    # float32\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.ycbcr2rgb(in_img)\n    computed_rgb = np.empty_like(in_img)\n    in_img *= 255.\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            y, cb, cr = in_img[i, j]\n            r = -222.921 + y * 0.00456621 * 255 + cr * 0.00625893 * 255\n            g = 135.576 + y * 0.00456621 * 255 - cb * 0.00153632 * 255 - \\\n                cr * 0.00318811 * 255\n            b = -276.836 + y * 0.00456621 * 255. + cb * 0.00791071 * 255\n            computed_rgb[i, j, :] = [r, g, b]\n    computed_rgb /= 255.\n    assert_array_almost_equal(out_img, computed_rgb, decimal=2)\n\n    # uint8\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = mmcv.ycbcr2rgb(in_img)\n    computed_rgb = np.empty_like(in_img)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            y, cb, cr = in_img[i, j]\n            r = -222.921 + y * 0.00456621 * 255 + cr * 0.00625893 * 255\n            g = 135.576 + y * 0.00456621 * 255 - cb * 0.00153632 * 255 - \\\n                cr * 0.00318811 * 255\n            b = -276.836 + y * 0.00456621 * 255. + cb * 0.00791071 * 255\n            r, g, b = r.round(), g.round(), b.round()\n            computed_rgb[i, j, :] = [r, g, b]\n    assert_array_almost_equal(out_img, computed_rgb, decimal=2)\n\n\ndef test_ycbcr2bgr():\n    # float32\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.ycbcr2bgr(in_img)\n    computed_bgr = np.empty_like(in_img)\n    in_img *= 255.\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            y, cb, cr = in_img[i, j]\n            r = -222.921 + y * 0.00456621 * 255 + cr * 0.00625893 * 255\n            g = 135.576 + y * 0.00456621 * 255 - cb * 0.00153632 * 255 - \\\n                cr * 0.00318811 * 255\n            b = -276.836 + y * 0.00456621 * 255. + cb * 0.00791071 * 255\n            computed_bgr[i, j, :] = [b, g, r]\n    computed_bgr /= 255.\n    assert_array_almost_equal(out_img, computed_bgr, decimal=2)\n\n    # uint8\n    in_img = (np.random.rand(10, 10, 3) * 255).astype(np.uint8)\n    out_img = mmcv.ycbcr2bgr(in_img)\n    computed_bgr = np.empty_like(in_img)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            y, cb, cr = in_img[i, j]\n            r = -222.921 + y * 0.00456621 * 255 + cr * 0.00625893 * 255\n            g = 135.576 + y * 0.00456621 * 255 - cb * 0.00153632 * 255 - \\\n                cr * 0.00318811 * 255\n            b = -276.836 + y * 0.00456621 * 255. + cb * 0.00791071 * 255\n            r, g, b = r.round(), g.round(), b.round()\n            computed_bgr[i, j, :] = [b, g, r]\n    assert_array_almost_equal(out_img, computed_bgr, decimal=2)\n\n\ndef test_bgr2hls():\n    in_img = np.random.rand(10, 10, 3).astype(np.float32)\n    out_img = mmcv.bgr2hls(in_img)\n    argmax = in_img.argmax(axis=2)\n    computed_hls = np.empty_like(in_img)\n    for i in range(in_img.shape[0]):\n        for j in range(in_img.shape[1]):\n            b, g, r = in_img[i, j]\n            maxc = max(r, g, b)\n            minc = min(r, g, b)\n            _l = (minc + maxc) / 2.0\n            if minc == maxc:\n                h = 0.0\n                s = 0.0\n            if _l <= 0.5:\n                s = (maxc - minc) / (maxc + minc)\n            else:\n                s = (maxc - minc) / (2.0 - maxc - minc)\n            if argmax[i, j] == 2:\n                h = 60 * (g - b) / (maxc - minc)\n            elif argmax[i, j] == 1:\n                h = 60 * (2.0 + (b - r) / (maxc - minc))\n            else:\n                h = 60 * (4.0 + (r - g) / (maxc - minc))\n            if h < 0:\n                h += 360\n            computed_hls[i, j, :] = [h, _l, s]\n    assert_array_almost_equal(out_img, computed_hls, decimal=2)\n\n\n@pytest.mark.parametrize('src,dst,ref', [('bgr', 'gray', cv2.COLOR_BGR2GRAY),\n                                         ('rgb', 'gray', cv2.COLOR_RGB2GRAY),\n                                         ('bgr', 'rgb', cv2.COLOR_BGR2RGB),\n                                         ('rgb', 'bgr', cv2.COLOR_RGB2BGR),\n                                         ('bgr', 'hsv', cv2.COLOR_BGR2HSV),\n                                         ('hsv', 'bgr', cv2.COLOR_HSV2BGR),\n                                         ('bgr', 'hls', cv2.COLOR_BGR2HLS),\n                                         ('hls', 'bgr', cv2.COLOR_HLS2BGR)])\ndef test_imconvert(src, dst, ref):\n    img = np.random.rand(10, 10, 3).astype(np.float32)\n    assert_array_equal(mmcv.imconvert(img, src, dst), cv2.cvtColor(img, ref))\n"
  },
  {
    "path": "code/mmcv/tests/test_image/test_geometric.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\n\nimport cv2\nimport numpy as np\nimport pytest\nfrom numpy.testing import assert_array_equal\n\nimport mmcv\n\n\nclass TestGeometric:\n\n    @classmethod\n    def setup_class(cls):\n        cls.data_dir = osp.join(osp.dirname(__file__), '../data')\n        # the test img resolution is 400x300\n        cls.img_path = osp.join(cls.data_dir, 'color.jpg')\n        cls.img = cv2.imread(cls.img_path)\n\n    def test_imresize(self):\n        resized_img = mmcv.imresize(self.img, (1000, 600))\n        assert resized_img.shape == (600, 1000, 3)\n        resized_img, w_scale, h_scale = mmcv.imresize(self.img, (1000, 600),\n                                                      True)\n        assert (resized_img.shape == (600, 1000, 3) and w_scale == 2.5\n                and h_scale == 2.0)\n        resized_img_dst = np.empty((600, 1000, 3), dtype=self.img.dtype)\n        resized_img = mmcv.imresize(self.img, (1000, 600), out=resized_img_dst)\n        assert id(resized_img_dst) == id(resized_img)\n        assert_array_equal(resized_img_dst,\n                           mmcv.imresize(self.img, (1000, 600)))\n        for mode in ['nearest', 'bilinear', 'bicubic', 'area', 'lanczos']:\n            resized_img = mmcv.imresize(\n                self.img, (1000, 600), interpolation=mode)\n            assert resized_img.shape == (600, 1000, 3)\n\n    def test_imresize_like(self):\n        a = np.zeros((100, 200, 3))\n        resized_img = mmcv.imresize_like(self.img, a)\n        assert resized_img.shape == (100, 200, 3)\n\n    def test_rescale_size(self):\n        new_size, scale_factor = mmcv.rescale_size((400, 300), 1.5, True)\n        assert new_size == (600, 450) and scale_factor == 1.5\n        new_size, scale_factor = mmcv.rescale_size((400, 300), 0.934, True)\n        assert new_size == (374, 280) and scale_factor == 0.934\n\n        new_size = mmcv.rescale_size((400, 300), 1.5)\n        assert new_size == (600, 450)\n        new_size = mmcv.rescale_size((400, 300), 0.934)\n        assert new_size == (374, 280)\n\n        new_size, scale_factor = mmcv.rescale_size((400, 300), (1000, 600),\n                                                   True)\n        assert new_size == (800, 600) and scale_factor == 2.0\n        new_size, scale_factor = mmcv.rescale_size((400, 300), (180, 200),\n                                                   True)\n        assert new_size == (200, 150) and scale_factor == 0.5\n\n        new_size = mmcv.rescale_size((400, 300), (1000, 600))\n        assert new_size == (800, 600)\n        new_size = mmcv.rescale_size((400, 300), (180, 200))\n        assert new_size == (200, 150)\n\n        with pytest.raises(ValueError):\n            mmcv.rescale_size((400, 300), -0.5)\n        with pytest.raises(TypeError):\n            mmcv.rescale_size()((400, 300), [100, 100])\n\n    def test_imrescale(self):\n        # rescale by a certain factor\n        resized_img = mmcv.imrescale(self.img, 1.5)\n        assert resized_img.shape == (450, 600, 3)\n        resized_img = mmcv.imrescale(self.img, 0.934)\n        assert resized_img.shape == (280, 374, 3)\n\n        # rescale by a certain max_size\n        # resize (400, 300) to (max_1000, max_600)\n        resized_img = mmcv.imrescale(self.img, (1000, 600))\n        assert resized_img.shape == (600, 800, 3)\n        resized_img, scale = mmcv.imrescale(\n            self.img, (1000, 600), return_scale=True)\n        assert resized_img.shape == (600, 800, 3) and scale == 2.0\n        # resize (400, 300) to (max_200, max_180)\n        resized_img = mmcv.imrescale(self.img, (180, 200))\n        assert resized_img.shape == (150, 200, 3)\n        resized_img, scale = mmcv.imrescale(\n            self.img, (180, 200), return_scale=True)\n        assert resized_img.shape == (150, 200, 3) and scale == 0.5\n\n        # test exceptions\n        with pytest.raises(ValueError):\n            mmcv.imrescale(self.img, -0.5)\n        with pytest.raises(TypeError):\n            mmcv.imrescale(self.img, [100, 100])\n\n    def test_imflip(self):\n        # test horizontal flip (color image)\n        img = np.random.rand(80, 60, 3)\n        h, w, c = img.shape\n        flipped_img = mmcv.imflip(img)\n        assert flipped_img.shape == img.shape\n        for i in range(h):\n            for j in range(w):\n                for k in range(c):\n                    assert flipped_img[i, j, k] == img[i, w - 1 - j, k]\n        # test vertical flip (color image)\n        flipped_img = mmcv.imflip(img, direction='vertical')\n        assert flipped_img.shape == img.shape\n        for i in range(h):\n            for j in range(w):\n                for k in range(c):\n                    assert flipped_img[i, j, k] == img[h - 1 - i, j, k]\n        # test horizontal flip (grayscale image)\n        img = np.random.rand(80, 60)\n        h, w = img.shape\n        flipped_img = mmcv.imflip(img)\n        assert flipped_img.shape == img.shape\n        for i in range(h):\n            for j in range(w):\n                assert flipped_img[i, j] == img[i, w - 1 - j]\n        # test vertical flip (grayscale image)\n        flipped_img = mmcv.imflip(img, direction='vertical')\n        assert flipped_img.shape == img.shape\n        for i in range(h):\n            for j in range(w):\n                assert flipped_img[i, j] == img[h - 1 - i, j]\n\n    def test_imflip_(self):\n        # test horizontal flip (color image)\n        img = np.random.rand(80, 60, 3)\n        h, w, c = img.shape\n        img_for_flip = img.copy()\n        flipped_img = mmcv.imflip_(img_for_flip)\n        assert flipped_img.shape == img.shape\n        assert flipped_img.shape == img_for_flip.shape\n        assert id(flipped_img) == id(img_for_flip)\n        for i in range(h):\n            for j in range(w):\n                for k in range(c):\n                    assert flipped_img[i, j, k] == img[i, w - 1 - j, k]\n                    assert flipped_img[i, j, k] == img_for_flip[i, j, k]\n\n        # test vertical flip (color image)\n        img_for_flip = img.copy()\n        flipped_img = mmcv.imflip_(img_for_flip, direction='vertical')\n        assert flipped_img.shape == img.shape\n        assert flipped_img.shape == img_for_flip.shape\n        assert id(flipped_img) == id(img_for_flip)\n        for i in range(h):\n            for j in range(w):\n                for k in range(c):\n                    assert flipped_img[i, j, k] == img[h - 1 - i, j, k]\n                    assert flipped_img[i, j, k] == img_for_flip[i, j, k]\n\n        # test horizontal flip (grayscale image)\n        img = np.random.rand(80, 60)\n        h, w = img.shape\n        img_for_flip = img.copy()\n        flipped_img = mmcv.imflip_(img_for_flip)\n        assert flipped_img.shape == img.shape\n        assert flipped_img.shape == img_for_flip.shape\n        assert id(flipped_img) == id(img_for_flip)\n        for i in range(h):\n            for j in range(w):\n                assert flipped_img[i, j] == img[i, w - 1 - j]\n                assert flipped_img[i, j] == img_for_flip[i, j]\n\n        # test vertical flip (grayscale image)\n        img_for_flip = img.copy()\n        flipped_img = mmcv.imflip_(img_for_flip, direction='vertical')\n        assert flipped_img.shape == img.shape\n        assert flipped_img.shape == img_for_flip.shape\n        assert id(flipped_img) == id(img_for_flip)\n        for i in range(h):\n            for j in range(w):\n                assert flipped_img[i, j] == img[h - 1 - i, j]\n                assert flipped_img[i, j] == img_for_flip[i, j]\n\n    def test_imcrop(self):\n        # yapf: disable\n        bboxes = np.array([[100, 100, 199, 199],  # center\n                           [0, 0, 150, 100],  # left-top corner\n                           [250, 200, 399, 299],  # right-bottom corner\n                           [0, 100, 399, 199],  # wide\n                           [150, 0, 299, 299]])  # tall\n        # yapf: enable\n\n        # crop one bbox\n        patch = mmcv.imcrop(self.img, bboxes[0, :])\n        patches = mmcv.imcrop(self.img, bboxes[[0], :])\n        assert patch.shape == (100, 100, 3)\n        patch_path = osp.join(self.data_dir, 'patches')\n        ref_patch = np.load(patch_path + '/0.npy')\n        assert_array_equal(patch, ref_patch)\n        assert isinstance(patches, list) and len(patches) == 1\n        assert_array_equal(patches[0], ref_patch)\n\n        # crop with no scaling and padding\n        patches = mmcv.imcrop(self.img, bboxes)\n        assert len(patches) == bboxes.shape[0]\n        for i in range(len(patches)):\n            ref_patch = np.load(patch_path + '/{}.npy'.format(i))\n            assert_array_equal(patches[i], ref_patch)\n\n        # crop with scaling and no padding\n        patches = mmcv.imcrop(self.img, bboxes, 1.2)\n        for i in range(len(patches)):\n            ref_patch = np.load(patch_path + '/scale_{}.npy'.format(i))\n            assert_array_equal(patches[i], ref_patch)\n\n        # crop with scaling and padding\n        patches = mmcv.imcrop(self.img, bboxes, 1.2, pad_fill=[255, 255, 0])\n        for i in range(len(patches)):\n            ref_patch = np.load(patch_path + '/pad_{}.npy'.format(i))\n            assert_array_equal(patches[i], ref_patch)\n        patches = mmcv.imcrop(self.img, bboxes, 1.2, pad_fill=0)\n        for i in range(len(patches)):\n            ref_patch = np.load(patch_path + '/pad0_{}.npy'.format(i))\n            assert_array_equal(patches[i], ref_patch)\n\n    def test_impad(self):\n        # grayscale image\n        img = np.random.rand(10, 10).astype(np.float32)\n        padded_img = mmcv.impad(img, (15, 12), 0)\n        assert_array_equal(img, padded_img[:10, :10])\n        assert_array_equal(\n            np.zeros((5, 12), dtype='float32'), padded_img[10:, :])\n        assert_array_equal(\n            np.zeros((15, 2), dtype='float32'), padded_img[:, 10:])\n\n        # RGB image\n        img = np.random.rand(10, 10, 3).astype(np.float32)\n        padded_img = mmcv.impad(img, (15, 12), 0)\n        assert_array_equal(img, padded_img[:10, :10, :])\n        assert_array_equal(\n            np.zeros((5, 12, 3), dtype='float32'), padded_img[10:, :, :])\n        assert_array_equal(\n            np.zeros((15, 2, 3), dtype='float32'), padded_img[:, 10:, :])\n\n        img = np.random.randint(256, size=(10, 10, 3)).astype('uint8')\n        padded_img = mmcv.impad(img, (15, 12, 3), [100, 110, 120])\n        assert_array_equal(img, padded_img[:10, :10, :])\n        assert_array_equal(\n            np.array([100, 110, 120], dtype='uint8') * np.ones(\n                (5, 12, 3), dtype='uint8'), padded_img[10:, :, :])\n        assert_array_equal(\n            np.array([100, 110, 120], dtype='uint8') * np.ones(\n                (15, 2, 3), dtype='uint8'), padded_img[:, 10:, :])\n\n        with pytest.raises(AssertionError):\n            mmcv.impad(img, (15, ), 0)\n        with pytest.raises(AssertionError):\n            mmcv.impad(img, (5, 5), 0)\n        with pytest.raises(AssertionError):\n            mmcv.impad(img, (5, 5), [0, 1])\n\n    def test_impad_to_multiple(self):\n        img = np.random.rand(11, 14, 3).astype(np.float32)\n        padded_img = mmcv.impad_to_multiple(img, 4)\n        assert padded_img.shape == (12, 16, 3)\n        img = np.random.rand(20, 12).astype(np.float32)\n        padded_img = mmcv.impad_to_multiple(img, 5)\n        assert padded_img.shape == (20, 15)\n        img = np.random.rand(20, 12).astype(np.float32)\n        padded_img = mmcv.impad_to_multiple(img, 2)\n        assert padded_img.shape == (20, 12)\n\n    def test_imrotate(self):\n        img = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).astype(np.uint8)\n        assert_array_equal(mmcv.imrotate(img, 0), img)\n        img_r = np.array([[7, 4, 1], [8, 5, 2], [9, 6, 3]])\n        assert_array_equal(mmcv.imrotate(img, 90), img_r)\n        img_r = np.array([[3, 6, 9], [2, 5, 8], [1, 4, 7]])\n        assert_array_equal(mmcv.imrotate(img, -90), img_r)\n\n        img = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]).astype(np.uint8)\n        img_r = np.array([[0, 6, 2, 0], [0, 7, 3, 0]])\n        assert_array_equal(mmcv.imrotate(img, 90), img_r)\n        img_r = np.array([[1, 0, 0, 0], [2, 0, 0, 0]])\n        assert_array_equal(mmcv.imrotate(img, 90, center=(0, 0)), img_r)\n        img_r = np.array([[255, 6, 2, 255], [255, 7, 3, 255]])\n        assert_array_equal(mmcv.imrotate(img, 90, border_value=255), img_r)\n        img_r = np.array([[5, 1], [6, 2], [7, 3], [8, 4]])\n        assert_array_equal(mmcv.imrotate(img, 90, auto_bound=True), img_r)\n\n        with pytest.raises(ValueError):\n            mmcv.imrotate(img, 90, center=(0, 0), auto_bound=True)\n"
  },
  {
    "path": "code/mmcv/tests/test_image/test_io.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport tempfile\nfrom pathlib import Path\nfrom unittest.mock import patch\n\nimport cv2\nimport numpy as np\nimport pytest\nfrom numpy.testing import assert_allclose, assert_array_equal\n\nimport mmcv\n\n\nclass TestIO:\n\n    @classmethod\n    def setup_class(cls):\n        cls.data_dir = osp.join(osp.dirname(__file__), '../data')\n        # the test img resolution is 400x300\n        cls.img_path = osp.join(cls.data_dir, 'color.jpg')\n        cls.img_path_obj = Path(cls.img_path)\n        cls.gray_img_path = osp.join(cls.data_dir, 'grayscale.jpg')\n        cls.gray_img_path_obj = Path(cls.gray_img_path)\n        cls.gray_img_dim3_path = osp.join(cls.data_dir, 'grayscale_dim3.jpg')\n        cls.gray_alpha_img_path = osp.join(cls.data_dir, 'gray_alpha.png')\n        cls.palette_img_path = osp.join(cls.data_dir, 'palette.gif')\n        cls.img = cv2.imread(cls.img_path)\n\n    def assert_img_equal(self, img, ref_img, ratio_thr=0.999):\n        assert img.shape == ref_img.shape\n        assert img.dtype == ref_img.dtype\n        area = ref_img.shape[0] * ref_img.shape[1]\n        diff = np.abs(img.astype('int32') - ref_img.astype('int32'))\n        assert np.sum(diff <= 1) / float(area) > ratio_thr\n\n    def test_imread(self):\n        # backend cv2\n        mmcv.use_backend('cv2')\n\n        img_cv2_color_bgr = mmcv.imread(self.img_path)\n        assert img_cv2_color_bgr.shape == (300, 400, 3)\n        img_cv2_color_rgb = mmcv.imread(self.img_path, channel_order='rgb')\n        assert img_cv2_color_rgb.shape == (300, 400, 3)\n        assert_array_equal(img_cv2_color_rgb[:, :, ::-1], img_cv2_color_bgr)\n        img_cv2_grayscale1 = mmcv.imread(self.img_path, 'grayscale')\n        assert img_cv2_grayscale1.shape == (300, 400)\n        img_cv2_grayscale2 = mmcv.imread(self.gray_img_path)\n        assert img_cv2_grayscale2.shape == (300, 400, 3)\n        img_cv2_unchanged = mmcv.imread(self.gray_img_path, 'unchanged')\n        assert img_cv2_unchanged.shape == (300, 400)\n        img_cv2_unchanged = mmcv.imread(img_cv2_unchanged)\n        assert_array_equal(img_cv2_unchanged, mmcv.imread(img_cv2_unchanged))\n\n        img_cv2_color_bgr = mmcv.imread(self.img_path_obj)\n        assert img_cv2_color_bgr.shape == (300, 400, 3)\n        img_cv2_color_rgb = mmcv.imread(self.img_path_obj, channel_order='rgb')\n        assert img_cv2_color_rgb.shape == (300, 400, 3)\n        assert_array_equal(img_cv2_color_rgb[:, :, ::-1], img_cv2_color_bgr)\n        img_cv2_grayscale1 = mmcv.imread(self.img_path_obj, 'grayscale')\n        assert img_cv2_grayscale1.shape == (300, 400)\n        img_cv2_grayscale2 = mmcv.imread(self.gray_img_path_obj)\n        assert img_cv2_grayscale2.shape == (300, 400, 3)\n        img_cv2_unchanged = mmcv.imread(self.gray_img_path_obj, 'unchanged')\n        assert img_cv2_unchanged.shape == (300, 400)\n        with pytest.raises(TypeError):\n            mmcv.imread(1)\n\n        # test arg backend pillow\n        img_pil_gray_alpha = mmcv.imread(\n            self.gray_alpha_img_path, 'grayscale', backend='pillow')\n        assert img_pil_gray_alpha.shape == (400, 500)\n        mean = img_pil_gray_alpha[300:, 400:].mean()\n        assert_allclose(img_pil_gray_alpha[300:, 400:] - mean, 0)\n        img_pil_gray_alpha = mmcv.imread(\n            self.gray_alpha_img_path, backend='pillow')\n        mean = img_pil_gray_alpha[300:, 400:].mean(axis=(0, 1))\n        assert_allclose(img_pil_gray_alpha[300:, 400:] - mean, 0)\n        assert img_pil_gray_alpha.shape == (400, 500, 3)\n        img_pil_gray_alpha = mmcv.imread(\n            self.gray_alpha_img_path, 'unchanged', backend='pillow')\n        assert img_pil_gray_alpha.shape == (400, 500, 2)\n        img_pil_palette = mmcv.imread(\n            self.palette_img_path, 'grayscale', backend='pillow')\n        assert img_pil_palette.shape == (300, 400)\n        img_pil_palette = mmcv.imread(self.palette_img_path, backend='pillow')\n        assert img_pil_palette.shape == (300, 400, 3)\n        img_pil_palette = mmcv.imread(\n            self.palette_img_path, 'unchanged', backend='pillow')\n        assert img_pil_palette.shape == (300, 400)\n\n        # backend pillow\n        mmcv.use_backend('pillow')\n        img_pil_grayscale1 = mmcv.imread(self.img_path, 'grayscale')\n        assert img_pil_grayscale1.shape == (300, 400)\n        img_pil_gray_alpha = mmcv.imread(self.gray_alpha_img_path, 'grayscale')\n        assert img_pil_gray_alpha.shape == (400, 500)\n        mean = img_pil_gray_alpha[300:, 400:].mean()\n        assert_allclose(img_pil_gray_alpha[300:, 400:] - mean, 0)\n        img_pil_gray_alpha = mmcv.imread(self.gray_alpha_img_path)\n        mean = img_pil_gray_alpha[300:, 400:].mean(axis=(0, 1))\n        assert_allclose(img_pil_gray_alpha[300:, 400:] - mean, 0)\n        assert img_pil_gray_alpha.shape == (400, 500, 3)\n        img_pil_gray_alpha = mmcv.imread(self.gray_alpha_img_path, 'unchanged')\n        assert img_pil_gray_alpha.shape == (400, 500, 2)\n        img_pil_palette = mmcv.imread(self.palette_img_path, 'grayscale')\n        assert img_pil_palette.shape == (300, 400)\n        img_pil_palette = mmcv.imread(self.palette_img_path)\n        assert img_pil_palette.shape == (300, 400, 3)\n        img_pil_palette = mmcv.imread(self.palette_img_path, 'unchanged')\n        assert img_pil_palette.shape == (300, 400)\n        img_pil_grayscale2 = mmcv.imread(self.gray_img_path)\n        assert img_pil_grayscale2.shape == (300, 400, 3)\n        img_pil_unchanged = mmcv.imread(self.gray_img_path, 'unchanged')\n        assert img_pil_unchanged.shape == (300, 400)\n        img_pil_unchanged = mmcv.imread(img_pil_unchanged)\n        assert_array_equal(img_pil_unchanged, mmcv.imread(img_pil_unchanged))\n\n        img_pil_color_bgr = mmcv.imread(self.img_path_obj)\n        assert img_pil_color_bgr.shape == (300, 400, 3)\n        img_pil_color_rgb = mmcv.imread(self.img_path_obj, channel_order='rgb')\n        assert img_pil_color_rgb.shape == (300, 400, 3)\n        assert (img_pil_color_rgb == img_cv2_color_rgb).sum() / float(\n            img_cv2_color_rgb.size) > 0.5\n        assert_array_equal(img_pil_color_rgb[:, :, ::-1], img_pil_color_bgr)\n        img_pil_grayscale1 = mmcv.imread(self.img_path_obj, 'grayscale')\n        assert img_pil_grayscale1.shape == (300, 400)\n        img_pil_grayscale2 = mmcv.imread(self.gray_img_path_obj)\n        assert img_pil_grayscale2.shape == (300, 400, 3)\n        img_pil_unchanged = mmcv.imread(self.gray_img_path_obj, 'unchanged')\n        assert img_pil_unchanged.shape == (300, 400)\n        with pytest.raises(TypeError):\n            mmcv.imread(1)\n\n        # backend turbojpeg\n        mmcv.use_backend('turbojpeg')\n\n        img_turbojpeg_color_bgr = mmcv.imread(self.img_path)\n        assert img_turbojpeg_color_bgr.shape == (300, 400, 3)\n        assert_array_equal(img_turbojpeg_color_bgr, img_cv2_color_bgr)\n\n        img_turbojpeg_color_rgb = mmcv.imread(\n            self.img_path, channel_order='rgb')\n        assert img_turbojpeg_color_rgb.shape == (300, 400, 3)\n        assert_array_equal(img_turbojpeg_color_rgb, img_cv2_color_rgb)\n\n        with pytest.raises(ValueError):\n            mmcv.imread(self.img_path, channel_order='unsupport_order')\n\n        img_turbojpeg_grayscale1 = mmcv.imread(self.img_path, flag='grayscale')\n        assert img_turbojpeg_grayscale1.shape == (300, 400)\n        assert_array_equal(img_turbojpeg_grayscale1, img_cv2_grayscale1)\n\n        img_turbojpeg_grayscale2 = mmcv.imread(self.gray_img_path)\n        assert img_turbojpeg_grayscale2.shape == (300, 400, 3)\n        assert_array_equal(img_turbojpeg_grayscale2, img_cv2_grayscale2)\n\n        img_turbojpeg_grayscale2 = mmcv.imread(img_turbojpeg_grayscale2)\n        assert_array_equal(img_turbojpeg_grayscale2,\n                           mmcv.imread(img_turbojpeg_grayscale2))\n\n        with pytest.raises(ValueError):\n            mmcv.imread(self.gray_img_path, 'unchanged')\n\n        with pytest.raises(TypeError):\n            mmcv.imread(1)\n\n        with pytest.raises(AssertionError):\n            mmcv.use_backend('unsupport_backend')\n\n        with pytest.raises(ValueError):\n            mmcv.imread(self.img_path, 'unsupported_backend')\n\n        mmcv.use_backend('cv2')\n\n    def test_imfrombytes(self):\n        # backend cv2, channel order: bgr\n        mmcv.use_backend('cv2')\n        with open(self.img_path, 'rb') as f:\n            img_bytes = f.read()\n        img_cv2 = mmcv.imfrombytes(img_bytes)\n        assert img_cv2.shape == (300, 400, 3)\n\n        # backend cv2, channel order: rgb\n        mmcv.use_backend('cv2')\n        with open(self.img_path, 'rb') as f:\n            img_bytes = f.read()\n        img_rgb_cv2 = mmcv.imfrombytes(img_bytes, channel_order='rgb')\n        assert img_rgb_cv2.shape == (300, 400, 3)\n        assert_array_equal(img_rgb_cv2, img_cv2[:, :, ::-1])\n\n        # backend cv2, grayscale, decode as 3 channels\n        with open(self.gray_img_path, 'rb') as f:\n            img_bytes = f.read()\n        gray_img_rgb_cv2 = mmcv.imfrombytes(img_bytes)\n        assert gray_img_rgb_cv2.shape == (300, 400, 3)\n\n        # backend cv2, grayscale\n        with open(self.gray_img_path, 'rb') as f:\n            img_bytes = f.read()\n        gray_img_cv2 = mmcv.imfrombytes(img_bytes, flag='grayscale')\n        assert gray_img_cv2.shape == (300, 400)\n\n        # backend cv2, grayscale dim3\n        with open(self.gray_img_dim3_path, 'rb') as f:\n            img_bytes = f.read()\n        gray_img_dim3_cv2 = mmcv.imfrombytes(img_bytes, flag='grayscale')\n        assert gray_img_dim3_cv2.shape == (300, 400)\n\n        # arg backend pillow, channel order: bgr\n        with open(self.img_path, 'rb') as f:\n            img_bytes = f.read()\n        img_pillow = mmcv.imfrombytes(img_bytes, backend='pillow')\n        assert img_pillow.shape == (300, 400, 3)\n        # Pillow and opencv decoding may not be the same\n        assert (img_cv2 == img_pillow).sum() / float(img_cv2.size) > 0.5\n\n        # backend pillow, channel order: bgr\n        mmcv.use_backend('pillow')\n        with open(self.img_path, 'rb') as f:\n            img_bytes = f.read()\n        img_pillow = mmcv.imfrombytes(img_bytes)\n        assert img_pillow.shape == (300, 400, 3)\n        # Pillow and opencv decoding may not be the same\n        assert (img_cv2 == img_pillow).sum() / float(img_cv2.size) > 0.5\n\n        # backend turbojpeg, channel order: bgr\n        mmcv.use_backend('turbojpeg')\n        with open(self.img_path, 'rb') as f:\n            img_bytes = f.read()\n        img_turbojpeg = mmcv.imfrombytes(img_bytes)\n        assert img_turbojpeg.shape == (300, 400, 3)\n        assert_array_equal(img_cv2, img_turbojpeg)\n\n        # backend turbojpeg, channel order: rgb\n        with open(self.img_path, 'rb') as f:\n            img_bytes = f.read()\n        img_rgb_turbojpeg = mmcv.imfrombytes(img_bytes, channel_order='rgb')\n        assert img_rgb_turbojpeg.shape == (300, 400, 3)\n        assert_array_equal(img_rgb_turbojpeg, img_cv2[:, :, ::-1])\n\n        # backend turbojpeg, grayscale, decode as 3 channels\n        with open(self.gray_img_path, 'rb') as f:\n            img_bytes = f.read()\n        gray_img_turbojpeg = mmcv.imfrombytes(img_bytes)\n        assert gray_img_turbojpeg.shape == (300, 400, 3)\n        assert_array_equal(gray_img_rgb_cv2, gray_img_turbojpeg)\n\n        # backend turbojpeg, grayscale\n        with open(self.gray_img_path, 'rb') as f:\n            img_bytes = f.read()\n        gray_img_turbojpeg = mmcv.imfrombytes(img_bytes, flag='grayscale')\n        assert gray_img_turbojpeg.shape == (300, 400)\n        assert_array_equal(gray_img_cv2, gray_img_turbojpeg)\n\n        # backend turbojpeg, grayscale dim3\n        with open(self.gray_img_dim3_path, 'rb') as f:\n            img_bytes = f.read()\n        gray_img_dim3_turbojpeg = mmcv.imfrombytes(img_bytes, flag='grayscale')\n        assert gray_img_dim3_turbojpeg.shape == (300, 400)\n        assert_array_equal(gray_img_dim3_cv2, gray_img_dim3_turbojpeg)\n\n        mmcv.use_backend('cv2')\n\n        with pytest.raises(ValueError):\n            with open(self.img_path, 'rb') as f:\n                img_bytes = f.read()\n            mmcv.imfrombytes(img_bytes, backend='unsupported_backend')\n\n    def test_imwrite(self):\n        img = mmcv.imread(self.img_path)\n        out_file = osp.join(tempfile.gettempdir(), 'mmcv_test.jpg')\n        mmcv.imwrite(img, out_file)\n        rewrite_img = mmcv.imread(out_file)\n        os.remove(out_file)\n        self.assert_img_equal(img, rewrite_img)\n\n        ret = mmcv.imwrite(\n            img, './non_exist_path/mmcv_test.jpg', auto_mkdir=False)\n        assert ret is False\n\n    @patch('mmcv.image.io.TurboJPEG', None)\n    def test_no_turbojpeg(self):\n        with pytest.raises(ImportError):\n            mmcv.use_backend('turbojpeg')\n\n        mmcv.use_backend('cv2')\n\n    @patch('mmcv.image.io.Image', None)\n    def test_no_pillow(self):\n        with pytest.raises(ImportError):\n            mmcv.use_backend('pillow')\n\n        mmcv.use_backend('cv2')\n"
  },
  {
    "path": "code/mmcv/tests/test_image/test_photometric.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\n\nimport cv2\nimport numpy as np\nfrom numpy.testing import assert_array_equal\n\nimport mmcv\n\n\nclass TestPhotometric:\n\n    @classmethod\n    def setup_class(cls):\n        # the test img resolution is 400x300\n        cls.img_path = osp.join(osp.dirname(__file__), '../data/color.jpg')\n        cls.img = cv2.imread(cls.img_path)\n        cls.mean = np.array([123.675, 116.28, 103.53], dtype=np.float32)\n        cls.std = np.array([58.395, 57.12, 57.375], dtype=np.float32)\n\n    def test_imnormalize(self):\n        rgb_img = self.img[:, :, ::-1]\n        baseline = (rgb_img - self.mean) / self.std\n        img = mmcv.imnormalize(self.img, self.mean, self.std)\n        assert np.allclose(img, baseline)\n        assert id(img) != id(self.img)\n        img = mmcv.imnormalize(rgb_img, self.mean, self.std, to_rgb=False)\n        assert np.allclose(img, baseline)\n        assert id(img) != id(rgb_img)\n\n    def test_imnormalize_(self):\n        img_for_normalize = np.float32(self.img)\n        rgb_img_for_normalize = np.float32(self.img[:, :, ::-1])\n        baseline = (rgb_img_for_normalize - self.mean) / self.std\n        img = mmcv.imnormalize_(img_for_normalize, self.mean, self.std)\n        assert np.allclose(img_for_normalize, baseline)\n        assert id(img) == id(img_for_normalize)\n        img = mmcv.imnormalize_(\n            rgb_img_for_normalize, self.mean, self.std, to_rgb=False)\n        assert np.allclose(img, baseline)\n        assert id(img) == id(rgb_img_for_normalize)\n\n    def test_imdenormalize(self):\n        norm_img = (self.img[:, :, ::-1] - self.mean) / self.std\n        rgb_baseline = (norm_img * self.std + self.mean)\n        bgr_baseline = rgb_baseline[:, :, ::-1]\n        img = mmcv.imdenormalize(norm_img, self.mean, self.std)\n        assert np.allclose(img, bgr_baseline)\n        img = mmcv.imdenormalize(norm_img, self.mean, self.std, to_bgr=False)\n        assert np.allclose(img, rgb_baseline)\n\n    def test_iminvert(self):\n        img = np.array([[0, 128, 255], [1, 127, 254], [2, 129, 253]],\n                       dtype=np.uint8)\n        img_r = np.array([[255, 127, 0], [254, 128, 1], [253, 126, 2]],\n                         dtype=np.uint8)\n        assert_array_equal(mmcv.iminvert(img), img_r)\n\n    def test_solarize(self):\n        img = np.array([[0, 128, 255], [1, 127, 254], [2, 129, 253]],\n                       dtype=np.uint8)\n        img_r = np.array([[0, 127, 0], [1, 127, 1], [2, 126, 2]],\n                         dtype=np.uint8)\n        assert_array_equal(mmcv.solarize(img), img_r)\n        img_r = np.array([[0, 127, 0], [1, 128, 1], [2, 126, 2]],\n                         dtype=np.uint8)\n        assert_array_equal(mmcv.solarize(img, 100), img_r)\n\n    def test_posterize(self):\n        img = np.array([[0, 128, 255], [1, 127, 254], [2, 129, 253]],\n                       dtype=np.uint8)\n        img_r = np.array([[0, 128, 128], [0, 0, 128], [0, 128, 128]],\n                         dtype=np.uint8)\n        assert_array_equal(mmcv.posterize(img, 1), img_r)\n        img_r = np.array([[0, 128, 224], [0, 96, 224], [0, 128, 224]],\n                         dtype=np.uint8)\n        assert_array_equal(mmcv.posterize(img, 3), img_r)\n"
  },
  {
    "path": "code/mmcv/tests/test_load_model_zoo.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nfrom unittest.mock import patch\n\nimport pytest\n\nimport mmcv\nfrom mmcv.runner.checkpoint import (DEFAULT_CACHE_DIR, ENV_MMCV_HOME,\n                                    ENV_XDG_CACHE_HOME, _get_mmcv_home,\n                                    _load_checkpoint,\n                                    get_deprecated_model_names,\n                                    get_external_models)\n\n\n@patch('mmcv.__path__', [osp.join(osp.dirname(__file__), 'data/')])\ndef test_set_mmcv_home():\n    os.environ.pop(ENV_MMCV_HOME, None)\n    mmcv_home = osp.join(osp.dirname(__file__), 'data/model_zoo/mmcv_home/')\n    os.environ[ENV_MMCV_HOME] = mmcv_home\n    assert _get_mmcv_home() == mmcv_home\n\n\n@patch('mmcv.__path__', [osp.join(osp.dirname(__file__), 'data/')])\ndef test_default_mmcv_home():\n    os.environ.pop(ENV_MMCV_HOME, None)\n    os.environ.pop(ENV_XDG_CACHE_HOME, None)\n    assert _get_mmcv_home() == os.path.expanduser(\n        os.path.join(DEFAULT_CACHE_DIR, 'mmcv'))\n    model_urls = get_external_models()\n    assert model_urls == mmcv.load(\n        osp.join(mmcv.__path__[0], 'model_zoo/open_mmlab.json'))\n\n\n@patch('mmcv.__path__', [osp.join(osp.dirname(__file__), 'data/')])\ndef test_get_external_models():\n    os.environ.pop(ENV_MMCV_HOME, None)\n    mmcv_home = osp.join(osp.dirname(__file__), 'data/model_zoo/mmcv_home/')\n    os.environ[ENV_MMCV_HOME] = mmcv_home\n    ext_urls = get_external_models()\n    assert ext_urls == {\n        'train': 'https://localhost/train.pth',\n        'test': 'test.pth',\n        'val': 'val.pth',\n        'train_empty': 'train.pth'\n    }\n\n\n@patch('mmcv.__path__', [osp.join(osp.dirname(__file__), 'data/')])\ndef test_get_deprecated_models():\n    os.environ.pop(ENV_MMCV_HOME, None)\n    mmcv_home = osp.join(osp.dirname(__file__), 'data/model_zoo/mmcv_home/')\n    os.environ[ENV_MMCV_HOME] = mmcv_home\n    dep_urls = get_deprecated_model_names()\n    assert dep_urls == {\n        'train_old': 'train',\n        'test_old': 'test',\n    }\n\n\ndef load_url_dist(url):\n    return 'url:' + url\n\n\ndef load(filepath, map_location=None):\n    return 'local:' + filepath\n\n\n@patch('mmcv.__path__', [osp.join(osp.dirname(__file__), 'data/')])\n@patch('mmcv.runner.checkpoint.load_url_dist', load_url_dist)\n@patch('torch.load', load)\ndef test_load_external_url():\n    # test modelzoo://\n    url = _load_checkpoint('modelzoo://resnet50')\n    assert url == 'url:https://download.pytorch.org/models/resnet50-19c8e357' \\\n                  '.pth'\n\n    # test torchvision://\n    url = _load_checkpoint('torchvision://resnet50')\n    assert url == 'url:https://download.pytorch.org/models/resnet50-19c8e357' \\\n                  '.pth'\n\n    # test open-mmlab:// with default MMCV_HOME\n    os.environ.pop(ENV_MMCV_HOME, None)\n    os.environ.pop(ENV_XDG_CACHE_HOME, None)\n    url = _load_checkpoint('open-mmlab://train')\n    assert url == 'url:https://localhost/train.pth'\n\n    # test open-mmlab:// with deprecated model name\n    os.environ.pop(ENV_MMCV_HOME, None)\n    os.environ.pop(ENV_XDG_CACHE_HOME, None)\n    with pytest.warns(\n            Warning,\n            match='open-mmlab://train_old is deprecated in favor of '\n            'open-mmlab://train'):\n        url = _load_checkpoint('open-mmlab://train_old')\n        assert url == 'url:https://localhost/train.pth'\n\n    # test open-mmlab:// with user-defined MMCV_HOME\n    os.environ.pop(ENV_MMCV_HOME, None)\n    mmcv_home = osp.join(osp.dirname(__file__), 'data/model_zoo/mmcv_home')\n    os.environ[ENV_MMCV_HOME] = mmcv_home\n    url = _load_checkpoint('open-mmlab://train')\n    assert url == 'url:https://localhost/train.pth'\n    with pytest.raises(IOError, match='train.pth is not a checkpoint ' 'file'):\n        _load_checkpoint('open-mmlab://train_empty')\n    url = _load_checkpoint('open-mmlab://test')\n    assert url == f'local:{osp.join(_get_mmcv_home(), \"test.pth\")}'\n    url = _load_checkpoint('open-mmlab://val')\n    assert url == f'local:{osp.join(_get_mmcv_home(), \"val.pth\")}'\n\n    # test http:// https://\n    url = _load_checkpoint('http://localhost/train.pth')\n    assert url == 'url:http://localhost/train.pth'\n\n    # test local file\n    with pytest.raises(IOError, match='train.pth is not a checkpoint ' 'file'):\n        _load_checkpoint('train.pth')\n    url = _load_checkpoint(osp.join(_get_mmcv_home(), 'test.pth'))\n    assert url == f'local:{osp.join(_get_mmcv_home(), \"test.pth\")}'\n"
  },
  {
    "path": "code/mmcv/tests/test_logging.py",
    "content": "import logging\nimport re\nimport tempfile\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom mmcv import get_logger, print_log\n\n\n@patch('torch.distributed.get_rank', lambda: 0)\n@patch('torch.distributed.is_initialized', lambda: True)\n@patch('torch.distributed.is_available', lambda: True)\ndef test_get_logger_rank0():\n    logger = get_logger('rank0.pkg1')\n    assert isinstance(logger, logging.Logger)\n    assert len(logger.handlers) == 1\n    assert isinstance(logger.handlers[0], logging.StreamHandler)\n    assert logger.handlers[0].level == logging.INFO\n\n    logger = get_logger('rank0.pkg2', log_level=logging.DEBUG)\n    assert isinstance(logger, logging.Logger)\n    assert len(logger.handlers) == 1\n    assert logger.handlers[0].level == logging.DEBUG\n\n    with tempfile.NamedTemporaryFile() as f:\n        logger = get_logger('rank0.pkg3', log_file=f.name)\n    assert isinstance(logger, logging.Logger)\n    assert len(logger.handlers) == 2\n    assert isinstance(logger.handlers[0], logging.StreamHandler)\n    assert isinstance(logger.handlers[1], logging.FileHandler)\n\n    logger_pkg3 = get_logger('rank0.pkg3')\n    assert id(logger_pkg3) == id(logger)\n\n    logger_pkg3 = get_logger('rank0.pkg3.subpkg')\n    assert logger_pkg3.handlers == logger_pkg3.handlers\n\n\n@patch('torch.distributed.get_rank', lambda: 1)\n@patch('torch.distributed.is_initialized', lambda: True)\n@patch('torch.distributed.is_available', lambda: True)\ndef test_get_logger_rank1():\n    logger = get_logger('rank1.pkg1')\n    assert isinstance(logger, logging.Logger)\n    assert len(logger.handlers) == 1\n    assert isinstance(logger.handlers[0], logging.StreamHandler)\n    assert logger.handlers[0].level == logging.INFO\n\n    with tempfile.NamedTemporaryFile() as f:\n        logger = get_logger('rank1.pkg2', log_file=f.name)\n    assert isinstance(logger, logging.Logger)\n    assert len(logger.handlers) == 1\n    assert logger.handlers[0].level == logging.INFO\n\n\ndef test_print_log_print(capsys):\n    print_log('welcome', logger=None)\n    out, _ = capsys.readouterr()\n    assert out == 'welcome\\n'\n\n\ndef test_print_log_silent(capsys, caplog):\n    print_log('welcome', logger='silent')\n    out, _ = capsys.readouterr()\n    assert out == ''\n    assert len(caplog.records) == 0\n\n\ndef test_print_log_logger(caplog):\n    print_log('welcome', logger='mmcv')\n    assert caplog.record_tuples[-1] == ('mmcv', logging.INFO, 'welcome')\n\n    print_log('welcome', logger='mmcv', level=logging.ERROR)\n    assert caplog.record_tuples[-1] == ('mmcv', logging.ERROR, 'welcome')\n\n    with tempfile.NamedTemporaryFile() as f:\n        logger = get_logger('abc', log_file=f.name)\n        print_log('welcome', logger=logger)\n        assert caplog.record_tuples[-1] == ('abc', logging.INFO, 'welcome')\n        with open(f.name, 'r') as fin:\n            log_text = fin.read()\n            regex_time = r'\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3}'\n            match = re.fullmatch(regex_time + r' - abc - INFO - welcome\\n',\n                                 log_text)\n            assert match is not None\n\n\ndef test_print_log_exception():\n    with pytest.raises(TypeError):\n        print_log('welcome', logger=0)\n"
  },
  {
    "path": "code/mmcv/tests/test_misc.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport pytest\n\nimport mmcv\n\n\ndef test_iter_cast():\n    assert mmcv.list_cast([1, 2, 3], int) == [1, 2, 3]\n    assert mmcv.list_cast(['1.1', 2, '3'], float) == [1.1, 2.0, 3.0]\n    assert mmcv.list_cast([1, 2, 3], str) == ['1', '2', '3']\n    assert mmcv.tuple_cast((1, 2, 3), str) == ('1', '2', '3')\n    assert next(mmcv.iter_cast([1, 2, 3], str)) == '1'\n    with pytest.raises(TypeError):\n        mmcv.iter_cast([1, 2, 3], '')\n    with pytest.raises(TypeError):\n        mmcv.iter_cast(1, str)\n\n\ndef test_is_seq_of():\n    assert mmcv.is_seq_of([1.0, 2.0, 3.0], float)\n    assert mmcv.is_seq_of([(1, ), (2, ), (3, )], tuple)\n    assert mmcv.is_seq_of((1.0, 2.0, 3.0), float)\n    assert mmcv.is_list_of([1.0, 2.0, 3.0], float)\n    assert not mmcv.is_seq_of((1.0, 2.0, 3.0), float, seq_type=list)\n    assert not mmcv.is_tuple_of([1.0, 2.0, 3.0], float)\n    assert not mmcv.is_seq_of([1.0, 2, 3], int)\n    assert not mmcv.is_seq_of((1.0, 2, 3), int)\n\n\ndef test_slice_list():\n    in_list = [1, 2, 3, 4, 5, 6]\n    assert mmcv.slice_list(in_list, [1, 2, 3]) == [[1], [2, 3], [4, 5, 6]]\n    assert mmcv.slice_list(in_list, [len(in_list)]) == [in_list]\n    with pytest.raises(TypeError):\n        mmcv.slice_list(in_list, 2.0)\n    with pytest.raises(ValueError):\n        mmcv.slice_list(in_list, [1, 2])\n\n\ndef test_concat_list():\n    assert mmcv.concat_list([[1, 2]]) == [1, 2]\n    assert mmcv.concat_list([[1, 2], [3, 4, 5], [6]]) == [1, 2, 3, 4, 5, 6]\n\n\ndef test_requires_package(capsys):\n\n    @mmcv.requires_package('nnn')\n    def func_a():\n        pass\n\n    @mmcv.requires_package(['numpy', 'n1', 'n2'])\n    def func_b():\n        pass\n\n    @mmcv.requires_package('numpy')\n    def func_c():\n        return 1\n\n    with pytest.raises(RuntimeError):\n        func_a()\n    out, _ = capsys.readouterr()\n    assert out == ('Prerequisites \"nnn\" are required in method \"func_a\" but '\n                   'not found, please install them first.\\n')\n\n    with pytest.raises(RuntimeError):\n        func_b()\n    out, _ = capsys.readouterr()\n    assert out == (\n        'Prerequisites \"n1, n2\" are required in method \"func_b\" but not found,'\n        ' please install them first.\\n')\n\n    assert func_c() == 1\n\n\ndef test_requires_executable(capsys):\n\n    @mmcv.requires_executable('nnn')\n    def func_a():\n        pass\n\n    @mmcv.requires_executable(['ls', 'n1', 'n2'])\n    def func_b():\n        pass\n\n    @mmcv.requires_executable('mv')\n    def func_c():\n        return 1\n\n    with pytest.raises(RuntimeError):\n        func_a()\n    out, _ = capsys.readouterr()\n    assert out == ('Prerequisites \"nnn\" are required in method \"func_a\" but '\n                   'not found, please install them first.\\n')\n\n    with pytest.raises(RuntimeError):\n        func_b()\n    out, _ = capsys.readouterr()\n    assert out == (\n        'Prerequisites \"n1, n2\" are required in method \"func_b\" but not found,'\n        ' please install them first.\\n')\n\n    assert func_c() == 1\n"
  },
  {
    "path": "code/mmcv/tests/test_optimizer.py",
    "content": "import warnings\n\nimport pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmcv.runner import OPTIMIZER_BUILDERS, DefaultOptimizerConstructor\nfrom mmcv.runner.optimizer import build_optimizer, build_optimizer_constructor\nfrom mmcv.runner.optimizer.builder import TORCH_OPTIMIZERS\n\n\nclass SubModel(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.conv1 = nn.Conv2d(2, 2, kernel_size=1, groups=2)\n        self.gn = nn.GroupNorm(2, 2)\n        self.param1 = nn.Parameter(torch.ones(1))\n\n    def forward(self, x):\n        return x\n\n\nclass ExampleModel(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.param1 = nn.Parameter(torch.ones(1))\n        self.conv1 = nn.Conv2d(3, 4, kernel_size=1, bias=False)\n        self.conv2 = nn.Conv2d(4, 2, kernel_size=1)\n        self.bn = nn.BatchNorm2d(2)\n        self.sub = SubModel()\n\n    def forward(self, x):\n        return x\n\n\nclass ExampleDuplicateModel(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.param1 = nn.Parameter(torch.ones(1))\n        self.conv1 = nn.Sequential(nn.Conv2d(3, 4, kernel_size=1, bias=False))\n        self.conv2 = nn.Sequential(nn.Conv2d(4, 2, kernel_size=1))\n        self.bn = nn.BatchNorm2d(2)\n        self.sub = SubModel()\n        self.conv3 = nn.Sequential(nn.Conv2d(3, 4, kernel_size=1, bias=False))\n        self.conv3[0] = self.conv1[0]\n\n    def forward(self, x):\n        return x\n\n\nclass PseudoDataParallel(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.module = ExampleModel()\n\n    def forward(self, x):\n        return x\n\n\nbase_lr = 0.01\nbase_wd = 0.0001\nmomentum = 0.9\n\n\ndef check_default_optimizer(optimizer, model, prefix=''):\n    assert isinstance(optimizer, torch.optim.SGD)\n    assert optimizer.defaults['lr'] == base_lr\n    assert optimizer.defaults['momentum'] == momentum\n    assert optimizer.defaults['weight_decay'] == base_wd\n    param_groups = optimizer.param_groups[0]\n    param_names = [\n        'param1', 'conv1.weight', 'conv2.weight', 'conv2.bias', 'bn.weight',\n        'bn.bias', 'sub.param1', 'sub.conv1.weight', 'sub.conv1.bias',\n        'sub.gn.weight', 'sub.gn.bias'\n    ]\n    param_dict = dict(model.named_parameters())\n    assert len(param_groups['params']) == len(param_names)\n    for i in range(len(param_groups['params'])):\n        assert torch.equal(param_groups['params'][i],\n                           param_dict[prefix + param_names[i]])\n\n\ndef check_optimizer(optimizer,\n                    model,\n                    prefix='',\n                    bias_lr_mult=1,\n                    bias_decay_mult=1,\n                    norm_decay_mult=1,\n                    dwconv_decay_mult=1,\n                    bypass_duplicate=False):\n    param_groups = optimizer.param_groups\n    assert isinstance(optimizer, torch.optim.SGD)\n    assert optimizer.defaults['lr'] == base_lr\n    assert optimizer.defaults['momentum'] == momentum\n    assert optimizer.defaults['weight_decay'] == base_wd\n    model_parameters = list(model.parameters())\n    assert len(param_groups) == len(model_parameters)\n    for i, param in enumerate(model_parameters):\n        param_group = param_groups[i]\n        assert torch.equal(param_group['params'][0], param)\n        assert param_group['momentum'] == momentum\n    # param1\n    param1 = param_groups[0]\n    assert param1['lr'] == base_lr\n    assert param1['weight_decay'] == base_wd\n    # conv1.weight\n    conv1_weight = param_groups[1]\n    assert conv1_weight['lr'] == base_lr\n    assert conv1_weight['weight_decay'] == base_wd\n    # conv2.weight\n    conv2_weight = param_groups[2]\n    assert conv2_weight['lr'] == base_lr\n    assert conv2_weight['weight_decay'] == base_wd\n    # conv2.bias\n    conv2_bias = param_groups[3]\n    assert conv2_bias['lr'] == base_lr * bias_lr_mult\n    assert conv2_bias['weight_decay'] == base_wd * bias_decay_mult\n    # bn.weight\n    bn_weight = param_groups[4]\n    assert bn_weight['lr'] == base_lr\n    assert bn_weight['weight_decay'] == base_wd * norm_decay_mult\n    # bn.bias\n    bn_bias = param_groups[5]\n    assert bn_bias['lr'] == base_lr\n    assert bn_bias['weight_decay'] == base_wd * norm_decay_mult\n    # sub.param1\n    sub_param1 = param_groups[6]\n    assert sub_param1['lr'] == base_lr\n    assert sub_param1['weight_decay'] == base_wd\n    # sub.conv1.weight\n    sub_conv1_weight = param_groups[7]\n    assert sub_conv1_weight['lr'] == base_lr\n    assert sub_conv1_weight['weight_decay'] == base_wd * dwconv_decay_mult\n    # sub.conv1.bias\n    sub_conv1_bias = param_groups[8]\n    assert sub_conv1_bias['lr'] == base_lr * bias_lr_mult\n    assert sub_conv1_bias['weight_decay'] == base_wd * dwconv_decay_mult\n    # sub.gn.weight\n    sub_gn_weight = param_groups[9]\n    assert sub_gn_weight['lr'] == base_lr\n    assert sub_gn_weight['weight_decay'] == base_wd * norm_decay_mult\n    # sub.gn.bias\n    sub_gn_bias = param_groups[10]\n    assert sub_gn_bias['lr'] == base_lr\n    assert sub_gn_bias['weight_decay'] == base_wd * norm_decay_mult\n\n\ndef test_default_optimizer_constructor():\n    model = ExampleModel()\n\n    with pytest.raises(TypeError):\n        # optimizer_cfg must be a dict\n        optimizer_cfg = []\n        optim_constructor = DefaultOptimizerConstructor(optimizer_cfg)\n        optim_constructor(model)\n\n    with pytest.raises(TypeError):\n        # paramwise_cfg must be a dict or None\n        optimizer_cfg = dict(lr=0.0001)\n        paramwise_cfg = ['error']\n        optim_constructor = DefaultOptimizerConstructor(\n            optimizer_cfg, paramwise_cfg)\n        optim_constructor(model)\n\n    with pytest.raises(ValueError):\n        # bias_decay_mult/norm_decay_mult is specified but weight_decay is None\n        optimizer_cfg = dict(lr=0.0001, weight_decay=None)\n        paramwise_cfg = dict(bias_decay_mult=1, norm_decay_mult=1)\n        optim_constructor = DefaultOptimizerConstructor(\n            optimizer_cfg, paramwise_cfg)\n        optim_constructor(model)\n\n    # basic config with ExampleModel\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg)\n    optimizer = optim_constructor(model)\n    check_default_optimizer(optimizer, model)\n\n    # basic config with pseudo data parallel\n    model = PseudoDataParallel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = None\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg)\n    optimizer = optim_constructor(model)\n    check_default_optimizer(optimizer, model, prefix='module.')\n\n    # basic config with DataParallel\n    if torch.cuda.is_available():\n        model = torch.nn.DataParallel(ExampleModel())\n        optimizer_cfg = dict(\n            type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n        paramwise_cfg = None\n        optim_constructor = DefaultOptimizerConstructor(optimizer_cfg)\n        optimizer = optim_constructor(model)\n        check_default_optimizer(optimizer, model, prefix='module.')\n\n    # Empty paramwise_cfg with ExampleModel\n    model = ExampleModel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict()\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n    check_default_optimizer(optimizer, model)\n\n    # Empty paramwise_cfg with ExampleModel and no grad\n    model = ExampleModel()\n    for param in model.parameters():\n        param.requires_grad = False\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict()\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg)\n    optimizer = optim_constructor(model)\n    check_default_optimizer(optimizer, model)\n\n    # paramwise_cfg with ExampleModel\n    model = ExampleModel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict(\n        bias_lr_mult=2,\n        bias_decay_mult=0.5,\n        norm_decay_mult=0,\n        dwconv_decay_mult=0.1)\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n    check_optimizer(optimizer, model, **paramwise_cfg)\n\n    # paramwise_cfg with ExampleModel, weight decay is None\n    model = ExampleModel()\n    optimizer_cfg = dict(type='Rprop', lr=base_lr)\n    paramwise_cfg = dict(bias_lr_mult=2)\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n\n    param_groups = optimizer.param_groups\n    assert isinstance(optimizer, torch.optim.Rprop)\n    assert optimizer.defaults['lr'] == base_lr\n    model_parameters = list(model.parameters())\n    assert len(param_groups) == len(model_parameters)\n    for i, param in enumerate(model_parameters):\n        param_group = param_groups[i]\n        assert torch.equal(param_group['params'][0], param)\n    # param1\n    assert param_groups[0]['lr'] == base_lr\n    # conv1.weight\n    assert param_groups[1]['lr'] == base_lr\n    # conv2.weight\n    assert param_groups[2]['lr'] == base_lr\n    # conv2.bias\n    assert param_groups[3]['lr'] == base_lr * paramwise_cfg['bias_lr_mult']\n    # bn.weight\n    assert param_groups[4]['lr'] == base_lr\n    # bn.bias\n    assert param_groups[5]['lr'] == base_lr\n    # sub.param1\n    assert param_groups[6]['lr'] == base_lr\n    # sub.conv1.weight\n    assert param_groups[7]['lr'] == base_lr\n    # sub.conv1.bias\n    assert param_groups[8]['lr'] == base_lr * paramwise_cfg['bias_lr_mult']\n    # sub.gn.weight\n    assert param_groups[9]['lr'] == base_lr\n    # sub.gn.bias\n    assert param_groups[10]['lr'] == base_lr\n\n    # paramwise_cfg with pseudo data parallel\n    model = PseudoDataParallel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict(\n        bias_lr_mult=2,\n        bias_decay_mult=0.5,\n        norm_decay_mult=0,\n        dwconv_decay_mult=0.1)\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n    check_optimizer(optimizer, model, prefix='module.', **paramwise_cfg)\n\n    # paramwise_cfg with DataParallel\n    if torch.cuda.is_available():\n        model = torch.nn.DataParallel(ExampleModel())\n        optimizer_cfg = dict(\n            type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n        paramwise_cfg = dict(\n            bias_lr_mult=2,\n            bias_decay_mult=0.5,\n            norm_decay_mult=0,\n            dwconv_decay_mult=0.1)\n        optim_constructor = DefaultOptimizerConstructor(\n            optimizer_cfg, paramwise_cfg)\n        optimizer = optim_constructor(model)\n        check_optimizer(optimizer, model, prefix='module.', **paramwise_cfg)\n\n    # paramwise_cfg with ExampleModel and no grad\n    for param in model.parameters():\n        param.requires_grad = False\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n    param_groups = optimizer.param_groups\n    assert isinstance(optimizer, torch.optim.SGD)\n    assert optimizer.defaults['lr'] == base_lr\n    assert optimizer.defaults['momentum'] == momentum\n    assert optimizer.defaults['weight_decay'] == base_wd\n    for i, (name, param) in enumerate(model.named_parameters()):\n        param_group = param_groups[i]\n        assert torch.equal(param_group['params'][0], param)\n        assert param_group['momentum'] == momentum\n        assert param_group['lr'] == base_lr\n        assert param_group['weight_decay'] == base_wd\n\n    # paramwise_cfg with bypass_duplicate option\n    model = ExampleDuplicateModel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict(\n        bias_lr_mult=2,\n        bias_decay_mult=0.5,\n        norm_decay_mult=0,\n        dwconv_decay_mult=0.1)\n    with pytest.raises(ValueError) as excinfo:\n        optim_constructor = DefaultOptimizerConstructor(\n            optimizer_cfg, paramwise_cfg)\n        optim_constructor(model)\n        assert 'some parameters appear in more than one parameter ' \\\n               'group' == excinfo.value\n\n    paramwise_cfg = dict(\n        bias_lr_mult=2,\n        bias_decay_mult=0.5,\n        norm_decay_mult=0,\n        dwconv_decay_mult=0.1,\n        bypass_duplicate=True)\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    with warnings.catch_warnings(record=True) as w:\n        optimizer = optim_constructor(model)\n        warnings.simplefilter('always')\n        assert len(w) == 1\n        assert str(w[0].message) == 'conv3.0 is duplicate. It is skipped ' \\\n                                    'since bypass_duplicate=True'\n    model_parameters = list(model.parameters())\n    assert len(optimizer.param_groups) == len(model_parameters) == 11\n    check_optimizer(optimizer, model, **paramwise_cfg)\n\n    # test DefaultOptimizerConstructor with custom_keys and ExampleModel\n    model = ExampleModel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict(\n        custom_keys={\n            'param1': dict(lr_mult=10),\n            'sub': dict(lr_mult=0.1, decay_mult=0),\n            'sub.gn': dict(lr_mult=0.01),\n            'non_exist_key': dict(lr_mult=0.0)\n        },\n        norm_decay_mult=0.5)\n\n    with pytest.raises(TypeError):\n        # custom_keys should be a dict\n        paramwise_cfg_ = dict(custom_keys=[0.1, 0.0001])\n        optim_constructor = DefaultOptimizerConstructor(\n            optimizer_cfg, paramwise_cfg_)\n        optimizer = optim_constructor(model)\n\n    with pytest.raises(ValueError):\n        # if 'decay_mult' is specified in custom_keys, weight_decay should be\n        # specified\n        optimizer_cfg_ = dict(type='SGD', lr=0.01)\n        paramwise_cfg_ = dict(custom_keys={'.backbone': dict(decay_mult=0.5)})\n        optim_constructor = DefaultOptimizerConstructor(\n            optimizer_cfg_, paramwise_cfg_)\n        optimizer = optim_constructor(model)\n\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n    # check optimizer type and default config\n    assert isinstance(optimizer, torch.optim.SGD)\n    assert optimizer.defaults['lr'] == base_lr\n    assert optimizer.defaults['momentum'] == momentum\n    assert optimizer.defaults['weight_decay'] == base_wd\n\n    # check params groups\n    param_groups = optimizer.param_groups\n\n    groups = []\n    group_settings = []\n    # group 1, matches of 'param1'\n    # 'param1' is the longest match for 'sub.param1'\n    groups.append(['param1', 'sub.param1'])\n    group_settings.append({\n        'lr': base_lr * 10,\n        'momentum': momentum,\n        'weight_decay': base_wd,\n    })\n    # group 2, matches of 'sub.gn'\n    groups.append(['sub.gn.weight', 'sub.gn.bias'])\n    group_settings.append({\n        'lr': base_lr * 0.01,\n        'momentum': momentum,\n        'weight_decay': base_wd,\n    })\n    # group 3, matches of 'sub'\n    groups.append(['sub.conv1.weight', 'sub.conv1.bias'])\n    group_settings.append({\n        'lr': base_lr * 0.1,\n        'momentum': momentum,\n        'weight_decay': 0,\n    })\n    # group 4, bn is configured by 'norm_decay_mult'\n    groups.append(['bn.weight', 'bn.bias'])\n    group_settings.append({\n        'lr': base_lr,\n        'momentum': momentum,\n        'weight_decay': base_wd * 0.5,\n    })\n    # group 5, default group\n    groups.append(['conv1.weight', 'conv2.weight', 'conv2.bias'])\n    group_settings.append({\n        'lr': base_lr,\n        'momentum': momentum,\n        'weight_decay': base_wd\n    })\n\n    assert len(param_groups) == 11\n    for i, (name, param) in enumerate(model.named_parameters()):\n        assert torch.equal(param_groups[i]['params'][0], param)\n        for group, settings in zip(groups, group_settings):\n            if name in group:\n                for setting in settings:\n                    assert param_groups[i][setting] == settings[\n                        setting], f'{name} {setting}'\n\n    # test DefaultOptimizerConstructor with custom_keys and ExampleModel 2\n    model = ExampleModel()\n    optimizer_cfg = dict(type='SGD', lr=base_lr, momentum=momentum)\n    paramwise_cfg = dict(custom_keys={'param1': dict(lr_mult=10)})\n\n    optim_constructor = DefaultOptimizerConstructor(optimizer_cfg,\n                                                    paramwise_cfg)\n    optimizer = optim_constructor(model)\n    # check optimizer type and default config\n    assert isinstance(optimizer, torch.optim.SGD)\n    assert optimizer.defaults['lr'] == base_lr\n    assert optimizer.defaults['momentum'] == momentum\n    assert optimizer.defaults['weight_decay'] == 0\n\n    # check params groups\n    param_groups = optimizer.param_groups\n\n    groups = []\n    group_settings = []\n    # group 1, matches of 'param1'\n    groups.append(['param1', 'sub.param1'])\n    group_settings.append({\n        'lr': base_lr * 10,\n        'momentum': momentum,\n        'weight_decay': 0,\n    })\n    # group 2, default group\n    groups.append([\n        'sub.conv1.weight', 'sub.conv1.bias', 'sub.gn.weight', 'sub.gn.bias',\n        'conv1.weight', 'conv2.weight', 'conv2.bias', 'bn.weight', 'bn.bias'\n    ])\n    group_settings.append({\n        'lr': base_lr,\n        'momentum': momentum,\n        'weight_decay': 0\n    })\n\n    assert len(param_groups) == 11\n    for i, (name, param) in enumerate(model.named_parameters()):\n        assert torch.equal(param_groups[i]['params'][0], param)\n        for group, settings in zip(groups, group_settings):\n            if name in group:\n                for setting in settings:\n                    assert param_groups[i][setting] == settings[\n                        setting], f'{name} {setting}'\n\n\ndef test_torch_optimizers():\n    torch_optimizers = [\n        'ASGD', 'Adadelta', 'Adagrad', 'Adam', 'AdamW', 'Adamax', 'LBFGS',\n        'Optimizer', 'RMSprop', 'Rprop', 'SGD', 'SparseAdam'\n    ]\n    assert set(torch_optimizers).issubset(set(TORCH_OPTIMIZERS))\n\n\ndef test_build_optimizer_constructor():\n    model = ExampleModel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    paramwise_cfg = dict(\n        bias_lr_mult=2,\n        bias_decay_mult=0.5,\n        norm_decay_mult=0,\n        dwconv_decay_mult=0.1)\n    optim_constructor_cfg = dict(\n        type='DefaultOptimizerConstructor',\n        optimizer_cfg=optimizer_cfg,\n        paramwise_cfg=paramwise_cfg)\n    optim_constructor = build_optimizer_constructor(optim_constructor_cfg)\n    optimizer = optim_constructor(model)\n    check_optimizer(optimizer, model, **paramwise_cfg)\n\n    from mmcv.runner import OPTIMIZERS\n    from mmcv.utils import build_from_cfg\n\n    @OPTIMIZER_BUILDERS.register_module()\n    class MyOptimizerConstructor(DefaultOptimizerConstructor):\n\n        def __call__(self, model):\n            if hasattr(model, 'module'):\n                model = model.module\n\n            conv1_lr_mult = self.paramwise_cfg.get('conv1_lr_mult', 1.)\n\n            params = []\n            for name, param in model.named_parameters():\n                param_group = {'params': [param]}\n                if name.startswith('conv1') and param.requires_grad:\n                    param_group['lr'] = self.base_lr * conv1_lr_mult\n                params.append(param_group)\n            optimizer_cfg['params'] = params\n\n            return build_from_cfg(optimizer_cfg, OPTIMIZERS)\n\n    paramwise_cfg = dict(conv1_lr_mult=5)\n    optim_constructor_cfg = dict(\n        type='MyOptimizerConstructor',\n        optimizer_cfg=optimizer_cfg,\n        paramwise_cfg=paramwise_cfg)\n    optim_constructor = build_optimizer_constructor(optim_constructor_cfg)\n    optimizer = optim_constructor(model)\n\n    param_groups = optimizer.param_groups\n    assert isinstance(optimizer, torch.optim.SGD)\n    assert optimizer.defaults['lr'] == base_lr\n    assert optimizer.defaults['momentum'] == momentum\n    assert optimizer.defaults['weight_decay'] == base_wd\n    for i, param in enumerate(model.parameters()):\n        param_group = param_groups[i]\n        assert torch.equal(param_group['params'][0], param)\n        assert param_group['momentum'] == momentum\n    # conv1.weight\n    assert param_groups[1]['lr'] == base_lr * paramwise_cfg['conv1_lr_mult']\n    assert param_groups[1]['weight_decay'] == base_wd\n\n\ndef test_build_optimizer():\n    model = ExampleModel()\n    optimizer_cfg = dict(\n        type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)\n    optimizer = build_optimizer(model, optimizer_cfg)\n    check_default_optimizer(optimizer, model)\n\n    model = ExampleModel()\n    optimizer_cfg = dict(\n        type='SGD',\n        lr=base_lr,\n        weight_decay=base_wd,\n        momentum=momentum,\n        paramwise_cfg=dict(\n            bias_lr_mult=2,\n            bias_decay_mult=0.5,\n            norm_decay_mult=0,\n            dwconv_decay_mult=0.1))\n    optimizer = build_optimizer(model, optimizer_cfg)\n    check_optimizer(optimizer, model, **optimizer_cfg['paramwise_cfg'])\n"
  },
  {
    "path": "code/mmcv/tests/test_parallel.py",
    "content": "from unittest.mock import MagicMock, patch\n\nimport torch.nn as nn\nfrom torch.nn.parallel import DataParallel, DistributedDataParallel\n\nfrom mmcv.parallel import (MODULE_WRAPPERS, MMDataParallel,\n                           MMDistributedDataParallel, is_module_wrapper)\nfrom mmcv.parallel.distributed_deprecated import \\\n    MMDistributedDataParallel as DeprecatedMMDDP\n\n\n@patch('torch.distributed._broadcast_coalesced', MagicMock)\n@patch('torch.distributed.broadcast', MagicMock)\n@patch('torch.nn.parallel.DistributedDataParallel._ddp_init_helper', MagicMock)\ndef test_is_module_wrapper():\n\n    class Model(nn.Module):\n\n        def __init__(self):\n            super().__init__()\n            self.conv = nn.Conv2d(2, 2, 1)\n\n        def forward(self, x):\n            return self.conv(x)\n\n    model = Model()\n    assert not is_module_wrapper(model)\n\n    dp = DataParallel(model)\n    assert is_module_wrapper(dp)\n\n    mmdp = MMDataParallel(model)\n    assert is_module_wrapper(mmdp)\n\n    ddp = DistributedDataParallel(model, process_group=MagicMock())\n    assert is_module_wrapper(ddp)\n\n    mmddp = MMDistributedDataParallel(model, process_group=MagicMock())\n    assert is_module_wrapper(mmddp)\n\n    deprecated_mmddp = DeprecatedMMDDP(model)\n    assert is_module_wrapper(deprecated_mmddp)\n\n    # test module wrapper registry\n    @MODULE_WRAPPERS.register_module()\n    class ModuleWrapper(object):\n\n        def __init__(self, module):\n            self.module = module\n\n        def forward(self, *args, **kwargs):\n            return self.module(*args, **kwargs)\n\n    module_wraper = ModuleWrapper(model)\n    assert is_module_wrapper(module_wraper)\n"
  },
  {
    "path": "code/mmcv/tests/test_path.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os.path as osp\nfrom pathlib import Path\n\nimport pytest\n\nimport mmcv\n\n\ndef test_is_filepath():\n    assert mmcv.is_filepath(__file__)\n    assert mmcv.is_filepath('abc')\n    assert mmcv.is_filepath(Path('/etc'))\n    assert not mmcv.is_filepath(0)\n\n\ndef test_fopen():\n    assert hasattr(mmcv.fopen(__file__), 'read')\n    assert hasattr(mmcv.fopen(Path(__file__)), 'read')\n\n\ndef test_check_file_exist():\n    mmcv.check_file_exist(__file__)\n    with pytest.raises(FileNotFoundError):\n        mmcv.check_file_exist('no_such_file.txt')\n\n\ndef test_scandir():\n    folder = osp.join(osp.dirname(__file__), 'data/for_scan')\n    filenames = ['a.bin', '1.txt', '2.txt', '1.json', '2.json']\n    assert set(mmcv.scandir(folder)) == set(filenames)\n    assert set(mmcv.scandir(Path(folder))) == set(filenames)\n    assert set(mmcv.scandir(folder, '.txt')) == set(\n        [filename for filename in filenames if filename.endswith('.txt')])\n    assert set(mmcv.scandir(folder, ('.json', '.txt'))) == set([\n        filename for filename in filenames\n        if filename.endswith(('.txt', '.json'))\n    ])\n    assert set(mmcv.scandir(folder, '.png')) == set()\n\n    filenames_recursive = [\n        'a.bin', '1.txt', '2.txt', '1.json', '2.json', 'sub/1.json',\n        'sub/1.txt'\n    ]\n    assert set(mmcv.scandir(folder,\n                            recursive=True)) == set(filenames_recursive)\n    assert set(mmcv.scandir(Path(folder),\n                            recursive=True)) == set(filenames_recursive)\n    assert set(mmcv.scandir(folder, '.txt', recursive=True)) == set([\n        filename for filename in filenames_recursive\n        if filename.endswith('.txt')\n    ])\n    with pytest.raises(TypeError):\n        list(mmcv.scandir(123))\n    with pytest.raises(TypeError):\n        list(mmcv.scandir(folder, 111))\n"
  },
  {
    "path": "code/mmcv/tests/test_progressbar.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport time\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\ntry:\n    from StringIO import StringIO\nexcept ImportError:\n    from io import StringIO\n\nimport mmcv  # isort:skip\n\n\ndef reset_string_io(io):\n    io.truncate(0)\n    io.seek(0)\n\n\nclass TestProgressBar:\n\n    def test_start(self):\n        out = StringIO()\n        bar_width = 20\n        # without total task num\n        prog_bar = mmcv.ProgressBar(bar_width=bar_width, file=out)\n        assert out.getvalue() == 'completed: 0, elapsed: 0s'\n        reset_string_io(out)\n        prog_bar = mmcv.ProgressBar(bar_width=bar_width, start=False, file=out)\n        assert out.getvalue() == ''\n        reset_string_io(out)\n        prog_bar.start()\n        assert out.getvalue() == 'completed: 0, elapsed: 0s'\n        # with total task num\n        reset_string_io(out)\n        prog_bar = mmcv.ProgressBar(10, bar_width=bar_width, file=out)\n        assert out.getvalue() == f'[{\" \" * bar_width}] 0/10, elapsed: 0s, ETA:'\n        reset_string_io(out)\n        prog_bar = mmcv.ProgressBar(\n            10, bar_width=bar_width, start=False, file=out)\n        assert out.getvalue() == ''\n        reset_string_io(out)\n        prog_bar.start()\n        assert out.getvalue() == f'[{\" \" * bar_width}] 0/10, elapsed: 0s, ETA:'\n\n    def test_update(self):\n        out = StringIO()\n        bar_width = 20\n        # without total task num\n        prog_bar = mmcv.ProgressBar(bar_width=bar_width, file=out)\n        time.sleep(1)\n        reset_string_io(out)\n        prog_bar.update()\n        assert out.getvalue() == 'completed: 1, elapsed: 1s, 1.0 tasks/s'\n        reset_string_io(out)\n        # with total task num\n        prog_bar = mmcv.ProgressBar(10, bar_width=bar_width, file=out)\n        time.sleep(1)\n        reset_string_io(out)\n        prog_bar.update()\n        assert out.getvalue() == f'\\r[{\">\" * 2 + \" \" * 18}] 1/10, 1.0 ' \\\n                                 'task/s, elapsed: 1s, ETA:     9s'\n\n    def test_adaptive_length(self):\n        with patch.dict('os.environ', {'COLUMNS': '80'}):\n            out = StringIO()\n            bar_width = 20\n            prog_bar = mmcv.ProgressBar(10, bar_width=bar_width, file=out)\n            time.sleep(1)\n            reset_string_io(out)\n            prog_bar.update()\n            assert len(out.getvalue()) == 66\n\n            os.environ['COLUMNS'] = '30'\n            reset_string_io(out)\n            prog_bar.update()\n            assert len(out.getvalue()) == 48\n\n            os.environ['COLUMNS'] = '60'\n            reset_string_io(out)\n            prog_bar.update()\n            assert len(out.getvalue()) == 60\n\n\ndef sleep_1s(num):\n    time.sleep(1)\n    return num\n\n\ndef test_track_progress_list():\n    out = StringIO()\n    ret = mmcv.track_progress(sleep_1s, [1, 2, 3], bar_width=3, file=out)\n    assert out.getvalue() == (\n        '[   ] 0/3, elapsed: 0s, ETA:'\n        '\\r[>  ] 1/3, 1.0 task/s, elapsed: 1s, ETA:     2s'\n        '\\r[>> ] 2/3, 1.0 task/s, elapsed: 2s, ETA:     1s'\n        '\\r[>>>] 3/3, 1.0 task/s, elapsed: 3s, ETA:     0s\\n')\n    assert ret == [1, 2, 3]\n\n\ndef test_track_progress_iterator():\n    out = StringIO()\n    ret = mmcv.track_progress(\n        sleep_1s, ((i for i in [1, 2, 3]), 3), bar_width=3, file=out)\n    assert out.getvalue() == (\n        '[   ] 0/3, elapsed: 0s, ETA:'\n        '\\r[>  ] 1/3, 1.0 task/s, elapsed: 1s, ETA:     2s'\n        '\\r[>> ] 2/3, 1.0 task/s, elapsed: 2s, ETA:     1s'\n        '\\r[>>>] 3/3, 1.0 task/s, elapsed: 3s, ETA:     0s\\n')\n    assert ret == [1, 2, 3]\n\n\ndef test_track_iter_progress():\n    out = StringIO()\n    ret = []\n    for num in mmcv.track_iter_progress([1, 2, 3], bar_width=3, file=out):\n        ret.append(sleep_1s(num))\n    assert out.getvalue() == (\n        '[   ] 0/3, elapsed: 0s, ETA:'\n        '\\r[>  ] 1/3, 1.0 task/s, elapsed: 1s, ETA:     2s'\n        '\\r[>> ] 2/3, 1.0 task/s, elapsed: 2s, ETA:     1s'\n        '\\r[>>>] 3/3, 1.0 task/s, elapsed: 3s, ETA:     0s\\n')\n    assert ret == [1, 2, 3]\n\n\ndef test_track_enum_progress():\n    out = StringIO()\n    ret = []\n    count = []\n    for i, num in enumerate(\n            mmcv.track_iter_progress([1, 2, 3], bar_width=3, file=out)):\n        ret.append(sleep_1s(num))\n        count.append(i)\n    assert out.getvalue() == (\n        '[   ] 0/3, elapsed: 0s, ETA:'\n        '\\r[>  ] 1/3, 1.0 task/s, elapsed: 1s, ETA:     2s'\n        '\\r[>> ] 2/3, 1.0 task/s, elapsed: 2s, ETA:     1s'\n        '\\r[>>>] 3/3, 1.0 task/s, elapsed: 3s, ETA:     0s\\n')\n    assert ret == [1, 2, 3]\n    assert count == [0, 1, 2]\n\n\ndef test_track_parallel_progress_list():\n    out = StringIO()\n    results = mmcv.track_parallel_progress(\n        sleep_1s, [1, 2, 3, 4], 2, bar_width=4, file=out)\n    assert out.getvalue() == (\n        '[    ] 0/4, elapsed: 0s, ETA:'\n        '\\r[>   ] 1/4, 1.0 task/s, elapsed: 1s, ETA:     3s'\n        '\\r[>>  ] 2/4, 2.0 task/s, elapsed: 1s, ETA:     1s'\n        '\\r[>>> ] 3/4, 1.5 task/s, elapsed: 2s, ETA:     1s'\n        '\\r[>>>>] 4/4, 2.0 task/s, elapsed: 2s, ETA:     0s\\n')\n    assert results == [1, 2, 3, 4]\n\n\ndef test_track_parallel_progress_iterator():\n    out = StringIO()\n    results = mmcv.track_parallel_progress(\n        sleep_1s, ((i for i in [1, 2, 3, 4]), 4), 2, bar_width=4, file=out)\n    assert out.getvalue() == (\n        '[    ] 0/4, elapsed: 0s, ETA:'\n        '\\r[>   ] 1/4, 1.0 task/s, elapsed: 1s, ETA:     3s'\n        '\\r[>>  ] 2/4, 2.0 task/s, elapsed: 1s, ETA:     1s'\n        '\\r[>>> ] 3/4, 1.5 task/s, elapsed: 2s, ETA:     1s'\n        '\\r[>>>>] 4/4, 2.0 task/s, elapsed: 2s, ETA:     0s\\n')\n    assert results == [1, 2, 3, 4]\n"
  },
  {
    "path": "code/mmcv/tests/test_registry.py",
    "content": "import pytest\n\nimport mmcv\n\n\ndef test_registry():\n    CATS = mmcv.Registry('cat')\n    assert CATS.name == 'cat'\n    assert CATS.module_dict == {}\n    assert len(CATS) == 0\n\n    @CATS.register_module()\n    class BritishShorthair:\n        pass\n\n    assert len(CATS) == 1\n    assert CATS.get('BritishShorthair') is BritishShorthair\n\n    class Munchkin:\n        pass\n\n    CATS.register_module(Munchkin)\n    assert len(CATS) == 2\n    assert CATS.get('Munchkin') is Munchkin\n    assert 'Munchkin' in CATS\n\n    with pytest.raises(KeyError):\n        CATS.register_module(Munchkin)\n\n    CATS.register_module(Munchkin, force=True)\n    assert len(CATS) == 2\n\n    # force=False\n    with pytest.raises(KeyError):\n\n        @CATS.register_module()\n        class BritishShorthair:\n            pass\n\n    @CATS.register_module(force=True)\n    class BritishShorthair:\n        pass\n\n    assert len(CATS) == 2\n\n    assert CATS.get('PersianCat') is None\n    assert 'PersianCat' not in CATS\n\n    @CATS.register_module(name='Siamese')\n    class SiameseCat:\n        pass\n\n    assert CATS.get('Siamese').__name__ == 'SiameseCat'\n\n    class SphynxCat:\n        pass\n\n    CATS.register_module(name='Sphynx', module=SphynxCat)\n    assert CATS.get('Sphynx') is SphynxCat\n\n    repr_str = 'Registry(name=cat, items={'\n    repr_str += (\"'BritishShorthair': <class 'test_registry.test_registry.\"\n                 \"<locals>.BritishShorthair'>, \")\n    repr_str += (\"'Munchkin': <class 'test_registry.test_registry.\"\n                 \"<locals>.Munchkin'>, \")\n    repr_str += (\"'Siamese': <class 'test_registry.test_registry.\"\n                 \"<locals>.SiameseCat'>, \")\n    repr_str += (\"'Sphynx': <class 'test_registry.test_registry.\"\n                 \"<locals>.SphynxCat'>\")\n    repr_str += '})'\n    assert repr(CATS) == repr_str\n\n    # the registered module should be a class\n    with pytest.raises(TypeError):\n        CATS.register_module(0)\n\n    # can only decorate a class\n    with pytest.raises(TypeError):\n\n        @CATS.register_module()\n        def some_method():\n            pass\n\n    # begin: test old APIs\n    with pytest.warns(UserWarning):\n        CATS.register_module(SphynxCat)\n        assert CATS.get('SphynxCat').__name__ == 'SphynxCat'\n\n    with pytest.warns(UserWarning):\n        CATS.register_module(SphynxCat, force=True)\n        assert CATS.get('SphynxCat').__name__ == 'SphynxCat'\n\n    with pytest.warns(UserWarning):\n\n        @CATS.register_module\n        class NewCat:\n            pass\n\n        assert CATS.get('NewCat').__name__ == 'NewCat'\n\n    with pytest.warns(UserWarning):\n        CATS.deprecated_register_module(SphynxCat, force=True)\n        assert CATS.get('SphynxCat').__name__ == 'SphynxCat'\n\n    with pytest.warns(UserWarning):\n\n        @CATS.deprecated_register_module\n        class CuteCat:\n            pass\n\n        assert CATS.get('CuteCat').__name__ == 'CuteCat'\n\n    with pytest.warns(UserWarning):\n\n        @CATS.deprecated_register_module(force=True)\n        class NewCat2:\n            pass\n\n        assert CATS.get('NewCat2').__name__ == 'NewCat2'\n\n    # end: test old APIs\n\n\ndef test_build_from_cfg():\n    BACKBONES = mmcv.Registry('backbone')\n\n    @BACKBONES.register_module()\n    class ResNet:\n\n        def __init__(self, depth, stages=4):\n            self.depth = depth\n            self.stages = stages\n\n    @BACKBONES.register_module()\n    class ResNeXt:\n\n        def __init__(self, depth, stages=4):\n            self.depth = depth\n            self.stages = stages\n\n    cfg = dict(type='ResNet', depth=50)\n    model = mmcv.build_from_cfg(cfg, BACKBONES)\n    assert isinstance(model, ResNet)\n    assert model.depth == 50 and model.stages == 4\n\n    cfg = dict(type='ResNet', depth=50)\n    model = mmcv.build_from_cfg(cfg, BACKBONES, default_args={'stages': 3})\n    assert isinstance(model, ResNet)\n    assert model.depth == 50 and model.stages == 3\n\n    cfg = dict(type='ResNeXt', depth=50, stages=3)\n    model = mmcv.build_from_cfg(cfg, BACKBONES)\n    assert isinstance(model, ResNeXt)\n    assert model.depth == 50 and model.stages == 3\n\n    cfg = dict(type=ResNet, depth=50)\n    model = mmcv.build_from_cfg(cfg, BACKBONES)\n    assert isinstance(model, ResNet)\n    assert model.depth == 50 and model.stages == 4\n\n    # not a registry\n    with pytest.raises(TypeError):\n        cfg = dict(type='VGG')\n        model = mmcv.build_from_cfg(cfg, 'BACKBONES')\n\n    # non-registered class\n    with pytest.raises(KeyError):\n        cfg = dict(type='VGG')\n        model = mmcv.build_from_cfg(cfg, BACKBONES)\n\n    # default_args must be a dict or None\n    with pytest.raises(TypeError):\n        cfg = dict(type='ResNet', depth=50)\n        model = mmcv.build_from_cfg(cfg, BACKBONES, default_args=1)\n\n    # cfg['type'] should be a str or class\n    with pytest.raises(TypeError):\n        cfg = dict(type=1000)\n        model = mmcv.build_from_cfg(cfg, BACKBONES)\n\n    # cfg should contain the key \"type\"\n    with pytest.raises(KeyError):\n        cfg = dict(depth=50, stages=4)\n        model = mmcv.build_from_cfg(cfg, BACKBONES)\n\n    # incorrect registry type\n    with pytest.raises(TypeError):\n        cfg = dict(type='ResNet', depth=50)\n        model = mmcv.build_from_cfg(cfg, 'BACKBONES')\n\n    # incorrect default_args type\n    with pytest.raises(TypeError):\n        cfg = dict(type='ResNet', depth=50)\n        model = mmcv.build_from_cfg(cfg, BACKBONES, default_args=0)\n"
  },
  {
    "path": "code/mmcv/tests/test_runner/test_dist_utils.py",
    "content": "import os\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom mmcv.runner import init_dist\n\n\n@patch('torch.cuda.device_count', return_value=1)\n@patch('torch.cuda.set_device')\n@patch('torch.distributed.init_process_group')\n@patch('subprocess.getoutput', return_value='127.0.0.1')\ndef test_init_dist(mock_getoutput, mock_dist_init, mock_set_device,\n                   mock_device_count):\n    with pytest.raises(ValueError):\n        # launcher must be one of {'pytorch', 'mpi', 'slurm'}\n        init_dist('invaliad_launcher')\n\n    # test initialize with slurm launcher\n    os.environ['SLURM_PROCID'] = '0'\n    os.environ['SLURM_NTASKS'] = '1'\n    os.environ['SLURM_NODELIST'] = '[0]'  # haven't check the correct form\n\n    init_dist('slurm')\n    # no port is specified, use default port 29500\n    assert os.environ['MASTER_PORT'] == '29500'\n    assert os.environ['MASTER_ADDR'] == '127.0.0.1'\n    assert os.environ['WORLD_SIZE'] == '1'\n    assert os.environ['RANK'] == '0'\n    mock_set_device.assert_called_with(0)\n    mock_getoutput.assert_called_with('scontrol show hostname [0] | head -n1')\n    mock_dist_init.assert_called_with(backend='nccl')\n\n    init_dist('slurm', port=29505)\n    # port is specified with argument 'port'\n    assert os.environ['MASTER_PORT'] == '29505'\n    assert os.environ['MASTER_ADDR'] == '127.0.0.1'\n    assert os.environ['WORLD_SIZE'] == '1'\n    assert os.environ['RANK'] == '0'\n    mock_set_device.assert_called_with(0)\n    mock_getoutput.assert_called_with('scontrol show hostname [0] | head -n1')\n    mock_dist_init.assert_called_with(backend='nccl')\n\n    init_dist('slurm')\n    # port is specified by environment variable 'MASTER_PORT'\n    assert os.environ['MASTER_PORT'] == '29505'\n    assert os.environ['MASTER_ADDR'] == '127.0.0.1'\n    assert os.environ['WORLD_SIZE'] == '1'\n    assert os.environ['RANK'] == '0'\n    mock_set_device.assert_called_with(0)\n    mock_getoutput.assert_called_with('scontrol show hostname [0] | head -n1')\n    mock_dist_init.assert_called_with(backend='nccl')\n"
  },
  {
    "path": "code/mmcv/tests/test_runner/test_hooks.py",
    "content": "\"\"\"\nTests the hooks with runners.\n\nCommandLine:\n    pytest tests/test_hooks.py\n    xdoctest tests/test_hooks.py zero\n\n\"\"\"\nimport logging\nimport os.path as osp\nimport shutil\nimport sys\nimport tempfile\nfrom unittest.mock import MagicMock, call\n\nimport pytest\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\n\nfrom mmcv.runner import (EpochBasedRunner, IterTimerHook, MlflowLoggerHook,\n                         PaviLoggerHook, WandbLoggerHook)\nfrom mmcv.runner.hooks.lr_updater import (CosineAnealingLrUpdaterHook,\n                                          CosineRestartLrUpdaterHook,\n                                          CyclicLrUpdaterHook)\nfrom mmcv.runner.hooks.momentum_updater import (\n    CosineAnealingMomentumUpdaterHook, CyclicMomentumUpdaterHook)\n\n\ndef test_pavi_hook():\n    sys.modules['pavi'] = MagicMock()\n\n    loader = DataLoader(torch.ones((5, 2)))\n    runner = _build_demo_runner()\n    hook = PaviLoggerHook(add_graph=False, add_last_ckpt=True)\n    runner.register_hook(hook)\n    runner.run([loader, loader], [('train', 1), ('val', 1)], 1)\n    shutil.rmtree(runner.work_dir)\n\n    assert hasattr(hook, 'writer')\n    hook.writer.add_scalars.assert_called_with('val', {\n        'learning_rate': 0.02,\n        'momentum': 0.95\n    }, 5)\n    hook.writer.add_snapshot_file.assert_called_with(\n        tag=runner.work_dir.split('/')[-1],\n        snapshot_file_path=osp.join(runner.work_dir, 'latest.pth'),\n        iteration=5)\n\n\ndef test_momentum_runner_hook():\n    \"\"\"\n    xdoctest -m tests/test_hooks.py test_momentum_runner_hook\n    \"\"\"\n    sys.modules['pavi'] = MagicMock()\n    loader = DataLoader(torch.ones((10, 2)))\n    runner = _build_demo_runner()\n\n    # add momentum scheduler\n    hook = CyclicMomentumUpdaterHook(\n        by_epoch=False,\n        target_ratio=(0.85 / 0.95, 1),\n        cyclic_times=1,\n        step_ratio_up=0.4)\n    runner.register_hook(hook)\n\n    # add momentum LR scheduler\n    hook = CyclicLrUpdaterHook(\n        by_epoch=False,\n        target_ratio=(10, 1),\n        cyclic_times=1,\n        step_ratio_up=0.4)\n    runner.register_hook(hook)\n    runner.register_hook(IterTimerHook())\n\n    # add pavi hook\n    hook = PaviLoggerHook(interval=1, add_graph=False, add_last_ckpt=True)\n    runner.register_hook(hook)\n    runner.run([loader], [('train', 1)], 1)\n    shutil.rmtree(runner.work_dir)\n\n    # TODO: use a more elegant way to check values\n    assert hasattr(hook, 'writer')\n    calls = [\n        call('train', {\n            'learning_rate': 0.01999999999999999,\n            'momentum': 0.95\n        }, 0),\n        call('train', {\n            'learning_rate': 0.2,\n            'momentum': 0.85\n        }, 4),\n        call('train', {\n            'learning_rate': 0.155,\n            'momentum': 0.875\n        }, 6),\n    ]\n    hook.writer.add_scalars.assert_has_calls(calls, any_order=True)\n\n\ndef test_cosine_runner_hook():\n    \"\"\"\n    xdoctest -m tests/test_hooks.py test_cosine_runner_hook\n    \"\"\"\n    sys.modules['pavi'] = MagicMock()\n    loader = DataLoader(torch.ones((10, 2)))\n    runner = _build_demo_runner()\n\n    # add momentum scheduler\n    hook = CosineAnealingMomentumUpdaterHook(\n        min_momentum_ratio=0.99 / 0.95,\n        by_epoch=False,\n        warmup_iters=2,\n        warmup_ratio=0.9 / 0.95)\n    runner.register_hook(hook)\n\n    # add momentum LR scheduler\n    hook = CosineAnealingLrUpdaterHook(\n        by_epoch=False, min_lr_ratio=0, warmup_iters=2, warmup_ratio=0.9)\n    runner.register_hook(hook)\n    runner.register_hook(IterTimerHook())\n\n    # add pavi hook\n    hook = PaviLoggerHook(interval=1, add_graph=False, add_last_ckpt=True)\n    runner.register_hook(hook)\n    runner.run([loader], [('train', 1)], 1)\n    shutil.rmtree(runner.work_dir)\n\n    # TODO: use a more elegant way to check values\n    assert hasattr(hook, 'writer')\n    calls = [\n        call('train', {\n            'learning_rate': 0.02,\n            'momentum': 0.95\n        }, 0),\n        call('train', {\n            'learning_rate': 0.01,\n            'momentum': 0.97\n        }, 5),\n        call('train', {\n            'learning_rate': 0.0004894348370484647,\n            'momentum': 0.9890211303259032\n        }, 9)\n    ]\n    hook.writer.add_scalars.assert_has_calls(calls, any_order=True)\n\n\ndef test_cosine_restart_lr_update_hook():\n    \"\"\"Test CosineRestartLrUpdaterHook.\"\"\"\n    with pytest.raises(AssertionError):\n        # either `min_lr` or `min_lr_ratio` should be specified\n        CosineRestartLrUpdaterHook(\n            by_epoch=False,\n            periods=[2, 10],\n            restart_weights=[0.5, 0.5],\n            min_lr=0.1,\n            min_lr_ratio=0)\n\n    with pytest.raises(AssertionError):\n        # periods and restart_weights should have the same length\n        CosineRestartLrUpdaterHook(\n            by_epoch=False,\n            periods=[2, 10],\n            restart_weights=[0.5],\n            min_lr_ratio=0)\n\n    with pytest.raises(ValueError):\n        # the last cumulative_periods 7 (out of [5, 7]) should >= 10\n        sys.modules['pavi'] = MagicMock()\n        loader = DataLoader(torch.ones((10, 2)))\n        runner = _build_demo_runner()\n\n        # add cosine restart LR scheduler\n        hook = CosineRestartLrUpdaterHook(\n            by_epoch=False,\n            periods=[5, 2],  # cumulative_periods [5, 7 (5 + 2)]\n            restart_weights=[0.5, 0.5],\n            min_lr=0.0001)\n        runner.register_hook(hook)\n        runner.register_hook(IterTimerHook())\n\n        # add pavi hook\n        hook = PaviLoggerHook(interval=1, add_graph=False, add_last_ckpt=True)\n        runner.register_hook(hook)\n        runner.run([loader], [('train', 1)], 1)\n        shutil.rmtree(runner.work_dir)\n\n    sys.modules['pavi'] = MagicMock()\n    loader = DataLoader(torch.ones((10, 2)))\n    runner = _build_demo_runner()\n\n    # add cosine restart LR scheduler\n    hook = CosineRestartLrUpdaterHook(\n        by_epoch=False,\n        periods=[5, 5],\n        restart_weights=[0.5, 0.5],\n        min_lr_ratio=0)\n    runner.register_hook(hook)\n    runner.register_hook(IterTimerHook())\n\n    # add pavi hook\n    hook = PaviLoggerHook(interval=1, add_graph=False, add_last_ckpt=True)\n    runner.register_hook(hook)\n    runner.run([loader], [('train', 1)], 1)\n    shutil.rmtree(runner.work_dir)\n\n    # TODO: use a more elegant way to check values\n    assert hasattr(hook, 'writer')\n    calls = [\n        call('train', {\n            'learning_rate': 0.01,\n            'momentum': 0.95\n        }, 0),\n        call('train', {\n            'learning_rate': 0.0,\n            'momentum': 0.95\n        }, 5),\n        call('train', {\n            'learning_rate': 0.0009549150281252633,\n            'momentum': 0.95\n        }, 9)\n    ]\n    hook.writer.add_scalars.assert_has_calls(calls, any_order=True)\n\n\n@pytest.mark.parametrize('log_model', (True, False))\ndef test_mlflow_hook(log_model):\n    sys.modules['mlflow'] = MagicMock()\n    sys.modules['mlflow.pytorch'] = MagicMock()\n\n    runner = _build_demo_runner()\n    loader = DataLoader(torch.ones((5, 2)))\n\n    hook = MlflowLoggerHook(exp_name='test', log_model=log_model)\n    runner.register_hook(hook)\n    runner.run([loader, loader], [('train', 1), ('val', 1)], 1)\n    shutil.rmtree(runner.work_dir)\n\n    hook.mlflow.set_experiment.assert_called_with('test')\n    hook.mlflow.log_metrics.assert_called_with(\n        {\n            'learning_rate': 0.02,\n            'momentum': 0.95\n        }, step=5)\n    if log_model:\n        hook.mlflow_pytorch.log_model.assert_called_with(\n            runner.model, 'models')\n    else:\n        assert not hook.mlflow_pytorch.log_model.called\n\n\ndef test_wandb_hook():\n    sys.modules['wandb'] = MagicMock()\n    runner = _build_demo_runner()\n    hook = WandbLoggerHook()\n    loader = DataLoader(torch.ones((5, 2)))\n\n    runner.register_hook(hook)\n    runner.run([loader, loader], [('train', 1), ('val', 1)], 1)\n    shutil.rmtree(runner.work_dir)\n\n    hook.wandb.init.assert_called_with()\n    hook.wandb.log.assert_called_with({\n        'learning_rate': 0.02,\n        'momentum': 0.95\n    },\n                                      step=5)\n    hook.wandb.join.assert_called_with()\n\n\ndef _build_demo_runner():\n\n    class Model(nn.Module):\n\n        def __init__(self):\n            super().__init__()\n            self.linear = nn.Linear(2, 1)\n\n        def forward(self, x):\n            return self.linear(x)\n\n        def train_step(self, x, optimizer, **kwargs):\n            return dict(loss=self(x))\n\n        def val_step(self, x, optimizer, **kwargs):\n            return dict(loss=self(x))\n\n    model = Model()\n\n    optimizer = torch.optim.SGD(model.parameters(), lr=0.02, momentum=0.95)\n\n    log_config = dict(\n        interval=1, hooks=[\n            dict(type='TextLoggerHook'),\n        ])\n\n    tmp_dir = tempfile.mkdtemp()\n    runner = EpochBasedRunner(\n        model=model,\n        work_dir=tmp_dir,\n        optimizer=optimizer,\n        logger=logging.getLogger())\n\n    runner.register_logger_hooks(log_config)\n    return runner\n"
  },
  {
    "path": "code/mmcv/tests/test_runner/test_runner.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport logging\nimport os\nimport os.path as osp\nimport random\nimport string\nimport tempfile\n\nimport pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmcv.parallel import MMDataParallel\nfrom mmcv.runner import EpochBasedRunner\n\n\nclass OldStyleModel(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        self.conv = nn.Conv2d(3, 3, 1)\n\n\nclass Model(OldStyleModel):\n\n    def train_step(self):\n        pass\n\n    def val_step(self):\n        pass\n\n\ndef test_epoch_based_runner():\n\n    with pytest.warns(UserWarning):\n        # batch_processor is deprecated\n        model = OldStyleModel()\n\n        def batch_processor():\n            pass\n\n        _ = EpochBasedRunner(\n            model, batch_processor, logger=logging.getLogger())\n\n    with pytest.raises(TypeError):\n        # batch_processor must be callable\n        model = OldStyleModel()\n        _ = EpochBasedRunner(\n            model, batch_processor=0, logger=logging.getLogger())\n\n    with pytest.raises(TypeError):\n        # optimizer must be a optimizer or a dict of optimizers\n        model = Model()\n        optimizer = 'NotAOptimizer'\n        _ = EpochBasedRunner(\n            model, optimizer=optimizer, logger=logging.getLogger())\n\n    with pytest.raises(TypeError):\n        # optimizer must be a optimizer or a dict of optimizers\n        model = Model()\n        optimizers = dict(optim1=torch.optim.Adam(), optim2='NotAOptimizer')\n        _ = EpochBasedRunner(\n            model, optimizer=optimizers, logger=logging.getLogger())\n\n    with pytest.raises(TypeError):\n        # logger must be a logging.Logger\n        model = Model()\n        _ = EpochBasedRunner(model, logger=None)\n\n    with pytest.raises(TypeError):\n        # meta must be a dict or None\n        model = Model()\n        _ = EpochBasedRunner(model, logger=logging.getLogger(), meta=['list'])\n\n    with pytest.raises(AssertionError):\n        # model must implement the method train_step()\n        model = OldStyleModel()\n        _ = EpochBasedRunner(model, logger=logging.getLogger())\n\n    with pytest.raises(TypeError):\n        # work_dir must be a str or None\n        model = Model()\n        _ = EpochBasedRunner(model, work_dir=1, logger=logging.getLogger())\n\n    with pytest.raises(RuntimeError):\n        # batch_processor and train_step() cannot be both set\n\n        def batch_processor():\n            pass\n\n        model = Model()\n        _ = EpochBasedRunner(\n            model, batch_processor, logger=logging.getLogger())\n\n    # test work_dir\n    model = Model()\n    temp_root = tempfile.gettempdir()\n    dir_name = ''.join(\n        [random.choice(string.ascii_letters) for _ in range(10)])\n    work_dir = osp.join(temp_root, dir_name)\n    _ = EpochBasedRunner(model, work_dir=work_dir, logger=logging.getLogger())\n    assert osp.isdir(work_dir)\n    _ = EpochBasedRunner(model, work_dir=work_dir, logger=logging.getLogger())\n    assert osp.isdir(work_dir)\n    os.removedirs(work_dir)\n\n\ndef test_runner_with_parallel():\n\n    def batch_processor():\n        pass\n\n    model = MMDataParallel(OldStyleModel())\n    _ = EpochBasedRunner(model, batch_processor, logger=logging.getLogger())\n\n    model = MMDataParallel(Model())\n    _ = EpochBasedRunner(model, logger=logging.getLogger())\n\n    with pytest.raises(RuntimeError):\n        # batch_processor and train_step() cannot be both set\n\n        def batch_processor():\n            pass\n\n        model = MMDataParallel(Model())\n        _ = EpochBasedRunner(\n            model, batch_processor, logger=logging.getLogger())\n\n\ndef test_save_checkpoint():\n    model = Model()\n    runner = EpochBasedRunner(model=model, logger=logging.getLogger())\n\n    with tempfile.TemporaryDirectory() as root:\n        runner.save_checkpoint(root)\n\n        latest_path = osp.join(root, 'latest.pth')\n        epoch1_path = osp.join(root, 'epoch_1.pth')\n\n        assert osp.exists(latest_path)\n        assert osp.exists(epoch1_path)\n        assert osp.realpath(latest_path) == osp.realpath(epoch1_path)\n\n        torch.load(latest_path)\n\n\ndef test_build_lr_momentum_hook():\n    model = Model()\n    runner = EpochBasedRunner(model=model, logger=logging.getLogger())\n\n    # test policy that is already title\n    lr_config = dict(\n        policy='CosineAnealing',\n        by_epoch=False,\n        min_lr_ratio=0,\n        warmup_iters=2,\n        warmup_ratio=0.9)\n    runner.register_lr_hook(lr_config)\n    assert len(runner.hooks) == 1\n\n    # test policy that is already title\n    lr_config = dict(\n        policy='Cyclic',\n        by_epoch=False,\n        target_ratio=(10, 1),\n        cyclic_times=1,\n        step_ratio_up=0.4)\n    runner.register_lr_hook(lr_config)\n    assert len(runner.hooks) == 2\n\n    # test policy that is not title\n    lr_config = dict(\n        policy='cyclic',\n        by_epoch=False,\n        target_ratio=(0.85 / 0.95, 1),\n        cyclic_times=1,\n        step_ratio_up=0.4)\n    runner.register_lr_hook(lr_config)\n    assert len(runner.hooks) == 3\n\n    # test policy that is title\n    lr_config = dict(\n        policy='Step',\n        warmup='linear',\n        warmup_iters=500,\n        warmup_ratio=1.0 / 3,\n        step=[8, 11])\n    runner.register_lr_hook(lr_config)\n    assert len(runner.hooks) == 4\n\n    # test policy that is not title\n    lr_config = dict(\n        policy='step',\n        warmup='linear',\n        warmup_iters=500,\n        warmup_ratio=1.0 / 3,\n        step=[8, 11])\n    runner.register_lr_hook(lr_config)\n    assert len(runner.hooks) == 5\n\n    # test policy that is already title\n    mom_config = dict(\n        policy='CosineAnealing',\n        min_momentum_ratio=0.99 / 0.95,\n        by_epoch=False,\n        warmup_iters=2,\n        warmup_ratio=0.9 / 0.95)\n    runner.register_momentum_hook(mom_config)\n    assert len(runner.hooks) == 6\n\n    # test policy that is already title\n    mom_config = dict(\n        policy='Cyclic',\n        by_epoch=False,\n        target_ratio=(0.85 / 0.95, 1),\n        cyclic_times=1,\n        step_ratio_up=0.4)\n    runner.register_momentum_hook(mom_config)\n    assert len(runner.hooks) == 7\n\n    # test policy that is already title\n    mom_config = dict(\n        policy='cyclic',\n        by_epoch=False,\n        target_ratio=(0.85 / 0.95, 1),\n        cyclic_times=1,\n        step_ratio_up=0.4)\n    runner.register_momentum_hook(mom_config)\n    assert len(runner.hooks) == 8\n"
  },
  {
    "path": "code/mmcv/tests/test_timer.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport time\n\nimport pytest\n\nimport mmcv\n\n\ndef test_timer_init():\n    timer = mmcv.Timer(start=False)\n    assert not timer.is_running\n    timer.start()\n    assert timer.is_running\n    timer = mmcv.Timer()\n    assert timer.is_running\n\n\ndef test_timer_run():\n    timer = mmcv.Timer()\n    time.sleep(1)\n    assert abs(timer.since_start() - 1) < 1e-2\n    time.sleep(1)\n    assert abs(timer.since_last_check() - 1) < 1e-2\n    assert abs(timer.since_start() - 2) < 1e-2\n    timer = mmcv.Timer(False)\n    with pytest.raises(mmcv.TimerError):\n        timer.since_start()\n    with pytest.raises(mmcv.TimerError):\n        timer.since_last_check()\n\n\ndef test_timer_context(capsys):\n    with mmcv.Timer():\n        time.sleep(1)\n    out, _ = capsys.readouterr()\n    assert abs(float(out) - 1) < 1e-2\n    with mmcv.Timer(print_tmpl='time: {:.1f}s'):\n        time.sleep(1)\n    out, _ = capsys.readouterr()\n    assert out == 'time: 1.0s\\n'\n"
  },
  {
    "path": "code/mmcv/tests/test_video/test_optflow.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport tempfile\n\nimport numpy as np\nimport pytest\nfrom numpy.testing import assert_array_almost_equal, assert_array_equal\n\nimport mmcv\n\n\ndef test_flowread():\n    data_dir = osp.join(osp.dirname(__file__), '../data')\n    flow_shape = (60, 80, 2)\n\n    # read .flo file\n    flow = mmcv.flowread(osp.join(data_dir, 'optflow.flo'))\n    assert flow.shape == flow_shape\n\n    # pseudo read\n    flow_same = mmcv.flowread(flow)\n    assert_array_equal(flow, flow_same)\n\n    # read quantized flow concatenated vertically\n    flow = mmcv.flowread(\n        osp.join(data_dir, 'optflow_concat0.jpg'), quantize=True, denorm=True)\n    assert flow.shape == flow_shape\n\n    # read quantized flow concatenated horizontally\n    flow = mmcv.flowread(\n        osp.join(data_dir, 'optflow_concat1.jpg'),\n        quantize=True,\n        concat_axis=1,\n        denorm=True)\n    assert flow.shape == flow_shape\n\n    # test exceptions\n    notflow_file = osp.join(data_dir, 'color.jpg')\n    with pytest.raises(TypeError):\n        mmcv.flowread(1)\n    with pytest.raises(IOError):\n        mmcv.flowread(notflow_file)\n    with pytest.raises(IOError):\n        mmcv.flowread(notflow_file, quantize=True)\n    with pytest.raises(ValueError):\n        mmcv.flowread(np.zeros((100, 100, 1)))\n\n\ndef test_flowwrite():\n    flow = np.random.rand(100, 100, 2).astype(np.float32)\n\n    # write to a .flo file\n    _, filename = tempfile.mkstemp()\n    mmcv.flowwrite(flow, filename)\n    flow_from_file = mmcv.flowread(filename)\n    assert_array_equal(flow, flow_from_file)\n    os.remove(filename)\n\n    # write to two .jpg files\n    tmp_filename = osp.join(tempfile.gettempdir(), 'mmcv_test_flow.jpg')\n    for concat_axis in range(2):\n        mmcv.flowwrite(\n            flow, tmp_filename, quantize=True, concat_axis=concat_axis)\n        shape = (200, 100) if concat_axis == 0 else (100, 200)\n        assert osp.isfile(tmp_filename)\n        assert mmcv.imread(tmp_filename, flag='unchanged').shape == shape\n        os.remove(tmp_filename)\n\n    # test exceptions\n    with pytest.raises(AssertionError):\n        mmcv.flowwrite(flow, tmp_filename, quantize=True, concat_axis=2)\n\n\ndef test_quantize_flow():\n    flow = (np.random.rand(10, 8, 2).astype(np.float32) - 0.5) * 15\n    max_val = 5.0\n    dx, dy = mmcv.quantize_flow(flow, max_val=max_val, norm=False)\n    ref = np.zeros_like(flow, dtype=np.uint8)\n    for i in range(ref.shape[0]):\n        for j in range(ref.shape[1]):\n            for k in range(ref.shape[2]):\n                val = flow[i, j, k] + max_val\n                val = min(max(val, 0), 2 * max_val)\n                ref[i, j, k] = min(np.floor(255 * val / (2 * max_val)), 254)\n    assert_array_equal(dx, ref[..., 0])\n    assert_array_equal(dy, ref[..., 1])\n    max_val = 0.5\n    dx, dy = mmcv.quantize_flow(flow, max_val=max_val, norm=True)\n    ref = np.zeros_like(flow, dtype=np.uint8)\n    for i in range(ref.shape[0]):\n        for j in range(ref.shape[1]):\n            for k in range(ref.shape[2]):\n                scale = flow.shape[1] if k == 0 else flow.shape[0]\n                val = flow[i, j, k] / scale + max_val\n                val = min(max(val, 0), 2 * max_val)\n                ref[i, j, k] = min(np.floor(255 * val / (2 * max_val)), 254)\n    assert_array_equal(dx, ref[..., 0])\n    assert_array_equal(dy, ref[..., 1])\n\n\ndef test_dequantize_flow():\n    dx = np.random.randint(256, size=(10, 8), dtype=np.uint8)\n    dy = np.random.randint(256, size=(10, 8), dtype=np.uint8)\n    max_val = 5.0\n    flow = mmcv.dequantize_flow(dx, dy, max_val=max_val, denorm=False)\n    ref = np.zeros_like(flow, dtype=np.float32)\n    for i in range(ref.shape[0]):\n        for j in range(ref.shape[1]):\n            ref[i, j, 0] = float(dx[i, j] + 0.5) * 2 * max_val / 255 - max_val\n            ref[i, j, 1] = float(dy[i, j] + 0.5) * 2 * max_val / 255 - max_val\n    assert_array_almost_equal(flow, ref)\n    max_val = 0.5\n    flow = mmcv.dequantize_flow(dx, dy, max_val=max_val, denorm=True)\n    h, w = dx.shape\n    ref = np.zeros_like(flow, dtype=np.float32)\n    for i in range(ref.shape[0]):\n        for j in range(ref.shape[1]):\n            ref[i, j,\n                0] = (float(dx[i, j] + 0.5) * 2 * max_val / 255 - max_val) * w\n            ref[i, j,\n                1] = (float(dy[i, j] + 0.5) * 2 * max_val / 255 - max_val) * h\n    assert_array_almost_equal(flow, ref)\n\n\ndef test_flow2rgb():\n    flow = np.array([[[0, 0], [0.5, 0.5], [1, 1], [2, 1], [3, np.inf]]],\n                    dtype=np.float32)\n    flow_img = mmcv.flow2rgb(flow)\n    # yapf: disable\n    assert_array_almost_equal(\n        flow_img,\n        np.array([[[1., 1., 1.],\n                   [1., 0.826074731, 0.683772236],\n                   [1., 0.652149462, 0.367544472],\n                   [1., 0.265650552, 5.96046448e-08],\n                   [0., 0., 0.]]],\n                 dtype=np.float32))\n    # yapf: enable\n\n\ndef test_flow_warp():\n\n    def np_flow_warp(flow, img):\n        output = np.zeros_like(img, dtype=img.dtype)\n        height = flow.shape[0]\n        width = flow.shape[1]\n\n        grid = np.indices((height, width)).swapaxes(0, 1).swapaxes(1, 2)\n        dx = grid[:, :, 0] + flow[:, :, 1]\n        dy = grid[:, :, 1] + flow[:, :, 0]\n        sx = np.floor(dx).astype(int)\n        sy = np.floor(dy).astype(int)\n        valid = (sx >= 0) & (sx < height - 1) & (sy >= 0) & (sy < width - 1)\n\n        output[valid, :] = img[dx[valid].round().astype(int),\n                               dy[valid].round().astype(int), :]\n\n        return output\n\n    dim = 500\n    a = np.random.randn(dim, dim, 3) * 10 + 125\n    b = np.random.randn(dim, dim, 2) + 2 + 0.2\n\n    c = mmcv.flow_warp(a, b, interpolate_mode='nearest')\n\n    d = np_flow_warp(b, a)\n\n    simple_a = np.zeros((5, 5, 3))\n    simple_a[2, 2, 0] = 1\n    simple_b = np.ones((5, 5, 2))\n\n    simple_res_c = np.zeros((5, 5, 3))\n    simple_res_c[1, 1, 0] = 1\n\n    res_c = mmcv.flow_warp(simple_a, simple_b, interpolate_mode='bilinear')\n\n    assert_array_equal(c, d)\n    assert_array_equal(res_c, simple_res_c)\n\n\ndef test_make_color_wheel():\n    default_color_wheel = mmcv.make_color_wheel()\n    color_wheel = mmcv.make_color_wheel([2, 2, 2, 2, 2, 2])\n    # yapf: disable\n    assert_array_equal(default_color_wheel, np.array(\n        [[1.       , 0.        , 0.        ],  # noqa\n        [1.        , 0.06666667, 0.        ],  # noqa\n        [1.        , 0.13333334, 0.        ],  # noqa\n        [1.        , 0.2       , 0.        ],  # noqa\n        [1.        , 0.26666668, 0.        ],  # noqa\n        [1.        , 0.33333334, 0.        ],  # noqa\n        [1.        , 0.4       , 0.        ],  # noqa\n        [1.        , 0.46666667, 0.        ],  # noqa\n        [1.        , 0.53333336, 0.        ],  # noqa\n        [1.        , 0.6       , 0.        ],  # noqa\n        [1.        , 0.6666667 , 0.        ],  # noqa\n        [1.        , 0.73333335, 0.        ],  # noqa\n        [1.        , 0.8       , 0.        ],  # noqa\n        [1.        , 0.8666667 , 0.        ],  # noqa\n        [1.        , 0.93333334, 0.        ],  # noqa\n        [1.        , 1.        , 0.        ],  # noqa\n        [0.8333333 , 1.        , 0.        ],  # noqa\n        [0.6666667 , 1.        , 0.        ],  # noqa\n        [0.5       , 1.        , 0.        ],  # noqa\n        [0.33333334, 1.        , 0.        ],  # noqa\n        [0.16666667, 1.        , 0.        ],  # noqa\n        [0.        , 1.        , 0.        ],  # noqa\n        [0.        , 1.        , 0.25      ],  # noqa\n        [0.        , 1.        , 0.5       ],  # noqa\n        [0.        , 1.        , 0.75      ],  # noqa\n        [0.        , 1.        , 1.        ],  # noqa\n        [0.        , 0.90909094, 1.        ],  # noqa\n        [0.        , 0.8181818 , 1.        ],  # noqa\n        [0.        , 0.72727275, 1.        ],  # noqa\n        [0.        , 0.6363636 , 1.        ],  # noqa\n        [0.        , 0.54545456, 1.        ],  # noqa\n        [0.        , 0.45454547, 1.        ],  # noqa\n        [0.        , 0.36363637, 1.        ],  # noqa\n        [0.        , 0.27272728, 1.        ],  # noqa\n        [0.        , 0.18181819, 1.        ],  # noqa\n        [0.        , 0.09090909, 1.        ],  # noqa\n        [0.        , 0.        , 1.        ],  # noqa\n        [0.07692308, 0.        , 1.        ],  # noqa\n        [0.15384616, 0.        , 1.        ],  # noqa\n        [0.23076923, 0.        , 1.        ],  # noqa\n        [0.30769232, 0.        , 1.        ],  # noqa\n        [0.3846154 , 0.        , 1.        ],  # noqa\n        [0.46153846, 0.        , 1.        ],  # noqa\n        [0.53846157, 0.        , 1.        ],  # noqa\n        [0.61538464, 0.        , 1.        ],  # noqa\n        [0.6923077 , 0.        , 1.        ],  # noqa\n        [0.7692308 , 0.        , 1.        ],  # noqa\n        [0.84615386, 0.        , 1.        ],  # noqa\n        [0.9230769 , 0.        , 1.        ],  # noqa\n        [1.        , 0.        , 1.        ],  # noqa\n        [1.        , 0.        , 0.8333333 ],  # noqa\n        [1.        , 0.        , 0.6666667 ],  # noqa\n        [1.        , 0.        , 0.5       ],  # noqa\n        [1.        , 0.        , 0.33333334],  # noqa\n        [1.        , 0.        , 0.16666667]], dtype=np.float32))  # noqa\n\n    assert_array_equal(\n        color_wheel,\n        np.array([[1., 0. , 0. ],  # noqa\n                 [1. , 0.5, 0. ],  # noqa\n                 [1. , 1. , 0. ],  # noqa\n                 [0.5, 1. , 0. ],  # noqa\n                 [0. , 1. , 0. ],  # noqa\n                 [0. , 1. , 0.5],  # noqa\n                 [0. , 1. , 1. ],  # noqa\n                 [0. , 0.5, 1. ],  # noqa\n                 [0. , 0. , 1. ],  # noqa\n                 [0.5, 0. , 1. ],  # noqa\n                 [1. , 0. , 1. ],  # noqa\n                 [1. , 0. , 0.5]], dtype=np.float32))  # noqa\n    # yapf: enable\n"
  },
  {
    "path": "code/mmcv/tests/test_video/test_processing.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport tempfile\n\nimport mmcv\n\n\nclass TestVideoEditor:\n\n    @classmethod\n    def setup_class(cls):\n        cls.video_path = osp.join(osp.dirname(__file__), '../data/test.mp4')\n        cls.num_frames = 168\n\n    def test_cut_concat_video(self):\n        part1_file = osp.join(tempfile.gettempdir(), '.mmcv_test1.mp4')\n        part2_file = osp.join(tempfile.gettempdir(), '.mmcv_test2.mp4')\n        mmcv.cut_video(self.video_path, part1_file, end=3, vcodec='h264')\n        mmcv.cut_video(self.video_path, part2_file, start=3, vcodec='h264')\n        v1 = mmcv.VideoReader(part1_file)\n        v2 = mmcv.VideoReader(part2_file)\n        assert len(v1) == 75\n        assert len(v2) == self.num_frames - 75\n\n        out_file = osp.join(tempfile.gettempdir(), '.mmcv_test.mp4')\n        mmcv.concat_video([part1_file, part2_file], out_file)\n        v = mmcv.VideoReader(out_file)\n        assert len(v) == self.num_frames\n        os.remove(part1_file)\n        os.remove(part2_file)\n        os.remove(out_file)\n\n    def test_resize_video(self):\n        out_file = osp.join(tempfile.gettempdir(), '.mmcv_test.mp4')\n        mmcv.resize_video(\n            self.video_path, out_file, (200, 100), log_level='panic')\n        v = mmcv.VideoReader(out_file)\n        assert v.resolution == (200, 100)\n        os.remove(out_file)\n        mmcv.resize_video(self.video_path, out_file, ratio=2)\n        v = mmcv.VideoReader(out_file)\n        assert v.resolution == (294 * 2, 240 * 2)\n        os.remove(out_file)\n        mmcv.resize_video(self.video_path, out_file, (1000, 480), keep_ar=True)\n        v = mmcv.VideoReader(out_file)\n        assert v.resolution == (294 * 2, 240 * 2)\n        os.remove(out_file)\n        mmcv.resize_video(\n            self.video_path, out_file, ratio=(2, 1.5), keep_ar=True)\n        v = mmcv.VideoReader(out_file)\n        assert v.resolution == (294 * 2, 360)\n        os.remove(out_file)\n"
  },
  {
    "path": "code/mmcv/tests/test_video/test_reader.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport os\nimport os.path as osp\nimport shutil\nimport tempfile\nfrom collections import OrderedDict\n\nimport pytest\n\nimport mmcv\n\n\nclass TestCache:\n\n    def test_init(self):\n        with pytest.raises(ValueError):\n            mmcv.Cache(0)\n        cache = mmcv.Cache(100)\n        assert cache.capacity == 100\n        assert cache.size == 0\n\n    def test_put(self):\n        cache = mmcv.Cache(3)\n        for i in range(1, 4):\n            cache.put(f'k{i}', i)\n            assert cache.size == i\n        assert cache._cache == OrderedDict([('k1', 1), ('k2', 2), ('k3', 3)])\n        cache.put('k4', 4)\n        assert cache.size == 3\n        assert cache._cache == OrderedDict([('k2', 2), ('k3', 3), ('k4', 4)])\n        cache.put('k2', 2)\n        assert cache._cache == OrderedDict([('k2', 2), ('k3', 3), ('k4', 4)])\n\n    def test_get(self):\n        cache = mmcv.Cache(3)\n        assert cache.get('key_none') is None\n        assert cache.get('key_none', 0) == 0\n        cache.put('k1', 1)\n        assert cache.get('k1') == 1\n\n\nclass TestVideoReader:\n\n    @classmethod\n    def setup_class(cls):\n        cls.video_path = osp.join(osp.dirname(__file__), '../data/test.mp4')\n        cls.num_frames = 168\n\n    def test_load(self):\n        v = mmcv.VideoReader(self.video_path)\n        assert v.width == 294\n        assert v.height == 240\n        assert v.fps == 25\n        assert v.frame_cnt == self.num_frames\n        assert len(v) == self.num_frames\n        assert v.opened\n        import cv2\n        assert isinstance(v.vcap, type(cv2.VideoCapture()))\n\n    def test_read(self):\n        v = mmcv.VideoReader(self.video_path)\n        img = v.read()\n        assert int(round(img.mean())) == 94\n        img = v.get_frame(63)\n        assert int(round(img.mean())) == 94\n        img = v[64]\n        assert int(round(img.mean())) == 205\n        img = v[-104]\n        assert int(round(img.mean())) == 205\n        img = v[63]\n        assert int(round(img.mean())) == 94\n        img = v[-105]\n        assert int(round(img.mean())) == 94\n        img = v.read()\n        assert int(round(img.mean())) == 205\n        with pytest.raises(IndexError):\n            v.get_frame(self.num_frames + 1)\n        with pytest.raises(IndexError):\n            v[-self.num_frames - 1]\n\n    def test_slice(self):\n        v = mmcv.VideoReader(self.video_path)\n        imgs = v[-105:-103]\n        assert int(round(imgs[0].mean())) == 94\n        assert int(round(imgs[1].mean())) == 205\n        assert len(imgs) == 2\n        imgs = v[63:65]\n        assert int(round(imgs[0].mean())) == 94\n        assert int(round(imgs[1].mean())) == 205\n        assert len(imgs) == 2\n        imgs = v[64:62:-1]\n        assert int(round(imgs[0].mean())) == 205\n        assert int(round(imgs[1].mean())) == 94\n        assert len(imgs) == 2\n        imgs = v[:5]\n        assert len(imgs) == 5\n        for img in imgs:\n            assert int(round(img.mean())) == 94\n        imgs = v[165:]\n        assert len(imgs) == 3\n        for img in imgs:\n            assert int(round(img.mean())) == 0\n        imgs = v[-3:]\n        assert len(imgs) == 3\n        for img in imgs:\n            assert int(round(img.mean())) == 0\n\n    def test_current_frame(self):\n        v = mmcv.VideoReader(self.video_path)\n        assert v.current_frame() is None\n        v.read()\n        img = v.current_frame()\n        assert int(round(img.mean())) == 94\n\n    def test_position(self):\n        v = mmcv.VideoReader(self.video_path)\n        assert v.position == 0\n        for _ in range(10):\n            v.read()\n        assert v.position == 10\n        v.get_frame(99)\n        assert v.position == 100\n\n    def test_iterator(self):\n        cnt = 0\n        for img in mmcv.VideoReader(self.video_path):\n            cnt += 1\n            assert img.shape == (240, 294, 3)\n        assert cnt == self.num_frames\n\n    def test_with(self):\n        with mmcv.VideoReader(self.video_path) as v:\n            assert v.opened\n        assert not v.opened\n\n    def test_cvt2frames(self):\n        v = mmcv.VideoReader(self.video_path)\n        frame_dir = tempfile.mkdtemp()\n        v.cvt2frames(frame_dir)\n        assert osp.isdir(frame_dir)\n        for i in range(self.num_frames):\n            filename = f'{frame_dir}/{i:06d}.jpg'\n            assert osp.isfile(filename)\n            os.remove(filename)\n\n        v = mmcv.VideoReader(self.video_path)\n        v.cvt2frames(frame_dir, show_progress=False)\n        assert osp.isdir(frame_dir)\n        for i in range(self.num_frames):\n            filename = f'{frame_dir}/{i:06d}.jpg'\n            assert osp.isfile(filename)\n            os.remove(filename)\n\n        v = mmcv.VideoReader(self.video_path)\n        v.cvt2frames(\n            frame_dir,\n            file_start=100,\n            filename_tmpl='{:03d}.JPEG',\n            start=100,\n            max_num=20)\n        assert osp.isdir(frame_dir)\n        for i in range(100, 120):\n            filename = f'{frame_dir}/{i:03d}.JPEG'\n            assert osp.isfile(filename)\n            os.remove(filename)\n        shutil.rmtree(frame_dir)\n\n    def test_frames2video(self):\n        v = mmcv.VideoReader(self.video_path)\n        frame_dir = tempfile.mkdtemp()\n        v.cvt2frames(frame_dir)\n        assert osp.isdir(frame_dir)\n        for i in range(self.num_frames):\n            filename = f'{frame_dir}/{i:06d}.jpg'\n            assert osp.isfile(filename)\n\n        out_filename = osp.join(tempfile.gettempdir(), 'mmcv_test.avi')\n        mmcv.frames2video(frame_dir, out_filename)\n        v = mmcv.VideoReader(out_filename)\n        assert v.fps == 30\n        assert len(v) == self.num_frames\n\n        mmcv.frames2video(\n            frame_dir,\n            out_filename,\n            fps=25,\n            start=10,\n            end=50,\n            show_progress=False)\n        v = mmcv.VideoReader(out_filename)\n        assert v.fps == 25\n        assert len(v) == 40\n\n        for i in range(self.num_frames):\n            filename = f'{frame_dir}/{i:06d}.jpg'\n            os.remove(filename)\n        shutil.rmtree(frame_dir)\n        os.remove(out_filename)\n"
  },
  {
    "path": "code/mmcv/tests/test_visualization.py",
    "content": "# Copyright (c) Open-MMLab. All rights reserved.\nimport numpy as np\nimport pytest\n\nimport mmcv\n\n\ndef test_color():\n    assert mmcv.color_val(mmcv.Color.blue) == (255, 0, 0)\n    assert mmcv.color_val('green') == (0, 255, 0)\n    assert mmcv.color_val((1, 2, 3)) == (1, 2, 3)\n    assert mmcv.color_val(100) == (100, 100, 100)\n    assert mmcv.color_val(np.zeros(3, dtype=np.int)) == (0, 0, 0)\n    with pytest.raises(TypeError):\n        mmcv.color_val([255, 255, 255])\n    with pytest.raises(TypeError):\n        mmcv.color_val(1.0)\n    with pytest.raises(AssertionError):\n        mmcv.color_val((0, 0, 500))\n"
  },
  {
    "path": "code/mmdet/VERSION",
    "content": "2.2.0\n"
  },
  {
    "path": "code/mmdet/__init__.py",
    "content": "from .version import __version__, short_version\n\n__all__ = ['__version__', 'short_version']\n"
  },
  {
    "path": "code/mmdet/apis/__init__.py",
    "content": "from .inference import (async_inference_detector, inference_detector,\n                        init_detector, show_result_pyplot)\nfrom .test import multi_gpu_test, single_gpu_test\nfrom .train import get_root_logger, set_random_seed, train_detector\n\n__all__ = [\n    'get_root_logger', 'set_random_seed', 'train_detector', 'init_detector',\n    'async_inference_detector', 'inference_detector', 'show_result_pyplot',\n    'multi_gpu_test', 'single_gpu_test'\n]\n"
  },
  {
    "path": "code/mmdet/apis/inference.py",
    "content": "import warnings\n\nimport matplotlib.pyplot as plt\nimport mmcv\nimport torch\nfrom mmcv.parallel import collate, scatter\nfrom mmcv.runner import load_checkpoint\n\nfrom mmdet.core import get_classes\nfrom mmdet.datasets.pipelines import Compose\nfrom mmdet.models import build_detector\nfrom mmdet.ops import RoIAlign, RoIPool\n\n\ndef init_detector(config, checkpoint=None, device='cuda:0'):\n    \"\"\"Initialize a detector from config file.\n\n    Args:\n        config (str or :obj:`mmcv.Config`): Config file path or the config\n            object.\n        checkpoint (str, optional): Checkpoint path. If left as None, the model\n            will not load any weights.\n\n    Returns:\n        nn.Module: The constructed detector.\n    \"\"\"\n    if isinstance(config, str):\n        config = mmcv.Config.fromfile(config)\n    elif not isinstance(config, mmcv.Config):\n        raise TypeError('config must be a filename or Config object, '\n                        f'but got {type(config)}')\n    config.model.pretrained = None\n    model = build_detector(config.model, test_cfg=config.test_cfg)\n    if checkpoint is not None:\n        checkpoint = load_checkpoint(model, checkpoint)\n        if 'CLASSES' in checkpoint['meta']:\n            model.CLASSES = checkpoint['meta']['CLASSES']\n        else:\n            warnings.simplefilter('once')\n            warnings.warn('Class names are not saved in the checkpoint\\'s '\n                          'meta data, use COCO classes by default.')\n            model.CLASSES = get_classes('coco')\n    model.cfg = config  # save the config in the model for convenience\n    model.to(device)\n    model.eval()\n    return model\n\n\nclass LoadImage(object):\n    \"\"\"A simple pipeline to load image\"\"\"\n\n    def __call__(self, results):\n        \"\"\"Call function to load images into results\n\n        Args:\n            results (dict): A result dict contains the file name\n                of the image to be read.\n\n        Returns:\n            dict: ``results`` will be returned containing loaded image.\n        \"\"\"\n        if isinstance(results['img'], str):\n            results['filename'] = results['img']\n            results['ori_filename'] = results['img']\n        else:\n            results['filename'] = None\n            results['ori_filename'] = None\n        img = mmcv.imread(results['img'])\n        results['img'] = img\n        results['img_fields'] = ['img']\n        results['img_shape'] = img.shape\n        results['ori_shape'] = img.shape\n        return results\n\n\ndef inference_detector(model, img):\n    \"\"\"Inference image(s) with the detector.\n\n    Args:\n        model (nn.Module): The loaded detector.\n        imgs (str/ndarray or list[str/ndarray]): Either image files or loaded\n            images.\n\n    Returns:\n        If imgs is a str, a generator will be returned, otherwise return the\n        detection results directly.\n    \"\"\"\n    cfg = model.cfg\n    device = next(model.parameters()).device  # model device\n    # build the data pipeline\n    test_pipeline = [LoadImage()] + cfg.data.test.pipeline[1:]\n    test_pipeline = Compose(test_pipeline)\n    # prepare data\n    data = dict(img=img)\n    data = test_pipeline(data)\n    data = collate([data], samples_per_gpu=1)\n    if next(model.parameters()).is_cuda:\n        # scatter to specified GPU\n        data = scatter(data, [device])[0]\n    else:\n        # Use torchvision ops for CPU mode instead\n        for m in model.modules():\n            if isinstance(m, (RoIPool, RoIAlign)):\n                if not m.aligned:\n                    # aligned=False is not implemented on CPU\n                    # set use_torchvision on-the-fly\n                    m.use_torchvision = True\n        warnings.warn('We set use_torchvision=True in CPU mode.')\n        # just get the actual data from DataContainer\n        data['img_metas'] = data['img_metas'][0].data\n\n    # forward the model\n    with torch.no_grad():\n        result = model(return_loss=False, rescale=True, **data)\n    return result\n\n\nasync def async_inference_detector(model, img):\n    \"\"\"Async inference image(s) with the detector.\n\n    Args:\n        model (nn.Module): The loaded detector.\n        imgs (str/ndarray or list[str/ndarray]): Either image files or loaded\n            images.\n\n    Returns:\n        Awaitable detection results.\n    \"\"\"\n    cfg = model.cfg\n    device = next(model.parameters()).device  # model device\n    # build the data pipeline\n    test_pipeline = [LoadImage()] + cfg.data.test.pipeline[1:]\n    test_pipeline = Compose(test_pipeline)\n    # prepare data\n    data = dict(img=img)\n    data = test_pipeline(data)\n    data = scatter(collate([data], samples_per_gpu=1), [device])[0]\n\n    # We don't restore `torch.is_grad_enabled()` value during concurrent\n    # inference since execution can overlap\n    torch.set_grad_enabled(False)\n    result = await model.aforward_test(rescale=True, **data)\n    return result\n\n\ndef show_result_pyplot(model, img, result, score_thr=0.3, fig_size=(15, 10)):\n    \"\"\"Visualize the detection results on the image.\n\n    Args:\n        model (nn.Module): The loaded detector.\n        img (str or np.ndarray): Image filename or loaded image.\n        result (tuple[list] or list): The detection result, can be either\n            (bbox, segm) or just bbox.\n        score_thr (float): The threshold to visualize the bboxes and masks.\n        fig_size (tuple): Figure size of the pyplot figure.\n    \"\"\"\n    if hasattr(model, 'module'):\n        model = model.module\n    img = model.show_result(img, result, score_thr=score_thr, show=False)\n    plt.figure(figsize=fig_size)\n    plt.imshow(mmcv.bgr2rgb(img))\n    plt.show()\n"
  },
  {
    "path": "code/mmdet/apis/test.py",
    "content": "import os.path as osp\nimport pickle\nimport shutil\nimport tempfile\nimport time\nimport pdb\n\nimport mmcv\nimport torch\nimport torch.distributed as dist\nfrom mmcv.runner import get_dist_info\n\nfrom mmdet.core import encode_mask_results, encode_poly_results, tensor2imgs\n\n\ndef single_gpu_test(model,\n                    data_loader,\n                    bbox_head = None,\n                    show=False,\n                    out_dir=None,\n                    show_score_thr=0.3):\n    model.eval()\n    results = []\n    dataset = data_loader.dataset\n    prog_bar = mmcv.ProgressBar(len(dataset))\n    for i, data in enumerate(data_loader):\n        with torch.no_grad():\n            result = model(return_loss=False, rescale=True, show=show, out_dir=out_dir, **data)\n\n        if show or out_dir:\n            img_tensor = data['img'][0]\n            img_metas = data['img_metas'][0].data[0]\n            imgs = tensor2imgs(img_tensor, **img_metas[0]['img_norm_cfg'])\n            assert len(imgs) == len(img_metas)\n\n            for img, img_meta in zip(imgs, img_metas):\n                h, w, _ = img_meta['img_shape']\n                img_show = img[:h, :w, :]\n\n                ori_h, ori_w = img_meta['ori_shape'][:-1]\n                img_show = mmcv.imresize(img_show, (ori_w, ori_h))\n\n                if out_dir:\n                    out_file = osp.join(out_dir, img_meta['ori_filename'])\n                else:\n                    out_file = None\n\n                model.module.show_result(\n                    img_show,\n                    result,\n                    show=show,\n                    out_file=out_file,\n                    score_thr=show_score_thr)\n\n        if bbox_head.type == 'LSHead':\n            if bbox_head.task == 'bbox':\n                extremes = result.pop(-1)\n                result = result[0]\n            elif bbox_head.task == 'segm':\n                bbox_results, poly_results = result\n                img_metas = data['img_metas'][0].data[0]\n                ori_h, ori_w = img_metas[0]['ori_shape'][:-1]\n                encoded_poly_results = encode_poly_results(poly_results, ori_h, ori_w)\n                result = bbox_results, encoded_poly_results\n        elif isinstance(result, tuple):\n            bbox_results, mask_results = result\n            encoded_mask_results = encode_mask_results(mask_results)\n            result = bbox_results, encoded_mask_results\n        results.append(result)\n\n        batch_size = len(data['img_metas'][0].data)\n        for _ in range(batch_size):\n            prog_bar.update()\n    return results\n\n\ndef multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False, bbox_head=None):\n    \"\"\"Test model with multiple gpus.\n\n    This method tests model with multiple gpus and collects the results\n    under two different modes: gpu and cpu modes. By setting 'gpu_collect=True'\n    it encodes results to gpu tensors and use gpu communication for results\n    collection. On cpu mode it saves the results on different gpus to 'tmpdir'\n    and collects them by the rank 0 worker.\n\n    Args:\n        model (nn.Module): Model to be tested.\n        data_loader (nn.Dataloader): Pytorch data loader.\n        tmpdir (str): Path of directory to save the temporary results from\n            different gpus under cpu mode.\n        gpu_collect (bool): Option to use either gpu or cpu to collect results.\n\n    Returns:\n        list: The prediction results.\n    \"\"\"\n    model.eval()\n    results = []\n    dataset = data_loader.dataset\n    rank, world_size = get_dist_info()\n    if rank == 0:\n        prog_bar = mmcv.ProgressBar(len(dataset))\n    time.sleep(2)  # This line can prevent deadlock problem in some cases.\n    for i, data in enumerate(data_loader):\n        with torch.no_grad():\n            result = model(return_loss=False, rescale=True, **data)\n            if bbox_head.type == 'LSHead':\n                if bbox_head.task == 'bbox':\n                    extremes = result.pop(-1)\n                    result = result[0]\n                elif bbox_head.task == 'segm':\n                    bbox_results, poly_results = result\n                    img_metas = data['img_metas'][0].data[0]\n                    ori_h, ori_w = img_metas[0]['ori_shape'][:-1]\n                    encoded_poly_results = encode_poly_results(poly_results, ori_h, ori_w)\n                    result = bbox_results, encoded_poly_results\n            elif isinstance(result, tuple):\n                bbox_results, mask_results = result\n                encoded_mask_results = encode_mask_results(mask_results)\n                result = bbox_results, encoded_mask_results\n        results.append(result)\n\n        if rank == 0:\n            batch_size = len(data['img_metas'][0].data)\n            for _ in range(batch_size * world_size):\n                prog_bar.update()\n\n    # collect results from all ranks\n    if gpu_collect:\n        results = collect_results_gpu(results, len(dataset))\n    else:\n        results = collect_results_cpu(results, len(dataset), tmpdir)\n    return results\n\n\ndef collect_results_cpu(result_part, size, tmpdir=None):\n    rank, world_size = get_dist_info()\n    # create a tmp dir if it is not specified\n    if tmpdir is None:\n        MAX_LEN = 512\n        # 32 is whitespace\n        dir_tensor = torch.full((MAX_LEN, ),\n                                32,\n                                dtype=torch.uint8,\n                                device='cuda')\n        if rank == 0:\n            tmpdir = tempfile.mkdtemp()\n            tmpdir = torch.tensor(\n                bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')\n            dir_tensor[:len(tmpdir)] = tmpdir\n        dist.broadcast(dir_tensor, 0)\n        tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()\n    else:\n        mmcv.mkdir_or_exist(tmpdir)\n    # dump the part result to the dir\n    mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))\n    dist.barrier()\n    # collect all parts\n    if rank != 0:\n        return None\n    else:\n        # load results of all parts from tmp dir\n        part_list = []\n        for i in range(world_size):\n            part_file = osp.join(tmpdir, f'part_{i}.pkl')\n            part_list.append(mmcv.load(part_file))\n        # sort the results\n        ordered_results = []\n        for res in zip(*part_list):\n            ordered_results.extend(list(res))\n        # the dataloader may pad some samples\n        ordered_results = ordered_results[:size]\n        # remove tmp dir\n        shutil.rmtree(tmpdir)\n        return ordered_results\n\n\ndef collect_results_gpu(result_part, size):\n    rank, world_size = get_dist_info()\n    # dump result part to tensor with pickle\n    part_tensor = torch.tensor(\n        bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda')\n    # gather all result part tensor shape\n    shape_tensor = torch.tensor(part_tensor.shape, device='cuda')\n    shape_list = [shape_tensor.clone() for _ in range(world_size)]\n    dist.all_gather(shape_list, shape_tensor)\n    # padding result part tensor to max length\n    shape_max = torch.tensor(shape_list).max()\n    part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')\n    part_send[:shape_tensor[0]] = part_tensor\n    part_recv_list = [\n        part_tensor.new_zeros(shape_max) for _ in range(world_size)\n    ]\n    # gather all result part\n    dist.all_gather(part_recv_list, part_send)\n\n    if rank == 0:\n        part_list = []\n        for recv, shape in zip(part_recv_list, shape_list):\n            part_list.append(\n                pickle.loads(recv[:shape[0]].cpu().numpy().tobytes()))\n        # sort the results\n        ordered_results = []\n        for res in zip(*part_list):\n            ordered_results.extend(list(res))\n        # the dataloader may pad some samples\n        ordered_results = ordered_results[:size]\n        return ordered_results\n"
  },
  {
    "path": "code/mmdet/apis/train.py",
    "content": "import random\n\nimport numpy as np\nimport torch\nfrom mmcv.parallel import MMDataParallel, MMDistributedDataParallel\nfrom mmcv.runner import (DistSamplerSeedHook, EpochBasedRunner, OptimizerHook,\n                         build_optimizer)\n\nfrom mmdet.core import DistEvalHook, EvalHook, Fp16OptimizerHook\nfrom mmdet.datasets import build_dataloader, build_dataset\nfrom mmdet.utils import get_root_logger\n\n\ndef set_random_seed(seed, deterministic=False):\n    \"\"\"Set random seed.\n\n    Args:\n        seed (int): Seed to be used.\n        deterministic (bool): Whether to set the deterministic option for\n            CUDNN backend, i.e., set `torch.backends.cudnn.deterministic`\n            to True and `torch.backends.cudnn.benchmark` to False.\n            Default: False.\n    \"\"\"\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    if deterministic:\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n\n\ndef train_detector(model,\n                   dataset,\n                   cfg,\n                   distributed=False,\n                   validate=False,\n                   timestamp=None,\n                   meta=None):\n    logger = get_root_logger(cfg.log_level)\n\n    # prepare data loaders\n    dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset]\n    if 'imgs_per_gpu' in cfg.data:\n        logger.warning('\"imgs_per_gpu\" is deprecated in MMDet V2.0. '\n                       'Please use \"samples_per_gpu\" instead')\n        if 'samples_per_gpu' in cfg.data:\n            logger.warning(\n                f'Got \"imgs_per_gpu\"={cfg.data.imgs_per_gpu} and '\n                f'\"samples_per_gpu\"={cfg.data.samples_per_gpu}, \"imgs_per_gpu\"'\n                f'={cfg.data.imgs_per_gpu} is used in this experiments')\n        else:\n            logger.warning(\n                'Automatically set \"samples_per_gpu\"=\"imgs_per_gpu\"='\n                f'{cfg.data.imgs_per_gpu} in this experiments')\n        cfg.data.samples_per_gpu = cfg.data.imgs_per_gpu\n\n    data_loaders = [\n        build_dataloader(\n            ds,\n            cfg.data.samples_per_gpu,\n            cfg.data.workers_per_gpu,\n            # cfg.gpus will be ignored if distributed\n            len(cfg.gpu_ids),\n            dist=distributed,\n            seed=cfg.seed) for ds in dataset\n    ]\n\n    # put model on gpus\n    if distributed:\n        find_unused_parameters = cfg.get('find_unused_parameters', False)\n        # Sets the `find_unused_parameters` parameter in\n        # torch.nn.parallel.DistributedDataParallel\n        model = MMDistributedDataParallel(\n            model.cuda(),\n            device_ids=[torch.cuda.current_device()],\n            broadcast_buffers=False,\n            find_unused_parameters=find_unused_parameters)\n    else:\n        model = MMDataParallel(\n            model.cuda(cfg.gpu_ids[0]), device_ids=cfg.gpu_ids)\n\n    # build runner\n    optimizer = build_optimizer(model, cfg.optimizer)\n    runner = EpochBasedRunner(\n        model,\n        optimizer=optimizer,\n        work_dir=cfg.work_dir,\n        logger=logger,\n        meta=meta)\n    # an ugly workaround to make .log and .log.json filenames the same\n    runner.timestamp = timestamp\n\n    # fp16 setting\n    fp16_cfg = cfg.get('fp16', None)\n    if fp16_cfg is not None:\n        optimizer_config = Fp16OptimizerHook(\n            **cfg.optimizer_config, **fp16_cfg, distributed=distributed)\n    elif distributed and 'type' not in cfg.optimizer_config:\n        optimizer_config = OptimizerHook(**cfg.optimizer_config)\n    else:\n        optimizer_config = cfg.optimizer_config\n\n    # register hooks\n    runner.register_training_hooks(cfg.lr_config, optimizer_config,\n                                   cfg.checkpoint_config, cfg.log_config,\n                                   cfg.get('momentum_config', None))\n    if distributed:\n        runner.register_hook(DistSamplerSeedHook())\n\n    # register eval hooks\n    if validate:\n        val_dataset = build_dataset(cfg.data.val, dict(test_mode=True))\n        val_dataloader = build_dataloader(\n            val_dataset,\n            samples_per_gpu=1,\n            workers_per_gpu=cfg.data.workers_per_gpu,\n            dist=distributed,\n            shuffle=False)\n        eval_cfg = cfg.get('evaluation', {})\n        eval_hook = DistEvalHook if distributed else EvalHook\n        runner.register_hook(eval_hook(val_dataloader, bbox_head=cfg.model.bbox_head, **eval_cfg))\n\n    if cfg.resume_from:\n        runner.resume(cfg.resume_from)\n    elif cfg.load_from:\n        runner.load_checkpoint(cfg.load_from)\n    runner.run(data_loaders, cfg.workflow, cfg.total_epochs)\n"
  },
  {
    "path": "code/mmdet/core/__init__.py",
    "content": "from .anchor import *  # noqa: F401, F403\nfrom .bbox import *  # noqa: F401, F403\nfrom .evaluation import *  # noqa: F401, F403\nfrom .fp16 import *  # noqa: F401, F403\nfrom .mask import *  # noqa: F401, F403\nfrom .post_processing import *  # noqa: F401, F403\nfrom .utils import *  # noqa: F401, F403\n"
  },
  {
    "path": "code/mmdet/core/anchor/__init__.py",
    "content": "from .anchor_generator import AnchorGenerator, LegacyAnchorGenerator\nfrom .builder import ANCHOR_GENERATORS, build_anchor_generator\nfrom .point_generator import PointGenerator\nfrom .utils import anchor_inside_flags, calc_region, images_to_levels\n\n__all__ = [\n    'AnchorGenerator', 'LegacyAnchorGenerator', 'anchor_inside_flags',\n    'PointGenerator', 'images_to_levels', 'calc_region',\n    'build_anchor_generator', 'ANCHOR_GENERATORS'\n]\n"
  },
  {
    "path": "code/mmdet/core/anchor/anchor_generator.py",
    "content": "import mmcv\nimport numpy as np\nimport torch\nfrom torch.nn.modules.utils import _pair\n\nfrom .builder import ANCHOR_GENERATORS\n\n\n@ANCHOR_GENERATORS.register_module()\nclass AnchorGenerator(object):\n    \"\"\"Standard anchor generator for 2D anchor-based detectors\n\n    Args:\n        strides (list[int] | list[tuple[int, int]]): Strides of anchors\n            in multiple feature levels.\n        ratios (list[float]): The list of ratios between the height and width\n            of anchors in a single level.\n        scales (list[int] | None): Anchor scales for anchors in a single level.\n            It cannot be set at the same time if `octave_base_scale` and\n            `scales_per_octave` are set.\n        base_sizes (list[int] | None): The basic sizes\n            of anchors in multiple levels.\n            If None is given, strides will be used as base_sizes.\n            (If strides are non square, the shortest stride is taken.)\n        scale_major (bool): Whether to multiply scales first when generating\n            base anchors. If true, the anchors in the same row will have the\n            same scales. By default it is True in V2.0\n        octave_base_scale (int): The base scale of octave.\n        scales_per_octave (int): Number of scales for each octave.\n            `octave_base_scale` and `scales_per_octave` are usually used in\n            retinanet and the `scales` should be None when they are set.\n        centers (list[tuple[float, float]] | None): The centers of the anchor\n            relative to the feature grid center in multiple feature levels.\n            By default it is set to be None and not used. If a list of tuple of\n            float is given, they will be used to shift the centers of anchors.\n        center_offset (float): The offset of center in propotion to anchors'\n            width and height. By default it is 0 in V2.0.\n\n    Examples:\n        >>> from mmdet.core import AnchorGenerator\n        >>> self = AnchorGenerator([16], [1.], [1.], [9])\n        >>> all_anchors = self.grid_anchors([(2, 2)], device='cpu')\n        >>> print(all_anchors)\n        [tensor([[-4.5000, -4.5000,  4.5000,  4.5000],\n                [11.5000, -4.5000, 20.5000,  4.5000],\n                [-4.5000, 11.5000,  4.5000, 20.5000],\n                [11.5000, 11.5000, 20.5000, 20.5000]])]\n        >>> self = AnchorGenerator([16, 32], [1.], [1.], [9, 18])\n        >>> all_anchors = self.grid_anchors([(2, 2), (1, 1)], device='cpu')\n        >>> print(all_anchors)\n        [tensor([[-4.5000, -4.5000,  4.5000,  4.5000],\n                [11.5000, -4.5000, 20.5000,  4.5000],\n                [-4.5000, 11.5000,  4.5000, 20.5000],\n                [11.5000, 11.5000, 20.5000, 20.5000]]), \\\n        tensor([[-9., -9., 9., 9.]])]\n    \"\"\"\n\n    def __init__(self,\n                 strides,\n                 ratios,\n                 scales=None,\n                 base_sizes=None,\n                 scale_major=True,\n                 octave_base_scale=None,\n                 scales_per_octave=None,\n                 centers=None,\n                 center_offset=0.):\n        # check center and center_offset\n        if center_offset != 0:\n            assert centers is None, 'center cannot be set when center_offset' \\\n                f'!=0, {centers} is given.'\n        if not (0 <= center_offset <= 1):\n            raise ValueError('center_offset should be in range [0, 1], '\n                             f'{center_offset} is given.')\n        if centers is not None:\n            assert len(centers) == len(strides), \\\n                'The number of strides should be the same as centers, got ' \\\n                f'{strides} and {centers}'\n\n        # calculate base sizes of anchors\n        self.strides = [_pair(stride) for stride in strides]\n        self.base_sizes = [min(stride) for stride in self.strides\n                           ] if base_sizes is None else base_sizes\n        assert len(self.base_sizes) == len(self.strides), \\\n            'The number of strides should be the same as base sizes, got ' \\\n            f'{self.strides} and {self.base_sizes}'\n\n        # calculate scales of anchors\n        assert ((octave_base_scale is not None\n                and scales_per_octave is not None) ^ (scales is not None)), \\\n            'scales and octave_base_scale with scales_per_octave cannot' \\\n            ' be set at the same time'\n        if scales is not None:\n            self.scales = torch.Tensor(scales)\n        elif octave_base_scale is not None and scales_per_octave is not None:\n            octave_scales = np.array(\n                [2**(i / scales_per_octave) for i in range(scales_per_octave)])\n            scales = octave_scales * octave_base_scale\n            self.scales = torch.Tensor(scales)\n        else:\n            raise ValueError('Either scales or octave_base_scale with '\n                             'scales_per_octave should be set')\n\n        self.octave_base_scale = octave_base_scale\n        self.scales_per_octave = scales_per_octave\n        self.ratios = torch.Tensor(ratios)\n        self.scale_major = scale_major\n        self.centers = centers\n        self.center_offset = center_offset\n        self.base_anchors = self.gen_base_anchors()\n\n    @property\n    def num_base_anchors(self):\n        \"\"\"list[int]: total number of base anchors in a feature grid\"\"\"\n        return [base_anchors.size(0) for base_anchors in self.base_anchors]\n\n    @property\n    def num_levels(self):\n        \"\"\"int: number of feature levels that the generator will be applied\"\"\"\n        return len(self.strides)\n\n    def gen_base_anchors(self):\n        \"\"\"Generate base anchors\n\n        Returns:\n            list(torch.Tensor): Base anchors of a feature grid in multiple\n                feature levels.\n        \"\"\"\n        multi_level_base_anchors = []\n        for i, base_size in enumerate(self.base_sizes):\n            center = None\n            if self.centers is not None:\n                center = self.centers[i]\n            multi_level_base_anchors.append(\n                self.gen_single_level_base_anchors(\n                    base_size,\n                    scales=self.scales,\n                    ratios=self.ratios,\n                    center=center))\n        return multi_level_base_anchors\n\n    def gen_single_level_base_anchors(self,\n                                      base_size,\n                                      scales,\n                                      ratios,\n                                      center=None):\n        \"\"\"Generate base anchors of a single level\n\n        Args:\n            base_size (int | float): Basic size of an anchor.\n            scales (torch.Tensor): Scales of the anchor.\n            ratios (torch.Tensor): The ratio between between the height\n                and width of anchors in a single level.\n            center (tuple[float], optional): The center of the base anchor\n                related to a single feature grid. Defaults to None.\n\n        Returns:\n            torch.Tensor: Anchors in a single-level feature maps\n        \"\"\"\n        w = base_size\n        h = base_size\n        if center is None:\n            x_center = self.center_offset * w\n            y_center = self.center_offset * h\n        else:\n            x_center, y_center = center\n\n        h_ratios = torch.sqrt(ratios)\n        w_ratios = 1 / h_ratios\n        if self.scale_major:\n            ws = (w * w_ratios[:, None] * scales[None, :]).view(-1)\n            hs = (h * h_ratios[:, None] * scales[None, :]).view(-1)\n        else:\n            ws = (w * scales[:, None] * w_ratios[None, :]).view(-1)\n            hs = (h * scales[:, None] * h_ratios[None, :]).view(-1)\n\n        # use float anchor and the anchor's center is aligned with the\n        # pixel center\n        base_anchors = [\n            x_center - 0.5 * ws, y_center - 0.5 * hs, x_center + 0.5 * ws,\n            y_center + 0.5 * hs\n        ]\n        base_anchors = torch.stack(base_anchors, dim=-1)\n\n        return base_anchors\n\n    def _meshgrid(self, x, y, row_major=True):\n        \"\"\"Generate mesh grid of x and y\n\n        Args:\n            x (torch.Tensor): Grids of x dimension.\n            y (torch.Tensor): Grids of y dimension.\n            row_major (bool, optional): Whether to return y grids first.\n                Defaults to True.\n\n        Returns:\n            tuple[torch.Tensor]: The mesh grids of x and y.\n        \"\"\"\n        xx = x.repeat(len(y))\n        yy = y.view(-1, 1).repeat(1, len(x)).view(-1)\n        if row_major:\n            return xx, yy\n        else:\n            return yy, xx\n\n    def grid_anchors(self, featmap_sizes, device='cuda'):\n        \"\"\"Generate grid anchors in multiple feature levels\n\n        Args:\n            featmap_sizes (list[tuple]): List of feature map sizes in\n                multiple feature levels.\n            device (str): Device where the anchors will be put on.\n\n        Return:\n            list[torch.Tensor]: Anchors in multiple feature levels.\n                The sizes of each tensor should be [N, 4], where\n                N = width * height * num_base_anchors, width and height\n                are the sizes of the corresponding feature lavel,\n                num_base_anchors is the number of anchors for that level.\n        \"\"\"\n        assert self.num_levels == len(featmap_sizes)\n        multi_level_anchors = []\n        for i in range(self.num_levels):\n            anchors = self.single_level_grid_anchors(\n                self.base_anchors[i].to(device),\n                featmap_sizes[i],\n                self.strides[i],\n                device=device)\n            multi_level_anchors.append(anchors)\n        return multi_level_anchors\n\n    def single_level_grid_anchors(self,\n                                  base_anchors,\n                                  featmap_size,\n                                  stride=(16, 16),\n                                  device='cuda'):\n        \"\"\"Generate grid anchors of a single level.\n\n        Note:\n            This function is usually called by method ``self.grid_anchors``.\n\n        Args:\n            base_anchors (torch.Tensor): The base anchors of a feature grid.\n            featmap_size (tuple[int]): Size of the feature maps.\n            stride (tuple[int], optional): Stride of the feature map.\n                Defaults to (16, 16).\n            device (str, optional): Device the tensor will be put on.\n                Defaults to 'cuda'.\n\n        Returns:\n            torch.Tensor: Anchors in the overall feature maps.\n        \"\"\"\n        feat_h, feat_w = featmap_size\n        shift_x = torch.arange(0, feat_w, device=device) * stride[0]\n        shift_y = torch.arange(0, feat_h, device=device) * stride[1]\n        shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)\n        shifts = torch.stack([shift_xx, shift_yy, shift_xx, shift_yy], dim=-1)\n        shifts = shifts.type_as(base_anchors)\n        # first feat_w elements correspond to the first row of shifts\n        # add A anchors (1, A, 4) to K shifts (K, 1, 4) to get\n        # shifted anchors (K, A, 4), reshape to (K*A, 4)\n\n        all_anchors = base_anchors[None, :, :] + shifts[:, None, :]\n        all_anchors = all_anchors.view(-1, 4)\n        # first A rows correspond to A anchors of (0, 0) in feature map,\n        # then (0, 1), (0, 2), ...\n        return all_anchors\n\n    def valid_flags(self, featmap_sizes, pad_shape, device='cuda'):\n        \"\"\"Generate valid flags of anchors in multiple feature levels\n\n        Args:\n            featmap_sizes (list(tuple)): List of feature map sizes in\n                multiple feature levels.\n            pad_shape (tuple): The padded shape of the image.\n            device (str): Device where the anchors will be put on.\n\n        Return:\n            list(torch.Tensor): Valid flags of anchors in multiple levels.\n        \"\"\"\n        assert self.num_levels == len(featmap_sizes)\n        multi_level_flags = []\n        for i in range(self.num_levels):\n            anchor_stride = self.strides[i]\n            feat_h, feat_w = featmap_sizes[i]\n            h, w = pad_shape[:2]\n            valid_feat_h = min(int(np.ceil(h / anchor_stride[0])), feat_h)\n            valid_feat_w = min(int(np.ceil(w / anchor_stride[1])), feat_w)\n            flags = self.single_level_valid_flags((feat_h, feat_w),\n                                                  (valid_feat_h, valid_feat_w),\n                                                  self.num_base_anchors[i],\n                                                  device=device)\n            multi_level_flags.append(flags)\n        return multi_level_flags\n\n    def single_level_valid_flags(self,\n                                 featmap_size,\n                                 valid_size,\n                                 num_base_anchors,\n                                 device='cuda'):\n        \"\"\"Generate the valid flags of anchor in a single feature map\n\n        Args:\n            featmap_size (tuple[int]): The size of feature maps.\n            valid_size (tuple[int]): The valid size of the feature maps.\n            num_base_anchors (int): The number of base anchors.\n            device (str, optional): Device where the flags will be put on.\n                Defaults to 'cuda'.\n\n        Returns:\n            torch.Tensor: The valid flags of each anchor in a single level\n                feature map.\n        \"\"\"\n        feat_h, feat_w = featmap_size\n        valid_h, valid_w = valid_size\n        assert valid_h <= feat_h and valid_w <= feat_w\n        valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device)\n        valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device)\n        valid_x[:valid_w] = 1\n        valid_y[:valid_h] = 1\n        valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)\n        valid = valid_xx & valid_yy\n        valid = valid[:, None].expand(valid.size(0),\n                                      num_base_anchors).contiguous().view(-1)\n        return valid\n\n    def __repr__(self):\n        \"\"\"str: a string that describes the module\"\"\"\n        indent_str = '    '\n        repr_str = self.__class__.__name__ + '(\\n'\n        repr_str += f'{indent_str}strides={self.strides},\\n'\n        repr_str += f'{indent_str}ratios={self.ratios},\\n'\n        repr_str += f'{indent_str}scales={self.scales},\\n'\n        repr_str += f'{indent_str}base_sizes={self.base_sizes},\\n'\n        repr_str += f'{indent_str}scale_major={self.scale_major},\\n'\n        repr_str += f'{indent_str}octave_base_scale='\n        repr_str += f'{self.octave_base_scale},\\n'\n        repr_str += f'{indent_str}scales_per_octave='\n        repr_str += f'{self.scales_per_octave},\\n'\n        repr_str += f'{indent_str}num_levels={self.num_levels}\\n'\n        repr_str += f'{indent_str}centers={self.centers},\\n'\n        repr_str += f'{indent_str}center_offset={self.center_offset})'\n        return repr_str\n\n\n@ANCHOR_GENERATORS.register_module()\nclass SSDAnchorGenerator(AnchorGenerator):\n    \"\"\"Anchor generator for SSD\n\n    Args:\n        strides (list[int]  | list[tuple[int, int]]): Strides of anchors\n            in multiple feature levels.\n        ratios (list[float]): The list of ratios between the height and width\n            of anchors in a single level.\n        basesize_ratio_range (tuple(float)): Ratio range of anchors.\n        input_size (int): Size of feature map, 300 for SSD300,\n            512 for SSD512.\n        scale_major (bool): Whether to multiply scales first when generating\n            base anchors. If true, the anchors in the same row will have the\n            same scales. It is always set to be False in SSD.\n    \"\"\"\n\n    def __init__(self,\n                 strides,\n                 ratios,\n                 basesize_ratio_range,\n                 input_size=300,\n                 scale_major=True):\n        assert len(strides) == len(ratios)\n        assert mmcv.is_tuple_of(basesize_ratio_range, float)\n\n        self.strides = [_pair(stride) for stride in strides]\n        self.input_size = input_size\n        self.centers = [(stride[0] / 2., stride[1] / 2.)\n                        for stride in self.strides]\n        self.basesize_ratio_range = basesize_ratio_range\n\n        # calculate anchor ratios and sizes\n        min_ratio, max_ratio = basesize_ratio_range\n        min_ratio = int(min_ratio * 100)\n        max_ratio = int(max_ratio * 100)\n        step = int(np.floor(max_ratio - min_ratio) / (self.num_levels - 2))\n        min_sizes = []\n        max_sizes = []\n        for ratio in range(int(min_ratio), int(max_ratio) + 1, step):\n            min_sizes.append(int(self.input_size * ratio / 100))\n            max_sizes.append(int(self.input_size * (ratio + step) / 100))\n        if self.input_size == 300:\n            if basesize_ratio_range[0] == 0.15:  # SSD300 COCO\n                min_sizes.insert(0, int(self.input_size * 7 / 100))\n                max_sizes.insert(0, int(self.input_size * 15 / 100))\n            elif basesize_ratio_range[0] == 0.2:  # SSD300 VOC\n                min_sizes.insert(0, int(self.input_size * 10 / 100))\n                max_sizes.insert(0, int(self.input_size * 20 / 100))\n            else:\n                raise ValueError(\n                    'basesize_ratio_range[0] should be either 0.15'\n                    'or 0.2 when input_size is 300, got '\n                    f'{basesize_ratio_range[0]}.')\n        elif self.input_size == 512:\n            if basesize_ratio_range[0] == 0.1:  # SSD512 COCO\n                min_sizes.insert(0, int(self.input_size * 4 / 100))\n                max_sizes.insert(0, int(self.input_size * 10 / 100))\n            elif basesize_ratio_range[0] == 0.15:  # SSD512 VOC\n                min_sizes.insert(0, int(self.input_size * 7 / 100))\n                max_sizes.insert(0, int(self.input_size * 15 / 100))\n            else:\n                raise ValueError('basesize_ratio_range[0] should be either 0.1'\n                                 'or 0.15 when input_size is 512, got'\n                                 ' {basesize_ratio_range[0]}.')\n        else:\n            raise ValueError('Only support 300 or 512 in SSDAnchorGenerator'\n                             f', got {self.input_size}.')\n\n        anchor_ratios = []\n        anchor_scales = []\n        for k in range(len(self.strides)):\n            scales = [1., np.sqrt(max_sizes[k] / min_sizes[k])]\n            anchor_ratio = [1.]\n            for r in ratios[k]:\n                anchor_ratio += [1 / r, r]  # 4 or 6 ratio\n            anchor_ratios.append(torch.Tensor(anchor_ratio))\n            anchor_scales.append(torch.Tensor(scales))\n\n        self.base_sizes = min_sizes\n        self.scales = anchor_scales\n        self.ratios = anchor_ratios\n        self.scale_major = scale_major\n        self.center_offset = 0\n        self.base_anchors = self.gen_base_anchors()\n\n    def gen_base_anchors(self):\n        \"\"\"Generate base anchors\n\n        Returns:\n            list(torch.Tensor): Base anchors of a feature grid in multiple\n                feature levels.\n        \"\"\"\n        multi_level_base_anchors = []\n        for i, base_size in enumerate(self.base_sizes):\n            base_anchors = self.gen_single_level_base_anchors(\n                base_size,\n                scales=self.scales[i],\n                ratios=self.ratios[i],\n                center=self.centers[i])\n            indices = list(range(len(self.ratios[i])))\n            indices.insert(1, len(indices))\n            base_anchors = torch.index_select(base_anchors, 0,\n                                              torch.LongTensor(indices))\n            multi_level_base_anchors.append(base_anchors)\n        return multi_level_base_anchors\n\n    def __repr__(self):\n        \"\"\"str: a string that describes the module\"\"\"\n        indent_str = '    '\n        repr_str = self.__class__.__name__ + '(\\n'\n        repr_str += f'{indent_str}strides={self.strides},\\n'\n        repr_str += f'{indent_str}scales={self.scales},\\n'\n        repr_str += f'{indent_str}scale_major={self.scale_major},\\n'\n        repr_str += f'{indent_str}input_size={self.input_size},\\n'\n        repr_str += f'{indent_str}scales={self.scales},\\n'\n        repr_str += f'{indent_str}ratios={self.ratios},\\n'\n        repr_str += f'{indent_str}num_levels={self.num_levels},\\n'\n        repr_str += f'{indent_str}base_sizes={self.base_sizes},\\n'\n        repr_str += f'{indent_str}basesize_ratio_range='\n        repr_str += f'{self.basesize_ratio_range})'\n        return repr_str\n\n\n@ANCHOR_GENERATORS.register_module()\nclass LegacyAnchorGenerator(AnchorGenerator):\n    \"\"\"Legacy anchor generator used in MMDetection V1.x\n\n    Difference to the V2.0 anchor generator:\n\n    1. The center offset of V1.x anchors are set to be 0.5 rather than 0.\n    2. The width/height are minused by 1 when calculating the anchors' centers\n       and corners to meet the V1.x coordinate system.\n    3. The anchors' corners are quantized.\n\n    Args:\n        strides (list[int] | list[tuple[int]]): Strides of anchors\n            in multiple feature levels.\n        ratios (list[float]): The list of ratios between the height and width\n            of anchors in a single level.\n        scales (list[int] | None): Anchor scales for anchors in a single level.\n            It cannot be set at the same time if `octave_base_scale` and\n            `scales_per_octave` are set.\n        base_sizes (list[int]): The basic sizes of anchors in multiple levels.\n            If None is given, strides will be used to generate base_sizes.\n        scale_major (bool): Whether to multiply scales first when generating\n            base anchors. If true, the anchors in the same row will have the\n            same scales. By default it is True in V2.0\n        octave_base_scale (int): The base scale of octave.\n        scales_per_octave (int): Number of scales for each octave.\n            `octave_base_scale` and `scales_per_octave` are usually used in\n            retinanet and the `scales` should be None when they are set.\n        centers (list[tuple[float, float]] | None): The centers of the anchor\n            relative to the feature grid center in multiple feature levels.\n            By default it is set to be None and not used. It a list of float\n            is given, this list will be used to shift the centers of anchors.\n        center_offset (float): The offset of center in propotion to anchors'\n            width and height. By default it is 0.5 in V2.0 but it should be 0.5\n            in v1.x models.\n\n    Examples:\n        >>> from mmdet.core import LegacyAnchorGenerator\n        >>> self = LegacyAnchorGenerator(\n        >>>     [16], [1.], [1.], [9], center_offset=0.5)\n        >>> all_anchors = self.grid_anchors(((2, 2),), device='cpu')\n        >>> print(all_anchors)\n        [tensor([[ 0.,  0.,  8.,  8.],\n                [16.,  0., 24.,  8.],\n                [ 0., 16.,  8., 24.],\n                [16., 16., 24., 24.]])]\n    \"\"\"\n\n    def gen_single_level_base_anchors(self,\n                                      base_size,\n                                      scales,\n                                      ratios,\n                                      center=None):\n        \"\"\"Generate base anchors of a single level\n\n        Note:\n            The width/height of anchors are minused by 1 when calculating\n                the centers and corners to meet the V1.x coordinate system.\n\n        Args:\n            base_size (int | float): Basic size of an anchor.\n            scales (torch.Tensor): Scales of the anchor.\n            ratios (torch.Tensor): The ratio between between the height.\n                and width of anchors in a single level.\n            center (tuple[float], optional): The center of the base anchor\n                related to a single feature grid. Defaults to None.\n\n        Returns:\n            torch.Tensor: Anchors in a single-level feature map.\n        \"\"\"\n        w = base_size\n        h = base_size\n        if center is None:\n            x_center = self.center_offset * (w - 1)\n            y_center = self.center_offset * (h - 1)\n        else:\n            x_center, y_center = center\n\n        h_ratios = torch.sqrt(ratios)\n        w_ratios = 1 / h_ratios\n        if self.scale_major:\n            ws = (w * w_ratios[:, None] * scales[None, :]).view(-1)\n            hs = (h * h_ratios[:, None] * scales[None, :]).view(-1)\n        else:\n            ws = (w * scales[:, None] * w_ratios[None, :]).view(-1)\n            hs = (h * scales[:, None] * h_ratios[None, :]).view(-1)\n\n        # use float anchor and the anchor's center is aligned with the\n        # pixel center\n        base_anchors = [\n            x_center - 0.5 * (ws - 1), y_center - 0.5 * (hs - 1),\n            x_center + 0.5 * (ws - 1), y_center + 0.5 * (hs - 1)\n        ]\n        base_anchors = torch.stack(base_anchors, dim=-1).round()\n\n        return base_anchors\n\n\n@ANCHOR_GENERATORS.register_module()\nclass LegacySSDAnchorGenerator(SSDAnchorGenerator, LegacyAnchorGenerator):\n    \"\"\"Legacy anchor generator used in MMDetection V1.x\n\n    The difference between `LegacySSDAnchorGenerator` and `SSDAnchorGenerator`\n    can be found in `LegacyAnchorGenerator`.\n    \"\"\"\n\n    def __init__(self,\n                 strides,\n                 ratios,\n                 basesize_ratio_range,\n                 input_size=300,\n                 scale_major=True):\n        super(LegacySSDAnchorGenerator,\n              self).__init__(strides, ratios, basesize_ratio_range, input_size,\n                             scale_major)\n        self.centers = [((stride - 1) / 2., (stride - 1) / 2.)\n                        for stride in strides]\n        self.base_anchors = self.gen_base_anchors()\n"
  },
  {
    "path": "code/mmdet/core/anchor/builder.py",
    "content": "from mmcv.utils import Registry, build_from_cfg\n\nANCHOR_GENERATORS = Registry('Anchor generator')\n\n\ndef build_anchor_generator(cfg, default_args=None):\n    return build_from_cfg(cfg, ANCHOR_GENERATORS, default_args)\n"
  },
  {
    "path": "code/mmdet/core/anchor/point_generator.py",
    "content": "import torch\n\nfrom .builder import ANCHOR_GENERATORS\n\n\n@ANCHOR_GENERATORS.register_module()\nclass PointGenerator(object):\n\n    def _meshgrid(self, x, y, row_major=True):\n        xx = x.repeat(len(y))\n        yy = y.view(-1, 1).repeat(1, len(x)).view(-1)\n        if row_major:\n            return xx, yy\n        else:\n            return yy, xx\n\n    def grid_points(self, featmap_size, stride=16, device='cuda'):\n        feat_h, feat_w = featmap_size\n        shift_x = torch.arange(0., feat_w, device=device) * stride\n        shift_y = torch.arange(0., feat_h, device=device) * stride\n        shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)\n        stride = shift_x.new_full((shift_xx.shape[0], ), stride)\n        shifts = torch.stack([shift_xx, shift_yy, stride], dim=-1)\n        all_points = shifts.to(device)\n        return all_points\n\n    def valid_flags(self, featmap_size, valid_size, device='cuda'):\n        feat_h, feat_w = featmap_size\n        valid_h, valid_w = valid_size\n        assert valid_h <= feat_h and valid_w <= feat_w\n        valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device)\n        valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device)\n        valid_x[:valid_w] = 1\n        valid_y[:valid_h] = 1\n        valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)\n        valid = valid_xx & valid_yy\n        return valid\n"
  },
  {
    "path": "code/mmdet/core/anchor/utils.py",
    "content": "import torch\n\n\ndef images_to_levels(target, num_levels):\n    \"\"\"Convert targets by image to targets by feature level.\n\n    [target_img0, target_img1] -> [target_level0, target_level1, ...]\n    \"\"\"\n    target = torch.stack(target, 0)\n    level_targets = []\n    start = 0\n    for n in num_levels:\n        end = start + n\n        # level_targets.append(target[:, start:end].squeeze(0))\n        level_targets.append(target[:, start:end])\n        start = end\n    return level_targets\n\n\ndef anchor_inside_flags(flat_anchors,\n                        valid_flags,\n                        img_shape,\n                        allowed_border=0):\n    \"\"\"Check whether the anchors are inside the border\n\n    Args:\n        flat_anchors (torch.Tensor): Flatten anchors, shape (n, 4).\n        valid_flags (torch.Tensor): An existing valid flags of anchors.\n        img_shape (tuple(int)): Shape of current image.\n        allowed_border (int, optional): The border to allow the valid anchor.\n            Defaults to 0.\n\n    Returns:\n        torch.Tensor: Flags indicating whether the anchors are inside a\n            valid range.\n    \"\"\"\n    img_h, img_w = img_shape[:2]\n    if allowed_border >= 0:\n        inside_flags = valid_flags & \\\n            (flat_anchors[:, 0] >= -allowed_border) & \\\n            (flat_anchors[:, 1] >= -allowed_border) & \\\n            (flat_anchors[:, 2] < img_w + allowed_border) & \\\n            (flat_anchors[:, 3] < img_h + allowed_border)\n    else:\n        inside_flags = valid_flags\n    return inside_flags\n\n\ndef calc_region(bbox, ratio, featmap_size=None):\n    \"\"\"Calculate a proportional bbox region.\n\n    The bbox center are fixed and the new h' and w' is h * ratio and w * ratio.\n\n    Args:\n        bbox (Tensor): Bboxes to calculate regions, shape (n, 4).\n        ratio (float): Ratio of the output region.\n        featmap_size (tuple): Feature map size used for clipping the boundary.\n\n    Returns:\n        tuple: x1, y1, x2, y2\n    \"\"\"\n    x1 = torch.round((1 - ratio) * bbox[0] + ratio * bbox[2]).long()\n    y1 = torch.round((1 - ratio) * bbox[1] + ratio * bbox[3]).long()\n    x2 = torch.round(ratio * bbox[0] + (1 - ratio) * bbox[2]).long()\n    y2 = torch.round(ratio * bbox[1] + (1 - ratio) * bbox[3]).long()\n    if featmap_size is not None:\n        x1 = x1.clamp(min=0, max=featmap_size[1])\n        y1 = y1.clamp(min=0, max=featmap_size[0])\n        x2 = x2.clamp(min=0, max=featmap_size[1])\n        y2 = y2.clamp(min=0, max=featmap_size[0])\n    return (x1, y1, x2, y2)\n"
  },
  {
    "path": "code/mmdet/core/bbox/__init__.py",
    "content": "from .assigners import (AssignResult, BaseAssigner, CenterRegionAssigner,\n                        MaxIoUAssigner)\nfrom .builder import build_assigner, build_bbox_coder, build_sampler\nfrom .coder import (BaseBBoxCoder, DeltaXYWHBBoxCoder, PseudoBBoxCoder,\n                    TBLRBBoxCoder)\nfrom .iou_calculators import BboxOverlaps2D, bbox_overlaps\nfrom .samplers import (BaseSampler, CombinedSampler,\n                       InstanceBalancedPosSampler, IoUBalancedNegSampler,\n                       PseudoSampler, RandomSampler, SamplingResult)\nfrom .transforms import (bbox2distance, bbox2result, bbox2roi, bbox_flip,\n                         bbox_mapping, bbox_mapping_back, distance2bbox,\n                         roi2bbox, bbox_extreme2result, bbox_poly2result,\n                         instance_mapping_back)\n\n__all__ = [\n    'bbox_overlaps', 'BboxOverlaps2D', 'BaseAssigner', 'MaxIoUAssigner',\n    'AssignResult', 'BaseSampler', 'PseudoSampler', 'RandomSampler',\n    'InstanceBalancedPosSampler', 'IoUBalancedNegSampler', 'CombinedSampler',\n    'SamplingResult', 'build_assigner', 'build_sampler', 'bbox_flip',\n    'bbox_mapping', 'bbox_mapping_back', 'bbox2roi', 'roi2bbox', 'bbox2result',\n    'distance2bbox', 'bbox2distance', 'build_bbox_coder', 'BaseBBoxCoder',\n    'PseudoBBoxCoder', 'DeltaXYWHBBoxCoder', 'TBLRBBoxCoder',\n    'CenterRegionAssigner', 'bbox_extreme2result', 'bbox_poly2result', 'instance_mapping_back'\n]\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/__init__.py",
    "content": "from .approx_max_iou_assigner import ApproxMaxIoUAssigner\nfrom .assign_result import AssignResult\nfrom .atss_assigner import ATSSAssigner\nfrom .base_assigner import BaseAssigner\nfrom .center_region_assigner import CenterRegionAssigner\nfrom .max_iou_assigner import MaxIoUAssigner\nfrom .point_assigner import PointAssigner\nfrom .point_assigner_v2 import PointAssignerV2\nfrom .point_ct_assigner import PointCTAssigner\nfrom .point_hm_assigner import PointHMAssigner\nfrom .centroid_assigner import CentroidAssigner\nfrom .fcos_assigner import FCOSAssigner\n\n__all__ = [\n    'BaseAssigner', 'MaxIoUAssigner', 'ApproxMaxIoUAssigner', 'AssignResult',\n    'PointAssigner', 'PointAssignerV2', 'ATSSAssigner', 'CenterRegionAssigner', 'PointHMAssigner',\n    'PointCTAssigner', 'CentroidAssigner', 'FCOSAssigner'\n]\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/approx_max_iou_assigner.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom ..iou_calculators import build_iou_calculator\nfrom .max_iou_assigner import MaxIoUAssigner\n\n\n@BBOX_ASSIGNERS.register_module()\nclass ApproxMaxIoUAssigner(MaxIoUAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each bbox.\n\n    Each proposals will be assigned with an integer indicating the ground truth\n     index. (semi-positive index: gt label (0-based), -1: background)\n\n    - -1: negative sample, no assigned gt\n    - semi-positive integer: positive sample, index (0-based) of assigned gt\n\n    Args:\n        pos_iou_thr (float): IoU threshold for positive bboxes.\n        neg_iou_thr (float or tuple): IoU threshold for negative bboxes.\n        min_pos_iou (float): Minimum iou for a bbox to be considered as a\n            positive bbox. Positive samples can have smaller IoU than\n            pos_iou_thr due to the 4th step (assign max IoU sample to each gt).\n        gt_max_assign_all (bool): Whether to assign all bboxes with the same\n            highest overlap with some gt to that gt.\n        ignore_iof_thr (float): IoF threshold for ignoring bboxes (if\n            `gt_bboxes_ignore` is specified). Negative values mean not\n            ignoring any bboxes.\n        ignore_wrt_candidates (bool): Whether to compute the iof between\n            `bboxes` and `gt_bboxes_ignore`, or the contrary.\n        match_low_quality (bool): Whether to allow quality matches. This is\n            usually allowed for RPN and single stage detectors, but not allowed\n            in the second stage.\n        gpu_assign_thr (int): The upper bound of the number of GT for GPU\n            assign. When the number of gt is above this threshold, will assign\n            on CPU device. Negative values mean not assign on CPU.\n    \"\"\"\n\n    def __init__(self,\n                 pos_iou_thr,\n                 neg_iou_thr,\n                 min_pos_iou=.0,\n                 gt_max_assign_all=True,\n                 ignore_iof_thr=-1,\n                 ignore_wrt_candidates=True,\n                 match_low_quality=True,\n                 gpu_assign_thr=-1,\n                 iou_calculator=dict(type='BboxOverlaps2D')):\n        self.pos_iou_thr = pos_iou_thr\n        self.neg_iou_thr = neg_iou_thr\n        self.min_pos_iou = min_pos_iou\n        self.gt_max_assign_all = gt_max_assign_all\n        self.ignore_iof_thr = ignore_iof_thr\n        self.ignore_wrt_candidates = ignore_wrt_candidates\n        self.gpu_assign_thr = gpu_assign_thr\n        self.match_low_quality = match_low_quality\n        self.iou_calculator = build_iou_calculator(iou_calculator)\n\n    def assign(self,\n               approxs,\n               squares,\n               approxs_per_octave,\n               gt_bboxes,\n               gt_bboxes_ignore=None,\n               gt_labels=None):\n        \"\"\"Assign gt to approxs.\n\n        This method assign a gt bbox to each group of approxs (bboxes),\n        each group of approxs is represent by a base approx (bbox) and\n        will be assigned with -1, or a semi-positive number.\n        background_label (-1) means negative sample,\n        semi-positive number is the index (0-based) of assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every bbox to background_label (-1)\n        2. use the max IoU of each group of approxs to assign\n        2. assign proposals whose iou with all gts < neg_iou_thr to background\n        3. for each bbox, if the iou with its nearest gt >= pos_iou_thr,\n           assign it to that bbox\n        4. for each gt bbox, assign its nearest proposals (may be more than\n           one) to itself\n\n        Args:\n            approxs (Tensor): Bounding boxes to be assigned,\n                shape(approxs_per_octave*n, 4).\n            squares (Tensor): Base Bounding boxes to be assigned,\n                shape(n, 4).\n            approxs_per_octave (int): number of approxs per octave\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        num_squares = squares.size(0)\n        num_gts = gt_bboxes.size(0)\n\n        if num_squares == 0 or num_gts == 0:\n            # No predictions and/or truth, return empty assignment\n            overlaps = approxs.new(num_gts, num_squares)\n            assign_result = self.assign_wrt_overlaps(overlaps, gt_labels)\n            return assign_result\n\n        # re-organize anchors by approxs_per_octave x num_squares\n        approxs = torch.transpose(\n            approxs.view(num_squares, approxs_per_octave, 4), 0,\n            1).contiguous().view(-1, 4)\n        assign_on_cpu = True if (self.gpu_assign_thr > 0) and (\n            num_gts > self.gpu_assign_thr) else False\n        # compute overlap and assign gt on CPU when number of GT is large\n        if assign_on_cpu:\n            device = approxs.device\n            approxs = approxs.cpu()\n            gt_bboxes = gt_bboxes.cpu()\n            if gt_bboxes_ignore is not None:\n                gt_bboxes_ignore = gt_bboxes_ignore.cpu()\n            if gt_labels is not None:\n                gt_labels = gt_labels.cpu()\n        all_overlaps = self.iou_calculator(approxs, gt_bboxes)\n\n        overlaps, _ = all_overlaps.view(approxs_per_octave, num_squares,\n                                        num_gts).max(dim=0)\n        overlaps = torch.transpose(overlaps, 0, 1)\n\n        if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None\n                and gt_bboxes_ignore.numel() > 0 and squares.numel() > 0):\n            if self.ignore_wrt_candidates:\n                ignore_overlaps = self.iou_calculator(\n                    squares, gt_bboxes_ignore, mode='iof')\n                ignore_max_overlaps, _ = ignore_overlaps.max(dim=1)\n            else:\n                ignore_overlaps = self.iou_calculator(\n                    gt_bboxes_ignore, squares, mode='iof')\n                ignore_max_overlaps, _ = ignore_overlaps.max(dim=0)\n            overlaps[:, ignore_max_overlaps > self.ignore_iof_thr] = -1\n\n        assign_result = self.assign_wrt_overlaps(overlaps, gt_labels)\n        if assign_on_cpu:\n            assign_result.gt_inds = assign_result.gt_inds.to(device)\n            assign_result.max_overlaps = assign_result.max_overlaps.to(device)\n            if assign_result.labels is not None:\n                assign_result.labels = assign_result.labels.to(device)\n        return assign_result\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/assign_result.py",
    "content": "import torch\n\nfrom mmdet.utils import util_mixins\n\n\nclass AssignResult(util_mixins.NiceRepr):\n    \"\"\"\n    Stores assignments between predicted and truth boxes.\n\n    Attributes:\n        num_gts (int): the number of truth boxes considered when computing this\n            assignment\n\n        gt_inds (LongTensor): for each predicted box indicates the 1-based\n            index of the assigned truth box. 0 means unassigned and -1 means\n            ignore.\n\n        max_overlaps (FloatTensor): the iou between the predicted box and its\n            assigned truth box.\n\n        labels (None | LongTensor): If specified, for each predicted box\n            indicates the category label of the assigned truth box.\n\n    Example:\n        >>> # An assign result between 4 predicted boxes and 9 true boxes\n        >>> # where only two boxes were assigned.\n        >>> num_gts = 9\n        >>> max_overlaps = torch.LongTensor([0, .5, .9, 0])\n        >>> gt_inds = torch.LongTensor([-1, 1, 2, 0])\n        >>> labels = torch.LongTensor([0, 3, 4, 0])\n        >>> self = AssignResult(num_gts, gt_inds, max_overlaps, labels)\n        >>> print(str(self))  # xdoctest: +IGNORE_WANT\n        <AssignResult(num_gts=9, gt_inds.shape=(4,), max_overlaps.shape=(4,),\n                      labels.shape=(4,))>\n        >>> # Force addition of gt labels (when adding gt as proposals)\n        >>> new_labels = torch.LongTensor([3, 4, 5])\n        >>> self.add_gt_(new_labels)\n        >>> print(str(self))  # xdoctest: +IGNORE_WANT\n        <AssignResult(num_gts=9, gt_inds.shape=(7,), max_overlaps.shape=(7,),\n                      labels.shape=(7,))>\n    \"\"\"\n\n    def __init__(self, num_gts, gt_inds, max_overlaps, labels=None):\n        self.num_gts = num_gts\n        self.gt_inds = gt_inds\n        self.max_overlaps = max_overlaps\n        self.labels = labels\n        # Interface for possible user-defined properties\n        self._extra_properties = {}\n\n    @property\n    def num_preds(self):\n        \"\"\"int: the number of predictions in this assignment\"\"\"\n        return len(self.gt_inds)\n\n    def set_extra_property(self, key, value):\n        \"\"\"Set user-defined new property\"\"\"\n        assert key not in self.info\n        self._extra_properties[key] = value\n\n    def get_extra_property(self, key):\n        \"\"\"Get user-defined property\"\"\"\n        return self._extra_properties.get(key, None)\n\n    @property\n    def info(self):\n        \"\"\"dict: a dictionary of info about the object\"\"\"\n        basic_info = {\n            'num_gts': self.num_gts,\n            'num_preds': self.num_preds,\n            'gt_inds': self.gt_inds,\n            'max_overlaps': self.max_overlaps,\n            'labels': self.labels,\n        }\n        basic_info.update(self._extra_properties)\n        return basic_info\n\n    def __nice__(self):\n        \"\"\"str: a \"nice\" summary string describing this assign result\"\"\"\n        parts = []\n        parts.append(f'num_gts={self.num_gts!r}')\n        if self.gt_inds is None:\n            parts.append(f'gt_inds={self.gt_inds!r}')\n        else:\n            parts.append(f'gt_inds.shape={tuple(self.gt_inds.shape)!r}')\n        if self.max_overlaps is None:\n            parts.append(f'max_overlaps={self.max_overlaps!r}')\n        else:\n            parts.append('max_overlaps.shape='\n                         f'{tuple(self.max_overlaps.shape)!r}')\n        if self.labels is None:\n            parts.append(f'labels={self.labels!r}')\n        else:\n            parts.append(f'labels.shape={tuple(self.labels.shape)!r}')\n        return ', '.join(parts)\n\n    @classmethod\n    def random(cls, **kwargs):\n        \"\"\"Create random AssignResult for tests or debugging.\n\n        Args:\n            num_preds: number of predicted boxes\n            num_gts: number of true boxes\n            p_ignore (float): probability of a predicted box assinged to an\n                ignored truth\n            p_assigned (float): probability of a predicted box not being\n                assigned\n            p_use_label (float | bool): with labels or not\n            rng (None | int | numpy.random.RandomState): seed or state\n\n        Returns:\n            :obj:`AssignResult`: Randomly generated assign results.\n\n        Example:\n            >>> from mmdet.core.bbox.assigners.assign_result import *  # NOQA\n            >>> self = AssignResult.random()\n            >>> print(self.info)\n        \"\"\"\n        from mmdet.core.bbox import demodata\n        rng = demodata.ensure_rng(kwargs.get('rng', None))\n\n        num_gts = kwargs.get('num_gts', None)\n        num_preds = kwargs.get('num_preds', None)\n        p_ignore = kwargs.get('p_ignore', 0.3)\n        p_assigned = kwargs.get('p_assigned', 0.7)\n        p_use_label = kwargs.get('p_use_label', 0.5)\n        num_classes = kwargs.get('p_use_label', 3)\n\n        if num_gts is None:\n            num_gts = rng.randint(0, 8)\n        if num_preds is None:\n            num_preds = rng.randint(0, 16)\n\n        if num_gts == 0:\n            max_overlaps = torch.zeros(num_preds, dtype=torch.float32)\n            gt_inds = torch.zeros(num_preds, dtype=torch.int64)\n            if p_use_label is True or p_use_label < rng.rand():\n                labels = torch.zeros(num_preds, dtype=torch.int64)\n            else:\n                labels = None\n        else:\n            import numpy as np\n            # Create an overlap for each predicted box\n            max_overlaps = torch.from_numpy(rng.rand(num_preds))\n\n            # Construct gt_inds for each predicted box\n            is_assigned = torch.from_numpy(rng.rand(num_preds) < p_assigned)\n            # maximum number of assignments constraints\n            n_assigned = min(num_preds, min(num_gts, is_assigned.sum()))\n\n            assigned_idxs = np.where(is_assigned)[0]\n            rng.shuffle(assigned_idxs)\n            assigned_idxs = assigned_idxs[0:n_assigned]\n            assigned_idxs.sort()\n\n            is_assigned[:] = 0\n            is_assigned[assigned_idxs] = True\n\n            is_ignore = torch.from_numpy(\n                rng.rand(num_preds) < p_ignore) & is_assigned\n\n            gt_inds = torch.zeros(num_preds, dtype=torch.int64)\n\n            true_idxs = np.arange(num_gts)\n            rng.shuffle(true_idxs)\n            true_idxs = torch.from_numpy(true_idxs)\n            gt_inds[is_assigned] = true_idxs[:n_assigned]\n\n            gt_inds = torch.from_numpy(\n                rng.randint(1, num_gts + 1, size=num_preds))\n            gt_inds[is_ignore] = -1\n            gt_inds[~is_assigned] = 0\n            max_overlaps[~is_assigned] = 0\n\n            if p_use_label is True or p_use_label < rng.rand():\n                if num_classes == 0:\n                    labels = torch.zeros(num_preds, dtype=torch.int64)\n                else:\n                    labels = torch.from_numpy(\n                        # remind that we set FG labels to [0, num_class-1]\n                        # since mmdet v2.0\n                        # BG cat_id: num_class\n                        rng.randint(0, num_classes, size=num_preds))\n                    labels[~is_assigned] = 0\n            else:\n                labels = None\n\n        self = cls(num_gts, gt_inds, max_overlaps, labels)\n        return self\n\n    def add_gt_(self, gt_labels):\n        \"\"\"Add ground truth as assigned results\n\n        Args:\n            gt_labels (torch.Tensor): Labels of gt boxes\n        \"\"\"\n        self_inds = torch.arange(\n            1, len(gt_labels) + 1, dtype=torch.long, device=gt_labels.device)\n        self.gt_inds = torch.cat([self_inds, self.gt_inds])\n\n        self.max_overlaps = torch.cat(\n            [self.max_overlaps.new_ones(len(gt_labels)), self.max_overlaps])\n\n        if self.labels is not None:\n            self.labels = torch.cat([gt_labels, self.labels])\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/atss_assigner.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom ..iou_calculators import build_iou_calculator\nfrom .assign_result import AssignResult\nfrom .base_assigner import BaseAssigner\n\n\n@BBOX_ASSIGNERS.register_module()\nclass ATSSAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each bbox.\n\n    Each proposals will be assigned with `0` or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    Args:\n        topk (float): number of bbox selected in each level\n    \"\"\"\n\n    def __init__(self, topk, iou_calculator=dict(type='BboxOverlaps2D')):\n        self.topk = topk\n        self.iou_calculator = build_iou_calculator(iou_calculator)\n\n    # https://github.com/sfzhang15/ATSS/blob/master/atss_core/modeling/rpn/atss/loss.py\n\n    def assign(self,\n               bboxes,\n               num_level_bboxes,\n               gt_bboxes,\n               gt_bboxes_ignore=None,\n               gt_labels=None):\n        \"\"\"Assign gt to bboxes.\n\n        The assignment is done in following steps\n\n        1. compute iou between all bbox (bbox of all pyramid levels) and gt\n        2. compute center distance between all bbox and gt\n        3. on each pyramid level, for each gt, select k bbox whose center\n           are closest to the gt center, so we total select k*l bbox as\n           candidates for each gt\n        4. get corresponding iou for the these candidates, and compute the\n           mean and std, set mean + std as the iou threshold\n        5. select these candidates whose iou are greater than or equal to\n           the threshold as postive\n        6. limit the positive sample's center in gt\n\n\n        Args:\n            bboxes (Tensor): Bounding boxes to be assigned, shape(n, 4).\n            num_level_bboxes (List): num of bboxes in each level\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        INF = 100000000\n        bboxes = bboxes[:, :4]\n        num_gt, num_bboxes = gt_bboxes.size(0), bboxes.size(0)\n\n        # compute iou between all bbox and gt\n        overlaps = self.iou_calculator(bboxes, gt_bboxes)\n\n        # assign 0 by default\n        assigned_gt_inds = overlaps.new_full((num_bboxes, ),\n                                             0,\n                                             dtype=torch.long)\n\n        if num_gt == 0 or num_bboxes == 0:\n            # No ground truth or boxes, return empty assignment\n            max_overlaps = overlaps.new_zeros((num_bboxes, ))\n            if num_gt == 0:\n                # No truth, assign everything to background\n                assigned_gt_inds[:] = 0\n            if gt_labels is None:\n                assigned_labels = None\n            else:\n                assigned_labels = overlaps.new_full((num_bboxes, ),\n                                                    -1,\n                                                    dtype=torch.long)\n            return AssignResult(\n                num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels)\n\n        # compute center distance between all bbox and gt\n        gt_cx = (gt_bboxes[:, 0] + gt_bboxes[:, 2]) / 2.0\n        gt_cy = (gt_bboxes[:, 1] + gt_bboxes[:, 3]) / 2.0\n        gt_points = torch.stack((gt_cx, gt_cy), dim=1)\n\n        bboxes_cx = (bboxes[:, 0] + bboxes[:, 2]) / 2.0\n        bboxes_cy = (bboxes[:, 1] + bboxes[:, 3]) / 2.0\n        bboxes_points = torch.stack((bboxes_cx, bboxes_cy), dim=1)\n\n        distances = (bboxes_points[:, None, :] -\n                     gt_points[None, :, :]).pow(2).sum(-1).sqrt()\n\n        # Selecting candidates based on the center distance\n        candidate_idxs = []\n        start_idx = 0\n        for level, bboxes_per_level in enumerate(num_level_bboxes):\n            # on each pyramid level, for each gt,\n            # select k bbox whose center are closest to the gt center\n            end_idx = start_idx + bboxes_per_level\n            distances_per_level = distances[start_idx:end_idx, :]\n            _, topk_idxs_per_level = distances_per_level.topk(\n                self.topk, dim=0, largest=False)\n            candidate_idxs.append(topk_idxs_per_level + start_idx)\n            start_idx = end_idx\n        candidate_idxs = torch.cat(candidate_idxs, dim=0)\n\n        # get corresponding iou for the these candidates, and compute the\n        # mean and std, set mean + std as the iou threshold\n        candidate_overlaps = overlaps[candidate_idxs, torch.arange(num_gt)]\n        overlaps_mean_per_gt = candidate_overlaps.mean(0)\n        overlaps_std_per_gt = candidate_overlaps.std(0)\n        overlaps_thr_per_gt = overlaps_mean_per_gt + overlaps_std_per_gt\n\n        is_pos = candidate_overlaps >= overlaps_thr_per_gt[None, :]\n\n        # limit the positive sample's center in gt\n        for gt_idx in range(num_gt):\n            candidate_idxs[:, gt_idx] += gt_idx * num_bboxes\n        ep_bboxes_cx = bboxes_cx.view(1, -1).expand(\n            num_gt, num_bboxes).contiguous().view(-1)\n        ep_bboxes_cy = bboxes_cy.view(1, -1).expand(\n            num_gt, num_bboxes).contiguous().view(-1)\n        candidate_idxs = candidate_idxs.view(-1)\n\n        # calculate the left, top, right, bottom distance between positive\n        # bbox center and gt side\n        l_ = ep_bboxes_cx[candidate_idxs].view(-1, num_gt) - gt_bboxes[:, 0]\n        t_ = ep_bboxes_cy[candidate_idxs].view(-1, num_gt) - gt_bboxes[:, 1]\n        r_ = gt_bboxes[:, 2] - ep_bboxes_cx[candidate_idxs].view(-1, num_gt)\n        b_ = gt_bboxes[:, 3] - ep_bboxes_cy[candidate_idxs].view(-1, num_gt)\n        is_in_gts = torch.stack([l_, t_, r_, b_], dim=1).min(dim=1)[0] > 0.01\n        is_pos = is_pos & is_in_gts\n\n        # if an anchor box is assigned to multiple gts,\n        # the one with the highest IoU will be selected.\n        overlaps_inf = torch.full_like(overlaps,\n                                       -INF).t().contiguous().view(-1)\n        index = candidate_idxs.view(-1)[is_pos.view(-1)]\n        overlaps_inf[index] = overlaps.t().contiguous().view(-1)[index]\n        overlaps_inf = overlaps_inf.view(num_gt, -1).t()\n\n        max_overlaps, argmax_overlaps = overlaps_inf.max(dim=1)\n        assigned_gt_inds[\n            max_overlaps != -INF] = argmax_overlaps[max_overlaps != -INF] + 1\n\n        if gt_labels is not None:\n            assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_inds > 0, as_tuple=False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[\n                    assigned_gt_inds[pos_inds] - 1]\n        else:\n            assigned_labels = None\n        return AssignResult(\n            num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels)\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/base_assigner.py",
    "content": "from abc import ABCMeta, abstractmethod\n\n\nclass BaseAssigner(metaclass=ABCMeta):\n    \"\"\"Base assigner that assigns boxes to ground truth boxes\"\"\"\n\n    @abstractmethod\n    def assign(self, bboxes, gt_bboxes, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign boxes to either a ground truth boxe or a negative boxes\"\"\"\n        pass\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/center_region_assigner.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom ..iou_calculators import build_iou_calculator\nfrom .assign_result import AssignResult\nfrom .base_assigner import BaseAssigner\n\n\ndef scale_boxes(bboxes, scale):\n    \"\"\"Expand an array of boxes by a given scale.\n\n    Args:\n        bboxes (Tensor): Shape (m, 4)\n        scale (float): The scale factor of bboxes\n\n    Returns:\n        (Tensor): Shape (m, 4). Scaled bboxes\n    \"\"\"\n    assert bboxes.size(1) == 4\n    w_half = (bboxes[:, 2] - bboxes[:, 0]) * .5\n    h_half = (bboxes[:, 3] - bboxes[:, 1]) * .5\n    x_c = (bboxes[:, 2] + bboxes[:, 0]) * .5\n    y_c = (bboxes[:, 3] + bboxes[:, 1]) * .5\n\n    w_half *= scale\n    h_half *= scale\n\n    boxes_scaled = torch.zeros_like(bboxes)\n    boxes_scaled[:, 0] = x_c - w_half\n    boxes_scaled[:, 2] = x_c + w_half\n    boxes_scaled[:, 1] = y_c - h_half\n    boxes_scaled[:, 3] = y_c + h_half\n    return boxes_scaled\n\n\ndef is_located_in(points, bboxes):\n    \"\"\"Are points located in bboxes\n\n    Args:\n      points (Tensor): Points, shape: (m, 2).\n      bboxes (Tensor): Bounding boxes, shape: (n, 4).\n\n    Return:\n      Tensor: Flags indicating if points are located in bboxes, shape: (m, n).\n    \"\"\"\n    assert points.size(1) == 2\n    assert bboxes.size(1) == 4\n    return (points[:, 0].unsqueeze(1) > bboxes[:, 0].unsqueeze(0)) & \\\n           (points[:, 0].unsqueeze(1) < bboxes[:, 2].unsqueeze(0)) & \\\n           (points[:, 1].unsqueeze(1) > bboxes[:, 1].unsqueeze(0)) & \\\n           (points[:, 1].unsqueeze(1) < bboxes[:, 3].unsqueeze(0))\n\n\ndef bboxes_area(bboxes):\n    \"\"\"Compute the area of an array of bboxes.\n\n    Args:\n        bboxes (Tensor): The coordinates ox bboxes. Shape: (m, 4)\n\n    Returns:\n        Tensor: Area of the bboxes. Shape: (m, )\n\n    \"\"\"\n    assert bboxes.size(1) == 4\n    w = (bboxes[:, 2] - bboxes[:, 0])\n    h = (bboxes[:, 3] - bboxes[:, 1])\n    areas = w * h\n    return areas\n\n\n@BBOX_ASSIGNERS.register_module()\nclass CenterRegionAssigner(BaseAssigner):\n    \"\"\"Assign pixels at the center region of a bbox as positive.\n\n    Each proposals will be assigned with `-1`, `0`, or a positive integer\n    indicating the ground truth index.\n    - -1: negative samples\n    - semi-positive numbers: positive sample, index (0-based) of assigned gt\n\n    Args:\n        pos_scale (float): Threshold within which pixels are\n          labelled as positive.\n        neg_scale (float): Threshold above which pixels are\n          labelled as positive.\n        min_pos_iof (float): Minimum iof of a pixel with a gt to be\n          labelled as positive. Default: 1e-2\n        ignore_gt_scale (float): Threshold within which the pixels\n          are ignored when the gt is labelled as shadowed. Default: 0.5\n    \"\"\"\n\n    def __init__(self,\n                 pos_scale,\n                 neg_scale,\n                 min_pos_iof=1e-2,\n                 ignore_gt_scale=0.5,\n                 iou_calculator=dict(type='BboxOverlaps2D')):\n        self.pos_scale = pos_scale\n        self.neg_scale = neg_scale\n        self.min_pos_iof = min_pos_iof\n        self.ignore_gt_scale = ignore_gt_scale\n        self.iou_calculator = build_iou_calculator(iou_calculator)\n\n    def get_gt_priorities(self, gt_bboxes):\n        \"\"\"Get gt priorities according to their areas.\n\n        Smaller gt has higher priority.\n\n        Args:\n            gt_bboxes (Tensor): Ground truth boxes, shape (k, 4).\n\n        Returns:\n            Tensor: The priority of gts so that gts with larger priority is\n              more likely to be assigned. Shape (k, )\n\n        \"\"\"\n        gt_areas = bboxes_area(gt_bboxes)\n        # Rank all gt bbox areas. Smaller objects has larger priority\n        _, sort_idx = gt_areas.sort(descending=True)\n        return sort_idx\n\n    def assign(self, bboxes, gt_bboxes, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign gt to bboxes.\n\n        This method assigns gts to every bbox (proposal/anchor), each bbox will\n         be assigned with -1, or a semi-positive number. -1 means negative\n         sample, semi-positive number is the index (0-based) of assigned gt.\n\n        Args:\n            bboxes (Tensor): Bounding boxes to be assigned, shape(n, 4).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (tensor, optional): Ground truth bboxes that are\n              labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (tensor, optional): Label of gt_bboxes, shape (num_gts,).\n\n        Returns:\n            :obj:`AssignResult`: The assigned result. Note that shadowed_labels\n              of shape (N, 2) is also added as an `assign_result` attribute.\n              `shadowed_labels` is a tensor composed of N pairs of\n              [anchor_ind, class_label], where N is the number of anchors that\n              lie in the outer region of a gt, anchor_ind is the shadowed\n              anchor index and class_label is the shadowed class label.\n\n        Example:\n            >>> self = CenterRegionAssigner(0.2, 0.2)\n            >>> bboxes = torch.Tensor([[0, 0, 10, 10], [10, 10, 20, 20]])\n            >>> gt_bboxes = torch.Tensor([[0, 0, 10, 10]])\n            >>> assign_result = self.assign(bboxes, gt_bboxes)\n            >>> expected_gt_inds = torch.LongTensor([1, 0])\n            >>> assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n        \"\"\"\n        # There are in total 5 steps in the pixel assignment\n        # 1. Find core (the center region, say inner 0.2)\n        #     and shadow (the relatively ourter part, say inner 0.2-0.5)\n        #     regions of every gt.\n        # 2. Find all prior bboxes that lie in gt_core and gt_shadow regions\n        # 3. Assign prior bboxes in gt_core with a one-hot id of the gt in\n        #      the image.\n        #    3.1. For overlapping objects, the prior bboxes in gt_core is\n        #           assigned with the object with smallest area\n        # 4. Assign prior bboxes with class label according to its gt id.\n        #    4.1. Assign -1 to prior bboxes lying in shadowed gts\n        #    4.2. Assign positive prior boxes with the corresponding label\n        # 5. Find pixels lying in the shadow of an object and assign them with\n        #      background label, but set the loss weight of its corresponding\n        #      gt to zero.\n        assert bboxes.size(1) == 4, 'bboxes must have size of 4'\n        # 1. Find core positive and shadow region of every gt\n        gt_core = scale_boxes(gt_bboxes, self.pos_scale)\n        gt_shadow = scale_boxes(gt_bboxes, self.neg_scale)\n\n        # 2. Find prior bboxes that lie in gt_core and gt_shadow regions\n        bbox_centers = (bboxes[:, 2:4] + bboxes[:, 0:2]) / 2\n        # The center points lie within the gt boxes\n        is_bbox_in_gt = is_located_in(bbox_centers, gt_bboxes)\n        # Only calculate bbox and gt_core IoF. This enables small prior bboxes\n        #   to match large gts\n        bbox_and_gt_core_overlaps = self.iou_calculator(\n            bboxes, gt_core, mode='iof')\n        # The center point of effective priors should be within the gt box\n        is_bbox_in_gt_core = is_bbox_in_gt & (\n            bbox_and_gt_core_overlaps > self.min_pos_iof)  # shape (n, k)\n\n        is_bbox_in_gt_shadow = (\n            self.iou_calculator(bboxes, gt_shadow, mode='iof') >\n            self.min_pos_iof)\n        # Rule out center effective positive pixels\n        is_bbox_in_gt_shadow &= (~is_bbox_in_gt_core)\n\n        num_gts, num_bboxes = gt_bboxes.size(0), bboxes.size(0)\n        if num_gts == 0 or num_bboxes == 0:\n            # If no gts exist, assign all pixels to negative\n            assigned_gt_ids = \\\n                is_bbox_in_gt_core.new_zeros((num_bboxes,),\n                                             dtype=torch.long)\n            pixels_in_gt_shadow = assigned_gt_ids.new_empty((0, 2))\n        else:\n            # Step 3: assign a one-hot gt id to each pixel, and smaller objects\n            #    have high priority to assign the pixel.\n            sort_idx = self.get_gt_priorities(gt_bboxes)\n            assigned_gt_ids, pixels_in_gt_shadow = \\\n                self.assign_one_hot_gt_indices(is_bbox_in_gt_core,\n                                               is_bbox_in_gt_shadow,\n                                               gt_priority=sort_idx)\n\n        if gt_bboxes_ignore is not None and gt_bboxes_ignore.numel() > 0:\n            # No ground truth or boxes, return empty assignment\n            gt_bboxes_ignore = scale_boxes(\n                gt_bboxes_ignore, scale=self.ignore_gt_scale)\n            is_bbox_in_ignored_gts = is_located_in(bbox_centers,\n                                                   gt_bboxes_ignore)\n            is_bbox_in_ignored_gts = is_bbox_in_ignored_gts.any(dim=1)\n            assigned_gt_ids[is_bbox_in_ignored_gts] = -1\n\n        # 4. Assign prior bboxes with class label according to its gt id.\n        assigned_labels = None\n        shadowed_pixel_labels = None\n        if gt_labels is not None:\n            # Default assigned label is the background (-1)\n            assigned_labels = assigned_gt_ids.new_full((num_bboxes, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_ids > 0, as_tuple=False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[assigned_gt_ids[pos_inds]\n                                                      - 1]\n            # 5. Find pixels lying in the shadow of an object\n            shadowed_pixel_labels = pixels_in_gt_shadow.clone()\n            if pixels_in_gt_shadow.numel() > 0:\n                pixel_idx, gt_idx =\\\n                    pixels_in_gt_shadow[:, 0], pixels_in_gt_shadow[:, 1]\n                assert (assigned_gt_ids[pixel_idx] != gt_idx).all(), \\\n                    'Some pixels are dually assigned to ignore and gt!'\n                shadowed_pixel_labels[:, 1] = gt_labels[gt_idx - 1]\n                # When a pixel is both positive and shadowed, set it as shadow.\n                override = (\n                    assigned_labels[pixel_idx] == shadowed_pixel_labels[:, 1])\n                assigned_labels[pixel_idx[override]] = -1\n                assigned_gt_ids[pixel_idx[override]] = 0\n\n        assign_result = AssignResult(\n            num_gts, assigned_gt_ids, None, labels=assigned_labels)\n        # Add shadowed_labels as assign_result property. Shape: (num_shadow, 2)\n        assign_result.set_extra_property('shadowed_labels',\n                                         shadowed_pixel_labels)\n        return assign_result\n\n    def assign_one_hot_gt_indices(self,\n                                  is_bbox_in_gt_core,\n                                  is_bbox_in_gt_shadow,\n                                  gt_priority=None):\n        \"\"\"Assign only one gt index to each prior box\n\n        Gts with large gt_priority are more likely to be assigned.\n\n        Args:\n            is_bbox_in_gt_core (Tensor): Bool tensor indicating the bbox center\n              is in the core area of a gt (e.g. 0-0.2).\n              Shape: (num_prior, num_gt).\n            is_bbox_in_gt_shadow (Tensor): Bool tensor indicating the bbox\n              center is in the shadowed area of a gt (e.g. 0.2-0.5).\n              Shape: (num_prior, num_gt).\n            gt_priority (Tensor): Priorities of gts. The gt with a higher\n              priority is more likely to be assigned to the bbox when the bbox\n              match with multiple gts. Shape: (num_gt, ).\n\n        Returns:\n            assigned_gt_inds: The assigned gt index of each prior bbox\n              (i.e. index from 1 to num_gts). Shape: (num_prior, ).\n            shadowed_gt_inds: shadowed gt indices. It is a tensor of shape\n              (num_ignore, 2) with first column being the shadowed prior bbox\n              indices and the second column the shadowed gt indices (1-based)\n        \"\"\"\n        num_bboxes, num_gts = is_bbox_in_gt_core.shape\n\n        if gt_priority is None:\n            gt_priority = torch.arange(\n                num_gts, device=is_bbox_in_gt_core.device)\n        assert gt_priority.size(0) == num_gts\n        # The bigger gt_priority, the more preferable to be assigned\n        # The assigned inds are by default 0 (background)\n        assigned_gt_inds = is_bbox_in_gt_core.new_zeros((num_bboxes, ),\n                                                        dtype=torch.long)\n        # Shadowed bboxes are assigned to be background. But the corresponding\n        #   label is ignored during loss calculation, which is done through\n        #   shadowed_gt_inds\n        shadowed_gt_inds = torch.nonzero(is_bbox_in_gt_shadow, as_tuple=False)\n        if is_bbox_in_gt_core.sum() == 0:  # No gt match\n            shadowed_gt_inds[:, 1] += 1  # 1-based. For consistency issue\n            return assigned_gt_inds, shadowed_gt_inds\n\n        # The priority of each prior box and gt pair. If one prior box is\n        #  matched bo multiple gts. Only the pair with the highest priority\n        #  is saved\n        pair_priority = is_bbox_in_gt_core.new_full((num_bboxes, num_gts),\n                                                    -1,\n                                                    dtype=torch.long)\n\n        # Each bbox could match with multiple gts.\n        # The following codes deal with this situation\n        # Matched  bboxes (to any gt). Shape: (num_pos_anchor, )\n        inds_of_match = torch.any(is_bbox_in_gt_core, dim=1)\n        # The matched gt index of each positive bbox. Length >= num_pos_anchor\n        #   , since one bbox could match multiple gts\n        matched_bbox_gt_inds = torch.nonzero(\n            is_bbox_in_gt_core, as_tuple=False)[:, 1]\n        # Assign priority to each bbox-gt pair.\n        pair_priority[is_bbox_in_gt_core] = gt_priority[matched_bbox_gt_inds]\n        _, argmax_priority = pair_priority[inds_of_match].max(dim=1)\n        assigned_gt_inds[inds_of_match] = argmax_priority + 1  # 1-based\n        # Zero-out the assigned anchor box to filter the shadowed gt indices\n        is_bbox_in_gt_core[inds_of_match, argmax_priority] = 0\n        # Concat the shadowed indices due to overlapping with that out side of\n        #   effective scale. shape: (total_num_ignore, 2)\n        shadowed_gt_inds = torch.cat(\n            (shadowed_gt_inds, torch.nonzero(\n                is_bbox_in_gt_core, as_tuple=False)),\n            dim=0)\n        # `is_bbox_in_gt_core` should be changed back to keep arguments intact.\n        is_bbox_in_gt_core[inds_of_match, argmax_priority] = 1\n        # 1-based shadowed gt indices, to be consistent with `assigned_gt_inds`\n        shadowed_gt_inds[:, 1] += 1\n        return assigned_gt_inds, shadowed_gt_inds\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/centroid_assigner.py",
    "content": "import pdb\nimport torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom .base_assigner import BaseAssigner\nfrom .assign_result import AssignResult\n\n\n@BBOX_ASSIGNERS.register_module()\nclass CentroidAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each point.\n\n    Each proposals will be assigned with `0`, or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    \"\"\"\n\n    def __init__(self, scale=4, pos_num=3, iou_type='center'):\n        self.scale    = scale\n        self.pos_num  = pos_num\n        self.iou_type = iou_type\n\n    def assign(self, points, gt_bboxes, gt_extreme_pts, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign gt to points.\n        if iou_type == 'center':\n             assign gt to the center points, which is the same as point_assigner_v2. \n        elif iou_type == 'centroid':\n            assign gt to the centroid points. \n        \"\"\"\n        INF = 1e8\n        num_gts, num_points = gt_bboxes.shape[0], points.shape[0]\n\n        if num_gts == 0 or num_points == 0:\n            # If no truth assign everything to the background\n            assigned_gt_inds = points.new_full((num_points, ),\n                                               0,\n                                               dtype=torch.long)\n            if gt_labels is None:\n                assigned_labels = None\n            else:\n                assigned_labels = points.new_full((num_points, ),\n                                                  -1,\n                                                  dtype=torch.long)\n            return AssignResult(\n                num_gts, assigned_gt_inds, None, labels=assigned_labels)\n\n        points_xy = points[:, :2]\n        points_stride = points[:, 2]\n        points_lvl = torch.log2(points_stride).int()  # [3...,4...,5...,6...,7...]\n        lvl_min, lvl_max = points_lvl.min(), points_lvl.max()\n\n        # assign gt box\n        if self.iou_type == 'centroid':\n            gt_bboxes_xy = self.gen_centroid(gt_extreme_pts, num_gts)\n        else: \n            gt_bboxes_xy = (gt_bboxes[:, :2] + gt_bboxes[:, 2:]) / 2\n            \n        gt_bboxes_wh = (gt_bboxes[:, 2:] - gt_bboxes[:, :2]).clamp(min=1e-6)\n        scale = self.scale\n        gt_bboxes_lvl = ((torch.log2(gt_bboxes_wh[:, 0] / scale) +\n                          torch.log2(gt_bboxes_wh[:, 1] / scale)) / 2).int()\n        gt_bboxes_lvl = torch.clamp(gt_bboxes_lvl, min=lvl_min, max=lvl_max)\n\n        distances = ((points_xy[:, None, :] - gt_bboxes_xy[None, :, :]) / gt_bboxes_wh[None, :, :]).norm(dim=2)\n\n        distances[points_lvl[:, None] != gt_bboxes_lvl[None, :]] = INF\n\n        # stores the assigned gt index of each point\n        assigned_gt_inds = points.new_zeros((num_points, ), dtype=torch.long)\n\n        min_dist, min_dist_index = torch.topk(distances, self.pos_num, dim=0, largest=False)\n\n        distances_inf = torch.full_like(distances, INF)\n        distances_inf[min_dist_index, torch.arange(num_gts)] = min_dist\n\n        min_dist, min_dist_index = distances_inf.min(dim=1)\n        assigned_gt_inds[min_dist != INF] = min_dist_index[min_dist != INF] + 1\n\n        if gt_labels is not None:\n            assigned_labels = assigned_gt_inds.new_full((num_points, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_inds > 0, as_tuple=False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[\n                    assigned_gt_inds[pos_inds] - 1]\n        else:\n            assigned_labels = None\n\n        return AssignResult(\n            num_gts, assigned_gt_inds, None, labels=assigned_labels)\n\n    def gen_centroid(self, pts, num_gts):\n        extreme_pts = pts[:, :-2]\n        pts_repeat = extreme_pts.repeat(1, 2)\n        pts_reshape = pts_repeat.view(pts_repeat.shape[0], -1, 2, *pts_repeat.shape[2:])\n        pts_x = pts_reshape[:, :, 0, ...]\n        pts_y = pts_reshape[:, :, 1, ...]\n\n        centroid_x_list = []\n        centroid_y_list = []\n        for i in range(4):\n            triangle_x = pts_x[:, i:i+3]\n            triangle_y = pts_y[:, i:i+3]\n\n            centroid_x = (torch.sum(triangle_x, -1)/3.0).unsqueeze(-1)\n            centroid_y = (torch.sum(triangle_y, -1)/3.0).unsqueeze(-1)\n\n            centroid_x_list.append(centroid_x)\n            centroid_y_list.append(centroid_y)\n        \n        centroid_xs = torch.cat(centroid_x_list, -1)\n        centroid_ys = torch.cat(centroid_y_list, -1)\n\n        line1_start_xs, line1_start_ys = centroid_xs[:, 0], centroid_ys[:, 0]\n        line1_end_xs,   line1_end_ys   = centroid_xs[:, 2], centroid_ys[:, 2]\n\n        line2_start_xs, line2_start_ys = centroid_xs[:, 1], centroid_ys[:, 1]\n        line2_end_xs,   line2_end_ys   = centroid_xs[:, 3], centroid_ys[:, 3]\n\n        detL1 = line1_start_xs * line1_end_ys - line1_start_ys * line1_end_xs\n        detL2 = line2_start_xs * line2_end_ys - line2_start_ys * line2_end_xs\n\n        x1mx2 = line1_start_xs - line1_end_xs\n        x3mx4 = line2_start_xs - line2_end_xs\n\n        y1my2 = line1_start_ys - line1_end_ys\n        y3my4 = line2_start_ys - line2_end_ys\n\n        xnom = detL1*x3mx4 - detL2*x1mx2\n        ynom = detL1*y3my4 - detL2*y1my2\n\n        denom = x1mx2*y3my4 - y1my2*x3mx4\n\n        polygon_centroid_xs = (xnom/denom).unsqueeze(-1)\n        polygon_centroid_ys = (ynom/denom).unsqueeze(-1)\n\n        return torch.cat((polygon_centroid_xs, polygon_centroid_ys), -1)"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/fcos_assigner.py",
    "content": "import pdb\nimport torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom .base_assigner import BaseAssigner\nfrom .assign_result import AssignResult\nINF = 1e8\n\n@BBOX_ASSIGNERS.register_module()\nclass FCOSAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each point.\n\n    Each proposals will be assigned with `0`, or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    \"\"\"\n\n    def __init__(self, strides=[8, 16, 32, 64, 128], \n                       regress_ranges = ((-1, 64), (64, 128), (128, 256), (256, 512), (512, INF)),\n                       center_sampling = False,\n                       center_sampling_radius = 1.5):\n        self.strides                = strides\n        self.regress_ranges         = regress_ranges\n        self.center_sampling        = center_sampling\n        self.center_sampling_radius = center_sampling_radius\n\n\n    def assign(self, points, num_points_per_lvl, gt_bboxes, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign gt to points.\n\n        This method assign a gt bbox to every points set, each points set\n        will be assigned with  the background_label (-1), or a label number.\n        -1 is background, and semi-positive number is the index (0-based) of\n        assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every points to the background_label (-1)\n        2. A point is assigned to some gt bbox if\n            (i) the point is within the k closest points to the gt bbox\n            (ii) the distance between this point and the gt is smaller than\n                other gt bboxes\n\n        Args:\n            points (Tensor): points to be assigned, shape(n, 3) while last\n                dimension stands for (x, y, stride).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n                NOTE: currently unused.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        num_gts, num_points = gt_bboxes.shape[0], points.shape[0]\n\n        if num_gts == 0 or num_points == 0:\n            # If no truth assign everything to the background\n            assigned_gt_inds = points.new_full((num_points, ),\n                                               0,\n                                               dtype=torch.long)\n            if gt_labels is None:\n                assigned_labels = None\n            else:\n                assigned_labels = points.new_full((num_points, ),\n                                                  -1,\n                                                  dtype=torch.long)\n            return AssignResult(\n                num_gts, assigned_gt_inds, None, labels=assigned_labels)\n\n        # assign gt box\n        assigned_gt_inds = points.new_zeros((num_points, ), dtype=torch.long)\n\n        num_levels = len(num_points_per_lvl)\n        expanded_regress_ranges = [\n            points.new_tensor(self.regress_ranges[i])[None].expand(\n                num_points_per_lvl[i], 2) for i in range(num_levels)\n        ]\n        concate_regress_ranges = torch.cat(expanded_regress_ranges, dim=0)\n\n        areas = (gt_bboxes[:, 2] - gt_bboxes[:,0])*(\n                 gt_bboxes[:, 3] - gt_bboxes[:, 1])\n\n        areas = areas[None].repeat(num_points, 1)\n        regress_ranges = concate_regress_ranges[:, None, :].expand(num_points, num_gts, 2)\n        gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4)\n\n        xs, ys = points[:, 0], points[:, 1]\n        xs = xs[:, None].expand(num_points, num_gts)\n        ys = ys[:, None].expand(num_points, num_gts)\n\n        left   = xs - gt_bboxes[..., 0]\n        right  = gt_bboxes[..., 2] - xs\n        top    = ys - gt_bboxes[..., 1]\n        bottom = gt_bboxes[..., 3] - ys\n        bbox_targets = torch.stack((left, top, right, bottom), -1)\n\n        if self.center_sampling:\n            # condition1: inside a 'center bbox'\n            radius = self.center_sampling_radius\n            center_xs = (gt_bboxes[..., 0] + gt_bboxes[..., 2]) / 2\n            center_ys = (gt_bboxes[..., 1] + gt_bboxes[..., 3]) / 2\n            center_gts = torch.zeros_like(gt_bboxes)\n            stride = center_xs.new_zeros(center_xs.shape)\n\n            #project the points on current lvl back to the 'original' sizes\n            lvl_begin = 0\n            for lvl_idx, num_points_lvl in enumerate(num_points_per_lvl):\n                lvl_end = lvl_begin + num_points_lvl\n                stride[lvl_begin:lvl_end] = self.strides[lvl_idx] * radius\n                lvl_begin = lvl_end\n            \n            x_mins = center_xs - stride\n            y_mins = center_ys - stride\n            x_maxs = center_xs + stride\n            y_maxs = center_ys + stride\n            center_gts[..., 0] = torch.where(x_mins > gt_bboxes[..., 0],\n                                             x_mins, gt_bboxes[..., 0])\n            center_gts[..., 1] = torch.where(y_mins > gt_bboxes[..., 1],\n                                             y_mins, gt_bboxes[..., 1])\n            center_gts[..., 2] = torch.where(x_maxs > gt_bboxes[..., 2],\n                                             gt_bboxes[..., 2], x_maxs)\n            center_gts[..., 3] = torch.where(y_maxs > gt_bboxes[..., 3],\n                                             gt_bboxes[..., 3], y_maxs)\n\n            cb_dist_left = xs - center_gts[..., 0]\n            cb_dist_right = center_gts[..., 2] - xs\n            cb_dist_top = ys - center_gts[..., 1]\n            cb_dist_bottom = center_gts[..., 3] - ys\n            center_bbox = torch.stack(\n                (cb_dist_left, cb_dist_top, cb_dist_right, cb_dist_bottom), -1)\n            inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0\n        else:\n            # condition2: inside a gt bbox\n            inside_gt_bbox_mask = bbox_targets.min(-1)[0] > 0\n\n        max_regress_distance = bbox_targets.max(-1)[0]\n        inside_regress_range = (\n            (max_regress_distance >= regress_ranges[..., 0])\n            & (max_regress_distance <= regress_ranges[..., 1]))\n\n        areas[inside_gt_bbox_mask == 0] = INF\n        areas[inside_regress_range == 0] = INF\n\n        min_area, min_area_inds = areas.min(dim=1)\n        assigned_gt_inds[min_area != INF] = min_area_inds[min_area != INF] +1\n\n        if gt_labels is not None:\n            assigned_labels = assigned_gt_inds.new_full((num_points, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_inds > 0, as_tuple = False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[\n                    assigned_gt_inds[pos_inds] -1]\n        else:\n            assigned_labels = None\n        \n        return AssignResult(\n            num_gts, assigned_gt_inds, None, labels=assigned_labels)\n\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/max_iou_assigner.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom ..iou_calculators import build_iou_calculator\nfrom .assign_result import AssignResult\nfrom .base_assigner import BaseAssigner\n\n\n@BBOX_ASSIGNERS.register_module()\nclass MaxIoUAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each bbox.\n\n    Each proposals will be assigned with `-1`, or a semi-positive integer\n    indicating the ground truth index.\n\n    - -1: negative sample, no assigned gt\n    - semi-positive integer: positive sample, index (0-based) of assigned gt\n\n    Args:\n        pos_iou_thr (float): IoU threshold for positive bboxes.\n        neg_iou_thr (float or tuple): IoU threshold for negative bboxes.\n        min_pos_iou (float): Minimum iou for a bbox to be considered as a\n            positive bbox. Positive samples can have smaller IoU than\n            pos_iou_thr due to the 4th step (assign max IoU sample to each gt).\n        gt_max_assign_all (bool): Whether to assign all bboxes with the same\n            highest overlap with some gt to that gt.\n        ignore_iof_thr (float): IoF threshold for ignoring bboxes (if\n            `gt_bboxes_ignore` is specified). Negative values mean not\n            ignoring any bboxes.\n        ignore_wrt_candidates (bool): Whether to compute the iof between\n            `bboxes` and `gt_bboxes_ignore`, or the contrary.\n        match_low_quality (bool): Whether to allow low quality matches. This is\n            usually allowed for RPN and single stage detectors, but not allowed\n            in the second stage. Details are demonetrated in Step 4.\n        gpu_assign_thr (int): The upper bound of the number of GT for GPU\n            assign. When the number of gt is above this threshold, will assign\n            on CPU device. Negative values mean not assign on CPU.\n    \"\"\"\n\n    def __init__(self,\n                 pos_iou_thr,\n                 neg_iou_thr,\n                 min_pos_iou=.0,\n                 gt_max_assign_all=True,\n                 ignore_iof_thr=-1,\n                 ignore_wrt_candidates=True,\n                 match_low_quality=True,\n                 gpu_assign_thr=-1,\n                 iou_calculator=dict(type='BboxOverlaps2D')):\n        self.pos_iou_thr = pos_iou_thr\n        self.neg_iou_thr = neg_iou_thr\n        self.min_pos_iou = min_pos_iou\n        self.gt_max_assign_all = gt_max_assign_all\n        self.ignore_iof_thr = ignore_iof_thr\n        self.ignore_wrt_candidates = ignore_wrt_candidates\n        self.gpu_assign_thr = gpu_assign_thr\n        self.match_low_quality = match_low_quality\n        self.iou_calculator = build_iou_calculator(iou_calculator)\n\n    def assign(self, bboxes, gt_bboxes, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign gt to bboxes.\n\n        This method assign a gt bbox to every bbox (proposal/anchor), each bbox\n        will be assigned with -1, or a semi-positive number. -1 means negative\n        sample, semi-positive number is the index (0-based) of assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every bbox to the background\n        2. assign proposals whose iou with all gts < neg_iou_thr to 0\n        3. for each bbox, if the iou with its nearest gt >= pos_iou_thr,\n           assign it to that bbox\n        4. for each gt bbox, assign its nearest proposals (may be more than\n           one) to itself\n\n        Args:\n            bboxes (Tensor): Bounding boxes to be assigned, shape(n, 4).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n\n        Example:\n            >>> self = MaxIoUAssigner(0.5, 0.5)\n            >>> bboxes = torch.Tensor([[0, 0, 10, 10], [10, 10, 20, 20]])\n            >>> gt_bboxes = torch.Tensor([[0, 0, 10, 9]])\n            >>> assign_result = self.assign(bboxes, gt_bboxes)\n            >>> expected_gt_inds = torch.LongTensor([1, 0])\n            >>> assert torch.all(assign_result.gt_inds == expected_gt_inds)\n        \"\"\"\n        assign_on_cpu = True if (self.gpu_assign_thr > 0) and (\n            gt_bboxes.shape[0] > self.gpu_assign_thr) else False\n        # compute overlap and assign gt on CPU when number of GT is large\n        if assign_on_cpu:\n            device = bboxes.device\n            bboxes = bboxes.cpu()\n            gt_bboxes = gt_bboxes.cpu()\n            if gt_bboxes_ignore is not None:\n                gt_bboxes_ignore = gt_bboxes_ignore.cpu()\n            if gt_labels is not None:\n                gt_labels = gt_labels.cpu()\n\n        overlaps = self.iou_calculator(gt_bboxes, bboxes)\n\n        if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None\n                and gt_bboxes_ignore.numel() > 0 and bboxes.numel() > 0):\n            if self.ignore_wrt_candidates:\n                ignore_overlaps = self.iou_calculator(\n                    bboxes, gt_bboxes_ignore, mode='iof')\n                ignore_max_overlaps, _ = ignore_overlaps.max(dim=1)\n            else:\n                ignore_overlaps = self.iou_calculator(\n                    gt_bboxes_ignore, bboxes, mode='iof')\n                ignore_max_overlaps, _ = ignore_overlaps.max(dim=0)\n            overlaps[:, ignore_max_overlaps > self.ignore_iof_thr] = -1\n\n        assign_result = self.assign_wrt_overlaps(overlaps, gt_labels)\n        if assign_on_cpu:\n            assign_result.gt_inds = assign_result.gt_inds.to(device)\n            assign_result.max_overlaps = assign_result.max_overlaps.to(device)\n            if assign_result.labels is not None:\n                assign_result.labels = assign_result.labels.to(device)\n        return assign_result\n\n    def assign_wrt_overlaps(self, overlaps, gt_labels=None):\n        \"\"\"Assign w.r.t. the overlaps of bboxes with gts.\n\n        Args:\n            overlaps (Tensor): Overlaps between k gt_bboxes and n bboxes,\n                shape(k, n).\n            gt_labels (Tensor, optional): Labels of k gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        num_gts, num_bboxes = overlaps.size(0), overlaps.size(1)\n\n        # 1. assign -1 by default\n        assigned_gt_inds = overlaps.new_full((num_bboxes, ),\n                                             -1,\n                                             dtype=torch.long)\n\n        if num_gts == 0 or num_bboxes == 0:\n            # No ground truth or boxes, return empty assignment\n            max_overlaps = overlaps.new_zeros((num_bboxes, ))\n            if num_gts == 0:\n                # No truth, assign everything to background\n                assigned_gt_inds[:] = 0\n            if gt_labels is None:\n                assigned_labels = None\n            else:\n                assigned_labels = overlaps.new_full((num_bboxes, ),\n                                                    -1,\n                                                    dtype=torch.long)\n            return AssignResult(\n                num_gts,\n                assigned_gt_inds,\n                max_overlaps,\n                labels=assigned_labels)\n\n        # for each anchor, which gt best overlaps with it\n        # for each anchor, the max iou of all gts\n        max_overlaps, argmax_overlaps = overlaps.max(dim=0)\n        # for each gt, which anchor best overlaps with it\n        # for each gt, the max iou of all proposals\n        gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1)\n\n        # 2. assign negative: below\n        # the negative inds are set to be 0\n        if isinstance(self.neg_iou_thr, float):\n            assigned_gt_inds[(max_overlaps >= 0)\n                             & (max_overlaps < self.neg_iou_thr)] = 0\n        elif isinstance(self.neg_iou_thr, tuple):\n            assert len(self.neg_iou_thr) == 2\n            assigned_gt_inds[(max_overlaps >= self.neg_iou_thr[0])\n                             & (max_overlaps < self.neg_iou_thr[1])] = 0\n\n        # 3. assign positive: above positive IoU threshold\n        pos_inds = max_overlaps >= self.pos_iou_thr\n        assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1\n\n        if self.match_low_quality:\n            # Low-quality matching will overwirte the assigned_gt_inds assigned\n            # in Step 3. Thus, the assigned gt might not be the best one for\n            # prediction.\n            # For example, if bbox A has 0.9 and 0.8 iou with GT bbox 1 & 2,\n            # bbox 1 will be assigned as the best target for bbox A in step 3.\n            # However, if GT bbox 2's gt_argmax_overlaps = A, bbox A's\n            # assigned_gt_inds will be overwritten to be bbox B.\n            # This might be the reason that it is not used in ROI Heads.\n            for i in range(num_gts):\n                if gt_max_overlaps[i] >= self.min_pos_iou:\n                    if self.gt_max_assign_all:\n                        max_iou_inds = overlaps[i, :] == gt_max_overlaps[i]\n                        assigned_gt_inds[max_iou_inds] = i + 1\n                    else:\n                        assigned_gt_inds[gt_argmax_overlaps[i]] = i + 1\n\n        if gt_labels is not None:\n            assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_inds > 0, as_tuple=False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[\n                    assigned_gt_inds[pos_inds] - 1]\n        else:\n            assigned_labels = None\n\n        return AssignResult(\n            num_gts, assigned_gt_inds, max_overlaps, labels=assigned_labels)\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/point_assigner.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom .assign_result import AssignResult\nfrom .base_assigner import BaseAssigner\n\n\n@BBOX_ASSIGNERS.register_module()\nclass PointAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each point.\n\n    Each proposals will be assigned with `0`, or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    \"\"\"\n\n    def __init__(self, scale=4, pos_num=3):\n        self.scale = scale\n        self.pos_num = pos_num\n\n    def assign(self, points, gt_bboxes, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign gt to points.\n\n        This method assign a gt bbox to every points set, each points set\n        will be assigned with  the background_label (-1), or a label number.\n        -1 is background, and semi-positive number is the index (0-based) of\n        assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every points to the background_label (-1)\n        2. A point is assigned to some gt bbox if\n            (i) the point is within the k closest points to the gt bbox\n            (ii) the distance between this point and the gt is smaller than\n                other gt bboxes\n\n        Args:\n            points (Tensor): points to be assigned, shape(n, 3) while last\n                dimension stands for (x, y, stride).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n                NOTE: currently unused.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        num_points = points.shape[0]\n        num_gts = gt_bboxes.shape[0]\n\n        if num_gts == 0 or num_points == 0:\n            # If no truth assign everything to the background\n            assigned_gt_inds = points.new_full((num_points, ),\n                                               0,\n                                               dtype=torch.long)\n            if gt_labels is None:\n                assigned_labels = None\n            else:\n                assigned_labels = points.new_full((num_points, ),\n                                                  -1,\n                                                  dtype=torch.long)\n            return AssignResult(\n                num_gts, assigned_gt_inds, None, labels=assigned_labels)\n\n        points_xy = points[:, :2]\n        points_stride = points[:, 2]\n        points_lvl = torch.log2(\n            points_stride).int()  # [3...,4...,5...,6...,7...]\n        lvl_min, lvl_max = points_lvl.min(), points_lvl.max()\n\n        # assign gt box\n        gt_bboxes_xy = (gt_bboxes[:, :2] + gt_bboxes[:, 2:]) / 2\n        gt_bboxes_wh = (gt_bboxes[:, 2:] - gt_bboxes[:, :2]).clamp(min=1e-6)\n        scale = self.scale\n        gt_bboxes_lvl = ((torch.log2(gt_bboxes_wh[:, 0] / scale) +\n                          torch.log2(gt_bboxes_wh[:, 1] / scale)) / 2).int()\n        gt_bboxes_lvl = torch.clamp(gt_bboxes_lvl, min=lvl_min, max=lvl_max)\n\n        # stores the assigned gt index of each point\n        assigned_gt_inds = points.new_zeros((num_points, ), dtype=torch.long)\n        # stores the assigned gt dist (to this point) of each point\n        assigned_gt_dist = points.new_full((num_points, ), float('inf'))\n        points_range = torch.arange(points.shape[0])\n\n        for idx in range(num_gts):\n            gt_lvl = gt_bboxes_lvl[idx]\n            # get the index of points in this level\n            lvl_idx = gt_lvl == points_lvl\n            points_index = points_range[lvl_idx]\n            # get the points in this level\n            lvl_points = points_xy[lvl_idx, :]\n            # get the center point of gt\n            gt_point = gt_bboxes_xy[[idx], :]\n            # get width and height of gt\n            gt_wh = gt_bboxes_wh[[idx], :]\n            # compute the distance between gt center and\n            #   all points in this level\n            points_gt_dist = ((lvl_points - gt_point) / gt_wh).norm(dim=1)\n            # find the nearest k points to gt center in this level\n            min_dist, min_dist_index = torch.topk(\n                points_gt_dist, self.pos_num, largest=False)\n            # the index of nearest k points to gt center in this level\n            min_dist_points_index = points_index[min_dist_index]\n            # The less_than_recorded_index stores the index\n            #   of min_dist that is less then the assigned_gt_dist. Where\n            #   assigned_gt_dist stores the dist from previous assigned gt\n            #   (if exist) to each point.\n            less_than_recorded_index = min_dist < assigned_gt_dist[\n                min_dist_points_index]\n            # The min_dist_points_index stores the index of points satisfy:\n            #   (1) it is k nearest to current gt center in this level.\n            #   (2) it is closer to current gt center than other gt center.\n            min_dist_points_index = min_dist_points_index[\n                less_than_recorded_index]\n            # assign the result\n            assigned_gt_inds[min_dist_points_index] = idx + 1\n            assigned_gt_dist[min_dist_points_index] = min_dist[\n                less_than_recorded_index]\n\n        if gt_labels is not None:\n            assigned_labels = assigned_gt_inds.new_full((num_points, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_inds > 0, as_tuple=False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[\n                    assigned_gt_inds[pos_inds] - 1]\n        else:\n            assigned_labels = None\n\n        return AssignResult(\n            num_gts, assigned_gt_inds, None, labels=assigned_labels)\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/point_assigner_v2.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom .base_assigner import BaseAssigner\nfrom .assign_result import AssignResult\n\n\n@BBOX_ASSIGNERS.register_module()\nclass PointAssignerV2(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each point.\n\n    Each proposals will be assigned with `0`, or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    \"\"\"\n\n    def __init__(self, scale=4, pos_num=3):\n        self.scale = scale\n        self.pos_num = pos_num\n\n    def assign(self, points, gt_bboxes, gt_bboxes_ignore=None, gt_labels=None):\n        \"\"\"Assign gt to bboxes.\n\n        This method assign a gt bbox to every point, each bbox\n        will be assigned with  0, or a positive number.\n        0 means negative sample, positive number is the index (1-based) of\n        assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every points to 0\n        2. for each gt box, we find the k most closest points to the\n            box center and assign the gt bbox to those points, we also record\n            the minimum distance from each point to the closest gt box. When we\n            assign the bbox to the points, we check whether its distance to the\n            points is closest.\n\n        Args:\n            points (Tensor): points to be assigned, shape(n, 3) while last\n                dimension stands for (x, y, stride).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        INF = 1e8\n        num_gts, num_points = gt_bboxes.shape[0], points.shape[0]\n\n        if num_gts == 0 or num_points == 0:\n            # If no truth assign everything to the background\n            assigned_gt_inds = points.new_full((num_points, ),\n                                               0,\n                                               dtype=torch.long)\n            if gt_labels is None:\n                assigned_labels = None\n            else:\n                assigned_labels = points.new_full((num_points, ),\n                                                  -1,\n                                                  dtype=torch.long)\n            return AssignResult(\n                num_gts, assigned_gt_inds, None, labels=assigned_labels)\n\n        points_xy = points[:, :2]\n        points_stride = points[:, 2]\n        points_lvl = torch.log2(points_stride).int()  # [3...,4...,5...,6...,7...]\n        lvl_min, lvl_max = points_lvl.min(), points_lvl.max()\n\n        # assign gt box\n        gt_bboxes_xy = (gt_bboxes[:, :2] + gt_bboxes[:, 2:]) / 2\n        gt_bboxes_wh = (gt_bboxes[:, 2:] - gt_bboxes[:, :2]).clamp(min=1e-6)\n        scale = self.scale\n        gt_bboxes_lvl = ((torch.log2(gt_bboxes_wh[:, 0] / scale) +\n                          torch.log2(gt_bboxes_wh[:, 1] / scale)) / 2).int()\n        gt_bboxes_lvl = torch.clamp(gt_bboxes_lvl, min=lvl_min, max=lvl_max)\n\n        distances = ((points_xy[:, None, :] - gt_bboxes_xy[None, :, :]) / gt_bboxes_wh[None, :, :]).norm(dim=2)\n        distances[points_lvl[:, None] != gt_bboxes_lvl[None, :]] = INF\n\n        # stores the assigned gt index of each point\n        assigned_gt_inds = points.new_zeros((num_points, ), dtype=torch.long)\n\n        min_dist, min_dist_index = torch.topk(distances, self.pos_num, dim=0, largest=False)\n\n        distances_inf = torch.full_like(distances, INF)\n        distances_inf[min_dist_index, torch.arange(num_gts)] = min_dist\n\n        min_dist, min_dist_index = distances_inf.min(dim=1)\n        assigned_gt_inds[min_dist != INF] = min_dist_index[min_dist != INF] + 1\n\n        if gt_labels is not None:\n            assigned_labels = assigned_gt_inds.new_full((num_points, ), -1)\n            pos_inds = torch.nonzero(\n                assigned_gt_inds > 0, as_tuple=False).squeeze()\n            if pos_inds.numel() > 0:\n                assigned_labels[pos_inds] = gt_labels[\n                    assigned_gt_inds[pos_inds] - 1]\n        else:\n            assigned_labels = None\n\n        return AssignResult(\n            num_gts, assigned_gt_inds, None, labels=assigned_labels)\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/point_ct_assigner.py",
    "content": "import numpy as np\nimport cv2\nimport mmcv\n\nimport torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom .base_assigner import BaseAssigner\nfrom .assign_result import AssignResult\n\n\n@BBOX_ASSIGNERS.register_module()\nclass PointCTAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each point.\n\n    Each proposals will be assigned with `0`, or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    \"\"\"\n    def assign(self, points, gt_bboxes, gt_contours, sizes):\n        \"\"\"Assign gt to bboxes.\n\n        This method assign a gt bbox to every point, each bbox\n        will be assigned with  0, or a positive number.\n        0 means negative sample, positive number is the index (1-based) of\n        assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every points to 0\n        2. for each gt box, we find the k most closest points to the\n            box center and assign the gt bbox to those points, we also record\n            the minimum distance from each point to the closest gt box. When we\n            assign the bbox to the points, we check whether its distance to the\n            points is closest.\n\n        Args:\n            points (Tensor): points to be assigned, shape(n, 3) while last\n                dimension stands for (x, y, stride).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        num_gts, num_points = gt_contours.shape[0], points.shape[0]\n\n        if num_points == 0 or num_gts == 0:\n            # stores the assigned gt heatmap of each point\n            assigned_gt_ct = points.new_ones((num_points,), dtype=torch.long)\n            # stores the assigned gt dist (to this point) of each point\n            assigned_gt_offsets = points.new_zeros((num_points, 2), dtype=torch.float32)\n            pos_inds = torch.nonzero(assigned_gt_ct == 0, as_tuple=False).squeeze(-1).unique()\n            neg_inds = torch.nonzero(assigned_gt_ct > 0, as_tuple=False).squeeze(-1).unique()\n\n            return assigned_gt_ct, assigned_gt_offsets, pos_inds, neg_inds\n\n        points_range = torch.arange(num_points)\n        points_xy = points[:, :2]\n        points_stride = points[:, 2]\n        points_lvl = torch.log2(points_stride).int()  # [3...,4...,5...,6...,7...]\n        lvl_min, lvl_max = points_lvl.min(), points_lvl.max()\n\n        # stores the assigned gt heatmap of each point\n        assigned_gt_ct = points.new_ones((num_points,), dtype=torch.long)\n        # stores the assigned gt dist (to this point) of each point\n        assigned_gt_offsets = points.new_zeros((num_points, 2), dtype=torch.float32)\n\n        lvls = torch.arange(lvl_min, lvl_max + 1, dtype=points_lvl.dtype, device=points_lvl.device)\n        for gt_lvl in lvls:\n            lvl_size = sizes[gt_lvl - 3]\n\n            lvl_idx = gt_lvl == points_lvl\n            points_index = points_range[lvl_idx]\n\n            # generate contours\n            downscale_factor = torch.pow(2, gt_lvl)\n\n            f_lvl_contours_pts_x = (gt_contours[:, 0] / downscale_factor).clamp_(max=lvl_size[1] - 1)\n            lvl_contours_pts_x = torch.round(f_lvl_contours_pts_x)\n            f_lvl_contours_pts_y = (gt_contours[:, 1] / downscale_factor).clamp_(max=lvl_size[0] - 1)\n            lvl_contours_pts_y = torch.round(f_lvl_contours_pts_y)\n            lvl_indices = (lvl_contours_pts_x + lvl_contours_pts_y * lvl_size[1]).to(torch.long)\n\n            points_index = points_index[lvl_indices]\n\n            assigned_gt_offsets[points_index, 0] = f_lvl_contours_pts_x - lvl_contours_pts_x\n            assigned_gt_offsets[points_index, 1] = f_lvl_contours_pts_y - lvl_contours_pts_y\n\n            assigned_gt_ct[points_index] = 0\n\n        pos_inds = torch.nonzero(assigned_gt_ct == 0, as_tuple=False).squeeze(-1).unique()\n        neg_inds = torch.nonzero(assigned_gt_ct > 0, as_tuple=False).squeeze(-1).unique()\n\n        return assigned_gt_ct, assigned_gt_offsets, pos_inds, neg_inds\n"
  },
  {
    "path": "code/mmdet/core/bbox/assigners/point_hm_assigner.py",
    "content": "import torch\n\nfrom ..builder import BBOX_ASSIGNERS\nfrom .base_assigner import BaseAssigner\nfrom .assign_result import AssignResult\n\n\n@BBOX_ASSIGNERS.register_module()\nclass PointHMAssigner(BaseAssigner):\n    \"\"\"Assign a corresponding gt bbox or background to each point.\n\n    Each proposals will be assigned with `0`, or a positive integer\n    indicating the ground truth index.\n\n    - 0: negative sample, no assigned gt\n    - positive integer: positive sample, index (1-based) of assigned gt\n\n    \"\"\"\n\n    def __init__(self, gaussian_bump=False, gaussian_iou=0.7):\n        self.gaussian_bump = gaussian_bump\n        self.gaussian_iou = gaussian_iou\n\n    def assign(self, points, gt_bboxes, gt_labels=None):\n        \"\"\"Assign gt to bboxes.\n\n        This method assign a gt bbox to every point, each bbox\n        will be assigned with  0, or a positive number.\n        0 means negative sample, positive number is the index (1-based) of\n        assigned gt.\n        The assignment is done in following steps, the order matters.\n\n        1. assign every points to 0\n        2. for each gt box, we find the k most closest points to the\n            box center and assign the gt bbox to those points, we also record\n            the minimum distance from each point to the closest gt box. When we\n            assign the bbox to the points, we check whether its distance to the\n            points is closest.\n\n        Args:\n            points (Tensor): points to be assigned, shape(n, 3) while last\n                dimension stands for (x, y, stride).\n            gt_bboxes (Tensor): Groundtruth boxes, shape (k, 4).\n            gt_bboxes_ignore (Tensor, optional): Ground truth bboxes that are\n                labelled as `ignored`, e.g., crowd boxes in COCO.\n            gt_labels (Tensor, optional): Label of gt_bboxes, shape (k, ).\n\n        Returns:\n            :obj:`AssignResult`: The assign result.\n        \"\"\"\n        INF = 1e8\n        num_gts, num_points = gt_bboxes.shape[0], points.shape[0]\n\n        if self.gaussian_bump:\n            dtype = torch.float32\n        else:\n            dtype = torch.long\n        if num_points == 0 or num_gts == 0:\n            assigned_gt_hm_tl = points.new_zeros((num_points,), dtype=dtype)\n            assigned_gt_hm_br = points.new_zeros((num_points,), dtype=dtype)\n            # stores the assigned gt dist (to this point) of each point\n            assigned_gt_offset_tl = points.new_zeros((num_points, 2), dtype=torch.float32)\n            assigned_gt_offset_br = points.new_zeros((num_points, 2), dtype=torch.float32)\n\n            pos_inds_tl = torch.nonzero(assigned_gt_hm_tl == 1, as_tuple=False).squeeze(-1).unique()\n            pos_inds_br = torch.nonzero(assigned_gt_hm_br == 1, as_tuple=False).squeeze(-1).unique()\n            neg_inds_tl = torch.nonzero(assigned_gt_hm_tl < 1, as_tuple=False).squeeze(-1).unique()\n            neg_inds_br = torch.nonzero(assigned_gt_hm_br < 1, as_tuple=False).squeeze(-1).unique()\n\n            return assigned_gt_hm_tl, assigned_gt_offset_tl, pos_inds_tl, neg_inds_tl, \\\n                   assigned_gt_hm_br, assigned_gt_offset_br, pos_inds_br, neg_inds_br\n        points_range = torch.arange(num_points)\n        points_xy = points[:, :2]\n        points_stride = points[:, 2]\n        points_lvl = torch.log2(points_stride).int()  # [3...,4...,5...,6...,7...]\n        lvl_min, lvl_max = points_lvl.min(), points_lvl.max()\n\n        # assign gt box\n        gt_bboxes_xtl, gt_bboxes_ytl, gt_bboxes_xbr, gt_bboxes_ybr = torch.chunk(gt_bboxes, 4, dim=1)\n        gt_bboxes_xytl = torch.cat([gt_bboxes_xtl, gt_bboxes_ytl], -1)\n        gt_bboxes_xybr = torch.cat([gt_bboxes_xbr, gt_bboxes_ybr], -1)\n        if self.gaussian_bump:\n            gt_bboxes_w = gt_bboxes[:, 2] - gt_bboxes[:, 0]\n            gt_bboxes_h = gt_bboxes[:, 3] - gt_bboxes[:, 1]\n            radius = gaussian_radius((gt_bboxes_h, gt_bboxes_w), self.gaussian_iou)\n            diameter = 2 * radius + 1\n            sigma = diameter / 6\n        else:\n            radius = None\n\n        distances_tl = (points_xy[:, None, :] - gt_bboxes_xytl[None, :, :]).norm(dim=2)\n        distances_br = (points_xy[:, None, :] - gt_bboxes_xybr[None, :, :]).norm(dim=2)\n\n        # stores the assigned gt heatmap of each point\n        assigned_gt_hm_tl = points.new_zeros((num_points,), dtype=dtype)\n        assigned_gt_hm_br = points.new_zeros((num_points,), dtype=dtype)\n        # stores the assigned gt dist (to this point) of each point\n        assigned_gt_offset_tl = points.new_zeros((num_points, 2), dtype=torch.float32)\n        assigned_gt_offset_br = points.new_zeros((num_points, 2), dtype=torch.float32)\n\n        lvls = torch.arange(lvl_min, lvl_max + 1, dtype=points_lvl.dtype, device=points_lvl.device)\n        for gt_lvl in lvls:\n            lvl_idx = gt_lvl == points_lvl\n            points_index = points_range[lvl_idx]\n            lvl_points = points_xy[lvl_idx, :]\n\n            downscale_factor = torch.pow(2, gt_lvl)\n            lvl_distances_tl = distances_tl[lvl_idx, :]\n            lvl_distances_br = distances_br[lvl_idx, :]\n\n            _, min_dist_index_tl = lvl_distances_tl.min(dim=0)\n            min_dist_points_index_tl = points_index[min_dist_index_tl]\n\n            assigned_gt_offset_tl[min_dist_points_index_tl, :] = \\\n                (gt_bboxes_xytl - lvl_points[min_dist_index_tl, :]) / downscale_factor\n\n            _, min_dist_index_br = lvl_distances_br.min(dim=0)\n            min_dist_points_index_br = points_index[min_dist_index_br]\n            assigned_gt_offset_br[min_dist_points_index_br, :] = \\\n                (gt_bboxes_xybr - lvl_points[min_dist_index_br, :]) / downscale_factor\n            if self.gaussian_bump:\n                out_index_tl = lvl_distances_tl >= radius[None, :]\n                lvl_gaussian_tl = torch.exp(-torch.pow(lvl_distances_tl, 2) / (2 * sigma * sigma)[None, :])\n                lvl_gaussian_tl[out_index_tl] = -INF\n                max_gaussian_tl, _ = lvl_gaussian_tl.max(dim=1)\n                assigned_gt_hm_tl[points_index[max_gaussian_tl != -INF]] = max_gaussian_tl[max_gaussian_tl != -INF]\n\n                out_index_br = lvl_distances_br >= radius[None, :]\n                lvl_gaussian_br = torch.exp(-torch.pow(lvl_distances_br, 2) / (2 * sigma * sigma)[None, :])\n                lvl_gaussian_br[out_index_br] = -INF\n                max_gaussian_br, _ = lvl_gaussian_br.max(dim=1)\n                assigned_gt_hm_br[points_index[max_gaussian_br != -INF]] = max_gaussian_br[max_gaussian_br != -INF]\n            assigned_gt_hm_tl[min_dist_points_index_tl] = 1\n            assigned_gt_hm_br[min_dist_points_index_br] = 1\n\n        pos_inds_tl = torch.nonzero(assigned_gt_hm_tl == 1, as_tuple=False).squeeze(-1).unique()\n        pos_inds_br = torch.nonzero(assigned_gt_hm_br == 1, as_tuple=False).squeeze(-1).unique()\n        neg_inds_tl = torch.nonzero(assigned_gt_hm_tl < 1, as_tuple=False).squeeze(-1).unique()\n        neg_inds_br = torch.nonzero(assigned_gt_hm_br < 1, as_tuple=False).squeeze(-1).unique()\n\n        return assigned_gt_hm_tl, assigned_gt_offset_tl, pos_inds_tl, neg_inds_tl, \\\n               assigned_gt_hm_br, assigned_gt_offset_br, pos_inds_br, neg_inds_br\n\n\ndef gaussian_radius(det_size, min_overlap):\n    height, width = det_size\n\n    a1  = 1\n    b1  = (height + width)\n    c1  = width * height * (1 - min_overlap) / (1 + min_overlap)\n    sq1 = torch.sqrt(b1 ** 2 - 4 * a1 * c1)\n    r1  = (b1 - sq1) / (2 * a1)\n\n    a2  = 4\n    b2  = 2 * (height + width)\n    c2  = (1 - min_overlap) * width * height\n    sq2 = torch.sqrt(b2 ** 2 - 4 * a2 * c2)\n    r2  = (b2 - sq2) / (2 * a2)\n\n    a3  = 4 * min_overlap\n    b3  = -2 * min_overlap * (height + width)\n    c3  = (min_overlap - 1) * width * height\n    sq3 = torch.sqrt(b3 ** 2 - 4 * a3 * c3)\n    r3  = (b3 + sq3) / (2 * a3)\n\n    r = torch.stack([r1, r2, r3], dim=1)\n    return torch.min(r, dim=1)[0]"
  },
  {
    "path": "code/mmdet/core/bbox/builder.py",
    "content": "from mmcv.utils import Registry, build_from_cfg\n\nBBOX_ASSIGNERS = Registry('bbox_assigner')\nBBOX_SAMPLERS = Registry('bbox_sampler')\nBBOX_CODERS = Registry('bbox_coder')\n\n\ndef build_assigner(cfg, **default_args):\n    \"\"\"Builder of box assigner\"\"\"\n    return build_from_cfg(cfg, BBOX_ASSIGNERS, default_args)\n\n\ndef build_sampler(cfg, **default_args):\n    \"\"\"Builder of box sampler\"\"\"\n    return build_from_cfg(cfg, BBOX_SAMPLERS, default_args)\n\n\ndef build_bbox_coder(cfg, **default_args):\n    \"\"\"Builder of box coder\"\"\"\n    return build_from_cfg(cfg, BBOX_CODERS, default_args)\n"
  },
  {
    "path": "code/mmdet/core/bbox/coder/__init__.py",
    "content": "from .base_bbox_coder import BaseBBoxCoder\nfrom .delta_xywh_bbox_coder import DeltaXYWHBBoxCoder\nfrom .legacy_delta_xywh_bbox_coder import LegacyDeltaXYWHBBoxCoder\nfrom .pseudo_bbox_coder import PseudoBBoxCoder\nfrom .tblr_bbox_coder import TBLRBBoxCoder\n\n__all__ = [\n    'BaseBBoxCoder', 'PseudoBBoxCoder', 'DeltaXYWHBBoxCoder',\n    'LegacyDeltaXYWHBBoxCoder', 'TBLRBBoxCoder'\n]\n"
  },
  {
    "path": "code/mmdet/core/bbox/coder/base_bbox_coder.py",
    "content": "from abc import ABCMeta, abstractmethod\n\n\nclass BaseBBoxCoder(metaclass=ABCMeta):\n    \"\"\"Base bounding box coder\"\"\"\n\n    def __init__(self, **kwargs):\n        pass\n\n    @abstractmethod\n    def encode(self, bboxes, gt_bboxes):\n        \"\"\"Encode deltas between bboxes and ground truth boxes\"\"\"\n        pass\n\n    @abstractmethod\n    def decode(self, bboxes, bboxes_pred):\n        \"\"\"\n        Decode the predicted bboxes according to prediction and base boxes\n        \"\"\"\n        pass\n"
  },
  {
    "path": "code/mmdet/core/bbox/coder/delta_xywh_bbox_coder.py",
    "content": "import numpy as np\nimport torch\n\nfrom ..builder import BBOX_CODERS\nfrom .base_bbox_coder import BaseBBoxCoder\n\n\n@BBOX_CODERS.register_module()\nclass DeltaXYWHBBoxCoder(BaseBBoxCoder):\n    \"\"\"Delta XYWH BBox coder\n\n    Following the practice in `R-CNN <https://arxiv.org/abs/1311.2524>`_,\n    this coder encodes bbox (x1, y1, x2, y2) into delta (dx, dy, dw, dh) and\n    decodes delta (dx, dy, dw, dh) back to original bbox (x1, y1, x2, y2).\n\n    Args:\n        target_means (Sequence[float]): Denormalizing means of target for\n            delta coordinates\n        target_stds (Sequence[float]): Denormalizing standard deviation of\n            target for delta coordinates\n    \"\"\"\n\n    def __init__(self,\n                 target_means=(0., 0., 0., 0.),\n                 target_stds=(1., 1., 1., 1.)):\n        super(BaseBBoxCoder, self).__init__()\n        self.means = target_means\n        self.stds = target_stds\n\n    def encode(self, bboxes, gt_bboxes):\n        \"\"\"Get box regression transformation deltas that can be used\n        to transform the `bboxes` into the `gt_bboxes`.\n\n        Args:\n            bboxes (torch.Tensor): Source boxes, e.g., object proposals.\n            gt_bboxes (torch.Tensor): Target of the transformation, e.g.,\n                ground-truth boxes.\n\n        Returns:\n            torch.Tensor: Box transformation deltas\n        \"\"\"\n\n        assert bboxes.size(0) == gt_bboxes.size(0)\n        assert bboxes.size(-1) == gt_bboxes.size(-1) == 4\n        encoded_bboxes = bbox2delta(bboxes, gt_bboxes, self.means, self.stds)\n        return encoded_bboxes\n\n    def decode(self,\n               bboxes,\n               pred_bboxes,\n               max_shape=None,\n               wh_ratio_clip=16 / 1000):\n        \"\"\"Apply transformation `pred_bboxes` to `boxes`.\n\n        Args:\n            boxes (torch.Tensor): Basic boxes.\n            pred_bboxes (torch.Tensor): Encoded boxes with shape\n            max_shape (tuple[int], optional): Maximum shape of boxes.\n                Defaults to None.\n            wh_ratio_clip (float, optional): The allowed ratio between\n                width and height.\n\n        Returns:\n            torch.Tensor: Decoded boxes.\n        \"\"\"\n\n        assert pred_bboxes.size(0) == bboxes.size(0)\n        decoded_bboxes = delta2bbox(bboxes, pred_bboxes, self.means, self.stds,\n                                    max_shape, wh_ratio_clip)\n\n        return decoded_bboxes\n\n\ndef bbox2delta(proposals, gt, means=(0., 0., 0., 0.), stds=(1., 1., 1., 1.)):\n    \"\"\"Compute deltas of proposals w.r.t. gt.\n\n    We usually compute the deltas of x, y, w, h of proposals w.r.t ground\n    truth bboxes to get regression target.\n    This is the inverse function of `delta2bbox()`\n\n    Args:\n        proposals (Tensor): Boxes to be transformed, shape (N, ..., 4)\n        gt (Tensor): Gt bboxes to be used as base, shape (N, ..., 4)\n        means (Sequence[float]): Denormalizing means for delta coordinates\n        stds (Sequence[float]): Denormalizing standard deviation for delta\n            coordinates\n\n    Returns:\n        Tensor: deltas with shape (N, 4), where columns represent dx, dy,\n            dw, dh.\n\n    \"\"\"\n    assert proposals.size() == gt.size()\n\n    proposals = proposals.float()\n    gt = gt.float()\n    px = (proposals[..., 0] + proposals[..., 2]) * 0.5\n    py = (proposals[..., 1] + proposals[..., 3]) * 0.5\n    pw = proposals[..., 2] - proposals[..., 0]\n    ph = proposals[..., 3] - proposals[..., 1]\n\n    gx = (gt[..., 0] + gt[..., 2]) * 0.5\n    gy = (gt[..., 1] + gt[..., 3]) * 0.5\n    gw = gt[..., 2] - gt[..., 0]\n    gh = gt[..., 3] - gt[..., 1]\n\n    dx = (gx - px) / pw\n    dy = (gy - py) / ph\n    dw = torch.log(gw / pw)\n    dh = torch.log(gh / ph)\n    deltas = torch.stack([dx, dy, dw, dh], dim=-1)\n\n    means = deltas.new_tensor(means).unsqueeze(0)\n    stds = deltas.new_tensor(stds).unsqueeze(0)\n    deltas = deltas.sub_(means).div_(stds)\n\n    return deltas\n\n\ndef delta2bbox(rois,\n               deltas,\n               means=(0., 0., 0., 0.),\n               stds=(1., 1., 1., 1.),\n               max_shape=None,\n               wh_ratio_clip=16 / 1000):\n    \"\"\"Apply deltas to shift/scale base boxes.\n\n    Typically the rois are anchor or proposed bounding boxes and the deltas are\n    network outputs used to shift/scale those boxes.\n    This is the inverse function of `bbox2delta()`\n\n    Args:\n        rois (Tensor): Boxes to be transformed. Has shape (N, 4)\n        deltas (Tensor): Encoded offsets with respect to each roi.\n            Has shape (N, 4 * num_classes). Note N = num_anchors * W * H when\n            rois is a grid of anchors. Offset encoding follows [1]_.\n        means (Sequence[float]): Denormalizing means for delta coordinates\n        stds (Sequence[float]): Denormalizing standard deviation for delta\n            coordinates\n        max_shape (tuple[int, int]): Maximum bounds for boxes. specifies (H, W)\n        wh_ratio_clip (float): Maximum aspect ratio for boxes.\n\n    Returns:\n        Tensor: Boxes with shape (N, 4), where columns represent\n            tl_x, tl_y, br_x, br_y.\n\n    References:\n        .. [1] https://arxiv.org/abs/1311.2524\n\n    Example:\n        >>> rois = torch.Tensor([[ 0.,  0.,  1.,  1.],\n        >>>                      [ 0.,  0.,  1.,  1.],\n        >>>                      [ 0.,  0.,  1.,  1.],\n        >>>                      [ 5.,  5.,  5.,  5.]])\n        >>> deltas = torch.Tensor([[  0.,   0.,   0.,   0.],\n        >>>                        [  1.,   1.,   1.,   1.],\n        >>>                        [  0.,   0.,   2.,  -1.],\n        >>>                        [ 0.7, -1.9, -0.5,  0.3]])\n        >>> delta2bbox(rois, deltas, max_shape=(32, 32))\n        tensor([[0.0000, 0.0000, 1.0000, 1.0000],\n                [0.1409, 0.1409, 2.8591, 2.8591],\n                [0.0000, 0.3161, 4.1945, 0.6839],\n                [5.0000, 5.0000, 5.0000, 5.0000]])\n    \"\"\"\n    means = deltas.new_tensor(means).repeat(1, deltas.size(1) // 4)\n    stds = deltas.new_tensor(stds).repeat(1, deltas.size(1) // 4)\n    denorm_deltas = deltas * stds + means\n    dx = denorm_deltas[:, 0::4]\n    dy = denorm_deltas[:, 1::4]\n    dw = denorm_deltas[:, 2::4]\n    dh = denorm_deltas[:, 3::4]\n    max_ratio = np.abs(np.log(wh_ratio_clip))\n    dw = dw.clamp(min=-max_ratio, max=max_ratio)\n    dh = dh.clamp(min=-max_ratio, max=max_ratio)\n    # Compute center of each roi\n    px = ((rois[:, 0] + rois[:, 2]) * 0.5).unsqueeze(1).expand_as(dx)\n    py = ((rois[:, 1] + rois[:, 3]) * 0.5).unsqueeze(1).expand_as(dy)\n    # Compute width/height of each roi\n    pw = (rois[:, 2] - rois[:, 0]).unsqueeze(1).expand_as(dw)\n    ph = (rois[:, 3] - rois[:, 1]).unsqueeze(1).expand_as(dh)\n    # Use exp(network energy) to enlarge/shrink each roi\n    gw = pw * dw.exp()\n    gh = ph * dh.exp()\n    # Use network energy to shift the center of each roi\n    gx = px + pw * dx\n    gy = py + ph * dy\n    # Convert center-xy/width/height to top-left, bottom-right\n    x1 = gx - gw * 0.5\n    y1 = gy - gh * 0.5\n    x2 = gx + gw * 0.5\n    y2 = gy + gh * 0.5\n    if max_shape is not None:\n        x1 = x1.clamp(min=0, max=max_shape[1])\n        y1 = y1.clamp(min=0, max=max_shape[0])\n        x2 = x2.clamp(min=0, max=max_shape[1])\n        y2 = y2.clamp(min=0, max=max_shape[0])\n    bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view_as(deltas)\n    return bboxes\n"
  },
  {
    "path": "code/mmdet/core/bbox/coder/legacy_delta_xywh_bbox_coder.py",
    "content": "import numpy as np\nimport torch\n\nfrom ..builder import BBOX_CODERS\nfrom .base_bbox_coder import BaseBBoxCoder\n\n\n@BBOX_CODERS.register_module()\nclass LegacyDeltaXYWHBBoxCoder(BaseBBoxCoder):\n    \"\"\"Legacy Delta XYWH BBox coder used in MMDet V1.x\n\n    Following the practice in R-CNN [1]_, this coder encodes bbox (x1, y1, x2,\n    y2) into delta (dx, dy, dw, dh) and decodes delta (dx, dy, dw, dh)\n    back to original bbox (x1, y1, x2, y2).\n\n    Note:\n        The main difference between `LegacyDeltaXYWHBBoxCoder` and\n        `DeltaXYWHBBoxCoder` is whether ``+ 1`` is used during width and height\n        calculation. We suggest to only use this coder when testing with\n        MMDet V1.x models.\n\n    References:\n        .. [1] https://arxiv.org/abs/1311.2524\n\n    Args:\n        target_means (Sequence[float]): denormalizing means of target for\n            delta coordinates\n        target_stds (Sequence[float]): denormalizing standard deviation of\n            target for delta coordinates\n    \"\"\"\n\n    def __init__(self,\n                 target_means=(0., 0., 0., 0.),\n                 target_stds=(1., 1., 1., 1.)):\n        super(BaseBBoxCoder, self).__init__()\n        self.means = target_means\n        self.stds = target_stds\n\n    def encode(self, bboxes, gt_bboxes):\n        \"\"\"Get box regression transformation deltas that can be used\n        to transform the `bboxes` into the `gt_bboxes`.\n\n        Args:\n            bboxes (torch.Tensor): source boxes, e.g., object proposals.\n            gt_bboxes (torch.Tensor): target of the transformation, e.g.,\n                ground-truth boxes.\n\n        Returns:\n            torch.Tensor: Box transformation deltas\n        \"\"\"\n        assert bboxes.size(0) == gt_bboxes.size(0)\n        assert bboxes.size(-1) == gt_bboxes.size(-1) == 4\n        encoded_bboxes = legacy_bbox2delta(bboxes, gt_bboxes, self.means,\n                                           self.stds)\n        return encoded_bboxes\n\n    def decode(self,\n               bboxes,\n               pred_bboxes,\n               max_shape=None,\n               wh_ratio_clip=16 / 1000):\n        \"\"\"Apply transformation `pred_bboxes` to `boxes`.\n\n        Args:\n            boxes (torch.Tensor): Basic boxes.\n            pred_bboxes (torch.Tensor): Encoded boxes with shape\n            max_shape (tuple[int], optional): Maximum shape of boxes.\n                Defaults to None.\n            wh_ratio_clip (float, optional): The allowed ratio between\n                width and height.\n\n        Returns:\n            torch.Tensor: Decoded boxes.\n        \"\"\"\n        assert pred_bboxes.size(0) == bboxes.size(0)\n        decoded_bboxes = legacy_delta2bbox(bboxes, pred_bboxes, self.means,\n                                           self.stds, max_shape, wh_ratio_clip)\n\n        return decoded_bboxes\n\n\ndef legacy_bbox2delta(proposals,\n                      gt,\n                      means=(0., 0., 0., 0.),\n                      stds=(1., 1., 1., 1.)):\n    \"\"\"Compute deltas of proposals w.r.t. gt in the MMDet V1.x manner.\n\n    We usually compute the deltas of x, y, w, h of proposals w.r.t ground\n    truth bboxes to get regression target.\n    This is the inverse function of `delta2bbox()`\n\n    Args:\n        proposals (Tensor): Boxes to be transformed, shape (N, ..., 4)\n        gt (Tensor): Gt bboxes to be used as base, shape (N, ..., 4)\n        means (Sequence[float]): Denormalizing means for delta coordinates\n        stds (Sequence[float]): Denormalizing standard deviation for delta\n            coordinates\n\n    Returns:\n        Tensor: deltas with shape (N, 4), where columns represent dx, dy,\n            dw, dh.\n\n    \"\"\"\n    assert proposals.size() == gt.size()\n\n    proposals = proposals.float()\n    gt = gt.float()\n    px = (proposals[..., 0] + proposals[..., 2]) * 0.5\n    py = (proposals[..., 1] + proposals[..., 3]) * 0.5\n    pw = proposals[..., 2] - proposals[..., 0] + 1.0\n    ph = proposals[..., 3] - proposals[..., 1] + 1.0\n\n    gx = (gt[..., 0] + gt[..., 2]) * 0.5\n    gy = (gt[..., 1] + gt[..., 3]) * 0.5\n    gw = gt[..., 2] - gt[..., 0] + 1.0\n    gh = gt[..., 3] - gt[..., 1] + 1.0\n\n    dx = (gx - px) / pw\n    dy = (gy - py) / ph\n    dw = torch.log(gw / pw)\n    dh = torch.log(gh / ph)\n    deltas = torch.stack([dx, dy, dw, dh], dim=-1)\n\n    means = deltas.new_tensor(means).unsqueeze(0)\n    stds = deltas.new_tensor(stds).unsqueeze(0)\n    deltas = deltas.sub_(means).div_(stds)\n\n    return deltas\n\n\ndef legacy_delta2bbox(rois,\n                      deltas,\n                      means=(0., 0., 0., 0.),\n                      stds=(1., 1., 1., 1.),\n                      max_shape=None,\n                      wh_ratio_clip=16 / 1000):\n    \"\"\"Apply deltas to shift/scale base boxes in the MMDet V1.x manner.\n\n    Typically the rois are anchor or proposed bounding boxes and the deltas are\n    network outputs used to shift/scale those boxes.\n    This is the inverse function of `bbox2delta()`\n\n    Args:\n        rois (Tensor): Boxes to be transformed. Has shape (N, 4)\n        deltas (Tensor): Encoded offsets with respect to each roi.\n            Has shape (N, 4 * num_classes). Note N = num_anchors * W * H when\n            rois is a grid of anchors. Offset encoding follows [1]_.\n        means (Sequence[float]): Denormalizing means for delta coordinates\n        stds (Sequence[float]): Denormalizing standard deviation for delta\n            coordinates\n        max_shape (tuple[int, int]): Maximum bounds for boxes. specifies (H, W)\n        wh_ratio_clip (float): Maximum aspect ratio for boxes.\n\n    Returns:\n        Tensor: Boxes with shape (N, 4), where columns represent\n            tl_x, tl_y, br_x, br_y.\n\n    References:\n        .. [1] https://arxiv.org/abs/1311.2524\n\n    Example:\n        >>> rois = torch.Tensor([[ 0.,  0.,  1.,  1.],\n        >>>                      [ 0.,  0.,  1.,  1.],\n        >>>                      [ 0.,  0.,  1.,  1.],\n        >>>                      [ 5.,  5.,  5.,  5.]])\n        >>> deltas = torch.Tensor([[  0.,   0.,   0.,   0.],\n        >>>                        [  1.,   1.,   1.,   1.],\n        >>>                        [  0.,   0.,   2.,  -1.],\n        >>>                        [ 0.7, -1.9, -0.5,  0.3]])\n        >>> legacy_delta2bbox(rois, deltas, max_shape=(32, 32))\n        tensor([[0.0000, 0.0000, 1.5000, 1.5000],\n                [0.0000, 0.0000, 5.2183, 5.2183],\n                [0.0000, 0.1321, 7.8891, 0.8679],\n                [5.3967, 2.4251, 6.0033, 3.7749]])\n    \"\"\"\n    means = deltas.new_tensor(means).repeat(1, deltas.size(1) // 4)\n    stds = deltas.new_tensor(stds).repeat(1, deltas.size(1) // 4)\n    denorm_deltas = deltas * stds + means\n    dx = denorm_deltas[:, 0::4]\n    dy = denorm_deltas[:, 1::4]\n    dw = denorm_deltas[:, 2::4]\n    dh = denorm_deltas[:, 3::4]\n    max_ratio = np.abs(np.log(wh_ratio_clip))\n    dw = dw.clamp(min=-max_ratio, max=max_ratio)\n    dh = dh.clamp(min=-max_ratio, max=max_ratio)\n    # Compute center of each roi\n    px = ((rois[:, 0] + rois[:, 2]) * 0.5).unsqueeze(1).expand_as(dx)\n    py = ((rois[:, 1] + rois[:, 3]) * 0.5).unsqueeze(1).expand_as(dy)\n    # Compute width/height of each roi\n    pw = (rois[:, 2] - rois[:, 0] + 1.0).unsqueeze(1).expand_as(dw)\n    ph = (rois[:, 3] - rois[:, 1] + 1.0).unsqueeze(1).expand_as(dh)\n    # Use exp(network energy) to enlarge/shrink each roi\n    gw = pw * dw.exp()\n    gh = ph * dh.exp()\n    # Use network energy to shift the center of each roi\n    gx = px + pw * dx\n    gy = py + ph * dy\n    # Convert center-xy/width/height to top-left, bottom-right\n\n    # The true legacy box coder should +- 0.5 here.\n    # However, current implementation improves the performance when testing\n    # the models trained in MMDetection 1.X (~0.5 bbox AP, 0.2 mask AP)\n    x1 = gx - gw * 0.5\n    y1 = gy - gh * 0.5\n    x2 = gx + gw * 0.5\n    y2 = gy + gh * 0.5\n    if max_shape is not None:\n        x1 = x1.clamp(min=0, max=max_shape[1] - 1)\n        y1 = y1.clamp(min=0, max=max_shape[0] - 1)\n        x2 = x2.clamp(min=0, max=max_shape[1] - 1)\n        y2 = y2.clamp(min=0, max=max_shape[0] - 1)\n    bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view_as(deltas)\n    return bboxes\n"
  },
  {
    "path": "code/mmdet/core/bbox/coder/pseudo_bbox_coder.py",
    "content": "from ..builder import BBOX_CODERS\nfrom .base_bbox_coder import BaseBBoxCoder\n\n\n@BBOX_CODERS.register_module()\nclass PseudoBBoxCoder(BaseBBoxCoder):\n    \"\"\"Pseudo bounding box coder\"\"\"\n\n    def __init__(self, **kwargs):\n        super(BaseBBoxCoder, self).__init__(**kwargs)\n\n    def encode(self, bboxes, gt_bboxes):\n        \"\"\"torch.Tensor: return the given ``bboxes``\"\"\"\n        return gt_bboxes\n\n    def decode(self, bboxes, pred_bboxes):\n        \"\"\"torch.Tensor: return the given ``pred_bboxes``\"\"\"\n        return pred_bboxes\n"
  },
  {
    "path": "code/mmdet/core/bbox/coder/tblr_bbox_coder.py",
    "content": "import torch\n\nfrom ..builder import BBOX_CODERS\nfrom .base_bbox_coder import BaseBBoxCoder\n\n\n@BBOX_CODERS.register_module()\nclass TBLRBBoxCoder(BaseBBoxCoder):\n    \"\"\"TBLR BBox coder\n\n    Following the practice in `FSAF <https://arxiv.org/abs/1903.00621>`_,\n    this coder encodes gt bboxes (x1, y1, x2, y2) into (top, bottom, left,\n    right) and decode it back to the original.\n\n    Args:\n        normalizer (list | float): Normalization factor to be\n          divided with when coding the coordinates. If it is a list, it should\n          have length of 4 indicating normalization factor in tblr dims.\n          Otherwise it is a unified float factor for all dims. Default: 4.0\n    \"\"\"\n\n    def __init__(self, normalizer=4.0):\n        super(BaseBBoxCoder, self).__init__()\n        self.normalizer = normalizer\n\n    def encode(self, bboxes, gt_bboxes):\n        \"\"\"Get box regression transformation deltas that can be used\n        to transform the `bboxes` into the `gt_bboxes` in the top,\n        left, bottom, right order.\n\n        Args:\n            bboxes (torch.Tensor): source boxes, e.g., object proposals.\n            gt_bboxes (torch.Tensor): target of the transformation, e.g.,\n                ground truth boxes.\n\n        Returns:\n            torch.Tensor: Box transformation deltas\n        \"\"\"\n        assert bboxes.size(0) == gt_bboxes.size(0)\n        assert bboxes.size(-1) == gt_bboxes.size(-1) == 4\n        encoded_bboxes = bboxes2tblr(\n            bboxes, gt_bboxes, normalizer=self.normalizer)\n        return encoded_bboxes\n\n    def decode(self, bboxes, pred_bboxes, max_shape=None):\n        \"\"\"Apply transformation `pred_bboxes` to `boxes`.\n\n        Args:\n            boxes (torch.Tensor): Basic boxes.\n            pred_bboxes (torch.Tensor): Encoded boxes with shape\n            max_shape (tuple[int], optional): Maximum shape of boxes.\n                Defaults to None.\n\n        Returns:\n            torch.Tensor: Decoded boxes.\n        \"\"\"\n        assert pred_bboxes.size(0) == bboxes.size(0)\n        decoded_bboxes = tblr2bboxes(\n            bboxes,\n            pred_bboxes,\n            normalizer=self.normalizer,\n            max_shape=max_shape)\n\n        return decoded_bboxes\n\n\ndef bboxes2tblr(priors, gts, normalizer=4.0, normalize_by_wh=True):\n    \"\"\"Encode ground truth boxes to tblr coordinate\n\n    It first convert the gt coordinate to tblr format,\n     (top, bottom, left, right), relative to prior box centers.\n     The tblr coordinate may be normalized by the side length of prior bboxes\n     if `normalize_by_wh` is specified as True, and it is then normalized by\n     the `normalizer` factor.\n\n    Args:\n        priors (Tensor): Prior boxes in point form\n            Shape: (num_proposals,4).\n        gts (Tensor): Coords of ground truth for each prior in point-form\n            Shape: (num_proposals, 4).\n        normalizer (Sequence[float] | float): normalization parameter of\n            encoded boxes. If it is a list, it has to have length = 4.\n            Default: 4.0\n        normalize_by_wh (bool): Whether to normalize tblr coordinate by the\n            side length (wh) of prior bboxes.\n\n    Return:\n        encoded boxes (Tensor), Shape: (num_proposals, 4)\n    \"\"\"\n\n    # dist b/t match center and prior's center\n    if not isinstance(normalizer, float):\n        normalizer = torch.tensor(normalizer, device=priors.device)\n        assert len(normalizer) == 4, 'Normalizer must have length = 4'\n    assert priors.size(0) == gts.size(0)\n    prior_centers = (priors[:, 0:2] + priors[:, 2:4]) / 2\n    xmin, ymin, xmax, ymax = gts.split(1, dim=1)\n    top = prior_centers[:, 1].unsqueeze(1) - ymin\n    bottom = ymax - prior_centers[:, 1].unsqueeze(1)\n    left = prior_centers[:, 0].unsqueeze(1) - xmin\n    right = xmax - prior_centers[:, 0].unsqueeze(1)\n    loc = torch.cat((top, bottom, left, right), dim=1)\n    if normalize_by_wh:\n        # Normalize tblr by anchor width and height\n        wh = priors[:, 2:4] - priors[:, 0:2]\n        w, h = torch.split(wh, 1, dim=1)\n        loc[:, :2] /= h  # tb is normalized by h\n        loc[:, 2:] /= w  # lr is normalized by w\n    # Normalize tblr by the given normalization factor\n    return loc / normalizer\n\n\ndef tblr2bboxes(priors,\n                tblr,\n                normalizer=4.0,\n                normalize_by_wh=True,\n                max_shape=None):\n    \"\"\"Decode tblr outputs to prediction boxes\n\n    The process includes 3 steps: 1) De-normalize tblr coordinates by\n    multiplying it with `normalizer`; 2) De-normalize tblr coordinates by the\n    prior bbox width and height if `normalize_by_wh` is `True`; 3) Convert\n    tblr (top, bottom, left, right) pair relative to the center of priors back\n    to (xmin, ymin, xmax, ymax) coordinate.\n\n    Args:\n        priors (Tensor): Prior boxes in point form (x0, y0, x1, y1)\n          Shape: (n,4).\n        tblr (Tensor): Coords of network output in tblr form\n          Shape: (n, 4).\n        normalizer (Sequence[float] | float): Normalization parameter of\n          encoded boxes. By list, it represents the normalization factors at\n          tblr dims. By float, it is the unified normalization factor at all\n          dims. Default: 4.0\n        normalize_by_wh (bool): Whether the tblr coordinates have been\n          normalized by the side length (wh) of prior bboxes.\n        max_shape (tuple, optional): Shape of the image. Decoded bboxes\n          exceeding which will be clamped.\n\n    Return:\n        encoded boxes (Tensor), Shape: (n, 4)\n    \"\"\"\n    if not isinstance(normalizer, float):\n        normalizer = torch.tensor(normalizer, device=priors.device)\n        assert len(normalizer) == 4, 'Normalizer must have length = 4'\n    assert priors.size(0) == tblr.size(0)\n    loc_decode = tblr * normalizer\n    prior_centers = (priors[:, 0:2] + priors[:, 2:4]) / 2\n    if normalize_by_wh:\n        wh = priors[:, 2:4] - priors[:, 0:2]\n        w, h = torch.split(wh, 1, dim=1)\n        loc_decode[:, :2] *= h  # tb\n        loc_decode[:, 2:] *= w  # lr\n    top, bottom, left, right = loc_decode.split(1, dim=1)\n    xmin = prior_centers[:, 0].unsqueeze(1) - left\n    xmax = prior_centers[:, 0].unsqueeze(1) + right\n    ymin = prior_centers[:, 1].unsqueeze(1) - top\n    ymax = prior_centers[:, 1].unsqueeze(1) + bottom\n    boxes = torch.cat((xmin, ymin, xmax, ymax), dim=1)\n    if max_shape is not None:\n        boxes[:, 0].clamp_(min=0, max=max_shape[1])\n        boxes[:, 1].clamp_(min=0, max=max_shape[0])\n        boxes[:, 2].clamp_(min=0, max=max_shape[1])\n        boxes[:, 3].clamp_(min=0, max=max_shape[0])\n    return boxes\n"
  },
  {
    "path": "code/mmdet/core/bbox/demodata.py",
    "content": "import numpy as np\nimport torch\n\n\ndef ensure_rng(rng=None):\n    \"\"\"\n    Simple version of the ``kwarray.ensure_rng``\n\n    Args:\n        rng (int | numpy.random.RandomState | None):\n            if None, then defaults to the global rng. Otherwise this can be an\n            integer or a RandomState class\n    Returns:\n        (numpy.random.RandomState) : rng -\n            a numpy random number generator\n\n    References:\n        https://gitlab.kitware.com/computer-vision/kwarray/blob/master/kwarray/util_random.py#L270\n    \"\"\"\n\n    if rng is None:\n        rng = np.random.mtrand._rand\n    elif isinstance(rng, int):\n        rng = np.random.RandomState(rng)\n    else:\n        rng = rng\n    return rng\n\n\ndef random_boxes(num=1, scale=1, rng=None):\n    \"\"\"\n    Simple version of ``kwimage.Boxes.random``\n\n    Returns:\n        Tensor: shape (n, 4) in x1, y1, x2, y2 format.\n\n    References:\n        https://gitlab.kitware.com/computer-vision/kwimage/blob/master/kwimage/structs/boxes.py#L1390\n\n    Example:\n        >>> num = 3\n        >>> scale = 512\n        >>> rng = 0\n        >>> boxes = random_boxes(num, scale, rng)\n        >>> print(boxes)\n        tensor([[280.9925, 278.9802, 308.6148, 366.1769],\n                [216.9113, 330.6978, 224.0446, 456.5878],\n                [405.3632, 196.3221, 493.3953, 270.7942]])\n    \"\"\"\n    rng = ensure_rng(rng)\n\n    tlbr = rng.rand(num, 4).astype(np.float32)\n\n    tl_x = np.minimum(tlbr[:, 0], tlbr[:, 2])\n    tl_y = np.minimum(tlbr[:, 1], tlbr[:, 3])\n    br_x = np.maximum(tlbr[:, 0], tlbr[:, 2])\n    br_y = np.maximum(tlbr[:, 1], tlbr[:, 3])\n\n    tlbr[:, 0] = tl_x * scale\n    tlbr[:, 1] = tl_y * scale\n    tlbr[:, 2] = br_x * scale\n    tlbr[:, 3] = br_y * scale\n\n    boxes = torch.from_numpy(tlbr)\n    return boxes\n"
  },
  {
    "path": "code/mmdet/core/bbox/iou_calculators/__init__.py",
    "content": "from .builder import build_iou_calculator\nfrom .iou2d_calculator import BboxOverlaps2D, bbox_overlaps\n\n__all__ = ['build_iou_calculator', 'BboxOverlaps2D', 'bbox_overlaps']\n"
  },
  {
    "path": "code/mmdet/core/bbox/iou_calculators/builder.py",
    "content": "from mmcv.utils import Registry, build_from_cfg\n\nIOU_CALCULATORS = Registry('IoU calculator')\n\n\ndef build_iou_calculator(cfg, default_args=None):\n    \"\"\"Builder of IoU calculator\"\"\"\n    return build_from_cfg(cfg, IOU_CALCULATORS, default_args)\n"
  },
  {
    "path": "code/mmdet/core/bbox/iou_calculators/iou2d_calculator.py",
    "content": "import torch\n\nfrom .builder import IOU_CALCULATORS\n\n\n@IOU_CALCULATORS.register_module()\nclass BboxOverlaps2D(object):\n    \"\"\"2D IoU Calculator\"\"\"\n\n    def __call__(self, bboxes1, bboxes2, mode='iou', is_aligned=False):\n        \"\"\"Calculate IoU between 2D bboxes\n\n        Args:\n            bboxes1 (Tensor): bboxes have shape (m, 4) in <x1, y1, x2, y2>\n                format, or shape (m, 5) in <x1, y1, x2, y2, score> format.\n            bboxes2 (Tensor): bboxes have shape (m, 4) in <x1, y1, x2, y2>\n                format, shape (m, 5) in <x1, y1, x2, y2, score> format, or be\n                empty. If is_aligned is ``True``, then m and n must be equal.\n            mode (str): \"iou\" (intersection over union) or iof (intersection\n                over foreground).\n\n        Returns:\n            ious(Tensor): shape (m, n) if is_aligned == False else shape (m, 1)\n        \"\"\"\n        assert bboxes1.size(-1) in [0, 4, 5]\n        assert bboxes2.size(-1) in [0, 4, 5]\n        if bboxes2.size(-1) == 5:\n            bboxes2 = bboxes2[..., :4]\n        if bboxes1.size(-1) == 5:\n            bboxes1 = bboxes1[..., :4]\n        return bbox_overlaps(bboxes1, bboxes2, mode, is_aligned)\n\n    def __repr__(self):\n        \"\"\"str: a string describing the module\"\"\"\n        repr_str = self.__class__.__name__ + '()'\n        return repr_str\n\n\ndef bbox_overlaps(bboxes1, bboxes2, mode='iou', is_aligned=False, eps=1e-6):\n    \"\"\"Calculate overlap between two set of bboxes.\n\n    If ``is_aligned`` is ``False``, then calculate the ious between each bbox\n    of bboxes1 and bboxes2, otherwise the ious between each aligned pair of\n    bboxes1 and bboxes2.\n\n    Args:\n        bboxes1 (Tensor): shape (m, 4) in <x1, y1, x2, y2> format or empty.\n        bboxes2 (Tensor): shape (n, 4) in <x1, y1, x2, y2> format or empty.\n            If is_aligned is ``True``, then m and n must be equal.\n        mode (str): \"iou\" (intersection over union) or iof (intersection over\n            foreground).\n\n    Returns:\n        ious(Tensor): shape (m, n) if is_aligned == False else shape (m, 1)\n\n    Example:\n        >>> bboxes1 = torch.FloatTensor([\n        >>>     [0, 0, 10, 10],\n        >>>     [10, 10, 20, 20],\n        >>>     [32, 32, 38, 42],\n        >>> ])\n        >>> bboxes2 = torch.FloatTensor([\n        >>>     [0, 0, 10, 20],\n        >>>     [0, 10, 10, 19],\n        >>>     [10, 10, 20, 20],\n        >>> ])\n        >>> bbox_overlaps(bboxes1, bboxes2)\n        tensor([[0.5000, 0.0000, 0.0000],\n                [0.0000, 0.0000, 1.0000],\n                [0.0000, 0.0000, 0.0000]])\n\n    Example:\n        >>> empty = torch.FloatTensor([])\n        >>> nonempty = torch.FloatTensor([\n        >>>     [0, 0, 10, 9],\n        >>> ])\n        >>> assert tuple(bbox_overlaps(empty, nonempty).shape) == (0, 1)\n        >>> assert tuple(bbox_overlaps(nonempty, empty).shape) == (1, 0)\n        >>> assert tuple(bbox_overlaps(empty, empty).shape) == (0, 0)\n    \"\"\"\n\n    assert mode in ['iou', 'iof']\n    # Either the boxes are empty or the length of boxes's last dimenstion is 4\n    assert (bboxes1.size(-1) == 4 or bboxes1.size(0) == 0)\n    assert (bboxes2.size(-1) == 4 or bboxes2.size(0) == 0)\n\n    rows = bboxes1.size(0)\n    cols = bboxes2.size(0)\n    if is_aligned:\n        assert rows == cols\n\n    if rows * cols == 0:\n        return bboxes1.new(rows, 1) if is_aligned else bboxes1.new(rows, cols)\n\n    if is_aligned:\n        lt = torch.max(bboxes1[:, :2], bboxes2[:, :2])  # [rows, 2]\n        rb = torch.min(bboxes1[:, 2:], bboxes2[:, 2:])  # [rows, 2]\n\n        wh = (rb - lt).clamp(min=0)  # [rows, 2]\n        overlap = wh[:, 0] * wh[:, 1]\n        area1 = (bboxes1[:, 2] - bboxes1[:, 0]) * (\n            bboxes1[:, 3] - bboxes1[:, 1])\n\n        if mode == 'iou':\n            area2 = (bboxes2[:, 2] - bboxes2[:, 0]) * (\n                bboxes2[:, 3] - bboxes2[:, 1])\n            union = area1 + area2 - overlap\n        else:\n            union = area1\n    else:\n        lt = torch.max(bboxes1[:, None, :2], bboxes2[:, :2])  # [rows, cols, 2]\n        rb = torch.min(bboxes1[:, None, 2:], bboxes2[:, 2:])  # [rows, cols, 2]\n\n        wh = (rb - lt).clamp(min=0)  # [rows, cols, 2]\n        overlap = wh[:, :, 0] * wh[:, :, 1]\n        area1 = (bboxes1[:, 2] - bboxes1[:, 0]) * (\n            bboxes1[:, 3] - bboxes1[:, 1])\n\n        if mode == 'iou':\n            area2 = (bboxes2[:, 2] - bboxes2[:, 0]) * (\n                bboxes2[:, 3] - bboxes2[:, 1])\n            union = area1[:, None] + area2 - overlap\n        else:\n            union = area1[:, None]\n\n    eps = union.new_tensor([eps])\n    union = torch.max(union, eps)\n    ious = overlap / union\n\n    return ious\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/__init__.py",
    "content": "from .base_sampler import BaseSampler\nfrom .combined_sampler import CombinedSampler\nfrom .instance_balanced_pos_sampler import InstanceBalancedPosSampler\nfrom .iou_balanced_neg_sampler import IoUBalancedNegSampler\nfrom .ohem_sampler import OHEMSampler\nfrom .pseudo_sampler import PseudoSampler\nfrom .random_sampler import RandomSampler\nfrom .sampling_result import SamplingResult\nfrom .score_hlr_sampler import ScoreHLRSampler\n\n__all__ = [\n    'BaseSampler', 'PseudoSampler', 'RandomSampler',\n    'InstanceBalancedPosSampler', 'IoUBalancedNegSampler', 'CombinedSampler',\n    'OHEMSampler', 'SamplingResult', 'ScoreHLRSampler'\n]\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/base_sampler.py",
    "content": "from abc import ABCMeta, abstractmethod\n\nimport torch\n\nfrom .sampling_result import SamplingResult\n\n\nclass BaseSampler(metaclass=ABCMeta):\n    \"\"\"Base class of samplers\"\"\"\n\n    def __init__(self,\n                 num,\n                 pos_fraction,\n                 neg_pos_ub=-1,\n                 add_gt_as_proposals=True,\n                 **kwargs):\n        self.num = num\n        self.pos_fraction = pos_fraction\n        self.neg_pos_ub = neg_pos_ub\n        self.add_gt_as_proposals = add_gt_as_proposals\n        self.pos_sampler = self\n        self.neg_sampler = self\n\n    @abstractmethod\n    def _sample_pos(self, assign_result, num_expected, **kwargs):\n        \"\"\"Sample positive samples\"\"\"\n        pass\n\n    @abstractmethod\n    def _sample_neg(self, assign_result, num_expected, **kwargs):\n        \"\"\"Sample negative samples\"\"\"\n        pass\n\n    def sample(self,\n               assign_result,\n               bboxes,\n               gt_bboxes,\n               gt_labels=None,\n               **kwargs):\n        \"\"\"Sample positive and negative bboxes.\n\n        This is a simple implementation of bbox sampling given candidates,\n        assigning results and ground truth bboxes.\n\n        Args:\n            assign_result (:obj:`AssignResult`): Bbox assigning results.\n            bboxes (Tensor): Boxes to be sampled from.\n            gt_bboxes (Tensor): Ground truth bboxes.\n            gt_labels (Tensor, optional): Class labels of ground truth bboxes.\n\n        Returns:\n            :obj:`SamplingResult`: Sampling result.\n\n        Example:\n            >>> from mmdet.core.bbox import RandomSampler\n            >>> from mmdet.core.bbox import AssignResult\n            >>> from mmdet.core.bbox.demodata import ensure_rng, random_boxes\n            >>> rng = ensure_rng(None)\n            >>> assign_result = AssignResult.random(rng=rng)\n            >>> bboxes = random_boxes(assign_result.num_preds, rng=rng)\n            >>> gt_bboxes = random_boxes(assign_result.num_gts, rng=rng)\n            >>> gt_labels = None\n            >>> self = RandomSampler(num=32, pos_fraction=0.5, neg_pos_ub=-1,\n            >>>                      add_gt_as_proposals=False)\n            >>> self = self.sample(assign_result, bboxes, gt_bboxes, gt_labels)\n        \"\"\"\n        if len(bboxes.shape) < 2:\n            bboxes = bboxes[None, :]\n\n        bboxes = bboxes[:, :4]\n\n        gt_flags = bboxes.new_zeros((bboxes.shape[0], ), dtype=torch.uint8)\n        if self.add_gt_as_proposals and len(gt_bboxes) > 0:\n            if gt_labels is None:\n                raise ValueError(\n                    'gt_labels must be given when add_gt_as_proposals is True')\n            bboxes = torch.cat([gt_bboxes, bboxes], dim=0)\n            assign_result.add_gt_(gt_labels)\n            gt_ones = bboxes.new_ones(gt_bboxes.shape[0], dtype=torch.uint8)\n            gt_flags = torch.cat([gt_ones, gt_flags])\n\n        num_expected_pos = int(self.num * self.pos_fraction)\n        pos_inds = self.pos_sampler._sample_pos(\n            assign_result, num_expected_pos, bboxes=bboxes, **kwargs)\n        # We found that sampled indices have duplicated items occasionally.\n        # (may be a bug of PyTorch)\n        pos_inds = pos_inds.unique()\n        num_sampled_pos = pos_inds.numel()\n        num_expected_neg = self.num - num_sampled_pos\n        if self.neg_pos_ub >= 0:\n            _pos = max(1, num_sampled_pos)\n            neg_upper_bound = int(self.neg_pos_ub * _pos)\n            if num_expected_neg > neg_upper_bound:\n                num_expected_neg = neg_upper_bound\n        neg_inds = self.neg_sampler._sample_neg(\n            assign_result, num_expected_neg, bboxes=bboxes, **kwargs)\n        neg_inds = neg_inds.unique()\n\n        sampling_result = SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes,\n                                         assign_result, gt_flags)\n        return sampling_result\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/combined_sampler.py",
    "content": "from ..builder import BBOX_SAMPLERS, build_sampler\nfrom .base_sampler import BaseSampler\n\n\n@BBOX_SAMPLERS.register_module()\nclass CombinedSampler(BaseSampler):\n    \"\"\"A sampler that combines positive sampler and negative sampler\"\"\"\n\n    def __init__(self, pos_sampler, neg_sampler, **kwargs):\n        super(CombinedSampler, self).__init__(**kwargs)\n        self.pos_sampler = build_sampler(pos_sampler, **kwargs)\n        self.neg_sampler = build_sampler(neg_sampler, **kwargs)\n\n    def _sample_pos(self, **kwargs):\n        \"\"\"Sample positive samples\"\"\"\n        raise NotImplementedError\n\n    def _sample_neg(self, **kwargs):\n        \"\"\"Sample negative samples\"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/instance_balanced_pos_sampler.py",
    "content": "import numpy as np\nimport torch\n\nfrom ..builder import BBOX_SAMPLERS\nfrom .random_sampler import RandomSampler\n\n\n@BBOX_SAMPLERS.register_module()\nclass InstanceBalancedPosSampler(RandomSampler):\n    \"\"\"Instance balanced sampler that samples equal number of positive samples\n    for each instance.\"\"\"\n\n    def _sample_pos(self, assign_result, num_expected, **kwargs):\n        \"\"\"Sample positive boxes\n\n        Args:\n            assign_result (:obj:`AssignResult`): The assigned results of boxes.\n            num_expected (int): The number of expected positive samples\n\n        Returns:\n            Tensor or ndarray: sampled indices.\n        \"\"\"\n        pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False)\n        if pos_inds.numel() != 0:\n            pos_inds = pos_inds.squeeze(1)\n        if pos_inds.numel() <= num_expected:\n            return pos_inds\n        else:\n            unique_gt_inds = assign_result.gt_inds[pos_inds].unique()\n            num_gts = len(unique_gt_inds)\n            num_per_gt = int(round(num_expected / float(num_gts)) + 1)\n            sampled_inds = []\n            for i in unique_gt_inds:\n                inds = torch.nonzero(\n                    assign_result.gt_inds == i.item(), as_tuple=False)\n                if inds.numel() != 0:\n                    inds = inds.squeeze(1)\n                else:\n                    continue\n                if len(inds) > num_per_gt:\n                    inds = self.random_choice(inds, num_per_gt)\n                sampled_inds.append(inds)\n            sampled_inds = torch.cat(sampled_inds)\n            if len(sampled_inds) < num_expected:\n                num_extra = num_expected - len(sampled_inds)\n                extra_inds = np.array(\n                    list(set(pos_inds.cpu()) - set(sampled_inds.cpu())))\n                if len(extra_inds) > num_extra:\n                    extra_inds = self.random_choice(extra_inds, num_extra)\n                extra_inds = torch.from_numpy(extra_inds).to(\n                    assign_result.gt_inds.device).long()\n                sampled_inds = torch.cat([sampled_inds, extra_inds])\n            elif len(sampled_inds) > num_expected:\n                sampled_inds = self.random_choice(sampled_inds, num_expected)\n            return sampled_inds\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/iou_balanced_neg_sampler.py",
    "content": "import numpy as np\nimport torch\n\nfrom ..builder import BBOX_SAMPLERS\nfrom .random_sampler import RandomSampler\n\n\n@BBOX_SAMPLERS.register_module()\nclass IoUBalancedNegSampler(RandomSampler):\n    \"\"\"IoU Balanced Sampling\n\n    arXiv: https://arxiv.org/pdf/1904.02701.pdf (CVPR 2019)\n\n    Sampling proposals according to their IoU. `floor_fraction` of needed RoIs\n    are sampled from proposals whose IoU are lower than `floor_thr` randomly.\n    The others are sampled from proposals whose IoU are higher than\n    `floor_thr`. These proposals are sampled from some bins evenly, which are\n    split by `num_bins` via IoU evenly.\n\n    Args:\n        num (int): number of proposals.\n        pos_fraction (float): fraction of positive proposals.\n        floor_thr (float): threshold (minimum) IoU for IoU balanced sampling,\n            set to -1 if all using IoU balanced sampling.\n        floor_fraction (float): sampling fraction of proposals under floor_thr.\n        num_bins (int): number of bins in IoU balanced sampling.\n    \"\"\"\n\n    def __init__(self,\n                 num,\n                 pos_fraction,\n                 floor_thr=-1,\n                 floor_fraction=0,\n                 num_bins=3,\n                 **kwargs):\n        super(IoUBalancedNegSampler, self).__init__(num, pos_fraction,\n                                                    **kwargs)\n        assert floor_thr >= 0 or floor_thr == -1\n        assert 0 <= floor_fraction <= 1\n        assert num_bins >= 1\n\n        self.floor_thr = floor_thr\n        self.floor_fraction = floor_fraction\n        self.num_bins = num_bins\n\n    def sample_via_interval(self, max_overlaps, full_set, num_expected):\n        \"\"\"Sample according to the iou interval\n\n        Args:\n            max_overlaps (torch.Tensor): IoU between bounding boxes and ground\n                truth boxes.\n            full_set (set(int)): A full set of indices of boxes。\n            num_expected (int): Number of expected samples。\n\n        Returns:\n            np.ndarray: Indices  of samples\n        \"\"\"\n        max_iou = max_overlaps.max()\n        iou_interval = (max_iou - self.floor_thr) / self.num_bins\n        per_num_expected = int(num_expected / self.num_bins)\n\n        sampled_inds = []\n        for i in range(self.num_bins):\n            start_iou = self.floor_thr + i * iou_interval\n            end_iou = self.floor_thr + (i + 1) * iou_interval\n            tmp_set = set(\n                np.where(\n                    np.logical_and(max_overlaps >= start_iou,\n                                   max_overlaps < end_iou))[0])\n            tmp_inds = list(tmp_set & full_set)\n            if len(tmp_inds) > per_num_expected:\n                tmp_sampled_set = self.random_choice(tmp_inds,\n                                                     per_num_expected)\n            else:\n                tmp_sampled_set = np.array(tmp_inds, dtype=np.int)\n            sampled_inds.append(tmp_sampled_set)\n\n        sampled_inds = np.concatenate(sampled_inds)\n        if len(sampled_inds) < num_expected:\n            num_extra = num_expected - len(sampled_inds)\n            extra_inds = np.array(list(full_set - set(sampled_inds)))\n            if len(extra_inds) > num_extra:\n                extra_inds = self.random_choice(extra_inds, num_extra)\n            sampled_inds = np.concatenate([sampled_inds, extra_inds])\n\n        return sampled_inds\n\n    def _sample_neg(self, assign_result, num_expected, **kwargs):\n        \"\"\"Sample negative boxes\n\n        Args:\n            assign_result (:obj:`AssignResult`): The assigned results of boxes.\n            num_expected (int): The number of expected negative samples\n\n        Returns:\n            Tensor or ndarray: sampled indices.\n        \"\"\"\n        neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False)\n        if neg_inds.numel() != 0:\n            neg_inds = neg_inds.squeeze(1)\n        if len(neg_inds) <= num_expected:\n            return neg_inds\n        else:\n            max_overlaps = assign_result.max_overlaps.cpu().numpy()\n            # balance sampling for negative samples\n            neg_set = set(neg_inds.cpu().numpy())\n\n            if self.floor_thr > 0:\n                floor_set = set(\n                    np.where(\n                        np.logical_and(max_overlaps >= 0,\n                                       max_overlaps < self.floor_thr))[0])\n                iou_sampling_set = set(\n                    np.where(max_overlaps >= self.floor_thr)[0])\n            elif self.floor_thr == 0:\n                floor_set = set(np.where(max_overlaps == 0)[0])\n                iou_sampling_set = set(\n                    np.where(max_overlaps > self.floor_thr)[0])\n            else:\n                floor_set = set()\n                iou_sampling_set = set(\n                    np.where(max_overlaps > self.floor_thr)[0])\n                # for sampling interval calculation\n                self.floor_thr = 0\n\n            floor_neg_inds = list(floor_set & neg_set)\n            iou_sampling_neg_inds = list(iou_sampling_set & neg_set)\n            num_expected_iou_sampling = int(num_expected *\n                                            (1 - self.floor_fraction))\n            if len(iou_sampling_neg_inds) > num_expected_iou_sampling:\n                if self.num_bins >= 2:\n                    iou_sampled_inds = self.sample_via_interval(\n                        max_overlaps, set(iou_sampling_neg_inds),\n                        num_expected_iou_sampling)\n                else:\n                    iou_sampled_inds = self.random_choice(\n                        iou_sampling_neg_inds, num_expected_iou_sampling)\n            else:\n                iou_sampled_inds = np.array(\n                    iou_sampling_neg_inds, dtype=np.int)\n            num_expected_floor = num_expected - len(iou_sampled_inds)\n            if len(floor_neg_inds) > num_expected_floor:\n                sampled_floor_inds = self.random_choice(\n                    floor_neg_inds, num_expected_floor)\n            else:\n                sampled_floor_inds = np.array(floor_neg_inds, dtype=np.int)\n            sampled_inds = np.concatenate(\n                (sampled_floor_inds, iou_sampled_inds))\n            if len(sampled_inds) < num_expected:\n                num_extra = num_expected - len(sampled_inds)\n                extra_inds = np.array(list(neg_set - set(sampled_inds)))\n                if len(extra_inds) > num_extra:\n                    extra_inds = self.random_choice(extra_inds, num_extra)\n                sampled_inds = np.concatenate((sampled_inds, extra_inds))\n            sampled_inds = torch.from_numpy(sampled_inds).long().to(\n                assign_result.gt_inds.device)\n            return sampled_inds\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/ohem_sampler.py",
    "content": "import torch\n\nfrom ..builder import BBOX_SAMPLERS\nfrom ..transforms import bbox2roi\nfrom .base_sampler import BaseSampler\n\n\n@BBOX_SAMPLERS.register_module()\nclass OHEMSampler(BaseSampler):\n    \"\"\"\n    Online Hard Example Mining Sampler described in [1]_.\n\n    References:\n        .. [1] https://arxiv.org/pdf/1604.03540.pdf\n    \"\"\"\n\n    def __init__(self,\n                 num,\n                 pos_fraction,\n                 context,\n                 neg_pos_ub=-1,\n                 add_gt_as_proposals=True,\n                 **kwargs):\n        super(OHEMSampler, self).__init__(num, pos_fraction, neg_pos_ub,\n                                          add_gt_as_proposals)\n        if not hasattr(context, 'num_stages'):\n            self.bbox_roi_extractor = context.bbox_roi_extractor\n            self.bbox_head = context.bbox_head\n        else:\n            self.bbox_roi_extractor = context.bbox_roi_extractor[\n                context.current_stage]\n            self.bbox_head = context.bbox_head[context.current_stage]\n\n    def hard_mining(self, inds, num_expected, bboxes, labels, feats):\n        with torch.no_grad():\n            rois = bbox2roi([bboxes])\n            bbox_feats = self.bbox_roi_extractor(\n                feats[:self.bbox_roi_extractor.num_inputs], rois)\n            cls_score, _ = self.bbox_head(bbox_feats)\n            loss = self.bbox_head.loss(\n                cls_score=cls_score,\n                bbox_pred=None,\n                rois=rois,\n                labels=labels,\n                label_weights=cls_score.new_ones(cls_score.size(0)),\n                bbox_targets=None,\n                bbox_weights=None,\n                reduction_override='none')['loss_cls']\n            _, topk_loss_inds = loss.topk(num_expected)\n        return inds[topk_loss_inds]\n\n    def _sample_pos(self,\n                    assign_result,\n                    num_expected,\n                    bboxes=None,\n                    feats=None,\n                    **kwargs):\n        \"\"\"Sample positive boxes\n\n        Args:\n            assign_result (:obj:`AssignResult`): Assigned results\n            num_expected (int): Number of expected positive samples\n            bboxes (torch.Tensor, optional): Boxes. Defaults to None.\n            feats (list[torch.Tensor], optional): Multi-level features.\n                Defaults to None.\n\n        Returns:\n            torch.Tensor: Indices  of positive samples\n        \"\"\"\n        # Sample some hard positive samples\n        pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False)\n        if pos_inds.numel() != 0:\n            pos_inds = pos_inds.squeeze(1)\n        if pos_inds.numel() <= num_expected:\n            return pos_inds\n        else:\n            return self.hard_mining(pos_inds, num_expected, bboxes[pos_inds],\n                                    assign_result.labels[pos_inds], feats)\n\n    def _sample_neg(self,\n                    assign_result,\n                    num_expected,\n                    bboxes=None,\n                    feats=None,\n                    **kwargs):\n        \"\"\"Sample negative boxes\n\n        Args:\n            assign_result (:obj:`AssignResult`): Assigned results\n            num_expected (int): Number of expected negative samples\n            bboxes (torch.Tensor, optional): Boxes. Defaults to None.\n            feats (list[torch.Tensor], optional): Multi-level features.\n                Defaults to None.\n\n        Returns:\n            torch.Tensor: Indices  of negative samples\n        \"\"\"\n        # Sample some hard negative samples\n        neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False)\n        if neg_inds.numel() != 0:\n            neg_inds = neg_inds.squeeze(1)\n        if len(neg_inds) <= num_expected:\n            return neg_inds\n        else:\n            neg_labels = assign_result.labels.new_empty(\n                neg_inds.size(0)).fill_(self.bbox_head.num_classes)\n            return self.hard_mining(neg_inds, num_expected, bboxes[neg_inds],\n                                    neg_labels, feats)\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/pseudo_sampler.py",
    "content": "import torch\n\nfrom ..builder import BBOX_SAMPLERS\nfrom .base_sampler import BaseSampler\nfrom .sampling_result import SamplingResult\n\n\n@BBOX_SAMPLERS.register_module()\nclass PseudoSampler(BaseSampler):\n    \"\"\"A pseudo sampler that does not do sampling actually.\"\"\"\n\n    def __init__(self, **kwargs):\n        pass\n\n    def _sample_pos(self, **kwargs):\n        \"\"\"Sample positive samples\"\"\"\n        raise NotImplementedError\n\n    def _sample_neg(self, **kwargs):\n        \"\"\"Sample negative samples\"\"\"\n        raise NotImplementedError\n\n    def sample(self, assign_result, bboxes, gt_bboxes, **kwargs):\n        \"\"\"Directly returns the positive and negative indices  of samples\n\n        Args:\n            assign_result (:obj:`AssignResult`): Assigned results\n            bboxes (torch.Tensor): Bounding boxes\n            gt_bboxes (torch.Tensor): Ground truth boxes\n\n        Returns:\n            :obj:`SamplingResult`: sampler results\n        \"\"\"\n        pos_inds = torch.nonzero(\n            assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique()\n        neg_inds = torch.nonzero(\n            assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique()\n        gt_flags = bboxes.new_zeros(bboxes.shape[0], dtype=torch.uint8)\n        sampling_result = SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes,\n                                         assign_result, gt_flags)\n        return sampling_result\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/random_sampler.py",
    "content": "import torch\n\nfrom ..builder import BBOX_SAMPLERS\nfrom .base_sampler import BaseSampler\n\n\n@BBOX_SAMPLERS.register_module()\nclass RandomSampler(BaseSampler):\n    \"\"\"Random sampler\n\n    Args:\n        num (int): Number of samples\n        pos_fraction (float): Fraction of positive samples\n        neg_pos_up (int, optional): Upper bound number of negative and\n            positive samples. Defaults to -1.\n        add_gt_as_proposals (bool, optional): Whether to add ground truth\n            boxes as proposals. Defaults to True.\n    \"\"\"\n\n    def __init__(self,\n                 num,\n                 pos_fraction,\n                 neg_pos_ub=-1,\n                 add_gt_as_proposals=True,\n                 **kwargs):\n        from mmdet.core.bbox import demodata\n        super(RandomSampler, self).__init__(num, pos_fraction, neg_pos_ub,\n                                            add_gt_as_proposals)\n        self.rng = demodata.ensure_rng(kwargs.get('rng', None))\n\n    def random_choice(self, gallery, num):\n        \"\"\"Random select some elements from the gallery.\n\n        If `gallery` is a Tensor, the returned indices will be a Tensor;\n        If `gallery` is a ndarray or list, the returned indices will be a\n        ndarray.\n\n        Args:\n            gallery (Tensor | ndarray | list): indices pool.\n            num (int): expected sample num.\n\n        Returns:\n            Tensor or ndarray: sampled indices.\n        \"\"\"\n        assert len(gallery) >= num\n\n        is_tensor = isinstance(gallery, torch.Tensor)\n        if not is_tensor:\n            gallery = torch.tensor(\n                gallery, dtype=torch.long, device=torch.cuda.current_device())\n        perm = torch.randperm(gallery.numel(), device=gallery.device)[:num]\n        rand_inds = gallery[perm]\n        if not is_tensor:\n            rand_inds = rand_inds.cpu().numpy()\n        return rand_inds\n\n    def _sample_pos(self, assign_result, num_expected, **kwargs):\n        \"\"\"Randomly sample some positive samples.\"\"\"\n        pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False)\n        if pos_inds.numel() != 0:\n            pos_inds = pos_inds.squeeze(1)\n        if pos_inds.numel() <= num_expected:\n            return pos_inds\n        else:\n            return self.random_choice(pos_inds, num_expected)\n\n    def _sample_neg(self, assign_result, num_expected, **kwargs):\n        \"\"\"Randomly sample some negative samples.\"\"\"\n        neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False)\n        if neg_inds.numel() != 0:\n            neg_inds = neg_inds.squeeze(1)\n        if len(neg_inds) <= num_expected:\n            return neg_inds\n        else:\n            return self.random_choice(neg_inds, num_expected)\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/sampling_result.py",
    "content": "import torch\n\nfrom mmdet.utils import util_mixins\n\n\nclass SamplingResult(util_mixins.NiceRepr):\n    \"\"\"Bbox sampling result.\n\n    Example:\n        >>> # xdoctest: +IGNORE_WANT\n        >>> from mmdet.core.bbox.samplers.sampling_result import *  # NOQA\n        >>> self = SamplingResult.random(rng=10)\n        >>> print(f'self = {self}')\n        self = <SamplingResult({\n            'neg_bboxes': torch.Size([12, 4]),\n            'neg_inds': tensor([ 0,  1,  2,  4,  5,  6,  7,  8,  9, 10, 11, 12]),\n            'num_gts': 4,\n            'pos_assigned_gt_inds': tensor([], dtype=torch.int64),\n            'pos_bboxes': torch.Size([0, 4]),\n            'pos_inds': tensor([], dtype=torch.int64),\n            'pos_is_gt': tensor([], dtype=torch.uint8)\n        })>\n    \"\"\"\n\n    def __init__(self, pos_inds, neg_inds, bboxes, gt_bboxes, assign_result,\n                 gt_flags):\n        self.pos_inds = pos_inds\n        self.neg_inds = neg_inds\n        self.pos_bboxes = bboxes[pos_inds]\n        self.neg_bboxes = bboxes[neg_inds]\n        self.pos_is_gt = gt_flags[pos_inds]\n\n        self.num_gts = gt_bboxes.shape[0]\n        self.pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1\n\n        if gt_bboxes.numel() == 0:\n            # hack for index error case\n            assert self.pos_assigned_gt_inds.numel() == 0\n            self.pos_gt_bboxes = torch.empty_like(gt_bboxes).view(-1, 4)\n        else:\n            if len(gt_bboxes.shape) < 2:\n                gt_bboxes = gt_bboxes.view(-1, 4)\n\n            self.pos_gt_bboxes = gt_bboxes[self.pos_assigned_gt_inds, :]\n\n        if assign_result.labels is not None:\n            self.pos_gt_labels = assign_result.labels[pos_inds]\n        else:\n            self.pos_gt_labels = None\n\n    @property\n    def bboxes(self):\n        \"\"\"torch.Tensor: concatenated positive and negative boxes\"\"\"\n        return torch.cat([self.pos_bboxes, self.neg_bboxes])\n\n    def to(self, device):\n        \"\"\"Change the device of the data inplace.\n\n        Example:\n            >>> self = SamplingResult.random()\n            >>> print(f'self = {self.to(None)}')\n            >>> # xdoctest: +REQUIRES(--gpu)\n            >>> print(f'self = {self.to(0)}')\n        \"\"\"\n        _dict = self.__dict__\n        for key, value in _dict.items():\n            if isinstance(value, torch.Tensor):\n                _dict[key] = value.to(device)\n        return self\n\n    def __nice__(self):\n        data = self.info.copy()\n        data['pos_bboxes'] = data.pop('pos_bboxes').shape\n        data['neg_bboxes'] = data.pop('neg_bboxes').shape\n        parts = [f\"'{k}': {v!r}\" for k, v in sorted(data.items())]\n        body = '    ' + ',\\n    '.join(parts)\n        return '{\\n' + body + '\\n}'\n\n    @property\n    def info(self):\n        \"\"\"Returns a dictionary of info about the object.\"\"\"\n        return {\n            'pos_inds': self.pos_inds,\n            'neg_inds': self.neg_inds,\n            'pos_bboxes': self.pos_bboxes,\n            'neg_bboxes': self.neg_bboxes,\n            'pos_is_gt': self.pos_is_gt,\n            'num_gts': self.num_gts,\n            'pos_assigned_gt_inds': self.pos_assigned_gt_inds,\n        }\n\n    @classmethod\n    def random(cls, rng=None, **kwargs):\n        \"\"\"\n        Args:\n            rng (None | int | numpy.random.RandomState): seed or state.\n            kwargs (keyword arguments):\n                - num_preds: number of predicted boxes\n                - num_gts: number of true boxes\n                - p_ignore (float): probability of a predicted box assinged to\n                    an ignored truth.\n                - p_assigned (float): probability of a predicted box not being\n                    assigned.\n                - p_use_label (float | bool): with labels or not.\n\n        Returns:\n            :obj:`SamplingResult`: Randomly generated sampling result.\n\n        Example:\n            >>> from mmdet.core.bbox.samplers.sampling_result import *  # NOQA\n            >>> self = SamplingResult.random()\n            >>> print(self.__dict__)\n        \"\"\"\n        from mmdet.core.bbox.samplers.random_sampler import RandomSampler\n        from mmdet.core.bbox.assigners.assign_result import AssignResult\n        from mmdet.core.bbox import demodata\n        rng = demodata.ensure_rng(rng)\n\n        # make probabalistic?\n        num = 32\n        pos_fraction = 0.5\n        neg_pos_ub = -1\n\n        assign_result = AssignResult.random(rng=rng, **kwargs)\n\n        # Note we could just compute an assignment\n        bboxes = demodata.random_boxes(assign_result.num_preds, rng=rng)\n        gt_bboxes = demodata.random_boxes(assign_result.num_gts, rng=rng)\n\n        if rng.rand() > 0.2:\n            # sometimes algorithms squeeze their data, be robust to that\n            gt_bboxes = gt_bboxes.squeeze()\n            bboxes = bboxes.squeeze()\n\n        if assign_result.labels is None:\n            gt_labels = None\n        else:\n            gt_labels = None  # todo\n\n        if gt_labels is None:\n            add_gt_as_proposals = False\n        else:\n            add_gt_as_proposals = True  # make probabalistic?\n\n        sampler = RandomSampler(\n            num,\n            pos_fraction,\n            neg_pos_ubo=neg_pos_ub,\n            add_gt_as_proposals=add_gt_as_proposals,\n            rng=rng)\n        self = sampler.sample(assign_result, bboxes, gt_bboxes, gt_labels)\n        return self\n"
  },
  {
    "path": "code/mmdet/core/bbox/samplers/score_hlr_sampler.py",
    "content": "import torch\n\nfrom mmdet.ops import nms_match\nfrom ..builder import BBOX_SAMPLERS\nfrom ..transforms import bbox2roi\nfrom .base_sampler import BaseSampler\nfrom .sampling_result import SamplingResult\n\n\n@BBOX_SAMPLERS.register_module()\nclass ScoreHLRSampler(BaseSampler):\n    \"\"\"Importance-based Sample Reweighting (ISR_N), negative part,\n       described in `PISA <https://arxiv.org/abs/1904.04821>`_.\n\n    References:\n        .. [1] https://arxiv.org/pdf/1604.03540.pdf\n\n    Score hierarchical local rank (HLR) differentiates with RandomSampler in\n    negative part. It firstly computes Score-HLR in a two-step way,\n    then linearly maps score hlr to the loss weights.\n\n    Args:\n        num (int): Total number of sampled RoIs.\n        pos_fraction (float): Fraction of positive samples.\n        context (:obj:`BaseRoIHead`): RoI head that the sampler belongs to.\n        neg_pos_ub (int): Upper bound of the ratio of num negative to num\n            positive, -1 means no upper bound.\n        add_gt_as_proposals (bool): Whether to add ground truth as proposals.\n        k (float): Power of the non-linear mapping.\n        bias (float): Shift of the non-linear mapping.\n        score_thr (float): Minimum score that a negative sample is to be\n            considered as valid bbox.\n    \"\"\"\n\n    def __init__(self,\n                 num,\n                 pos_fraction,\n                 context,\n                 neg_pos_ub=-1,\n                 add_gt_as_proposals=True,\n                 k=0.5,\n                 bias=0,\n                 score_thr=0.05,\n                 iou_thr=0.5,\n                 **kwargs):\n        super().__init__(num, pos_fraction, neg_pos_ub, add_gt_as_proposals)\n        self.k = k\n        self.bias = bias\n        self.score_thr = score_thr\n        self.iou_thr = iou_thr\n        self.context = context\n        # context of cascade detectors is a list, so distinguish them here.\n        if not hasattr(context, 'num_stages'):\n            self.bbox_roi_extractor = context.bbox_roi_extractor\n            self.bbox_head = context.bbox_head\n            self.with_shared_head = context.with_shared_head\n            if self.with_shared_head:\n                self.shared_head = context.shared_head\n        else:\n            self.bbox_roi_extractor = context.bbox_roi_extractor[\n                context.current_stage]\n            self.bbox_head = context.bbox_head[context.current_stage]\n\n    @staticmethod\n    def random_choice(gallery, num):\n        \"\"\"Randomly select some elements from the gallery.\n\n        If `gallery` is a Tensor, the returned indices will be a Tensor;\n        If `gallery` is a ndarray or list, the returned indices will be a\n        ndarray.\n\n        Args:\n            gallery (Tensor | ndarray | list): indices pool.\n            num (int): expected sample num.\n\n        Returns:\n            Tensor or ndarray: sampled indices.\n        \"\"\"\n        assert len(gallery) >= num\n\n        is_tensor = isinstance(gallery, torch.Tensor)\n        if not is_tensor:\n            gallery = torch.tensor(\n                gallery, dtype=torch.long, device=torch.cuda.current_device())\n        perm = torch.randperm(gallery.numel(), device=gallery.device)[:num]\n        rand_inds = gallery[perm]\n        if not is_tensor:\n            rand_inds = rand_inds.cpu().numpy()\n        return rand_inds\n\n    def _sample_pos(self, assign_result, num_expected, **kwargs):\n        \"\"\"Randomly sample some positive samples.\"\"\"\n        pos_inds = torch.nonzero(assign_result.gt_inds > 0).flatten()\n        if pos_inds.numel() <= num_expected:\n            return pos_inds\n        else:\n            return self.random_choice(pos_inds, num_expected)\n\n    def _sample_neg(self,\n                    assign_result,\n                    num_expected,\n                    bboxes,\n                    feats=None,\n                    img_meta=None,\n                    **kwargs):\n        \"\"\"Sample negative samples.\n\n        Score-HLR sampler is done in the following steps:\n        1. Take the maximum positive score prediction of each negative samples\n            as s_i.\n        2. Filter out negative samples whose s_i <= score_thr, the left samples\n            are called valid samples.\n        3. Use NMS-Match to divide valid samples into different groups,\n            samples in the same group will greatly overlap with each other\n        4. Rank the matched samples in two-steps to get Score-HLR.\n            (1) In the same group, rank samples with their scores.\n            (2) In the same score rank across different groups,\n                rank samples with their scores again.\n        5. Linearly map Score-HLR to the final label weights.\n\n        Args:\n            assign_result (:obj:`AssignResult`): result of assigner.\n            num_expected (int): Expected number of samples.\n            bboxes (Tensor): bbox to be sampled.\n            feats (Tensor): Features come from FPN.\n            img_meta (dict): Meta information dictionary.\n        \"\"\"\n        neg_inds = torch.nonzero(assign_result.gt_inds == 0).flatten()\n        num_neg = neg_inds.size(0)\n        if num_neg == 0:\n            return neg_inds, None\n        with torch.no_grad():\n            neg_bboxes = bboxes[neg_inds]\n            neg_rois = bbox2roi([neg_bboxes])\n            bbox_result = self.context._bbox_forward(feats, neg_rois)\n            cls_score, bbox_pred = bbox_result['cls_score'], bbox_result[\n                'bbox_pred']\n\n            ori_loss = self.bbox_head.loss(\n                cls_score=cls_score,\n                bbox_pred=None,\n                rois=None,\n                labels=neg_inds.new_full((num_neg, ),\n                                         self.bbox_head.num_classes),\n                label_weights=cls_score.new_ones(num_neg),\n                bbox_targets=None,\n                bbox_weights=None,\n                reduction_override='none')['loss_cls']\n\n            # filter out samples with the max score lower than score_thr\n            max_score, argmax_score = cls_score.softmax(-1)[:, :-1].max(-1)\n            valid_inds = (max_score > self.score_thr).nonzero().view(-1)\n            invalid_inds = (max_score <= self.score_thr).nonzero().view(-1)\n            num_valid = valid_inds.size(0)\n            num_invalid = invalid_inds.size(0)\n\n            num_expected = min(num_neg, num_expected)\n            num_hlr = min(num_valid, num_expected)\n            num_rand = num_expected - num_hlr\n            if num_valid > 0:\n                valid_rois = neg_rois[valid_inds]\n                valid_max_score = max_score[valid_inds]\n                valid_argmax_score = argmax_score[valid_inds]\n                valid_bbox_pred = bbox_pred[valid_inds]\n\n                # valid_bbox_pred shape: [num_valid, #num_classes, 4]\n                valid_bbox_pred = valid_bbox_pred.view(\n                    valid_bbox_pred.size(0), -1, 4)\n                selected_bbox_pred = valid_bbox_pred[range(num_valid),\n                                                     valid_argmax_score]\n                pred_bboxes = self.bbox_head.bbox_coder.decode(\n                    valid_rois[:, 1:], selected_bbox_pred)\n                pred_bboxes_with_score = torch.cat(\n                    [pred_bboxes, valid_max_score[:, None]], -1)\n                group = nms_match(pred_bboxes_with_score, self.iou_thr)\n\n                # imp: importance\n                imp = cls_score.new_zeros(num_valid)\n                for g in group:\n                    g_score = valid_max_score[g]\n                    # g_score has already sorted\n                    rank = g_score.new_tensor(range(g_score.size(0)))\n                    imp[g] = num_valid - rank + g_score\n                _, imp_rank_inds = imp.sort(descending=True)\n                _, imp_rank = imp_rank_inds.sort()\n                hlr_inds = imp_rank_inds[:num_expected]\n\n                if num_rand > 0:\n                    rand_inds = torch.randperm(num_invalid)[:num_rand]\n                    select_inds = torch.cat(\n                        [valid_inds[hlr_inds], invalid_inds[rand_inds]])\n                else:\n                    select_inds = valid_inds[hlr_inds]\n\n                neg_label_weights = cls_score.new_ones(num_expected)\n\n                up_bound = max(num_expected, num_valid)\n                imp_weights = (up_bound -\n                               imp_rank[hlr_inds].float()) / up_bound\n                neg_label_weights[:num_hlr] = imp_weights\n                neg_label_weights[num_hlr:] = imp_weights.min()\n                neg_label_weights = (self.bias +\n                                     (1 - self.bias) * neg_label_weights).pow(\n                                         self.k)\n                ori_selected_loss = ori_loss[select_inds]\n                new_loss = ori_selected_loss * neg_label_weights\n                norm_ratio = ori_selected_loss.sum() / new_loss.sum()\n                neg_label_weights *= norm_ratio\n            else:\n                neg_label_weights = cls_score.new_ones(num_expected)\n                select_inds = torch.randperm(num_neg)[:num_expected]\n\n            return neg_inds[select_inds], neg_label_weights\n\n    def sample(self,\n               assign_result,\n               bboxes,\n               gt_bboxes,\n               gt_labels=None,\n               img_meta=None,\n               **kwargs):\n        \"\"\"Sample positive and negative bboxes.\n\n        This is a simple implementation of bbox sampling given candidates,\n        assigning results and ground truth bboxes.\n\n        Args:\n            assign_result (:obj:`AssignResult`): Bbox assigning results.\n            bboxes (Tensor): Boxes to be sampled from.\n            gt_bboxes (Tensor): Ground truth bboxes.\n            gt_labels (Tensor, optional): Class labels of ground truth bboxes.\n\n        Returns:\n            tuple[:obj:`SamplingResult`, Tensor]: Sampling result and negetive\n                label weights.\n        \"\"\"\n        bboxes = bboxes[:, :4]\n\n        gt_flags = bboxes.new_zeros((bboxes.shape[0], ), dtype=torch.uint8)\n        if self.add_gt_as_proposals:\n            bboxes = torch.cat([gt_bboxes, bboxes], dim=0)\n            assign_result.add_gt_(gt_labels)\n            gt_ones = bboxes.new_ones(gt_bboxes.shape[0], dtype=torch.uint8)\n            gt_flags = torch.cat([gt_ones, gt_flags])\n\n        num_expected_pos = int(self.num * self.pos_fraction)\n        pos_inds = self.pos_sampler._sample_pos(\n            assign_result, num_expected_pos, bboxes=bboxes, **kwargs)\n        num_sampled_pos = pos_inds.numel()\n        num_expected_neg = self.num - num_sampled_pos\n        if self.neg_pos_ub >= 0:\n            _pos = max(1, num_sampled_pos)\n            neg_upper_bound = int(self.neg_pos_ub * _pos)\n            if num_expected_neg > neg_upper_bound:\n                num_expected_neg = neg_upper_bound\n        neg_inds, neg_label_weights = self.neg_sampler._sample_neg(\n            assign_result,\n            num_expected_neg,\n            bboxes,\n            img_meta=img_meta,\n            **kwargs)\n\n        return SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes,\n                              assign_result, gt_flags), neg_label_weights\n"
  },
  {
    "path": "code/mmdet/core/bbox/transforms.py",
    "content": "import numpy as np\nimport torch\n\n\ndef bbox_flip(bboxes, img_shape, direction='horizontal'):\n    \"\"\"Flip bboxes horizontally or vertically.\n\n    Args:\n        bboxes (Tensor): Shape (..., 4*k)\n        img_shape (tuple): Image shape.\n        direction (str): Flip direction, options are \"horizontal\" and\n            \"vertical\". Default: \"horizontal\"\n\n\n    Returns:\n        Tensor: Flipped bboxes.\n    \"\"\"\n    assert bboxes.shape[-1] % 4 == 0\n    assert direction in ['horizontal', 'vertical']\n    flipped = bboxes.clone()\n    if direction == 'vertical':\n        flipped[..., 1::4] = img_shape[0] - bboxes[..., 3::4]\n        flipped[..., 3::4] = img_shape[0] - bboxes[..., 1::4]\n    else:\n        flipped[:, 0::4] = img_shape[1] - bboxes[:, 2::4]\n        flipped[:, 2::4] = img_shape[1] - bboxes[:, 0::4]\n    return flipped\n\n\ndef polygon_flip(polygons, img_shape, direction='horizontal'):\n    assert direction in ['horizontal', 'vertical']\n    flipped = polygons.clone()\n    if direction == 'horizontal':\n        dim = img_shape[1]\n        idx = 0\n    else:\n        dim = img_shape[0]\n        idx = 1\n    flipped[:, idx::2] = dim - flipped[:, idx::2]\n    if flipped.size(0) > 0:\n        x = flipped.reshape(flipped.size(0), -1, 2)\n        new_x = torch.zeros_like(x)\n        new_x[:, 1:] = torch.flip(x, [1])[:,:-1]\n        new_x[:, 0] = torch.flip(x, [1])[:, -1]\n        flipped = new_x.reshape(flipped.size(0), -1)\n    return flipped\n\ndef extreme_flip(extremes, img_shape, direction='horizontal'):\n    assert direction in ['horizontal', 'vertical']\n    flipped = extremes.clone()\n    if direction == 'horizontal':\n        w = img_shape[1]\n        flipped[..., 0::8] = w - extremes[..., 0::8]\n        flipped[..., 2::8] = w - extremes[..., 6::8]\n        flipped[..., 3::8] = extremes[..., 7::8]\n        flipped[..., 4::8] = w - extremes[..., 4::8]\n        flipped[..., 6::8] = w - extremes[..., 2::8]\n        flipped[..., 7::8] = extremes[..., 3::8]\n    else:\n        h = img_shape[0]\n        flipped[..., 1::8] = h - extremes[..., 5::8]\n        flipped[..., 0::8] = extremes[..., 4::8]\n        flipped[..., 3::8] = h - extremes[..., 3::8]\n        flipped[..., 5::8] = h - extremes[..., 1::8]\n        flipped[..., 4::8] = extremes[..., 0::8]\n        flipped[..., 7::8] = h - extremes[..., 7::8]\n    return flipped\n\ndef kps_flip(kps, img_shape, direction='horizontal'):\n    assert direction in ['horizontal', 'vertical']\n    keypoint_flip_idx = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10],\n                         [11, 12], [13, 14], [15, 16]]\n\n    flipped = kps.clone()\n    if direction == 'horizontal':\n        dim = img_shape[1]\n        idx = 0\n    else:\n        dim = img_shape[0]\n        idx = 1\n    if flipped.size(0) > 0:\n        flipped[:, idx::2] = dim - flipped[:, idx::2]\n        flipped = flipped.reshape(flipped.shape[0], -1, 2)\n        for e in keypoint_flip_idx:\n            flipped[:, e[0]], flipped[:, e[1]] = flipped[:, e[1]].clone(), flipped[:, e[0]].clone()\n        flipped = flipped.reshape(flipped.shape[0], -1)\n\n    return flipped\n\n\ndef bbox_mapping(bboxes,\n                 img_shape,\n                 scale_factor,\n                 flip,\n                 flip_direction='horizontal'):\n    \"\"\"Map bboxes from the original image scale to testing scale\"\"\"\n    new_bboxes = bboxes * bboxes.new_tensor(scale_factor)\n    if flip:\n        new_bboxes = bbox_flip(new_bboxes, img_shape, flip_direction)\n    return new_bboxes\n\n\ndef bbox_mapping_back(bboxes,\n                      img_shape,\n                      scale_factor,\n                      flip,\n                      flip_direction='horizontal'):\n    \"\"\"Map bboxes from testing scale to original image scale\"\"\"\n    new_bboxes = bbox_flip(bboxes, img_shape,\n                           flip_direction) if flip else bboxes\n    new_bboxes = new_bboxes.view(-1, 4) / new_bboxes.new_tensor(scale_factor)\n    return new_bboxes.view(bboxes.shape)\n\n\ndef instance_mapping_back(bboxes,\n                          vectors,\n                          img_shape,\n                          scale_factor,\n                          flip,\n                          task,\n                          flip_direction='horizontal'):\n    \n    new_bboxes = bbox_flip(bboxes, img_shape, flip_direction) if flip else bboxes\n    new_bboxes = new_bboxes.view(-1, 4) / new_bboxes.new_tensor(scale_factor)\n    if task == 'bbox':\n        new_vectors = extreme_flip(vectors, img_shape, flip_direction) if flip else vectors\n        vect_scale_factor = new_vectors.new_tensor(scale_factor[:2]).repeat(int(new_vectors.size(1)/2))\n        new_vectors_view = new_vectors.view(-1, new_vectors.size(1))/vect_scale_factor\n    elif task == 'segm':\n        new_vectors = polygon_flip(vectors, img_shape, flip_direction) if flip else vectors\n        vect_scale_factor = new_vectors.new_tensor(scale_factor[:2]).repeat(int(new_vectors.size(1)/2))\n        new_vectors_view = new_vectors.view(-1, new_vectors.size(1))/vect_scale_factor\n    elif task == 'pose_bbox' or task == 'pose_kbox':\n        new_vectors = kps_flip(vectors, img_shape, flip_direction) if flip else vectors\n        vect_scale_factor = new_vectors.new_tensor(scale_factor[:2]).repeat(int(new_vectors.size(1)/2))\n        new_vectors_view = new_vectors.view(-1, new_vectors.size(1))/vect_scale_factor\n    return new_bboxes.view(bboxes.shape), new_vectors_view.view(new_vectors.shape)\n\ndef bbox2roi(bbox_list):\n    \"\"\"Convert a list of bboxes to roi format.\n\n    Args:\n        bbox_list (list[Tensor]): a list of bboxes corresponding to a batch\n            of images.\n\n    Returns:\n        Tensor: shape (n, 5), [batch_ind, x1, y1, x2, y2]\n    \"\"\"\n    rois_list = []\n    for img_id, bboxes in enumerate(bbox_list):\n        if bboxes.size(0) > 0:\n            img_inds = bboxes.new_full((bboxes.size(0), 1), img_id)\n            rois = torch.cat([img_inds, bboxes[:, :4]], dim=-1)\n        else:\n            rois = bboxes.new_zeros((0, 5))\n        rois_list.append(rois)\n    rois = torch.cat(rois_list, 0)\n    return rois\n\n\ndef roi2bbox(rois):\n    \"\"\"Convert rois to bounding box format\n\n    Args:\n        rois (torch.Tensor): RoIs with the shape (n, 5) where the first\n            column indicates batch id of each RoI.\n\n    Returns:\n        list[torch.Tensor]: Converted boxes of corresponding rois.\n    \"\"\"\n    bbox_list = []\n    img_ids = torch.unique(rois[:, 0].cpu(), sorted=True)\n    for img_id in img_ids:\n        inds = (rois[:, 0] == img_id.item())\n        bbox = rois[inds, 1:]\n        bbox_list.append(bbox)\n    return bbox_list\n\n\ndef bbox2result(bboxes, labels, num_classes):\n    \"\"\"Convert detection results to a list of numpy arrays.\n\n    Args:\n        bboxes (Tensor): shape (n, 5)\n        labels (Tensor): shape (n, )\n        num_classes (int): class number, including background class\n\n    Returns:\n        list(ndarray): bbox results of each class\n    \"\"\"\n    if bboxes.shape[0] == 0:\n        return [np.zeros((0, 5), dtype=np.float32) for i in range(num_classes)]\n    else:\n        bboxes = bboxes.cpu().numpy()\n        labels = labels.cpu().numpy()\n        return [bboxes[labels == i, :] for i in range(num_classes)]\n\ndef bbox_extreme2result(bboxes, extremes, labels, num_classes):\n    if bboxes.shape[0] == 0:\n        return [[np.zeros((0, 5), dtype=np.float32) for i in range(num_classes)],\n                [np.zeros((0, 8), dtype=np.float32) for i in range(num_classes)]]\n    else:\n        bboxes = bboxes.cpu().numpy()\n        labels = labels.cpu().numpy()\n        extremes = extremes.cpu().numpy()\n        return [[bboxes[labels == i, :] for i in range(num_classes)],\n                [extremes[labels == i, :] for i in range(num_classes)]]\n\ndef bbox_poly2result(bboxes, polygons, labels, num_classes, num_contour_points):\n    if bboxes.shape[0] == 0:\n        return [[np.zeros((0, 5), dtype=np.float32) for i in range(num_classes)],\n                [np.zeros((0, num_contour_points*2), dtype=np.float32) for i in range(num_classes)]]\n    else:\n        bboxes = bboxes.cpu().numpy()\n        labels = labels.cpu().numpy()\n        polygons = polygons.cpu().numpy()\n        return [[bboxes[labels == i, :] for i in range(num_classes)],\n                [polygons[labels == i, :] for i in range(num_classes)]]\n\n\ndef distance2bbox(points, distance, max_shape=None):\n    \"\"\"Decode distance prediction to bounding box.\n\n    Args:\n        points (Tensor): Shape (n, 2), [x, y].\n        distance (Tensor): Distance from the given point to 4\n            boundaries (left, top, right, bottom).\n        max_shape (tuple): Shape of the image.\n\n    Returns:\n        Tensor: Decoded bboxes.\n    \"\"\"\n    x1 = points[:, 0] - distance[:, 0]\n    y1 = points[:, 1] - distance[:, 1]\n    x2 = points[:, 0] + distance[:, 2]\n    y2 = points[:, 1] + distance[:, 3]\n    if max_shape is not None:\n        x1 = x1.clamp(min=0, max=max_shape[1])\n        y1 = y1.clamp(min=0, max=max_shape[0])\n        x2 = x2.clamp(min=0, max=max_shape[1])\n        y2 = y2.clamp(min=0, max=max_shape[0])\n    return torch.stack([x1, y1, x2, y2], -1)\n\n\ndef bbox2distance(points, bbox, max_dis=None, eps=0.1):\n    \"\"\"Decode bounding box based on distances.\n\n    Args:\n        points (Tensor): Shape (n, 2), [x, y].\n        bbox (Tensor): Shape (n, 4), \"xyxy\" format\n        max_dis (float): Upper bound of the distance.\n        eps (float): a small value to ensure target < max_dis, instead <=\n\n    Returns:\n        Tensor: Decoded distances.\n    \"\"\"\n    left = points[:, 0] - bbox[:, 0]\n    top = points[:, 1] - bbox[:, 1]\n    right = bbox[:, 2] - points[:, 0]\n    bottom = bbox[:, 3] - points[:, 1]\n    if max_dis is not None:\n        left = left.clamp(min=0, max=max_dis - eps)\n        top = top.clamp(min=0, max=max_dis - eps)\n        right = right.clamp(min=0, max=max_dis - eps)\n        bottom = bottom.clamp(min=0, max=max_dis - eps)\n    return torch.stack([left, top, right, bottom], -1)\n"
  },
  {
    "path": "code/mmdet/core/evaluation/__init__.py",
    "content": "from .class_names import (cityscapes_classes, coco_classes, dataset_aliases,\n                          get_classes, imagenet_det_classes,\n                          imagenet_vid_classes, voc_classes)\nfrom .eval_hooks import DistEvalHook, EvalHook\nfrom .mean_ap import average_precision, eval_map, print_map_summary\nfrom .recall import (eval_recalls, plot_iou_recall, plot_num_recall,\n                     print_recall_summary)\n\n__all__ = [\n    'voc_classes', 'imagenet_det_classes', 'imagenet_vid_classes',\n    'coco_classes', 'cityscapes_classes', 'dataset_aliases', 'get_classes',\n    'DistEvalHook', 'EvalHook', 'average_precision', 'eval_map',\n    'print_map_summary', 'eval_recalls', 'print_recall_summary',\n    'plot_num_recall', 'plot_iou_recall'\n]\n"
  },
  {
    "path": "code/mmdet/core/evaluation/bbox_overlaps.py",
    "content": "import numpy as np\n\n\ndef bbox_overlaps(bboxes1, bboxes2, mode='iou', eps=1e-6):\n    \"\"\"Calculate the ious between each bbox of bboxes1 and bboxes2.\n\n    Args:\n        bboxes1(ndarray): shape (n, 4)\n        bboxes2(ndarray): shape (k, 4)\n        mode(str): iou (intersection over union) or iof (intersection\n            over foreground)\n\n    Returns:\n        ious(ndarray): shape (n, k)\n    \"\"\"\n\n    assert mode in ['iou', 'iof']\n\n    bboxes1 = bboxes1.astype(np.float32)\n    bboxes2 = bboxes2.astype(np.float32)\n    rows = bboxes1.shape[0]\n    cols = bboxes2.shape[0]\n    ious = np.zeros((rows, cols), dtype=np.float32)\n    if rows * cols == 0:\n        return ious\n    exchange = False\n    if bboxes1.shape[0] > bboxes2.shape[0]:\n        bboxes1, bboxes2 = bboxes2, bboxes1\n        ious = np.zeros((cols, rows), dtype=np.float32)\n        exchange = True\n    area1 = (bboxes1[:, 2] - bboxes1[:, 0]) * (bboxes1[:, 3] - bboxes1[:, 1])\n    area2 = (bboxes2[:, 2] - bboxes2[:, 0]) * (bboxes2[:, 3] - bboxes2[:, 1])\n    for i in range(bboxes1.shape[0]):\n        x_start = np.maximum(bboxes1[i, 0], bboxes2[:, 0])\n        y_start = np.maximum(bboxes1[i, 1], bboxes2[:, 1])\n        x_end = np.minimum(bboxes1[i, 2], bboxes2[:, 2])\n        y_end = np.minimum(bboxes1[i, 3], bboxes2[:, 3])\n        overlap = np.maximum(x_end - x_start, 0) * np.maximum(\n            y_end - y_start, 0)\n        if mode == 'iou':\n            union = area1[i] + area2 - overlap\n        else:\n            union = area1[i] if not exchange else area2\n        union = np.maximum(union, eps)\n        ious[i, :] = overlap / union\n    if exchange:\n        ious = ious.T\n    return ious\n"
  },
  {
    "path": "code/mmdet/core/evaluation/class_names.py",
    "content": "import mmcv\n\n\ndef wider_face_classes():\n    return ['face']\n\n\ndef voc_classes():\n    return [\n        'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat',\n        'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person',\n        'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'\n    ]\n\n\ndef imagenet_det_classes():\n    return [\n        'accordion', 'airplane', 'ant', 'antelope', 'apple', 'armadillo',\n        'artichoke', 'axe', 'baby_bed', 'backpack', 'bagel', 'balance_beam',\n        'banana', 'band_aid', 'banjo', 'baseball', 'basketball', 'bathing_cap',\n        'beaker', 'bear', 'bee', 'bell_pepper', 'bench', 'bicycle', 'binder',\n        'bird', 'bookshelf', 'bow_tie', 'bow', 'bowl', 'brassiere', 'burrito',\n        'bus', 'butterfly', 'camel', 'can_opener', 'car', 'cart', 'cattle',\n        'cello', 'centipede', 'chain_saw', 'chair', 'chime', 'cocktail_shaker',\n        'coffee_maker', 'computer_keyboard', 'computer_mouse', 'corkscrew',\n        'cream', 'croquet_ball', 'crutch', 'cucumber', 'cup_or_mug', 'diaper',\n        'digital_clock', 'dishwasher', 'dog', 'domestic_cat', 'dragonfly',\n        'drum', 'dumbbell', 'electric_fan', 'elephant', 'face_powder', 'fig',\n        'filing_cabinet', 'flower_pot', 'flute', 'fox', 'french_horn', 'frog',\n        'frying_pan', 'giant_panda', 'goldfish', 'golf_ball', 'golfcart',\n        'guacamole', 'guitar', 'hair_dryer', 'hair_spray', 'hamburger',\n        'hammer', 'hamster', 'harmonica', 'harp', 'hat_with_a_wide_brim',\n        'head_cabbage', 'helmet', 'hippopotamus', 'horizontal_bar', 'horse',\n        'hotdog', 'iPod', 'isopod', 'jellyfish', 'koala_bear', 'ladle',\n        'ladybug', 'lamp', 'laptop', 'lemon', 'lion', 'lipstick', 'lizard',\n        'lobster', 'maillot', 'maraca', 'microphone', 'microwave', 'milk_can',\n        'miniskirt', 'monkey', 'motorcycle', 'mushroom', 'nail', 'neck_brace',\n        'oboe', 'orange', 'otter', 'pencil_box', 'pencil_sharpener', 'perfume',\n        'person', 'piano', 'pineapple', 'ping-pong_ball', 'pitcher', 'pizza',\n        'plastic_bag', 'plate_rack', 'pomegranate', 'popsicle', 'porcupine',\n        'power_drill', 'pretzel', 'printer', 'puck', 'punching_bag', 'purse',\n        'rabbit', 'racket', 'ray', 'red_panda', 'refrigerator',\n        'remote_control', 'rubber_eraser', 'rugby_ball', 'ruler',\n        'salt_or_pepper_shaker', 'saxophone', 'scorpion', 'screwdriver',\n        'seal', 'sheep', 'ski', 'skunk', 'snail', 'snake', 'snowmobile',\n        'snowplow', 'soap_dispenser', 'soccer_ball', 'sofa', 'spatula',\n        'squirrel', 'starfish', 'stethoscope', 'stove', 'strainer',\n        'strawberry', 'stretcher', 'sunglasses', 'swimming_trunks', 'swine',\n        'syringe', 'table', 'tape_player', 'tennis_ball', 'tick', 'tie',\n        'tiger', 'toaster', 'traffic_light', 'train', 'trombone', 'trumpet',\n        'turtle', 'tv_or_monitor', 'unicycle', 'vacuum', 'violin',\n        'volleyball', 'waffle_iron', 'washer', 'water_bottle', 'watercraft',\n        'whale', 'wine_bottle', 'zebra'\n    ]\n\n\ndef imagenet_vid_classes():\n    return [\n        'airplane', 'antelope', 'bear', 'bicycle', 'bird', 'bus', 'car',\n        'cattle', 'dog', 'domestic_cat', 'elephant', 'fox', 'giant_panda',\n        'hamster', 'horse', 'lion', 'lizard', 'monkey', 'motorcycle', 'rabbit',\n        'red_panda', 'sheep', 'snake', 'squirrel', 'tiger', 'train', 'turtle',\n        'watercraft', 'whale', 'zebra'\n    ]\n\n\ndef coco_classes():\n    return [\n        'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',\n        'truck', 'boat', 'traffic_light', 'fire_hydrant', 'stop_sign',\n        'parking_meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep',\n        'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella',\n        'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard',\n        'sports_ball', 'kite', 'baseball_bat', 'baseball_glove', 'skateboard',\n        'surfboard', 'tennis_racket', 'bottle', 'wine_glass', 'cup', 'fork',\n        'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',\n        'broccoli', 'carrot', 'hot_dog', 'pizza', 'donut', 'cake', 'chair',\n        'couch', 'potted_plant', 'bed', 'dining_table', 'toilet', 'tv',\n        'laptop', 'mouse', 'remote', 'keyboard', 'cell_phone', 'microwave',\n        'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',\n        'scissors', 'teddy_bear', 'hair_drier', 'toothbrush'\n    ]\n\n\ndef cityscapes_classes():\n    return [\n        'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle',\n        'bicycle'\n    ]\n\n\ndataset_aliases = {\n    'voc': ['voc', 'pascal_voc', 'voc07', 'voc12'],\n    'imagenet_det': ['det', 'imagenet_det', 'ilsvrc_det'],\n    'imagenet_vid': ['vid', 'imagenet_vid', 'ilsvrc_vid'],\n    'coco': ['coco', 'mscoco', 'ms_coco'],\n    'wider_face': ['WIDERFaceDataset', 'wider_face', 'WDIERFace'],\n    'cityscapes': ['cityscapes']\n}\n\n\ndef get_classes(dataset):\n    \"\"\"Get class names of a dataset.\"\"\"\n    alias2name = {}\n    for name, aliases in dataset_aliases.items():\n        for alias in aliases:\n            alias2name[alias] = name\n\n    if mmcv.is_str(dataset):\n        if dataset in alias2name:\n            labels = eval(alias2name[dataset] + '_classes()')\n        else:\n            raise ValueError(f'Unrecognized dataset: {dataset}')\n    else:\n        raise TypeError(f'dataset must a str, but got {type(dataset)}')\n    return labels\n"
  },
  {
    "path": "code/mmdet/core/evaluation/eval_hooks.py",
    "content": "import os.path as osp\n\nfrom mmcv.runner import Hook\nfrom torch.utils.data import DataLoader\n\n\nclass EvalHook(Hook):\n    \"\"\"Evaluation hook.\n\n    Attributes:\n        dataloader (DataLoader): A PyTorch dataloader.\n        interval (int): Evaluation interval (by epochs). Default: 1.\n    \"\"\"\n\n    def __init__(self, dataloader, interval=1, bbox_head=None, **eval_kwargs):\n        if not isinstance(dataloader, DataLoader):\n            raise TypeError('dataloader must be a pytorch DataLoader, but got'\n                            f' {type(dataloader)}')\n        self.dataloader = dataloader\n        self.interval = interval\n        self.eval_kwargs = eval_kwargs\n        self.bbox_head = bbox_head\n\n    def after_train_epoch(self, runner):\n        if not self.every_n_epochs(runner, self.interval):\n            return\n        from mmdet.apis import single_gpu_test\n        results = single_gpu_test(runner.model, self.dataloader, \n                                  bbox_head=self.bbox_head, show=False)\n        self.evaluate(runner, results)\n\n    def evaluate(self, runner, results):\n        eval_res = self.dataloader.dataset.evaluate(\n            results, logger=runner.logger, **self.eval_kwargs)\n        for name, val in eval_res.items():\n            runner.log_buffer.output[name] = val\n        runner.log_buffer.ready = True\n\n\nclass DistEvalHook(EvalHook):\n    \"\"\"Distributed evaluation hook.\n\n    Attributes:\n        dataloader (DataLoader): A PyTorch dataloader.\n        interval (int): Evaluation interval (by epochs). Default: 1.\n        tmpdir (str | None): Temporary directory to save the results of all\n            processes. Default: None.\n        gpu_collect (bool): Whether to use gpu or cpu to collect results.\n            Default: False.\n    \"\"\"\n\n    def __init__(self,\n                 dataloader,\n                 interval=1,\n                 bbox_head=None,\n                 gpu_collect=False,\n                 **eval_kwargs):\n        if not isinstance(dataloader, DataLoader):\n            raise TypeError('dataloader must be a pytorch DataLoader, but got '\n                            f'{type(dataloader)}')\n        self.dataloader = dataloader\n        self.interval = interval\n        self.gpu_collect = gpu_collect\n        self.eval_kwargs = eval_kwargs\n        self.bbox_head = bbox_head\n\n    def after_train_epoch(self, runner):\n        if not self.every_n_epochs(runner, self.interval):\n            return\n        from mmdet.apis import multi_gpu_test\n        results = multi_gpu_test(\n            runner.model,\n            self.dataloader,\n            bbox_head=self.bbox_head,\n            tmpdir=osp.join(runner.work_dir, '.eval_hook'),\n            gpu_collect=self.gpu_collect)\n        if runner.rank == 0:\n            print('\\n')\n            self.evaluate(runner, results)\n"
  },
  {
    "path": "code/mmdet/core/evaluation/mean_ap.py",
    "content": "from multiprocessing import Pool\n\nimport mmcv\nimport numpy as np\nfrom mmcv.utils import print_log\nfrom terminaltables import AsciiTable\n\nfrom .bbox_overlaps import bbox_overlaps\nfrom .class_names import get_classes\n\n\ndef average_precision(recalls, precisions, mode='area'):\n    \"\"\"Calculate average precision (for single or multiple scales).\n\n    Args:\n        recalls (ndarray): shape (num_scales, num_dets) or (num_dets, )\n        precisions (ndarray): shape (num_scales, num_dets) or (num_dets, )\n        mode (str): 'area' or '11points', 'area' means calculating the area\n            under precision-recall curve, '11points' means calculating\n            the average precision of recalls at [0, 0.1, ..., 1]\n\n    Returns:\n        float or ndarray: calculated average precision\n    \"\"\"\n    no_scale = False\n    if recalls.ndim == 1:\n        no_scale = True\n        recalls = recalls[np.newaxis, :]\n        precisions = precisions[np.newaxis, :]\n    assert recalls.shape == precisions.shape and recalls.ndim == 2\n    num_scales = recalls.shape[0]\n    ap = np.zeros(num_scales, dtype=np.float32)\n    if mode == 'area':\n        zeros = np.zeros((num_scales, 1), dtype=recalls.dtype)\n        ones = np.ones((num_scales, 1), dtype=recalls.dtype)\n        mrec = np.hstack((zeros, recalls, ones))\n        mpre = np.hstack((zeros, precisions, zeros))\n        for i in range(mpre.shape[1] - 1, 0, -1):\n            mpre[:, i - 1] = np.maximum(mpre[:, i - 1], mpre[:, i])\n        for i in range(num_scales):\n            ind = np.where(mrec[i, 1:] != mrec[i, :-1])[0]\n            ap[i] = np.sum(\n                (mrec[i, ind + 1] - mrec[i, ind]) * mpre[i, ind + 1])\n    elif mode == '11points':\n        for i in range(num_scales):\n            for thr in np.arange(0, 1 + 1e-3, 0.1):\n                precs = precisions[i, recalls[i, :] >= thr]\n                prec = precs.max() if precs.size > 0 else 0\n                ap[i] += prec\n            ap /= 11\n    else:\n        raise ValueError(\n            'Unrecognized mode, only \"area\" and \"11points\" are supported')\n    if no_scale:\n        ap = ap[0]\n    return ap\n\n\ndef tpfp_imagenet(det_bboxes,\n                  gt_bboxes,\n                  gt_bboxes_ignore=None,\n                  default_iou_thr=0.5,\n                  area_ranges=None):\n    \"\"\"Check if detected bboxes are true positive or false positive.\n\n    Args:\n        det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5).\n        gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4).\n        gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image,\n            of shape (k, 4). Default: None\n        default_iou_thr (float): IoU threshold to be considered as matched for\n            medium and large bboxes (small ones have special rules).\n            Default: 0.5.\n        area_ranges (list[tuple] | None): Range of bbox areas to be evaluated,\n            in the format [(min1, max1), (min2, max2), ...]. Default: None.\n\n    Returns:\n        tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of\n            each array is (num_scales, m).\n    \"\"\"\n    # an indicator of ignored gts\n    gt_ignore_inds = np.concatenate(\n        (np.zeros(gt_bboxes.shape[0], dtype=np.bool),\n         np.ones(gt_bboxes_ignore.shape[0], dtype=np.bool)))\n    # stack gt_bboxes and gt_bboxes_ignore for convenience\n    gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore))\n\n    num_dets = det_bboxes.shape[0]\n    num_gts = gt_bboxes.shape[0]\n    if area_ranges is None:\n        area_ranges = [(None, None)]\n    num_scales = len(area_ranges)\n    # tp and fp are of shape (num_scales, num_gts), each row is tp or fp\n    # of a certain scale.\n    tp = np.zeros((num_scales, num_dets), dtype=np.float32)\n    fp = np.zeros((num_scales, num_dets), dtype=np.float32)\n    if gt_bboxes.shape[0] == 0:\n        if area_ranges == [(None, None)]:\n            fp[...] = 1\n        else:\n            det_areas = (det_bboxes[:, 2] - det_bboxes[:, 0]) * (\n                det_bboxes[:, 3] - det_bboxes[:, 1])\n            for i, (min_area, max_area) in enumerate(area_ranges):\n                fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1\n        return tp, fp\n    ious = bbox_overlaps(det_bboxes, gt_bboxes - 1)\n    gt_w = gt_bboxes[:, 2] - gt_bboxes[:, 0]\n    gt_h = gt_bboxes[:, 3] - gt_bboxes[:, 1]\n    iou_thrs = np.minimum((gt_w * gt_h) / ((gt_w + 10.0) * (gt_h + 10.0)),\n                          default_iou_thr)\n    # sort all detections by scores in descending order\n    sort_inds = np.argsort(-det_bboxes[:, -1])\n    for k, (min_area, max_area) in enumerate(area_ranges):\n        gt_covered = np.zeros(num_gts, dtype=bool)\n        # if no area range is specified, gt_area_ignore is all False\n        if min_area is None:\n            gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool)\n        else:\n            gt_areas = gt_w * gt_h\n            gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area)\n        for i in sort_inds:\n            max_iou = -1\n            matched_gt = -1\n            # find best overlapped available gt\n            for j in range(num_gts):\n                # different from PASCAL VOC: allow finding other gts if the\n                # best overlaped ones are already matched by other det bboxes\n                if gt_covered[j]:\n                    continue\n                elif ious[i, j] >= iou_thrs[j] and ious[i, j] > max_iou:\n                    max_iou = ious[i, j]\n                    matched_gt = j\n            # there are 4 cases for a det bbox:\n            # 1. it matches a gt, tp = 1, fp = 0\n            # 2. it matches an ignored gt, tp = 0, fp = 0\n            # 3. it matches no gt and within area range, tp = 0, fp = 1\n            # 4. it matches no gt but is beyond area range, tp = 0, fp = 0\n            if matched_gt >= 0:\n                gt_covered[matched_gt] = 1\n                if not (gt_ignore_inds[matched_gt]\n                        or gt_area_ignore[matched_gt]):\n                    tp[k, i] = 1\n            elif min_area is None:\n                fp[k, i] = 1\n            else:\n                bbox = det_bboxes[i, :4]\n                area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])\n                if area >= min_area and area < max_area:\n                    fp[k, i] = 1\n    return tp, fp\n\n\ndef tpfp_default(det_bboxes,\n                 gt_bboxes,\n                 gt_bboxes_ignore=None,\n                 iou_thr=0.5,\n                 area_ranges=None):\n    \"\"\"Check if detected bboxes are true positive or false positive.\n\n    Args:\n        det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5).\n        gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4).\n        gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image,\n            of shape (k, 4). Default: None\n        iou_thr (float): IoU threshold to be considered as matched.\n            Default: 0.5.\n        area_ranges (list[tuple] | None): Range of bbox areas to be evaluated,\n            in the format [(min1, max1), (min2, max2), ...]. Default: None.\n\n    Returns:\n        tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of\n            each array is (num_scales, m).\n    \"\"\"\n    # an indicator of ignored gts\n    gt_ignore_inds = np.concatenate(\n        (np.zeros(gt_bboxes.shape[0], dtype=np.bool),\n         np.ones(gt_bboxes_ignore.shape[0], dtype=np.bool)))\n    # stack gt_bboxes and gt_bboxes_ignore for convenience\n    gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore))\n\n    num_dets = det_bboxes.shape[0]\n    num_gts = gt_bboxes.shape[0]\n    if area_ranges is None:\n        area_ranges = [(None, None)]\n    num_scales = len(area_ranges)\n    # tp and fp are of shape (num_scales, num_gts), each row is tp or fp of\n    # a certain scale\n    tp = np.zeros((num_scales, num_dets), dtype=np.float32)\n    fp = np.zeros((num_scales, num_dets), dtype=np.float32)\n\n    # if there is no gt bboxes in this image, then all det bboxes\n    # within area range are false positives\n    if gt_bboxes.shape[0] == 0:\n        if area_ranges == [(None, None)]:\n            fp[...] = 1\n        else:\n            det_areas = (det_bboxes[:, 2] - det_bboxes[:, 0]) * (\n                det_bboxes[:, 3] - det_bboxes[:, 1])\n            for i, (min_area, max_area) in enumerate(area_ranges):\n                fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1\n        return tp, fp\n\n    ious = bbox_overlaps(det_bboxes, gt_bboxes)\n    # for each det, the max iou with all gts\n    ious_max = ious.max(axis=1)\n    # for each det, which gt overlaps most with it\n    ious_argmax = ious.argmax(axis=1)\n    # sort all dets in descending order by scores\n    sort_inds = np.argsort(-det_bboxes[:, -1])\n    for k, (min_area, max_area) in enumerate(area_ranges):\n        gt_covered = np.zeros(num_gts, dtype=bool)\n        # if no area range is specified, gt_area_ignore is all False\n        if min_area is None:\n            gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool)\n        else:\n            gt_areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * (\n                gt_bboxes[:, 3] - gt_bboxes[:, 1])\n            gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area)\n        for i in sort_inds:\n            if ious_max[i] >= iou_thr:\n                matched_gt = ious_argmax[i]\n                if not (gt_ignore_inds[matched_gt]\n                        or gt_area_ignore[matched_gt]):\n                    if not gt_covered[matched_gt]:\n                        gt_covered[matched_gt] = True\n                        tp[k, i] = 1\n                    else:\n                        fp[k, i] = 1\n                # otherwise ignore this detected bbox, tp = 0, fp = 0\n            elif min_area is None:\n                fp[k, i] = 1\n            else:\n                bbox = det_bboxes[i, :4]\n                area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])\n                if area >= min_area and area < max_area:\n                    fp[k, i] = 1\n    return tp, fp\n\n\ndef get_cls_results(det_results, annotations, class_id):\n    \"\"\"Get det results and gt information of a certain class.\n\n    Args:\n        det_results (list[list]): Same as `eval_map()`.\n        annotations (list[dict]): Same as `eval_map()`.\n        class_id (int): ID of a specific class.\n\n    Returns:\n        tuple[list[np.ndarray]]: detected bboxes, gt bboxes, ignored gt bboxes\n    \"\"\"\n    cls_dets = [img_res[class_id] for img_res in det_results]\n    cls_gts = []\n    cls_gts_ignore = []\n    for ann in annotations:\n        gt_inds = ann['labels'] == class_id\n        cls_gts.append(ann['bboxes'][gt_inds, :])\n\n        if ann.get('labels_ignore', None) is not None:\n            ignore_inds = ann['labels_ignore'] == class_id\n            cls_gts_ignore.append(ann['bboxes_ignore'][ignore_inds, :])\n        else:\n            cls_gts_ignore.append(np.empty((0, 4), dtype=np.float32))\n\n    return cls_dets, cls_gts, cls_gts_ignore\n\n\ndef eval_map(det_results,\n             annotations,\n             scale_ranges=None,\n             iou_thr=0.5,\n             dataset=None,\n             logger=None,\n             nproc=4):\n    \"\"\"Evaluate mAP of a dataset.\n\n    Args:\n        det_results (list[list]): [[cls1_det, cls2_det, ...], ...].\n            The outer list indicates images, and the inner list indicates\n            per-class detected bboxes.\n        annotations (list[dict]): Ground truth annotations where each item of\n            the list indicates an image. Keys of annotations are:\n\n            - `bboxes`: numpy array of shape (n, 4)\n            - `labels`: numpy array of shape (n, )\n            - `bboxes_ignore` (optional): numpy array of shape (k, 4)\n            - `labels_ignore` (optional): numpy array of shape (k, )\n        scale_ranges (list[tuple] | None): Range of scales to be evaluated,\n            in the format [(min1, max1), (min2, max2), ...]. A range of\n            (32, 64) means the area range between (32**2, 64**2).\n            Default: None.\n        iou_thr (float): IoU threshold to be considered as matched.\n            Default: 0.5.\n        dataset (list[str] | str | None): Dataset name or dataset classes,\n            there are minor differences in metrics for different datsets, e.g.\n            \"voc07\", \"imagenet_det\", etc. Default: None.\n        logger (logging.Logger | str | None): The way to print the mAP\n            summary. See `mmdet.utils.print_log()` for details. Default: None.\n        nproc (int): Processes used for computing TP and FP.\n            Default: 4.\n\n    Returns:\n        tuple: (mAP, [dict, dict, ...])\n    \"\"\"\n    assert len(det_results) == len(annotations)\n\n    num_imgs = len(det_results)\n    num_scales = len(scale_ranges) if scale_ranges is not None else 1\n    num_classes = len(det_results[0])  # positive class num\n    area_ranges = ([(rg[0]**2, rg[1]**2) for rg in scale_ranges]\n                   if scale_ranges is not None else None)\n\n    pool = Pool(nproc)\n    eval_results = []\n    for i in range(num_classes):\n        # get gt and det bboxes of this class\n        cls_dets, cls_gts, cls_gts_ignore = get_cls_results(\n            det_results, annotations, i)\n        # choose proper function according to datasets to compute tp and fp\n        if dataset in ['det', 'vid']:\n            tpfp_func = tpfp_imagenet\n        else:\n            tpfp_func = tpfp_default\n        # compute tp and fp for each image with multiple processes\n        tpfp = pool.starmap(\n            tpfp_func,\n            zip(cls_dets, cls_gts, cls_gts_ignore,\n                [iou_thr for _ in range(num_imgs)],\n                [area_ranges for _ in range(num_imgs)]))\n        tp, fp = tuple(zip(*tpfp))\n        # calculate gt number of each scale\n        # ignored gts or gts beyond the specific scale are not counted\n        num_gts = np.zeros(num_scales, dtype=int)\n        for j, bbox in enumerate(cls_gts):\n            if area_ranges is None:\n                num_gts[0] += bbox.shape[0]\n            else:\n                gt_areas = (bbox[:, 2] - bbox[:, 0]) * (\n                    bbox[:, 3] - bbox[:, 1])\n                for k, (min_area, max_area) in enumerate(area_ranges):\n                    num_gts[k] += np.sum((gt_areas >= min_area)\n                                         & (gt_areas < max_area))\n        # sort all det bboxes by score, also sort tp and fp\n        cls_dets = np.vstack(cls_dets)\n        num_dets = cls_dets.shape[0]\n        sort_inds = np.argsort(-cls_dets[:, -1])\n        tp = np.hstack(tp)[:, sort_inds]\n        fp = np.hstack(fp)[:, sort_inds]\n        # calculate recall and precision with tp and fp\n        tp = np.cumsum(tp, axis=1)\n        fp = np.cumsum(fp, axis=1)\n        eps = np.finfo(np.float32).eps\n        recalls = tp / np.maximum(num_gts[:, np.newaxis], eps)\n        precisions = tp / np.maximum((tp + fp), eps)\n        # calculate AP\n        if scale_ranges is None:\n            recalls = recalls[0, :]\n            precisions = precisions[0, :]\n            num_gts = num_gts.item()\n        mode = 'area' if dataset != 'voc07' else '11points'\n        ap = average_precision(recalls, precisions, mode)\n        eval_results.append({\n            'num_gts': num_gts,\n            'num_dets': num_dets,\n            'recall': recalls,\n            'precision': precisions,\n            'ap': ap\n        })\n    pool.close()\n    if scale_ranges is not None:\n        # shape (num_classes, num_scales)\n        all_ap = np.vstack([cls_result['ap'] for cls_result in eval_results])\n        all_num_gts = np.vstack(\n            [cls_result['num_gts'] for cls_result in eval_results])\n        mean_ap = []\n        for i in range(num_scales):\n            if np.any(all_num_gts[:, i] > 0):\n                mean_ap.append(all_ap[all_num_gts[:, i] > 0, i].mean())\n            else:\n                mean_ap.append(0.0)\n    else:\n        aps = []\n        for cls_result in eval_results:\n            if cls_result['num_gts'] > 0:\n                aps.append(cls_result['ap'])\n        mean_ap = np.array(aps).mean().item() if aps else 0.0\n\n    print_map_summary(\n        mean_ap, eval_results, dataset, area_ranges, logger=logger)\n\n    return mean_ap, eval_results\n\n\ndef print_map_summary(mean_ap,\n                      results,\n                      dataset=None,\n                      scale_ranges=None,\n                      logger=None):\n    \"\"\"Print mAP and results of each class.\n\n    A table will be printed to show the gts/dets/recall/AP of each class and\n    the mAP.\n\n    Args:\n        mean_ap (float): Calculated from `eval_map()`.\n        results (list[dict]): Calculated from `eval_map()`.\n        dataset (list[str] | str | None): Dataset name or dataset classes.\n        scale_ranges (list[tuple] | None): Range of scales to be evaluated.\n        logger (logging.Logger | str | None): The way to print the mAP\n            summary. See `mmdet.utils.print_log()` for details. Default: None.\n    \"\"\"\n\n    if logger == 'silent':\n        return\n\n    if isinstance(results[0]['ap'], np.ndarray):\n        num_scales = len(results[0]['ap'])\n    else:\n        num_scales = 1\n\n    if scale_ranges is not None:\n        assert len(scale_ranges) == num_scales\n\n    num_classes = len(results)\n\n    recalls = np.zeros((num_scales, num_classes), dtype=np.float32)\n    aps = np.zeros((num_scales, num_classes), dtype=np.float32)\n    num_gts = np.zeros((num_scales, num_classes), dtype=int)\n    for i, cls_result in enumerate(results):\n        if cls_result['recall'].size > 0:\n            recalls[:, i] = np.array(cls_result['recall'], ndmin=2)[:, -1]\n        aps[:, i] = cls_result['ap']\n        num_gts[:, i] = cls_result['num_gts']\n\n    if dataset is None:\n        label_names = [str(i) for i in range(num_classes)]\n    elif mmcv.is_str(dataset):\n        label_names = get_classes(dataset)\n    else:\n        label_names = dataset\n\n    if not isinstance(mean_ap, list):\n        mean_ap = [mean_ap]\n\n    header = ['class', 'gts', 'dets', 'recall', 'ap']\n    for i in range(num_scales):\n        if scale_ranges is not None:\n            print_log(f'Scale range {scale_ranges[i]}', logger=logger)\n        table_data = [header]\n        for j in range(num_classes):\n            row_data = [\n                label_names[j], num_gts[i, j], results[j]['num_dets'],\n                f'{recalls[i, j]:.3f}', f'{aps[i, j]:.3f}'\n            ]\n            table_data.append(row_data)\n        table_data.append(['mAP', '', '', '', f'{mean_ap[i]:.3f}'])\n        table = AsciiTable(table_data)\n        table.inner_footing_row_border = True\n        print_log('\\n' + table.table, logger=logger)\n"
  },
  {
    "path": "code/mmdet/core/evaluation/recall.py",
    "content": "from collections.abc import Sequence\n\nimport numpy as np\nfrom mmcv.utils import print_log\nfrom terminaltables import AsciiTable\n\nfrom .bbox_overlaps import bbox_overlaps\n\n\ndef _recalls(all_ious, proposal_nums, thrs):\n\n    img_num = all_ious.shape[0]\n    total_gt_num = sum([ious.shape[0] for ious in all_ious])\n\n    _ious = np.zeros((proposal_nums.size, total_gt_num), dtype=np.float32)\n    for k, proposal_num in enumerate(proposal_nums):\n        tmp_ious = np.zeros(0)\n        for i in range(img_num):\n            ious = all_ious[i][:, :proposal_num].copy()\n            gt_ious = np.zeros((ious.shape[0]))\n            if ious.size == 0:\n                tmp_ious = np.hstack((tmp_ious, gt_ious))\n                continue\n            for j in range(ious.shape[0]):\n                gt_max_overlaps = ious.argmax(axis=1)\n                max_ious = ious[np.arange(0, ious.shape[0]), gt_max_overlaps]\n                gt_idx = max_ious.argmax()\n                gt_ious[j] = max_ious[gt_idx]\n                box_idx = gt_max_overlaps[gt_idx]\n                ious[gt_idx, :] = -1\n                ious[:, box_idx] = -1\n            tmp_ious = np.hstack((tmp_ious, gt_ious))\n        _ious[k, :] = tmp_ious\n\n    _ious = np.fliplr(np.sort(_ious, axis=1))\n    recalls = np.zeros((proposal_nums.size, thrs.size))\n    for i, thr in enumerate(thrs):\n        recalls[:, i] = (_ious >= thr).sum(axis=1) / float(total_gt_num)\n\n    return recalls\n\n\ndef set_recall_param(proposal_nums, iou_thrs):\n    \"\"\"Check proposal_nums and iou_thrs and set correct format.\n    \"\"\"\n    if isinstance(proposal_nums, Sequence):\n        _proposal_nums = np.array(proposal_nums)\n    elif isinstance(proposal_nums, int):\n        _proposal_nums = np.array([proposal_nums])\n    else:\n        _proposal_nums = proposal_nums\n\n    if iou_thrs is None:\n        _iou_thrs = np.array([0.5])\n    elif isinstance(iou_thrs, Sequence):\n        _iou_thrs = np.array(iou_thrs)\n    elif isinstance(iou_thrs, float):\n        _iou_thrs = np.array([iou_thrs])\n    else:\n        _iou_thrs = iou_thrs\n\n    return _proposal_nums, _iou_thrs\n\n\ndef eval_recalls(gts,\n                 proposals,\n                 proposal_nums=None,\n                 iou_thrs=0.5,\n                 logger=None):\n    \"\"\"Calculate recalls.\n\n    Args:\n        gts (list[ndarray]): a list of arrays of shape (n, 4)\n        proposals (list[ndarray]): a list of arrays of shape (k, 4) or (k, 5)\n        proposal_nums (int | Sequence[int]): Top N proposals to be evaluated.\n        iou_thrs (float | Sequence[float]): IoU thresholds. Default: 0.5.\n        logger (logging.Logger | str | None): The way to print the recall\n            summary. See `mmdet.utils.print_log()` for details. Default: None.\n\n    Returns:\n        ndarray: recalls of different ious and proposal nums\n    \"\"\"\n\n    img_num = len(gts)\n    assert img_num == len(proposals)\n\n    proposal_nums, iou_thrs = set_recall_param(proposal_nums, iou_thrs)\n\n    all_ious = []\n    for i in range(img_num):\n        if proposals[i].ndim == 2 and proposals[i].shape[1] == 5:\n            scores = proposals[i][:, 4]\n            sort_idx = np.argsort(scores)[::-1]\n            img_proposal = proposals[i][sort_idx, :]\n        else:\n            img_proposal = proposals[i]\n        prop_num = min(img_proposal.shape[0], proposal_nums[-1])\n        if gts[i] is None or gts[i].shape[0] == 0:\n            ious = np.zeros((0, img_proposal.shape[0]), dtype=np.float32)\n        else:\n            ious = bbox_overlaps(gts[i], img_proposal[:prop_num, :4])\n        all_ious.append(ious)\n    all_ious = np.array(all_ious)\n    recalls = _recalls(all_ious, proposal_nums, iou_thrs)\n\n    print_recall_summary(recalls, proposal_nums, iou_thrs, logger=logger)\n    return recalls\n\n\ndef print_recall_summary(recalls,\n                         proposal_nums,\n                         iou_thrs,\n                         row_idxs=None,\n                         col_idxs=None,\n                         logger=None):\n    \"\"\"Print recalls in a table.\n\n    Args:\n        recalls (ndarray): calculated from `bbox_recalls`\n        proposal_nums (ndarray or list): top N proposals\n        iou_thrs (ndarray or list): iou thresholds\n        row_idxs (ndarray): which rows(proposal nums) to print\n        col_idxs (ndarray): which cols(iou thresholds) to print\n        logger (logging.Logger | str | None): The way to print the recall\n            summary. See `mmdet.utils.print_log()` for details. Default: None.\n    \"\"\"\n    proposal_nums = np.array(proposal_nums, dtype=np.int32)\n    iou_thrs = np.array(iou_thrs)\n    if row_idxs is None:\n        row_idxs = np.arange(proposal_nums.size)\n    if col_idxs is None:\n        col_idxs = np.arange(iou_thrs.size)\n    row_header = [''] + iou_thrs[col_idxs].tolist()\n    table_data = [row_header]\n    for i, num in enumerate(proposal_nums[row_idxs]):\n        row = [f'{val:.3f}' for val in recalls[row_idxs[i], col_idxs].tolist()]\n        row.insert(0, num)\n        table_data.append(row)\n    table = AsciiTable(table_data)\n    print_log('\\n' + table.table, logger=logger)\n\n\ndef plot_num_recall(recalls, proposal_nums):\n    \"\"\"Plot Proposal_num-Recalls curve.\n\n    Args:\n        recalls(ndarray or list): shape (k,)\n        proposal_nums(ndarray or list): same shape as `recalls`\n    \"\"\"\n    if isinstance(proposal_nums, np.ndarray):\n        _proposal_nums = proposal_nums.tolist()\n    else:\n        _proposal_nums = proposal_nums\n    if isinstance(recalls, np.ndarray):\n        _recalls = recalls.tolist()\n    else:\n        _recalls = recalls\n\n    import matplotlib.pyplot as plt\n    f = plt.figure()\n    plt.plot([0] + _proposal_nums, [0] + _recalls)\n    plt.xlabel('Proposal num')\n    plt.ylabel('Recall')\n    plt.axis([0, proposal_nums.max(), 0, 1])\n    f.show()\n\n\ndef plot_iou_recall(recalls, iou_thrs):\n    \"\"\"Plot IoU-Recalls curve.\n\n    Args:\n        recalls(ndarray or list): shape (k,)\n        iou_thrs(ndarray or list): same shape as `recalls`\n    \"\"\"\n    if isinstance(iou_thrs, np.ndarray):\n        _iou_thrs = iou_thrs.tolist()\n    else:\n        _iou_thrs = iou_thrs\n    if isinstance(recalls, np.ndarray):\n        _recalls = recalls.tolist()\n    else:\n        _recalls = recalls\n\n    import matplotlib.pyplot as plt\n    f = plt.figure()\n    plt.plot(_iou_thrs + [1.0], _recalls + [0.])\n    plt.xlabel('IoU')\n    plt.ylabel('Recall')\n    plt.axis([iou_thrs.min(), 1, 0, 1])\n    f.show()\n"
  },
  {
    "path": "code/mmdet/core/fp16/__init__.py",
    "content": "from .decorators import auto_fp16, force_fp32\nfrom .hooks import Fp16OptimizerHook, wrap_fp16_model\n\n__all__ = ['auto_fp16', 'force_fp32', 'Fp16OptimizerHook', 'wrap_fp16_model']\n"
  },
  {
    "path": "code/mmdet/core/fp16/decorators.py",
    "content": "import functools\nfrom inspect import getfullargspec\n\nimport torch\n\nfrom .utils import cast_tensor_type\n\n\ndef auto_fp16(apply_to=None, out_fp32=False):\n    \"\"\"Decorator to enable fp16 training automatically.\n\n    This decorator is useful when you write custom modules and want to support\n    mixed precision training. If inputs arguments are fp32 tensors, they will\n    be converted to fp16 automatically. Arguments other than fp32 tensors are\n    ignored.\n\n    Args:\n        apply_to (Iterable, optional): The argument names to be converted.\n            `None` indicates all arguments.\n        out_fp32 (bool): Whether to convert the output back to fp32.\n\n    Example:\n\n        >>> import torch.nn as nn\n        >>> class MyModule1(nn.Module):\n        >>>\n        >>>     # Convert x and y to fp16\n        >>>     @auto_fp16()\n        >>>     def forward(self, x, y):\n        >>>         pass\n\n        >>> import torch.nn as nn\n        >>> class MyModule2(nn.Module):\n        >>>\n        >>>     # convert pred to fp16\n        >>>     @auto_fp16(apply_to=('pred', ))\n        >>>     def do_something(self, pred, others):\n        >>>         pass\n    \"\"\"\n\n    def auto_fp16_wrapper(old_func):\n\n        @functools.wraps(old_func)\n        def new_func(*args, **kwargs):\n            # check if the module has set the attribute `fp16_enabled`, if not,\n            # just fallback to the original method.\n            if not isinstance(args[0], torch.nn.Module):\n                raise TypeError('@auto_fp16 can only be used to decorate the '\n                                'method of nn.Module')\n            if not (hasattr(args[0], 'fp16_enabled') and args[0].fp16_enabled):\n                return old_func(*args, **kwargs)\n            # get the arg spec of the decorated method\n            args_info = getfullargspec(old_func)\n            # get the argument names to be casted\n            args_to_cast = args_info.args if apply_to is None else apply_to\n            # convert the args that need to be processed\n            new_args = []\n            # NOTE: default args are not taken into consideration\n            if args:\n                arg_names = args_info.args[:len(args)]\n                for i, arg_name in enumerate(arg_names):\n                    if arg_name in args_to_cast:\n                        new_args.append(\n                            cast_tensor_type(args[i], torch.float, torch.half))\n                    else:\n                        new_args.append(args[i])\n            # convert the kwargs that need to be processed\n            new_kwargs = {}\n            if kwargs:\n                for arg_name, arg_value in kwargs.items():\n                    if arg_name in args_to_cast:\n                        new_kwargs[arg_name] = cast_tensor_type(\n                            arg_value, torch.float, torch.half)\n                    else:\n                        new_kwargs[arg_name] = arg_value\n            # apply converted arguments to the decorated method\n            output = old_func(*new_args, **new_kwargs)\n            # cast the results back to fp32 if necessary\n            if out_fp32:\n                output = cast_tensor_type(output, torch.half, torch.float)\n            return output\n\n        return new_func\n\n    return auto_fp16_wrapper\n\n\ndef force_fp32(apply_to=None, out_fp16=False):\n    \"\"\"Decorator to convert input arguments to fp32 in force.\n\n    This decorator is useful when you write custom modules and want to support\n    mixed precision training. If there are some inputs that must be processed\n    in fp32 mode, then this decorator can handle it. If inputs arguments are\n    fp16 tensors, they will be converted to fp32 automatically. Arguments other\n    than fp16 tensors are ignored.\n\n    Args:\n        apply_to (Iterable, optional): The argument names to be converted.\n            `None` indicates all arguments.\n        out_fp16 (bool): Whether to convert the output back to fp16.\n\n    Example:\n\n        >>> import torch.nn as nn\n        >>> class MyModule1(nn.Module):\n        >>>\n        >>>     # Convert x and y to fp32\n        >>>     @force_fp32()\n        >>>     def loss(self, x, y):\n        >>>         pass\n\n        >>> import torch.nn as nn\n        >>> class MyModule2(nn.Module):\n        >>>\n        >>>     # convert pred to fp32\n        >>>     @force_fp32(apply_to=('pred', ))\n        >>>     def post_process(self, pred, others):\n        >>>         pass\n    \"\"\"\n\n    def force_fp32_wrapper(old_func):\n\n        @functools.wraps(old_func)\n        def new_func(*args, **kwargs):\n            # check if the module has set the attribute `fp16_enabled`, if not,\n            # just fallback to the original method.\n            if not isinstance(args[0], torch.nn.Module):\n                raise TypeError('@force_fp32 can only be used to decorate the '\n                                'method of nn.Module')\n            if not (hasattr(args[0], 'fp16_enabled') and args[0].fp16_enabled):\n                return old_func(*args, **kwargs)\n            # get the arg spec of the decorated method\n            args_info = getfullargspec(old_func)\n            # get the argument names to be casted\n            args_to_cast = args_info.args if apply_to is None else apply_to\n            # convert the args that need to be processed\n            new_args = []\n            if args:\n                arg_names = args_info.args[:len(args)]\n                for i, arg_name in enumerate(arg_names):\n                    if arg_name in args_to_cast:\n                        new_args.append(\n                            cast_tensor_type(args[i], torch.half, torch.float))\n                    else:\n                        new_args.append(args[i])\n            # convert the kwargs that need to be processed\n            new_kwargs = dict()\n            if kwargs:\n                for arg_name, arg_value in kwargs.items():\n                    if arg_name in args_to_cast:\n                        new_kwargs[arg_name] = cast_tensor_type(\n                            arg_value, torch.half, torch.float)\n                    else:\n                        new_kwargs[arg_name] = arg_value\n            # apply converted arguments to the decorated method\n            output = old_func(*new_args, **new_kwargs)\n            # cast the results back to fp32 if necessary\n            if out_fp16:\n                output = cast_tensor_type(output, torch.float, torch.half)\n            return output\n\n        return new_func\n\n    return force_fp32_wrapper\n"
  },
  {
    "path": "code/mmdet/core/fp16/hooks.py",
    "content": "import copy\n\nimport torch\nimport torch.nn as nn\nfrom mmcv.runner import OptimizerHook\n\nfrom ..utils.dist_utils import allreduce_grads\nfrom .utils import cast_tensor_type\n\n\nclass Fp16OptimizerHook(OptimizerHook):\n    \"\"\"FP16 optimizer hook.\n\n    The steps of fp16 optimizer is as follows.\n    1. Scale the loss value.\n    2. BP in the fp16 model.\n    2. Copy gradients from fp16 model to fp32 weights.\n    3. Update fp32 weights.\n    4. Copy updated parameters from fp32 weights to fp16 model.\n\n    Refer to https://arxiv.org/abs/1710.03740 for more details.\n\n    Args:\n        loss_scale (float): Scale factor multiplied with loss.\n    \"\"\"\n\n    def __init__(self,\n                 grad_clip=None,\n                 coalesce=True,\n                 bucket_size_mb=-1,\n                 loss_scale=512.,\n                 distributed=True):\n        self.grad_clip = grad_clip\n        self.coalesce = coalesce\n        self.bucket_size_mb = bucket_size_mb\n        self.loss_scale = loss_scale\n        self.distributed = distributed\n\n    def before_run(self, runner):\n        \"\"\"Preparing steps before Mixed Precision Training.\n\n        1. Make a master copy of fp32 weights for optimization.\n        2. Convert the main model from fp32 to fp16.\n        \"\"\"\n        # keep a copy of fp32 weights\n        runner.optimizer.param_groups = copy.deepcopy(\n            runner.optimizer.param_groups)\n        # convert model to fp16\n        wrap_fp16_model(runner.model)\n\n    def copy_grads_to_fp32(self, fp16_net, fp32_weights):\n        \"\"\"Copy gradients from fp16 model to fp32 weight copy.\"\"\"\n        for fp32_param, fp16_param in zip(fp32_weights, fp16_net.parameters()):\n            if fp16_param.grad is not None:\n                if fp32_param.grad is None:\n                    fp32_param.grad = fp32_param.data.new(fp32_param.size())\n                fp32_param.grad.copy_(fp16_param.grad)\n\n    def copy_params_to_fp16(self, fp16_net, fp32_weights):\n        \"\"\"Copy updated params from fp32 weight copy to fp16 model.\"\"\"\n        for fp16_param, fp32_param in zip(fp16_net.parameters(), fp32_weights):\n            fp16_param.data.copy_(fp32_param.data)\n\n    def after_train_iter(self, runner):\n        \"\"\"Backward optimization steps for Mixed Precision Training.\n\n        1. Scale the loss by a scale factor.\n        2. Backward the loss to obtain the gradients (fp16).\n        3. Copy gradients from the model to the fp32 weight copy.\n        4. Scale the gradients back and update the fp32 weight copy.\n        5. Copy back the params from fp32 weight copy to the fp16 model.\n        \"\"\"\n        # clear grads of last iteration\n        runner.model.zero_grad()\n        runner.optimizer.zero_grad()\n        # scale the loss value\n        scaled_loss = runner.outputs['loss'] * self.loss_scale\n        scaled_loss.backward()\n        # copy fp16 grads in the model to fp32 params in the optimizer\n        fp32_weights = []\n        for param_group in runner.optimizer.param_groups:\n            fp32_weights += param_group['params']\n        self.copy_grads_to_fp32(runner.model, fp32_weights)\n        # allreduce grads\n        if self.distributed:\n            allreduce_grads(fp32_weights, self.coalesce, self.bucket_size_mb)\n        # scale the gradients back\n        for param in fp32_weights:\n            if param.grad is not None:\n                param.grad.div_(self.loss_scale)\n        if self.grad_clip is not None:\n            self.clip_grads(fp32_weights)\n        # update fp32 params\n        runner.optimizer.step()\n        # copy fp32 params to the fp16 model\n        self.copy_params_to_fp16(runner.model, fp32_weights)\n\n\ndef wrap_fp16_model(model):\n    \"\"\"Wrap the FP32 model to FP16.\n\n    1. Convert FP32 model to FP16.\n    2. Remain some necessary layers to be FP32, e.g., normalization layers.\n\n    Args:\n        model (nn.Module): Model in FP32.\n    \"\"\"\n    # convert model to fp16\n    model.half()\n    # patch the normalization layers to make it work in fp32 mode\n    patch_norm_fp32(model)\n    # set `fp16_enabled` flag\n    for m in model.modules():\n        if hasattr(m, 'fp16_enabled'):\n            m.fp16_enabled = True\n\n\ndef patch_norm_fp32(module):\n    \"\"\"Recursively convert normalization layers from FP16 to FP32.\n\n    Args:\n        module (nn.Module): The modules to be converted in FP16.\n\n    Returns:\n        nn.Module: The converted module, the normalization layers have been\n            converted to FP32.\n    \"\"\"\n    if isinstance(module, (nn.modules.batchnorm._BatchNorm, nn.GroupNorm)):\n        module.float()\n        if isinstance(module, nn.GroupNorm) or torch.__version__ < '1.3':\n            module.forward = patch_forward_method(module.forward, torch.half,\n                                                  torch.float)\n    for child in module.children():\n        patch_norm_fp32(child)\n    return module\n\n\ndef patch_forward_method(func, src_type, dst_type, convert_output=True):\n    \"\"\"Patch the forward method of a module.\n\n    Args:\n        func (callable): The original forward method.\n        src_type (torch.dtype): Type of input arguments to be converted from.\n        dst_type (torch.dtype): Type of input arguments to be converted to.\n        convert_output (bool): Whether to convert the output back to src_type.\n\n    Returns:\n        callable: The patched forward method.\n    \"\"\"\n\n    def new_forward(*args, **kwargs):\n        output = func(*cast_tensor_type(args, src_type, dst_type),\n                      **cast_tensor_type(kwargs, src_type, dst_type))\n        if convert_output:\n            output = cast_tensor_type(output, dst_type, src_type)\n        return output\n\n    return new_forward\n"
  },
  {
    "path": "code/mmdet/core/fp16/utils.py",
    "content": "from collections import abc\n\nimport numpy as np\nimport torch\n\n\ndef cast_tensor_type(inputs, src_type, dst_type):\n    \"\"\"Recursively convert Tensor in inputs from src_type to dst_type.\n\n    Args:\n        inputs: Inputs that to be casted.\n        src_type (torch.dtype): Source type..\n        dst_type (torch.dtype): Destination type.\n\n    Returns:\n        The same type with inputs, but all contained Tensors have been cast.\n    \"\"\"\n    if isinstance(inputs, torch.Tensor):\n        return inputs.to(dst_type)\n    elif isinstance(inputs, str):\n        return inputs\n    elif isinstance(inputs, np.ndarray):\n        return inputs\n    elif isinstance(inputs, abc.Mapping):\n        return type(inputs)({\n            k: cast_tensor_type(v, src_type, dst_type)\n            for k, v in inputs.items()\n        })\n    elif isinstance(inputs, abc.Iterable):\n        return type(inputs)(\n            cast_tensor_type(item, src_type, dst_type) for item in inputs)\n    else:\n        return inputs\n"
  },
  {
    "path": "code/mmdet/core/mask/__init__.py",
    "content": "from .mask_target import mask_target\nfrom .structures import BitmapMasks, PolygonMasks\nfrom .utils import encode_mask_results, split_combined_polys, encode_poly_results\n\n__all__ = [\n    'split_combined_polys', 'mask_target', 'BitmapMasks', 'PolygonMasks',\n    'encode_mask_results', 'encode_poly_results'\n]\n"
  },
  {
    "path": "code/mmdet/core/mask/mask_target.py",
    "content": "import numpy as np\nimport torch\nfrom torch.nn.modules.utils import _pair\n\n\ndef mask_target(pos_proposals_list, pos_assigned_gt_inds_list, gt_masks_list,\n                cfg):\n    \"\"\" Compute mask target for positive proposals in multiple images.\n\n    Args:\n        pos_proposals_list (list[Tensor]): Positive proposals in multiple\n            images.\n        pos_assigned_gt_inds_list (list[Tensor]): Assigned GT indices for each\n            positive proposals.\n        gt_masks_list (list[:obj:`BaseInstanceMasks`]): Ground truth masks of\n            each image.\n        cfg (dict): Config dict that specifies the mask size.\n\n    Returns:\n        list[Tensor]: Mask target of each image.\n    \"\"\"\n    cfg_list = [cfg for _ in range(len(pos_proposals_list))]\n    mask_targets = map(mask_target_single, pos_proposals_list,\n                       pos_assigned_gt_inds_list, gt_masks_list, cfg_list)\n    mask_targets = list(mask_targets)\n    if len(mask_targets) > 0:\n        mask_targets = torch.cat(mask_targets)\n    return mask_targets\n\n\ndef mask_target_single(pos_proposals, pos_assigned_gt_inds, gt_masks, cfg):\n    \"\"\"Compute mask target for each positive proposal in the image.\n\n    Args:\n        pos_proposals (Tensor): Positive proposals.\n        pos_assigned_gt_inds (Tensor): Assigned GT inds of positive proposals.\n        gt_masks (:obj:`BaseInstanceMasks`): GT masks in the format of Bitmap\n            or Polygon.\n        cfg (dict): Config dict that indicate the mask size.\n\n    Returns:\n        Tensor: Mask target of each positive proposals in the image.\n    \"\"\"\n    device = pos_proposals.device\n    mask_size = _pair(cfg.mask_size)\n    num_pos = pos_proposals.size(0)\n    if num_pos > 0:\n        proposals_np = pos_proposals.cpu().numpy()\n        maxh, maxw = gt_masks.height, gt_masks.width\n        proposals_np[:, [0, 2]] = np.clip(proposals_np[:, [0, 2]], 0, maxw)\n        proposals_np[:, [1, 3]] = np.clip(proposals_np[:, [1, 3]], 0, maxh)\n        pos_assigned_gt_inds = pos_assigned_gt_inds.cpu().numpy()\n\n        mask_targets = gt_masks.crop_and_resize(\n            proposals_np, mask_size, device=device,\n            inds=pos_assigned_gt_inds).to_ndarray()\n\n        mask_targets = torch.from_numpy(mask_targets).float().to(device)\n    else:\n        mask_targets = pos_proposals.new_zeros((0, ) + mask_size)\n\n    return mask_targets\n"
  },
  {
    "path": "code/mmdet/core/mask/structures.py",
    "content": "from abc import ABCMeta, abstractmethod\n\nimport mmcv\nimport numpy as np\nimport pycocotools.mask as maskUtils\nimport torch\n\nfrom mmdet.ops.roi_align import roi_align\n\n\nclass BaseInstanceMasks(metaclass=ABCMeta):\n\n    @abstractmethod\n    def rescale(self, scale, interpolation='nearest'):\n        \"\"\"Rescale masks as large as possible while keeping the aspect ratio.\n        For details can refer to `mmcv.imrescale`.\n\n        Args:\n            scale (tuple[int]): The maximum size (h, w) of rescaled mask.\n            interpolation (str): Same as :func:`mmcv.imrescale`.\n\n        Returns:\n            BaseInstanceMasks: The rescaled masks.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def resize(self, out_shape, interpolation='nearest'):\n        \"\"\"Resize masks to the given out_shape.\n\n        Args:\n            out_shape: Target (h, w) of resized mask.\n            interpolation (str): See `mmcv.imresize`.\n\n        Returns:\n            BaseInstanceMasks: The resized masks.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def flip(self, flip_direction='horizontal'):\n        \"\"\"Flip masks alone the given direction.\n\n        Args:\n            flip_direction (str): Either 'horizontal' or 'vertical'.\n\n        Returns:\n            BaseInstanceMasks: The flipped masks.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def pad(self, out_shape, pad_val):\n        \"\"\"Pad masks to the given size of (h, w).\n\n        Args:\n            out_shape (tuple[int]): Target (h, w) of padded mask.\n            pad_val (int): The padded value.\n\n        Returns:\n            BaseInstanceMasks: The padded masks.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def crop(self, bbox):\n        \"\"\"Crop each mask by the given bbox.\n\n        Args:\n            bbox (ndarray): Bbox in format [x1, y1, x2, y2], shape (4, ).\n\n        Return:\n            BaseInstanceMasks: The cropped masks.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def crop_and_resize(self,\n                        bboxes,\n                        out_shape,\n                        inds,\n                        device,\n                        interpolation='bilinear'):\n        \"\"\"Crop and resize masks by the given bboxes.\n\n        This function is mainly used in mask targets computation.\n        It firstly align mask to bboxes by assigned_inds, then crop mask by the\n        assigned bbox and resize to the size of (mask_h, mask_w)\n\n        Args:\n            bboxes (Tensor): Bboxes in format [x1, y1, x2, y2], shape (N, 4)\n            out_shape (tuple[int]): Target (h, w) of resized mask\n            inds (ndarray): Indexes to assign masks to each bbox\n            device (str): Device of bboxes\n            interpolation (str): See `mmcv.imresize`\n\n        Return:\n            BaseInstanceMasks: the cropped and resized masks.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def expand(self, expanded_h, expanded_w, top, left):\n        \"\"\"see `transforms.Expand`.\"\"\"\n        pass\n\n    @property\n    @abstractmethod\n    def areas(self):\n        \"\"\"ndarray: areas of each instance.\"\"\"\n        pass\n\n    @abstractmethod\n    def to_ndarray(self):\n        \"\"\"Convert masks to the format of ndarray.\n\n        Return:\n            ndarray: Converted masks in the format of ndarray.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def to_tensor(self, dtype, device):\n        \"\"\"Convert masks to the format of Tensor.\n\n        Args:\n            dtype (str): Dtype of converted mask.\n            device (torch.device): Device of conveted masks.\n\n        Returns:\n            Tensor: Converted masks in the format of Tensor.\n        \"\"\"\n        pass\n\n\nclass BitmapMasks(BaseInstanceMasks):\n    \"\"\"This class represents masks in the form of bitmaps.\n\n    Args:\n        masks (ndarray): ndarray of masks in shape (N, H, W), where N is\n            the number of objects.\n        height (int): height of masks\n        width (int): width of masks\n    \"\"\"\n\n    def __init__(self, masks, height, width):\n        self.height = height\n        self.width = width\n        if len(masks) == 0:\n            self.masks = np.empty((0, self.height, self.width), dtype=np.uint8)\n        else:\n            assert isinstance(masks, (list, np.ndarray))\n            if isinstance(masks, list):\n                assert isinstance(masks[0], np.ndarray)\n                assert masks[0].ndim == 2  # (H, W)\n            else:\n                assert masks.ndim == 3  # (N, H, W)\n\n            self.masks = np.stack(masks).reshape(-1, height, width)\n            assert self.masks.shape[1] == self.height\n            assert self.masks.shape[2] == self.width\n\n    def __getitem__(self, index):\n        \"\"\"Index the BitmapMask.\n\n        Args:\n            index (int | ndarray): Indices in the format of integer or ndarray.\n\n        Returns:\n            :obj:`BitmapMasks`: Indexed bitmap masks.\n\n        \"\"\"\n        masks = self.masks[index].reshape(-1, self.height, self.width)\n        return BitmapMasks(masks, self.height, self.width)\n\n    def __iter__(self):\n        return iter(self.masks)\n\n    def __repr__(self):\n        s = self.__class__.__name__ + '('\n        s += f'num_masks={len(self.masks)}, '\n        s += f'height={self.height}, '\n        s += f'width={self.width})'\n        return s\n\n    def __len__(self):\n        \"\"\"Number of masks.\"\"\"\n        return len(self.masks)\n\n    def rescale(self, scale, interpolation='nearest'):\n        \"\"\"See :func:`BaseInstanceMasks.rescale()`.\"\"\"\n        if len(self.masks) == 0:\n            new_w, new_h = mmcv.rescale_size((self.width, self.height), scale)\n            rescaled_masks = np.empty((0, new_h, new_w), dtype=np.uint8)\n        else:\n            rescaled_masks = np.stack([\n                mmcv.imrescale(mask, scale, interpolation=interpolation)\n                for mask in self.masks\n            ])\n        height, width = rescaled_masks.shape[1:]\n        return BitmapMasks(rescaled_masks, height, width)\n\n    def resize(self, out_shape, interpolation='nearest'):\n        \"\"\"See :func:`BaseInstanceMasks.resize()`.\"\"\"\n        if len(self.masks) == 0:\n            resized_masks = np.empty((0, *out_shape), dtype=np.uint8)\n        else:\n            resized_masks = np.stack([\n                mmcv.imresize(mask, out_shape, interpolation=interpolation)\n                for mask in self.masks\n            ])\n        return BitmapMasks(resized_masks, *out_shape)\n\n    def flip(self, flip_direction='horizontal'):\n        \"\"\"See :func:`BaseInstanceMasks.flip()`.\"\"\"\n        assert flip_direction in ('horizontal', 'vertical')\n\n        if len(self.masks) == 0:\n            flipped_masks = self.masks\n        else:\n            flipped_masks = np.stack([\n                mmcv.imflip(mask, direction=flip_direction)\n                for mask in self.masks\n            ])\n        return BitmapMasks(flipped_masks, self.height, self.width)\n\n    def pad(self, out_shape, pad_val=0):\n        \"\"\"See :func:`BaseInstanceMasks.pad()`.\"\"\"\n        if len(self.masks) == 0:\n            padded_masks = np.empty((0, *out_shape), dtype=np.uint8)\n        else:\n            padded_masks = np.stack([\n                mmcv.impad(mask, out_shape, pad_val=pad_val)\n                for mask in self.masks\n            ])\n        return BitmapMasks(padded_masks, *out_shape)\n\n    def crop(self, bbox):\n        \"\"\"See :func:`BaseInstanceMasks.crop()`.\"\"\"\n        assert isinstance(bbox, np.ndarray)\n        assert bbox.ndim == 1\n\n        # clip the boundary\n        bbox = bbox.copy()\n        bbox[0::2] = np.clip(bbox[0::2], 0, self.width)\n        bbox[1::2] = np.clip(bbox[1::2], 0, self.height)\n        x1, y1, x2, y2 = bbox\n        w = np.maximum(x2 - x1, 1)\n        h = np.maximum(y2 - y1, 1)\n\n        if len(self.masks) == 0:\n            cropped_masks = np.empty((0, h, w), dtype=np.uint8)\n        else:\n            cropped_masks = self.masks[:, y1:y1 + h, x1:x1 + w]\n        return BitmapMasks(cropped_masks, h, w)\n\n    def crop_and_resize(self,\n                        bboxes,\n                        out_shape,\n                        inds,\n                        device='cpu',\n                        interpolation='bilinear'):\n        \"\"\"See :func:`BaseInstanceMasks.crop_and_resize()`.\"\"\"\n        if len(self.masks) == 0:\n            empty_masks = np.empty((0, *out_shape), dtype=np.uint8)\n            return BitmapMasks(empty_masks, *out_shape)\n\n        # convert bboxes to tensor\n        if isinstance(bboxes, np.ndarray):\n            bboxes = torch.from_numpy(bboxes).to(device=device)\n        if isinstance(inds, np.ndarray):\n            inds = torch.from_numpy(inds).to(device=device)\n\n        num_bbox = bboxes.shape[0]\n        fake_inds = torch.arange(\n            num_bbox, device=device).to(dtype=bboxes.dtype)[:, None]\n        rois = torch.cat([fake_inds, bboxes], dim=1)  # Nx5\n        rois = rois.to(device=device)\n        if num_bbox > 0:\n            gt_masks_th = torch.from_numpy(self.masks).to(device).index_select(\n                0, inds).to(dtype=rois.dtype)\n            targets = roi_align(gt_masks_th[:, None, :, :], rois, out_shape,\n                                1.0, 0, True).squeeze(1)\n            resized_masks = (targets >= 0.5).cpu().numpy()\n        else:\n            resized_masks = []\n        return BitmapMasks(resized_masks, *out_shape)\n\n    def expand(self, expanded_h, expanded_w, top, left):\n        \"\"\"See :func:`BaseInstanceMasks.expand()`.\"\"\"\n        if len(self.masks) == 0:\n            expanded_mask = np.empty((0, expanded_h, expanded_w),\n                                     dtype=np.uint8)\n        else:\n            expanded_mask = np.zeros((len(self), expanded_h, expanded_w),\n                                     dtype=np.uint8)\n            expanded_mask[:, top:top + self.height,\n                          left:left + self.width] = self.masks\n        return BitmapMasks(expanded_mask, expanded_h, expanded_w)\n\n    @property\n    def areas(self):\n        \"\"\"See :func:`BaseInstanceMasks.areas`.\"\"\"\n        return self.masks.sum((1, 2))\n\n    def to_ndarray(self):\n        \"\"\"See :func:`BaseInstanceMasks.to_ndarray()`.\"\"\"\n        return self.masks\n\n    def to_tensor(self, dtype, device):\n        \"\"\"See :func:`BaseInstanceMasks.to_tensor()`.\"\"\"\n        return torch.tensor(self.masks, dtype=dtype, device=device)\n\n\nclass PolygonMasks(BaseInstanceMasks):\n    \"\"\"This class represents masks in the form of polygons.\n\n    Polygons is a list of three levels. The first level of the list\n    corresponds to objects, the second level to the polys that compose the\n    object, the third level to the poly coordinates\n\n    Args:\n        masks (list[list[ndarray]]): The first level of the list\n            corresponds to objects, the second level to the polys that\n            compose the object, the third level to the poly coordinates\n        height (int): height of masks\n        width (int): width of masks\n    \"\"\"\n\n    def __init__(self, masks, height, width):\n        assert isinstance(masks, list)\n        if len(masks) > 0:\n            assert isinstance(masks[0], list)\n            assert isinstance(masks[0][0], np.ndarray)\n\n        self.height = height\n        self.width = width\n        self.masks = masks\n\n    def __getitem__(self, index):\n        \"\"\"Index the polygon masks.\n\n        Args:\n            index (ndarray | List): The indices.\n\n        Returns:\n            :obj:`PolygonMasks`: The indexed polygon masks.\n        \"\"\"\n        if isinstance(index, np.ndarray):\n            index = index.tolist()\n        if isinstance(index, list):\n            masks = [self.masks[i] for i in index]\n        else:\n            try:\n                masks = self.masks[index]\n            except Exception:\n                raise ValueError(\n                    f'Unsupported input of type {type(index)} for indexing!')\n        if isinstance(masks[0], np.ndarray):\n            masks = [masks]  # ensure a list of three levels\n        return PolygonMasks(masks, self.height, self.width)\n\n    def __iter__(self):\n        return iter(self.masks)\n\n    def __repr__(self):\n        s = self.__class__.__name__ + '('\n        s += f'num_masks={len(self.masks)}, '\n        s += f'height={self.height}, '\n        s += f'width={self.width})'\n        return s\n\n    def __len__(self):\n        \"\"\"Number of masks.\"\"\"\n        return len(self.masks)\n\n    def rescale(self, scale, interpolation=None):\n        \"\"\"see :func:`BaseInstanceMasks.rescale()`\"\"\"\n        new_w, new_h = mmcv.rescale_size((self.width, self.height), scale)\n        if len(self.masks) == 0:\n            rescaled_masks = PolygonMasks([], new_h, new_w)\n        else:\n            rescaled_masks = self.resize((new_h, new_w))\n        return rescaled_masks\n\n    def resize(self, out_shape, interpolation=None):\n        \"\"\"see :func:`BaseInstanceMasks.resize()`\"\"\"\n        if len(self.masks) == 0:\n            resized_masks = PolygonMasks([], *out_shape)\n        else:\n            h_scale = out_shape[0] / self.height\n            w_scale = out_shape[1] / self.width\n            resized_masks = []\n            for poly_per_obj in self.masks:\n                resized_poly = []\n                for p in poly_per_obj:\n                    p = p.copy()\n                    p[0::2] *= w_scale\n                    p[1::2] *= h_scale\n                    resized_poly.append(p)\n                resized_masks.append(resized_poly)\n            resized_masks = PolygonMasks(resized_masks, *out_shape)\n        return resized_masks\n\n    def flip(self, flip_direction='horizontal', keep_cw=False):\n        \"\"\"see :func:`BaseInstanceMasks.flip()`\"\"\"\n        assert flip_direction in ('horizontal', 'vertical')\n        if len(self.masks) == 0:\n            flipped_masks = PolygonMasks([], self.height, self.width)\n        else:\n            if flip_direction == 'horizontal':\n                dim = self.width\n                idx = 0\n            else:\n                dim = self.height\n                idx = 1\n            flipped_masks = []\n            for poly_per_obj in self.masks:\n                flipped_poly_per_obj = []\n                for p in poly_per_obj:\n                    p = p.copy()\n                    p[idx::2] = dim - p[idx::2]\n                    if keep_cw:\n                        p = p.reshape(-1, 2)\n                        new_p = np.zeros_like(p)\n                        new_p[1:] = p[::-1][:-1]\n                        new_p[0] = p[::-1][-1]\n                        p = new_p.reshape(-1)\n                    flipped_poly_per_obj.append(p)\n                flipped_masks.append(flipped_poly_per_obj)\n            flipped_masks = PolygonMasks(flipped_masks, self.height,\n                                         self.width)\n        return flipped_masks\n\n    def crop(self, bbox):\n        \"\"\"see :func:`BaseInstanceMasks.crop()`\"\"\"\n        assert isinstance(bbox, np.ndarray)\n        assert bbox.ndim == 1\n\n        # clip the boundary\n        bbox = bbox.copy()\n        bbox[0::2] = np.clip(bbox[0::2], 0, self.width)\n        bbox[1::2] = np.clip(bbox[1::2], 0, self.height)\n        x1, y1, x2, y2 = bbox\n        w = np.maximum(x2 - x1, 1)\n        h = np.maximum(y2 - y1, 1)\n\n        if len(self.masks) == 0:\n            cropped_masks = PolygonMasks([], h, w)\n        else:\n            cropped_masks = []\n            for poly_per_obj in self.masks:\n                cropped_poly_per_obj = []\n                for p in poly_per_obj:\n                    # pycocotools will clip the boundary\n                    p = p.copy()\n                    p[0::2] -= bbox[0]\n                    p[1::2] -= bbox[1]\n                    cropped_poly_per_obj.append(p)\n                cropped_masks.append(cropped_poly_per_obj)\n            cropped_masks = PolygonMasks(cropped_masks, h, w)\n        return cropped_masks\n\n    def pad(self, out_shape, pad_val=0):\n        \"\"\"padding has no effect on polygons`\"\"\"\n        return PolygonMasks(self.masks, *out_shape)\n\n    def expand(self, *args, **kwargs):\n        \"\"\"TODO: Add expand for polygon\"\"\"\n        raise NotImplementedError\n\n    def crop_and_resize(self,\n                        bboxes,\n                        out_shape,\n                        inds,\n                        device='cpu',\n                        interpolation='bilinear'):\n        \"\"\"see :func:`BaseInstanceMasks.crop_and_resize()`\"\"\"\n        out_h, out_w = out_shape\n        if len(self.masks) == 0:\n            return PolygonMasks([], out_h, out_w)\n\n        resized_masks = []\n        for i in range(len(bboxes)):\n            mask = self.masks[inds[i]]\n            bbox = bboxes[i, :]\n            x1, y1, x2, y2 = bbox\n            w = np.maximum(x2 - x1, 1)\n            h = np.maximum(y2 - y1, 1)\n            h_scale = out_h / max(h, 0.1)  # avoid too large scale\n            w_scale = out_w / max(w, 0.1)\n\n            resized_mask = []\n            for p in mask:\n                p = p.copy()\n                # crop\n                # pycocotools will clip the boundary\n                p[0::2] -= bbox[0]\n                p[1::2] -= bbox[1]\n\n                # resize\n                p[0::2] *= w_scale\n                p[1::2] *= h_scale\n                resized_mask.append(p)\n            resized_masks.append(resized_mask)\n        return PolygonMasks(resized_masks, *out_shape)\n\n    def to_bitmap(self):\n        \"\"\"convert polygon masks to bitmap masks\"\"\"\n        bitmap_masks = self.to_ndarray()\n        return BitmapMasks(bitmap_masks, self.height, self.width)\n\n    @property\n    def areas(self):\n        \"\"\"Compute areas of masks.\n\n        This func is modified from\n        https://github.com/facebookresearch/detectron2/blob/ffff8acc35ea88ad1cb1806ab0f00b4c1c5dbfd9/detectron2/structures/masks.py#L387\n        Only works with Polygons, using the shoelace formula\n\n        Return:\n            ndarray: areas of each instance\n        \"\"\"  # noqa: W501\n        area = []\n        for polygons_per_obj in self.masks:\n            area_per_obj = 0\n            for p in polygons_per_obj:\n                area_per_obj += self._polygon_area(p[0::2], p[1::2])\n            area.append(area_per_obj)\n        return np.asarray(area)\n\n    def _polygon_area(self, x, y):\n        \"\"\"Compute the area of a component of a polygon.\n\n        Using the shoelace formula:\n        https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates\n\n        Args:\n            x (ndarray): x coordinates of the component\n            y (ndarray): y coordinates of the component\n\n        Return:\n            float: the are of the component\n        \"\"\"  # noqa: 501\n        return 0.5 * np.abs(\n            np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))\n\n    def to_ndarray(self):\n        \"\"\"Convert masks to the format of ndarray.\"\"\"\n        if len(self.masks) == 0:\n            return np.empty((0, self.height, self.width), dtype=np.uint8)\n        bitmap_masks = []\n        for poly_per_obj in self.masks:\n            bitmap_masks.append(\n                polygon_to_bitmap(poly_per_obj, self.height, self.width))\n        return np.stack(bitmap_masks)\n\n    def to_tensor(self, dtype, device):\n        \"\"\"See :func:`BaseInstanceMasks.to_tensor()`.\"\"\"\n        if len(self.masks) == 0:\n            return torch.empty((0, self.height, self.width),\n                               dtype=dtype,\n                               device=device)\n        ndarray_masks = self.to_ndarray()\n        return torch.tensor(ndarray_masks, dtype=dtype, device=device)\n\n\ndef polygon_to_bitmap(polygons, height, width):\n    \"\"\"Convert masks from the form of polygons to bitmaps.\n\n        Args:\n            polygons (list[ndarray]): masks in polygon representation\n            height (int): mask height\n            width (int): mask width\n\n        Return:\n            ndarray: the converted masks in bitmap representation\n    \"\"\"\n    rles = maskUtils.frPyObjects(polygons, height, width)\n    rle = maskUtils.merge(rles)\n    bitmap_mask = maskUtils.decode(rle).astype(np.bool)\n    return bitmap_mask\n"
  },
  {
    "path": "code/mmdet/core/mask/utils.py",
    "content": "import mmcv\nimport numpy as np\nimport pycocotools.mask as mask_util\n\n\ndef split_combined_polys(polys, poly_lens, polys_per_mask):\n    \"\"\"Split the combined 1-D polys into masks.\n\n    A mask is represented as a list of polys, and a poly is represented as\n    a 1-D array. In dataset, all masks are concatenated into a single 1-D\n    tensor. Here we need to split the tensor into original representations.\n\n    Args:\n        polys (list): a list (length = image num) of 1-D tensors\n        poly_lens (list): a list (length = image num) of poly length\n        polys_per_mask (list): a list (length = image num) of poly number\n            of each mask\n\n    Returns:\n        list: a list (length = image num) of list (length = mask num) of\n            list (length = poly num) of numpy array\n    \"\"\"\n    mask_polys_list = []\n    for img_id in range(len(polys)):\n        polys_single = polys[img_id]\n        polys_lens_single = poly_lens[img_id].tolist()\n        polys_per_mask_single = polys_per_mask[img_id].tolist()\n\n        split_polys = mmcv.slice_list(polys_single, polys_lens_single)\n        mask_polys = mmcv.slice_list(split_polys, polys_per_mask_single)\n        mask_polys_list.append(mask_polys)\n    return mask_polys_list\n\n\n# TODO: move this function to more proper place\ndef encode_mask_results(mask_results):\n    \"\"\"Encode bitmap mask to RLE code.\n\n    Args:\n        mask_results (list | tuple[list]): bitmap mask results.\n            In mask scoring rcnn, mask_results is a tuple of (segm_results,\n            segm_cls_score).\n\n    Returns:\n        list | tuple: RLE encoded mask.\n    \"\"\"\n    if isinstance(mask_results, tuple):  # mask scoring\n        cls_segms, cls_mask_scores = mask_results\n    else:\n        cls_segms = mask_results\n    num_classes = len(cls_segms)\n    encoded_mask_results = [[] for _ in range(num_classes)]\n    for i in range(len(cls_segms)):\n        for cls_segm in cls_segms[i]:\n            encoded_mask_results[i].append(\n                mask_util.encode(\n                    np.array(\n                        cls_segm[:, :, np.newaxis], order='F',\n                        dtype='uint8'))[0])  # encoded with RLE\n    if isinstance(mask_results, tuple):\n        return encoded_mask_results, cls_mask_scores\n    else:\n        return encoded_mask_results\n\ndef get_rle(cls_segm, img_h, img_w):\n    cls_segm = [cls_segm.copy().tolist()]\n    rles = mask_util.frPyObjects(cls_segm, img_h, img_w)\n    return mask_util.merge(rles)\n\ndef encode_poly_results(mask_results, img_h, img_w):\n    if isinstance(mask_results, tuple):  # mask scoring\n        cls_segms, cls_mask_scores = mask_results\n    else:\n        cls_segms = mask_results\n    num_classes = len(cls_segms)\n    encoded_mask_results = [[] for _ in range(num_classes)]\n    for i in range(len(cls_segms)):\n        for cls_segm in cls_segms[i]:\n            encoded_mask_results[i].append(\n                get_rle(cls_segm, img_h, img_w)\n                )  # encoded with RLE\n    if isinstance(mask_results, tuple):\n        return encoded_mask_results, cls_mask_scores\n    else:\n        return encoded_mask_results   "
  },
  {
    "path": "code/mmdet/core/post_processing/__init__.py",
    "content": "from .bbox_nms import (multiclass_nms, multiclass_nms_lsvr, multiclass_nms_pts, multiclass_nms_pts_refine)\nfrom .merge_augs import (merge_aug_bboxes, merge_aug_masks,\n                         merge_aug_proposals, merge_aug_scores)\n\n__all__ = [\n    'multiclass_nms', 'merge_aug_proposals', 'merge_aug_bboxes',\n    'merge_aug_scores', 'merge_aug_masks', 'multiclass_nms_lsvr',\n    'multiclass_nms_pts', 'multiclass_nms_pts_refine'\n]\n"
  },
  {
    "path": "code/mmdet/core/post_processing/bbox_nms.py",
    "content": "import pdb\nimport torch\n\nfrom mmdet.ops.nms import batched_nms\n\n\ndef multiclass_nms(multi_bboxes,\n                   multi_scores,\n                   score_thr,\n                   nms_cfg,\n                   max_num=-1,\n                   score_factors=None):\n    \"\"\"NMS for multi-class bboxes.\n\n    Args:\n        multi_bboxes (Tensor): shape (n, #class*4) or (n, 4)\n        multi_scores (Tensor): shape (n, #class), where the last column\n            contains scores of the background class, but this will be ignored.\n        score_thr (float): bbox threshold, bboxes with scores lower than it\n            will not be considered.\n        nms_thr (float): NMS IoU threshold\n        max_num (int): if there are more than max_num bboxes after NMS,\n            only top max_num will be kept.\n        score_factors (Tensor): The factors multiplied to scores before\n            applying NMS\n\n    Returns:\n        tuple: (bboxes, labels), tensors of shape (k, 5) and (k, 1). Labels\n            are 0-based.\n    \"\"\"\n    num_classes = multi_scores.size(1) - 1\n    # exclude background category\n    if multi_bboxes.shape[1] > 4:\n        bboxes = multi_bboxes.view(multi_scores.size(0), -1, 4)\n    else:\n        bboxes = multi_bboxes[:, None].expand(-1, num_classes, 4)\n    scores = multi_scores[:, :-1]\n\n    # filter out boxes with low scores\n    valid_mask = scores > score_thr\n    bboxes = bboxes[valid_mask]\n    if score_factors is not None:\n        scores = scores * score_factors[:, None]\n    scores = scores[valid_mask]\n    labels = valid_mask.nonzero()[:, 1]\n\n    if bboxes.numel() == 0:\n        bboxes = multi_bboxes.new_zeros((0, 5))\n        labels = multi_bboxes.new_zeros((0, ), dtype=torch.long)\n        return bboxes, labels\n\n    dets, keep = batched_nms(bboxes, scores, labels, nms_cfg)\n\n    if max_num > 0:\n        dets = dets[:max_num]\n        keep = keep[:max_num]\n\n    return dets, labels[keep]\n\ndef multiclass_nms_lsvr(multi_bboxes,\n                        multi_pts,\n                        multi_scores,\n                        npts,\n                        score_thr,\n                        nms_cfg,\n                        max_num=-1,\n                        score_factors=None):\n    num_classes = multi_scores.size(1) - 1\n    # exclude background category\n    if multi_bboxes.shape[1] > 4:\n        bboxes = multi_bboxes.view(multi_scores.size(0), -1, 4)\n    else:\n        bboxes = multi_bboxes[:, None].expand(-1, num_classes, 4)\n    \n    pts = multi_pts[:, None].expand(-1, num_classes, multi_pts.shape[-1])\n    scores = multi_scores[:, :-1]\n\n    # filter out boxes with low scores\n    valid_mask = scores > score_thr\n    bboxes = bboxes[valid_mask]\n    pts = pts[valid_mask]\n    if score_factors is not None:\n        scores = scores * score_factors[:, None]\n    scores = scores[valid_mask]\n    labels = valid_mask.nonzero()[:, 1]\n\n    if bboxes.numel() == 0:\n        bboxes = multi_bboxes.new_zeros((0, 5))\n        pts = pts.new_zeros((0, npts*2))\n        labels = multi_bboxes.new_zeros((0, ), dtype=torch.long)\n        return bboxes, pts, labels\n\n    dets, keep = batched_nms(bboxes, scores, labels, nms_cfg)\n\n    if max_num > 0:\n        dets = dets[:max_num]\n        keep = keep[:max_num]\n\n    return dets, pts[keep], labels[keep]\n\n\ndef multiclass_nms_pts(multi_bboxes,\n                       multi_pts,\n                       multi_scores,\n                       multi_masks,\n                       score_thr,\n                       nms_cfg,\n                       max_num=-1,\n                       score_factors=None\n                        ):\n    \"\"\"NMS for multi-class bboxes.\n\n    Args:\n        multi_bboxes (Tensor): shape (n, #class*4) or (n, 4)\n        multi_scores (Tensor): shape (n, #class), where the 0th column\n            contains scores of the background class, but this will be ignored.\n        score_thr (float): bbox threshold, bboxes with scores lower than it\n            will not be considered.\n        nms_thr (float): NMS IoU threshold\n        max_num (int): if there are more than max_num bboxes after NMS,\n            only top max_num will be kept.\n        score_factors (Tensor): The factors multiplied to scores before\n            applying NMS\n\n    Returns:\n        tuple: (bboxes, labels), tensors of shape (k, 5) and (k, 1). Labels\n            are 0-based.\n    \"\"\"\n    num_classes = multi_scores.size(1) - 1\n    # exclude background category\n    if multi_bboxes.shape[1] > 4:\n        bboxes = multi_bboxes.view(multi_scores.size(0), -1, 4)\n    else:\n        bboxes = multi_bboxes[:, None].expand(-1, num_classes, 4)\n\n    pts = multi_pts[:, None].expand(-1, num_classes, multi_pts.shape[-1])\n    masks = multi_masks[:, None].expand(-1, num_classes, multi_masks.shape[-1])\n    scores = multi_scores[:, :-1]\n\n    # filter out boxes with low scores\n    valid_mask = scores > score_thr\n    bboxes = bboxes[valid_mask]\n    pts = pts[valid_mask]\n    masks = masks[valid_mask]\n    if score_factors is not None:\n        scores = scores * score_factors[:, None]\n    scores = scores[valid_mask]\n    labels = valid_mask.nonzero()[:, 1]\n\n    if bboxes.numel() == 0:\n        bboxes = multi_bboxes.new_zeros((0, 5))\n        pts = multi_pts.new_zeros((0, 52))\n        masks = multi_masks.new_zeros((0, 26))\n        labels = multi_bboxes.new_zeros((0, ), dtype=torch.long)\n\n        return bboxes, pts, masks, labels\n\n    dets, keep = batched_nms(bboxes, scores, labels, nms_cfg)\n\n    if max_num > 0:\n        dets = dets[:max_num]\n        keep = keep[:max_num]\n\n    return dets, pts[keep], masks[keep], labels[keep]\n\n\ndef multiclass_nms_pts_refine(multi_bboxes,\n                              multi_pts,\n                              multi_pts_refine,\n                              multi_scores,\n                              multi_masks,\n                              multi_masks_refine,\n                              score_thr,\n                              nms_cfg,\n                              max_num=-1,\n                              score_factors=None\n                              ):\n    \"\"\"NMS for multi-class bboxes.\n\n    Args:\n        multi_bboxes (Tensor): shape (n, #class*4) or (n, 4)\n        multi_scores (Tensor): shape (n, #class), where the 0th column\n            contains scores of the background class, but this will be ignored.\n        score_thr (float): bbox threshold, bboxes with scores lower than it\n            will not be considered.\n        nms_thr (float): NMS IoU threshold\n        max_num (int): if there are more than max_num bboxes after NMS,\n            only top max_num will be kept.\n        score_factors (Tensor): The factors multiplied to scores before\n            applying NMS\n\n    Returns:\n        tuple: (bboxes, labels), tensors of shape (k, 5) and (k, 1). Labels\n            are 0-based.\n    \"\"\"\n    num_classes = multi_scores.size(1) - 1\n    # exclude background category\n    if multi_bboxes.shape[1] > 4:\n        bboxes = multi_bboxes.view(multi_scores.size(0), -1, 4)[:, 1:]\n    else:\n        bboxes = multi_bboxes[:, None].expand(-1, num_classes, 4)\n\n    pts = multi_pts[:,None].expand(-1, num_classes, multi_pts.shape[-1])\n    pts_refine = multi_pts_refine[:, None].expand(-1, num_classes, multi_pts_refine.shape[-1])\n    masks = multi_masks[:,None].expand(-1, num_classes, multi_masks.shape[-1])\n    masks_refine = multi_masks_refine[:, None].expand(-1, num_classes, multi_masks_refine.shape[-1])\n    scores = multi_scores[:, :-1]\n\n    # filter out boxes with low scores\n    valid_mask = scores > score_thr\n    bboxes = bboxes[valid_mask]\n    pts = pts[valid_mask]\n    pts_refine = pts_refine[valid_mask]\n    masks = masks[valid_mask]\n    masks_refine = masks_refine[valid_mask]\n    if score_factors is not None:\n        scores = scores * score_factors[:, None]\n    scores = scores[valid_mask]\n    labels = valid_mask.nonzero()[:, 1]\n\n    if bboxes.numel() == 0:\n        bboxes = multi_bboxes.new_zeros((0, 5))\n        pts = multi_pts.new_zeros((0, 52))\n        pts_refine = multi_pts_refine.new_zeros((0, 52))\n        masks = multi_masks.new_zeros((0, 26))\n        masks_refine = multi_masks_refine.new_zeros((0, 26))\n        labels = multi_bboxes.new_zeros((0,), dtype=torch.long)\n\n        return bboxes, pts, pts_refine, masks, masks_refine, labels\n\n    dets, keep = batched_nms(bboxes, scores, labels, nms_cfg)\n\n    if max_num > 0:\n        dets = dets[:max_num]\n        keep = keep[:max_num]\n\n    return dets, pts[keep], pts_refine[keep], masks[keep], masks_refine[keep], labels[keep]\n"
  },
  {
    "path": "code/mmdet/core/post_processing/merge_augs.py",
    "content": "import numpy as np\nimport torch\n\nfrom mmdet.ops import nms\nfrom ..bbox import bbox_mapping_back\n\n\ndef merge_aug_proposals(aug_proposals, img_metas, rpn_test_cfg):\n    \"\"\"Merge augmented proposals (multiscale, flip, etc.)\n\n    Args:\n        aug_proposals (list[Tensor]): proposals from different testing\n            schemes, shape (n, 5). Note that they are not rescaled to the\n            original image size.\n\n        img_metas (list[dict]): list of image info dict where each dict has:\n            'img_shape', 'scale_factor', 'flip', and my also contain\n            'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n            For details on the values of these keys see\n            `mmdet/datasets/pipelines/formatting.py:Collect`.\n\n        rpn_test_cfg (dict): rpn test config.\n\n    Returns:\n        Tensor: shape (n, 4), proposals corresponding to original image scale.\n    \"\"\"\n    recovered_proposals = []\n    for proposals, img_info in zip(aug_proposals, img_metas):\n        img_shape = img_info['img_shape']\n        scale_factor = img_info['scale_factor']\n        flip = img_info['flip']\n        flip_direction = img_info['flip_direction']\n        _proposals = proposals.clone()\n        _proposals[:, :4] = bbox_mapping_back(_proposals[:, :4], img_shape,\n                                              scale_factor, flip,\n                                              flip_direction)\n        recovered_proposals.append(_proposals)\n    aug_proposals = torch.cat(recovered_proposals, dim=0)\n    merged_proposals, _ = nms(aug_proposals, rpn_test_cfg.nms_thr)\n    scores = merged_proposals[:, 4]\n    _, order = scores.sort(0, descending=True)\n    num = min(rpn_test_cfg.max_num, merged_proposals.shape[0])\n    order = order[:num]\n    merged_proposals = merged_proposals[order, :]\n    return merged_proposals\n\n\ndef merge_aug_bboxes(aug_bboxes, aug_scores, img_metas, rcnn_test_cfg):\n    \"\"\"Merge augmented detection bboxes and scores.\n\n    Args:\n        aug_bboxes (list[Tensor]): shape (n, 4*#class)\n        aug_scores (list[Tensor] or None): shape (n, #class)\n        img_shapes (list[Tensor]): shape (3, ).\n        rcnn_test_cfg (dict): rcnn test config.\n\n    Returns:\n        tuple: (bboxes, scores)\n    \"\"\"\n    recovered_bboxes = []\n    for bboxes, img_info in zip(aug_bboxes, img_metas):\n        img_shape = img_info[0]['img_shape']\n        scale_factor = img_info[0]['scale_factor']\n        flip = img_info[0]['flip']\n        flip_direction = img_info[0]['flip_direction']\n        bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,\n                                   flip_direction)\n        recovered_bboxes.append(bboxes)\n    bboxes = torch.stack(recovered_bboxes).mean(dim=0)\n    if aug_scores is None:\n        return bboxes\n    else:\n        scores = torch.stack(aug_scores).mean(dim=0)\n        return bboxes, scores\n\n\ndef merge_aug_scores(aug_scores):\n    \"\"\"Merge augmented bbox scores.\"\"\"\n    if isinstance(aug_scores[0], torch.Tensor):\n        return torch.mean(torch.stack(aug_scores), dim=0)\n    else:\n        return np.mean(aug_scores, axis=0)\n\n\ndef merge_aug_masks(aug_masks, img_metas, rcnn_test_cfg, weights=None):\n    \"\"\"Merge augmented mask prediction.\n\n    Args:\n        aug_masks (list[ndarray]): shape (n, #class, h, w)\n        img_shapes (list[ndarray]): shape (3, ).\n        rcnn_test_cfg (dict): rcnn test config.\n\n    Returns:\n        tuple: (bboxes, scores)\n    \"\"\"\n    recovered_masks = []\n    for mask, img_info in zip(aug_masks, img_metas):\n        flip = img_info[0]['flip']\n        flip_direction = img_info[0]['flip_direction']\n        if flip:\n            if flip_direction == 'horizontal':\n                mask = mask[:, :, :, ::-1]\n            elif flip_direction == 'vertical':\n                mask = mask[:, :, ::-1, :]\n            else:\n                raise ValueError(\n                    f\"Invalid flipping direction '{flip_direction}'\")\n        recovered_masks.append(mask)\n\n    if weights is None:\n        merged_masks = np.mean(recovered_masks, axis=0)\n    else:\n        merged_masks = np.average(\n            np.array(recovered_masks), axis=0, weights=np.array(weights))\n    return merged_masks\n"
  },
  {
    "path": "code/mmdet/core/utils/__init__.py",
    "content": "from .dist_utils import DistOptimizerHook, allreduce_grads\nfrom .misc import multi_apply, tensor2imgs, unmap\n\n__all__ = [\n    'allreduce_grads', 'DistOptimizerHook', 'tensor2imgs', 'multi_apply',\n    'unmap'\n]\n"
  },
  {
    "path": "code/mmdet/core/utils/dist_utils.py",
    "content": "import warnings\nfrom collections import OrderedDict\n\nimport torch.distributed as dist\nfrom mmcv.runner import OptimizerHook\nfrom torch._utils import (_flatten_dense_tensors, _take_tensors,\n                          _unflatten_dense_tensors)\n\n\ndef _allreduce_coalesced(tensors, world_size, bucket_size_mb=-1):\n    if bucket_size_mb > 0:\n        bucket_size_bytes = bucket_size_mb * 1024 * 1024\n        buckets = _take_tensors(tensors, bucket_size_bytes)\n    else:\n        buckets = OrderedDict()\n        for tensor in tensors:\n            tp = tensor.type()\n            if tp not in buckets:\n                buckets[tp] = []\n            buckets[tp].append(tensor)\n        buckets = buckets.values()\n\n    for bucket in buckets:\n        flat_tensors = _flatten_dense_tensors(bucket)\n        dist.all_reduce(flat_tensors)\n        flat_tensors.div_(world_size)\n        for tensor, synced in zip(\n                bucket, _unflatten_dense_tensors(flat_tensors, bucket)):\n            tensor.copy_(synced)\n\n\ndef allreduce_grads(params, coalesce=True, bucket_size_mb=-1):\n    \"\"\"Allreduce gradients\n\n    Args:\n        params (list[torch.Parameters]): List of parameters of a model\n        coalesce (bool, optional): Whether allreduce parameters as a whole.\n            Defaults to True.\n        bucket_size_mb (int, optional): Size of bucket, the unit is MB.\n            Defaults to -1.\n    \"\"\"\n    grads = [\n        param.grad.data for param in params\n        if param.requires_grad and param.grad is not None\n    ]\n    world_size = dist.get_world_size()\n    if coalesce:\n        _allreduce_coalesced(grads, world_size, bucket_size_mb)\n    else:\n        for tensor in grads:\n            dist.all_reduce(tensor.div_(world_size))\n\n\nclass DistOptimizerHook(OptimizerHook):\n    \"\"\"Deprecated optimizer hook for distributed training\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        warnings.warn('\"DistOptimizerHook\" is deprecated, please switch to'\n                      '\"mmcv.runner.OptimizerHook\".')\n        super().__init__(*args, **kwargs)\n"
  },
  {
    "path": "code/mmdet/core/utils/misc.py",
    "content": "from functools import partial\n\nimport mmcv\nimport numpy as np\nimport torch\nfrom six.moves import map, zip\n\n\ndef tensor2imgs(tensor, mean=(0, 0, 0), std=(1, 1, 1), to_rgb=True):\n    \"\"\"Convert tensor to images\n\n    Args:\n        tensor (torch.Tensor): Tensor that contains multiple images\n        mean (tuple[float], optional): Mean of images. Defaults to (0, 0, 0).\n        std (tuple[float], optional): Standard deviation of images.\n            Defaults to (1, 1, 1).\n        to_rgb (bool, optional): Whether convert the images to RGB format.\n            Defaults to True.\n\n    Returns:\n        list[np.ndarray]: A list that contains multiple images.\n    \"\"\"\n    num_imgs = tensor.size(0)\n    mean = np.array(mean, dtype=np.float32)\n    std = np.array(std, dtype=np.float32)\n    imgs = []\n    for img_id in range(num_imgs):\n        img = tensor[img_id, ...].cpu().numpy().transpose(1, 2, 0)\n        img = mmcv.imdenormalize(\n            img, mean, std, to_bgr=to_rgb).astype(np.uint8)\n        imgs.append(np.ascontiguousarray(img))\n    return imgs\n\n\ndef multi_apply(func, *args, **kwargs):\n    \"\"\"Apply function to a list of arguments\n\n    Note:\n        This function applies the ``func`` to multiple inputs and\n            map the multiple outputs of the ``func`` into different\n            list. Each list contains the same type of outputs corresponding\n            to different inputs.\n\n    Args:\n        func (Function): A function that will be applied to a list of\n            arguments\n\n    Returns:\n        tuple(list): A tuple containing multiple list, each list contains\n            a kind of returned results by the function\n    \"\"\"\n    pfunc = partial(func, **kwargs) if kwargs else func\n    map_results = map(pfunc, *args)\n    return tuple(map(list, zip(*map_results)))\n\n\ndef unmap(data, count, inds, fill=0):\n    \"\"\" Unmap a subset of item (data) back to the original set of items (of\n    size count) \"\"\"\n    if data.dim() == 1:\n        ret = data.new_full((count, ), fill)\n        ret[inds.type(torch.bool)] = data\n    else:\n        new_size = (count, ) + data.size()[1:]\n        ret = data.new_full(new_size, fill)\n        ret[inds.type(torch.bool), :] = data\n    return ret\n"
  },
  {
    "path": "code/mmdet/datasets/__init__.py",
    "content": "from .builder import DATASETS, PIPELINES, build_dataloader, build_dataset\nfrom .cityscapes import CityscapesDataset\nfrom .coco import CocoDataset\nfrom .custom import CustomDataset\nfrom .dataset_wrappers import (ClassBalancedDataset, ConcatDataset,\n                               RepeatDataset)\nfrom .deepfashion import DeepFashionDataset\nfrom .lvis import LVISDataset\nfrom .samplers import DistributedGroupSampler, DistributedSampler, GroupSampler\nfrom .voc import VOCDataset\nfrom .wider_face import WIDERFaceDataset\nfrom .xml_style import XMLDataset\nfrom .coco_pose import CocoPoseDataset\n\n__all__ = [\n    'CustomDataset', 'XMLDataset', 'CocoDataset', 'DeepFashionDataset',\n    'VOCDataset', 'CityscapesDataset', 'LVISDataset', 'GroupSampler',\n    'CustomDataset', 'XMLDataset', 'CocoDataset', 'VOCDataset',\n    'CityscapesDataset', 'LVISDataset', 'DeepFashionDataset', 'GroupSampler',\n    'DistributedGroupSampler', 'DistributedSampler', 'build_dataloader',\n    'ConcatDataset', 'RepeatDataset', 'ClassBalancedDataset',\n    'WIDERFaceDataset', 'DATASETS', 'PIPELINES', 'build_dataset', 'CocoPoseDataset'\n]\n"
  },
  {
    "path": "code/mmdet/datasets/builder.py",
    "content": "import copy\nimport platform\nimport random\nfrom functools import partial\n\nimport numpy as np\nfrom mmcv.parallel import collate\nfrom mmcv.runner import get_dist_info\nfrom mmcv.utils import Registry, build_from_cfg\nfrom torch.utils.data import DataLoader\n\nfrom .samplers import DistributedGroupSampler, DistributedSampler, GroupSampler\n\nif platform.system() != 'Windows':\n    # https://github.com/pytorch/pytorch/issues/973\n    import resource\n    rlimit = resource.getrlimit(resource.RLIMIT_NOFILE)\n    hard_limit = rlimit[1]\n    soft_limit = min(4096, hard_limit)\n    resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit))\n\nDATASETS = Registry('dataset')\nPIPELINES = Registry('pipeline')\n\n\ndef _concat_dataset(cfg, default_args=None):\n    from .dataset_wrappers import ConcatDataset\n    ann_files = cfg['ann_file']\n    img_prefixes = cfg.get('img_prefix', None)\n    seg_prefixes = cfg.get('seg_prefix', None)\n    proposal_files = cfg.get('proposal_file', None)\n\n    datasets = []\n    num_dset = len(ann_files)\n    for i in range(num_dset):\n        data_cfg = copy.deepcopy(cfg)\n        data_cfg['ann_file'] = ann_files[i]\n        if isinstance(img_prefixes, (list, tuple)):\n            data_cfg['img_prefix'] = img_prefixes[i]\n        if isinstance(seg_prefixes, (list, tuple)):\n            data_cfg['seg_prefix'] = seg_prefixes[i]\n        if isinstance(proposal_files, (list, tuple)):\n            data_cfg['proposal_file'] = proposal_files[i]\n        datasets.append(build_dataset(data_cfg, default_args))\n\n    return ConcatDataset(datasets)\n\n\ndef build_dataset(cfg, default_args=None):\n    from .dataset_wrappers import (ConcatDataset, RepeatDataset,\n                                   ClassBalancedDataset)\n    if isinstance(cfg, (list, tuple)):\n        dataset = ConcatDataset([build_dataset(c, default_args) for c in cfg])\n    elif cfg['type'] == 'RepeatDataset':\n        dataset = RepeatDataset(\n            build_dataset(cfg['dataset'], default_args), cfg['times'])\n    elif cfg['type'] == 'ClassBalancedDataset':\n        dataset = ClassBalancedDataset(\n            build_dataset(cfg['dataset'], default_args), cfg['oversample_thr'])\n    elif isinstance(cfg.get('ann_file'), (list, tuple)):\n        dataset = _concat_dataset(cfg, default_args)\n    else:\n        dataset = build_from_cfg(cfg, DATASETS, default_args)\n\n    return dataset\n\n\ndef build_dataloader(dataset,\n                     samples_per_gpu,\n                     workers_per_gpu,\n                     num_gpus=1,\n                     dist=True,\n                     shuffle=True,\n                     seed=None,\n                     **kwargs):\n    \"\"\"Build PyTorch DataLoader.\n\n    In distributed training, each GPU/process has a dataloader.\n    In non-distributed training, there is only one dataloader for all GPUs.\n\n    Args:\n        dataset (Dataset): A PyTorch dataset.\n        samples_per_gpu (int): Number of training samples on each GPU, i.e.,\n            batch size of each GPU.\n        workers_per_gpu (int): How many subprocesses to use for data loading\n            for each GPU.\n        num_gpus (int): Number of GPUs. Only used in non-distributed training.\n        dist (bool): Distributed training/test or not. Default: True.\n        shuffle (bool): Whether to shuffle the data at every epoch.\n            Default: True.\n        kwargs: any keyword argument to be used to initialize DataLoader\n\n    Returns:\n        DataLoader: A PyTorch dataloader.\n    \"\"\"\n    rank, world_size = get_dist_info()\n    if dist:\n        # DistributedGroupSampler will definitely shuffle the data to satisfy\n        # that images on each GPU are in the same group\n        if shuffle:\n            sampler = DistributedGroupSampler(dataset, samples_per_gpu,\n                                              world_size, rank)\n        else:\n            sampler = DistributedSampler(\n                dataset, world_size, rank, shuffle=False)\n        batch_size = samples_per_gpu\n        num_workers = workers_per_gpu\n    else:\n        sampler = GroupSampler(dataset, samples_per_gpu) if shuffle else None\n        batch_size = num_gpus * samples_per_gpu\n        num_workers = num_gpus * workers_per_gpu\n\n    init_fn = partial(\n        worker_init_fn, num_workers=num_workers, rank=rank,\n        seed=seed) if seed is not None else None\n\n    data_loader = DataLoader(\n        dataset,\n        batch_size=batch_size,\n        sampler=sampler,\n        num_workers=num_workers,\n        collate_fn=partial(collate, samples_per_gpu=samples_per_gpu),\n        pin_memory=False,\n        worker_init_fn=init_fn,\n        **kwargs)\n\n    return data_loader\n\n\ndef worker_init_fn(worker_id, num_workers, rank, seed):\n    # The seed of each worker equals to\n    # num_worker * rank + worker_id + user_seed\n    worker_seed = num_workers * rank + worker_id + seed\n    np.random.seed(worker_seed)\n    random.seed(worker_seed)\n"
  },
  {
    "path": "code/mmdet/datasets/cityscapes.py",
    "content": "# Modified from https://github.com/facebookresearch/detectron2/blob/master/detectron2/data/datasets/cityscapes.py # noqa\n# and https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/evalInstanceLevelSemanticLabeling.py # noqa\n\nimport glob\nimport os\nimport os.path as osp\nimport tempfile\n\nimport mmcv\nimport numpy as np\nimport pycocotools.mask as maskUtils\nfrom mmcv.utils import print_log\n\nfrom .builder import DATASETS\nfrom .coco import CocoDataset\n\n\n@DATASETS.register_module()\nclass CityscapesDataset(CocoDataset):\n\n    CLASSES = ('person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle',\n               'bicycle')\n\n    def _filter_imgs(self, min_size=32):\n        \"\"\"Filter images too small or without ground truths.\"\"\"\n        valid_inds = []\n        ids_with_ann = set(_['image_id'] for _ in self.coco.anns.values())\n        for i, img_info in enumerate(self.data_infos):\n            img_id = img_info['id']\n            ann_ids = self.coco.getAnnIds(imgIds=[img_id])\n            ann_info = self.coco.loadAnns(ann_ids)\n            all_iscrowd = all([_['iscrowd'] for _ in ann_info])\n            if self.filter_empty_gt and (self.img_ids[i] not in ids_with_ann\n                                         or all_iscrowd):\n                continue\n            if min(img_info['width'], img_info['height']) >= min_size:\n                valid_inds.append(i)\n        return valid_inds\n\n    def _parse_ann_info(self, img_info, ann_info):\n        \"\"\"Parse bbox and mask annotation.\n\n        Args:\n            img_info (dict): Image info of an image.\n            ann_info (list[dict]): Annotation info of an image.\n\n        Returns:\n            dict: A dict containing the following keys: bboxes, bboxes_ignore,\n                labels, masks, seg_map.\n                \"masks\" are already decoded into binary masks.\n        \"\"\"\n        gt_bboxes = []\n        gt_labels = []\n        gt_bboxes_ignore = []\n        gt_masks_ann = []\n\n        for i, ann in enumerate(ann_info):\n            if ann.get('ignore', False):\n                continue\n            x1, y1, w, h = ann['bbox']\n            if ann['area'] <= 0 or w < 1 or h < 1:\n                continue\n            if ann['category_id'] not in self.cat_ids:\n                continue\n            bbox = [x1, y1, x1 + w, y1 + h]\n            if ann.get('iscrowd', False):\n                gt_bboxes_ignore.append(bbox)\n            else:\n                gt_bboxes.append(bbox)\n                gt_labels.append(self.cat2label[ann['category_id']])\n                gt_masks_ann.append(ann['segmentation'])\n\n        if gt_bboxes:\n            gt_bboxes = np.array(gt_bboxes, dtype=np.float32)\n            gt_labels = np.array(gt_labels, dtype=np.int64)\n        else:\n            gt_bboxes = np.zeros((0, 4), dtype=np.float32)\n            gt_labels = np.array([], dtype=np.int64)\n\n        if gt_bboxes_ignore:\n            gt_bboxes_ignore = np.array(gt_bboxes_ignore, dtype=np.float32)\n        else:\n            gt_bboxes_ignore = np.zeros((0, 4), dtype=np.float32)\n\n        ann = dict(\n            bboxes=gt_bboxes,\n            labels=gt_labels,\n            bboxes_ignore=gt_bboxes_ignore,\n            masks=gt_masks_ann,\n            seg_map=img_info['segm_file'])\n\n        return ann\n\n    def results2txt(self, results, outfile_prefix):\n        \"\"\"Dump the detection results to a txt file.\n\n        Args:\n            results (list[list | tuple]): Testing results of the\n                dataset.\n            outfile_prefix (str): The filename prefix of the json files.\n                If the prefix is \"somepath/xxx\",\n                the txt files will be named \"somepath/xxx.txt\".\n\n        Returns:\n            list[str: str]: result txt files which contains corresponding\n            instance segmentation images.\n        \"\"\"\n        try:\n            import cityscapesscripts.helpers.labels as CSLabels\n        except ImportError:\n            raise ImportError('Please run \"pip install citscapesscripts\" to '\n                              'install cityscapesscripts first.')\n        result_files = []\n        os.makedirs(outfile_prefix, exist_ok=True)\n        prog_bar = mmcv.ProgressBar(len(self))\n        for idx in range(len(self)):\n            result = results[idx]\n            filename = self.data_infos[idx]['filename']\n            basename = osp.splitext(osp.basename(filename))[0]\n            pred_txt = osp.join(outfile_prefix, basename + '_pred.txt')\n\n            bbox_result, segm_result = result\n            bboxes = np.vstack(bbox_result)\n            # segm results\n            if isinstance(segm_result, tuple):\n                # Some detectors use different scores for bbox and mask,\n                # like Mask Scoring R-CNN. Score of segm will be used instead\n                # of bbox score.\n                segms = mmcv.concat_list(segm_result[0])\n                mask_score = segm_result[1]\n            else:\n                # use bbox score for mask score\n                segms = mmcv.concat_list(segm_result)\n                mask_score = [bbox[-1] for bbox in bboxes]\n            labels = [\n                np.full(bbox.shape[0], i, dtype=np.int32)\n                for i, bbox in enumerate(bbox_result)\n            ]\n            labels = np.concatenate(labels)\n\n            assert len(bboxes) == len(segms) == len(labels)\n            num_instances = len(bboxes)\n            prog_bar.update()\n            with open(pred_txt, 'w') as fout:\n                for i in range(num_instances):\n                    pred_class = labels[i]\n                    classes = self.CLASSES[pred_class]\n                    class_id = CSLabels.name2label[classes].id\n                    score = mask_score[i]\n                    mask = maskUtils.decode(segms[i]).astype(np.uint8)\n                    png_filename = osp.join(outfile_prefix,\n                                            basename + f'_{i}_{classes}.png')\n                    mmcv.imwrite(mask, png_filename)\n                    fout.write(f'{osp.basename(png_filename)} {class_id} '\n                               f'{score}\\n')\n            result_files.append(pred_txt)\n\n        return result_files\n\n    def format_results(self, results, txtfile_prefix=None):\n        \"\"\"Format the results to txt (standard format for Cityscapes evaluation).\n\n        Args:\n            results (list): Testing results of the dataset.\n            txtfile_prefix (str | None): The prefix of txt files. It includes\n                the file path and the prefix of filename, e.g., \"a/b/prefix\".\n                If not specified, a temp file will be created. Default: None.\n\n        Returns:\n            tuple: (result_files, tmp_dir), result_files is a dict containing\n                the json filepaths, tmp_dir is the temporal directory created\n                for saving txt/png files when txtfile_prefix is not specified.\n        \"\"\"\n        assert isinstance(results, list), 'results must be a list'\n        assert len(results) == len(self), (\n            'The length of results is not equal to the dataset len: {} != {}'.\n            format(len(results), len(self)))\n\n        assert isinstance(results, list), 'results must be a list'\n        assert len(results) == len(self), (\n            'The length of results is not equal to the dataset len: {} != {}'.\n            format(len(results), len(self)))\n\n        if txtfile_prefix is None:\n            tmp_dir = tempfile.TemporaryDirectory()\n            txtfile_prefix = osp.join(tmp_dir.name, 'results')\n        else:\n            tmp_dir = None\n        result_files = self.results2txt(results, txtfile_prefix)\n\n        return result_files, tmp_dir\n\n    def evaluate(self,\n                 results,\n                 metric='bbox',\n                 logger=None,\n                 outfile_prefix=None,\n                 classwise=False,\n                 proposal_nums=(100, 300, 1000),\n                 iou_thrs=np.arange(0.5, 0.96, 0.05)):\n        \"\"\"Evaluation in Cityscapes/COCO protocol.\n\n        Args:\n            results (list[list | tuple]): Testing results of the dataset.\n            metric (str | list[str]): Metrics to be evaluated. Options are\n                'bbox', 'segm', 'proposal', 'proposal_fast'.\n            logger (logging.Logger | str | None): Logger used for printing\n                related information during evaluation. Default: None.\n            outfile_prefix (str | None): The prefix of output file. It includes\n                the file path and the prefix of filename, e.g., \"a/b/prefix\".\n                If results are evaluated with COCO protocol, it would be the\n                prefix of output json file. For example, the metric is 'bbox'\n                and 'segm', then json files would be \"a/b/prefix.bbox.json\" and\n                \"a/b/prefix.segm.json\".\n                If results are evaluated with cityscapes protocol, it would be\n                the prefix of output txt/png files. The output files would be\n                png images under folder \"a/b/prefix/xxx/\" and the file name of\n                images would be written into a txt file\n                \"a/b/prefix/xxx_pred.txt\", where \"xxx\" is the video name of\n                cityscapes. If not specified, a temp file will be created.\n                Default: None.\n            classwise (bool): Whether to evaluating the AP for each class.\n            proposal_nums (Sequence[int]): Proposal number used for evaluating\n                recalls, such as recall@100, recall@1000.\n                Default: (100, 300, 1000).\n            iou_thrs (Sequence[float]): IoU threshold used for evaluating\n                recalls. If set to a list, the average recall of all IoUs will\n                also be computed. Default: 0.5.\n\n        Returns:\n            dict[str, float]: COCO style evaluation metric or cityscapes mAP\n                and AP@50.\n        \"\"\"\n        eval_results = dict()\n\n        metrics = metric.copy() if isinstance(metric, list) else [metric]\n\n        if 'cityscapes' in metrics:\n            eval_results.update(\n                self._evaluate_cityscapes(results, outfile_prefix, logger))\n            metrics.remove('cityscapes')\n\n        # left metrics are all coco metric\n        if len(metrics) > 0:\n            # create CocoDataset with CityscapesDataset annotation\n            self_coco = CocoDataset(self.ann_file, self.pipeline.transforms,\n                                    None, self.data_root, self.img_prefix,\n                                    self.seg_prefix, self.proposal_file,\n                                    self.test_mode, self.filter_empty_gt)\n            # TODO: remove this in the future\n            # reload annotations of correct class\n            self_coco.CLASSES = self.CLASSES\n            self_coco.data_infos = self_coco.load_annotations(self.ann_file)\n            eval_results.update(\n                self_coco.evaluate(results, metrics, logger, outfile_prefix,\n                                   classwise, proposal_nums, iou_thrs))\n\n        return eval_results\n\n    def _evaluate_cityscapes(self, results, txtfile_prefix, logger):\n        \"\"\"Evaluation in Cityscapes protocol.\n\n        Args:\n            results (list): Testing results of the dataset.\n            txtfile_prefix (str | None): The prefix of output txt file\n            logger (logging.Logger | str | None): Logger used for printing\n                related information during evaluation. Default: None.\n\n        Returns:\n            dict[str: float]: Cityscapes evaluation results, contains 'mAP'\n                and 'AP@50'.\n        \"\"\"\n\n        try:\n            import cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling as CSEval  # noqa\n        except ImportError:\n            raise ImportError('Please run \"pip install citscapesscripts\" to '\n                              'install cityscapesscripts first.')\n        msg = 'Evaluating in Cityscapes style'\n        if logger is None:\n            msg = '\\n' + msg\n        print_log(msg, logger=logger)\n\n        result_files, tmp_dir = self.format_results(results, txtfile_prefix)\n\n        if tmp_dir is None:\n            result_dir = osp.join(txtfile_prefix, 'results')\n        else:\n            result_dir = osp.join(tmp_dir.name, 'results')\n\n        eval_results = {}\n        print_log(f'Evaluating results under {result_dir} ...', logger=logger)\n\n        # set global states in cityscapes evaluation API\n        CSEval.args.cityscapesPath = os.path.join(self.img_prefix, '../..')\n        CSEval.args.predictionPath = os.path.abspath(result_dir)\n        CSEval.args.predictionWalk = None\n        CSEval.args.JSONOutput = False\n        CSEval.args.colorized = False\n        CSEval.args.gtInstancesFile = os.path.join(result_dir,\n                                                   'gtInstances.json')\n        CSEval.args.groundTruthSearch = os.path.join(\n            self.img_prefix.replace('leftImg8bit', 'gtFine'),\n            '*/*_gtFine_instanceIds.png')\n\n        groundTruthImgList = glob.glob(CSEval.args.groundTruthSearch)\n        assert len(groundTruthImgList), 'Cannot find ground truth images' \\\n            f' in {CSEval.args.groundTruthSearch}.'\n        predictionImgList = []\n        for gt in groundTruthImgList:\n            predictionImgList.append(CSEval.getPrediction(gt, CSEval.args))\n        CSEval_results = CSEval.evaluateImgLists(predictionImgList,\n                                                 groundTruthImgList,\n                                                 CSEval.args)['averages']\n\n        eval_results['mAP'] = CSEval_results['allAp']\n        eval_results['AP@50'] = CSEval_results['allAp50%']\n        if tmp_dir is not None:\n            tmp_dir.cleanup()\n        return eval_results\n"
  },
  {
    "path": "code/mmdet/datasets/coco.py",
    "content": "import itertools\nimport logging\nimport os.path as osp\nimport tempfile\n\nimport mmcv\nimport numpy as np\nfrom mmcv.utils import print_log\nfrom pycocotools.coco import COCO\nfrom pycocotools.cocoeval import COCOeval\nfrom terminaltables import AsciiTable\n\nfrom mmdet.core import eval_recalls\nfrom .builder import DATASETS\nfrom .custom import CustomDataset\n\n\n@DATASETS.register_module()\nclass CocoDataset(CustomDataset):\n\n    CLASSES = ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',\n               'train', 'truck', 'boat', 'traffic light', 'fire hydrant',\n               'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog',\n               'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe',\n               'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',\n               'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat',\n               'baseball glove', 'skateboard', 'surfboard', 'tennis racket',\n               'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',\n               'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot',\n               'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',\n               'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop',\n               'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',\n               'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock',\n               'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush')\n\n    def load_annotations(self, ann_file):\n        \"\"\"Load annotation from COCO style annotation file.\n\n        Args:\n            ann_file (str): Path of annotation file.\n\n        Returns:\n            list[dict]: Annotation info from COCO api.\n        \"\"\"\n\n        self.coco = COCO(ann_file)\n        self.cat_ids = self.coco.get_cat_ids(cat_names=self.CLASSES)\n        self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)}\n        self.img_ids = self.coco.get_img_ids()\n        data_infos = []\n        for i in self.img_ids:\n            info = self.coco.load_imgs([i])[0]\n            info['filename'] = info['file_name']\n            data_infos.append(info)\n        return data_infos\n\n    def get_ann_info(self, idx):\n        \"\"\"Get COCO annotation by index.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Annotation info of specified index.\n        \"\"\"\n\n        img_id = self.data_infos[idx]['id']\n        ann_ids = self.coco.get_ann_ids(img_ids=[img_id])\n        ann_info = self.coco.load_anns(ann_ids)\n        return self._parse_ann_info(self.data_infos[idx], ann_info)\n\n    def get_cat_ids(self, idx):\n        \"\"\"Get COCO category ids by index.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            list[int]: All categories in the image of specified index.\n        \"\"\"\n\n        img_id = self.data_infos[idx]['id']\n        ann_ids = self.coco.get_ann_ids(img_ids=[img_id])\n        ann_info = self.coco.load_anns(ann_ids)\n        return [ann['category_id'] for ann in ann_info]\n\n    def _filter_imgs(self, min_size=32):\n        \"\"\"Filter images too small or without ground truths.\"\"\"\n        valid_inds = []\n        ids_with_ann = set(_['image_id'] for _ in self.coco.anns.values())\n        for i, img_info in enumerate(self.data_infos):\n            if self.filter_empty_gt and self.img_ids[i] not in ids_with_ann:\n                continue\n            if min(img_info['width'], img_info['height']) >= min_size:\n                valid_inds.append(i)\n        return valid_inds\n\n    def get_subset_by_classes(self):\n        \"\"\"Get img ids that contain any category in class_ids.\n\n        Different from the coco.getImgIds(), this function returns the id if\n        the img contains one of the categories rather than all.\n\n        Args:\n            class_ids (list[int]): list of category ids\n\n        Return:\n            ids (list[int]): integer list of img ids\n        \"\"\"\n\n        ids = set()\n        for i, class_id in enumerate(self.cat_ids):\n            ids |= set(self.coco.cat_img_map[class_id])\n        self.img_ids = list(ids)\n\n        data_infos = []\n        for i in self.img_ids:\n            info = self.coco.load_imgs([i])[0]\n            info['filename'] = info['file_name']\n            data_infos.append(info)\n        return data_infos\n\n    def _parse_ann_info(self, img_info, ann_info):\n        \"\"\"Parse bbox and mask annotation.\n\n        Args:\n            ann_info (list[dict]): Annotation info of an image.\n            with_mask (bool): Whether to parse mask annotations.\n\n        Returns:\n            dict: A dict containing the following keys: bboxes, bboxes_ignore,\n                labels, masks, seg_map. \"masks\" are raw annotations and not\n                decoded into binary masks.\n        \"\"\"\n        gt_bboxes = []\n        gt_labels = []\n        gt_bboxes_ignore = []\n        gt_masks_ann     = []\n        gt_extremes_ann  = []\n        for i, ann in enumerate(ann_info):\n            if ann.get('ignore', False):\n                continue\n            x1, y1, w, h = ann['bbox']\n            inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0))\n            inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0))\n            if inter_w * inter_h == 0:\n                continue\n            if ann['area'] <= 0 or w < 1 or h < 1:\n                continue\n            if ann['category_id'] not in self.cat_ids:\n                continue\n            bbox = [x1, y1, x1 + w, y1 + h]\n            if ann.get('iscrowd', False):\n                gt_bboxes_ignore.append(bbox)\n            else:\n                gt_bboxes.append(bbox)\n                gt_labels.append(self.cat2label[ann['category_id']])\n                gt_masks_ann.append(ann['segmentation'])\n                gt_extremes_ann.append(ann['extreme_points'])\n\n\n        if gt_bboxes:\n            gt_bboxes = np.array(gt_bboxes, dtype=np.float32)\n            gt_labels = np.array(gt_labels, dtype=np.int64)\n            gt_extremes_ann = np.array(gt_extremes_ann, dtype=np.float32)\n        else:\n            gt_bboxes = np.zeros((0, 4), dtype=np.float32)\n            gt_labels = np.array([], dtype=np.int64)\n            gt_extremes_ann = np.zeros((0, 10), dtype=np.float32)\n\n        if gt_bboxes_ignore:\n            gt_bboxes_ignore = np.array(gt_bboxes_ignore, dtype=np.float32)\n        else:\n            gt_bboxes_ignore = np.zeros((0, 4), dtype=np.float32)\n\n        seg_map = img_info['filename'].replace('jpg', 'png')\n\n        ann = dict(\n            bboxes=gt_bboxes,\n            labels=gt_labels,\n            bboxes_ignore=gt_bboxes_ignore,\n            masks=gt_masks_ann,\n            extremes = gt_extremes_ann,\n            seg_map=seg_map)\n\n        return ann\n\n    def xyxy2xywh(self, bbox):\n        \"\"\"Convert ``xyxy`` style bounding boxes to ``xywh`` style for COCO\n        evaluation.\n\n        Args:\n            bbox (numpy.ndarray): The bounding boxes, shape (4, ), in\n                ``xyxy`` order.\n\n        Returns:\n            list[float]: The converted bounding boxes, in ``xywh`` order.\n        \"\"\"\n\n        _bbox = bbox.tolist()\n        return [\n            _bbox[0],\n            _bbox[1],\n            _bbox[2] - _bbox[0],\n            _bbox[3] - _bbox[1],\n        ]\n\n    def _proposal2json(self, results):\n        \"\"\"Convert proposal results to COCO json style\"\"\"\n        json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            bboxes = results[idx]\n            for i in range(bboxes.shape[0]):\n                data = dict()\n                data['image_id'] = img_id\n                data['bbox'] = self.xyxy2xywh(bboxes[i])\n                data['score'] = float(bboxes[i][4])\n                data['category_id'] = 1\n                json_results.append(data)\n        return json_results\n\n    def _det2json(self, results):\n        \"\"\"Convert detection results to COCO json style\"\"\"\n        json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            result = results[idx]\n            for label in range(len(result)):\n                bboxes = result[label]\n                for i in range(bboxes.shape[0]):\n                    data = dict()\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['score'] = float(bboxes[i][4])\n                    data['category_id'] = self.cat_ids[label]\n                    json_results.append(data)\n        return json_results\n\n    def _segm2json(self, results):\n        \"\"\"Convert instance segmentation results to COCO json style\"\"\"\n        bbox_json_results = []\n        segm_json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            det, seg = results[idx]\n            for label in range(len(det)):\n                # bbox results\n                bboxes = det[label]\n                for i in range(bboxes.shape[0]):\n                    data = dict()\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['score'] = float(bboxes[i][4])\n                    data['category_id'] = self.cat_ids[label]\n                    bbox_json_results.append(data)\n\n                # segm results\n                # some detectors use different scores for bbox and mask\n                if isinstance(seg, tuple):\n                    segms = seg[0][label]\n                    mask_score = seg[1][label]\n                else:\n                    segms = seg[label]\n                    mask_score = [bbox[4] for bbox in bboxes]\n                for i in range(bboxes.shape[0]):\n                    data = dict()\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['score'] = float(mask_score[i])\n                    data['category_id'] = self.cat_ids[label]\n                    if isinstance(segms[i]['counts'], bytes):\n                        segms[i]['counts'] = segms[i]['counts'].decode()\n                    data['segmentation'] = segms[i]\n                    segm_json_results.append(data)\n        return bbox_json_results, segm_json_results\n\n    def results2json(self, results, outfile_prefix):\n        \"\"\"Dump the detection results to a COCO style json file.\n\n        There are 3 types of results: proposals, bbox predictions, mask\n        predictions, and they have different data types. This method will\n        automatically recognize the type, and dump them to json files.\n\n        Args:\n            results (list[list | tuple | ndarray]): Testing results of the\n                dataset.\n            outfile_prefix (str): The filename prefix of the json files. If the\n                prefix is \"somepath/xxx\", the json files will be named\n                \"somepath/xxx.bbox.json\", \"somepath/xxx.segm.json\",\n                \"somepath/xxx.proposal.json\".\n\n        Returns:\n            dict[str: str]: Possible keys are \"bbox\", \"segm\", \"proposal\", and\n                values are corresponding filenames.\n        \"\"\"\n        result_files = dict()\n        if isinstance(results[0], list):\n            json_results = self._det2json(results)\n            result_files['bbox'] = f'{outfile_prefix}.bbox.json'\n            result_files['proposal'] = f'{outfile_prefix}.bbox.json'\n            mmcv.dump(json_results, result_files['bbox'])\n        elif isinstance(results[0], tuple):\n            json_results = self._segm2json(results)\n            result_files['bbox'] = f'{outfile_prefix}.bbox.json'\n            result_files['proposal'] = f'{outfile_prefix}.bbox.json'\n            result_files['segm'] = f'{outfile_prefix}.segm.json'\n            mmcv.dump(json_results[0], result_files['bbox'])\n            mmcv.dump(json_results[1], result_files['segm'])\n        elif isinstance(results[0], np.ndarray):\n            json_results = self._proposal2json(results)\n            result_files['proposal'] = f'{outfile_prefix}.proposal.json'\n            mmcv.dump(json_results, result_files['proposal'])\n        else:\n            raise TypeError('invalid type of results')\n        return result_files\n\n    def fast_eval_recall(self, results, proposal_nums, iou_thrs, logger=None):\n        gt_bboxes = []\n        for i in range(len(self.img_ids)):\n            ann_ids = self.coco.get_ann_ids(img_ids=self.img_ids[i])\n            ann_info = self.coco.load_anns(ann_ids)\n            if len(ann_info) == 0:\n                gt_bboxes.append(np.zeros((0, 4)))\n                continue\n            bboxes = []\n            for ann in ann_info:\n                if ann.get('ignore', False) or ann['iscrowd']:\n                    continue\n                x1, y1, w, h = ann['bbox']\n                bboxes.append([x1, y1, x1 + w, y1 + h])\n            bboxes = np.array(bboxes, dtype=np.float32)\n            if bboxes.shape[0] == 0:\n                bboxes = np.zeros((0, 4))\n            gt_bboxes.append(bboxes)\n\n        recalls = eval_recalls(\n            gt_bboxes, results, proposal_nums, iou_thrs, logger=logger)\n        ar = recalls.mean(axis=1)\n        return ar\n\n    def format_results(self, results, jsonfile_prefix=None, **kwargs):\n        \"\"\"Format the results to json (standard format for COCO evaluation).\n\n        Args:\n            results (list[tuple | numpy.ndarray]): Testing results of the\n                dataset.\n            jsonfile_prefix (str | None): The prefix of json files. It includes\n                the file path and the prefix of filename, e.g., \"a/b/prefix\".\n                If not specified, a temp file will be created. Default: None.\n\n        Returns:\n            tuple: (result_files, tmp_dir), result_files is a dict containing\n                the json filepaths, tmp_dir is the temporal directory created\n                for saving json files when jsonfile_prefix is not specified.\n        \"\"\"\n        assert isinstance(results, list), 'results must be a list'\n        assert len(results) == len(self), (\n            'The length of results is not equal to the dataset len: {} != {}'.\n            format(len(results), len(self)))\n\n        if jsonfile_prefix is None:\n            tmp_dir = tempfile.TemporaryDirectory()\n            jsonfile_prefix = osp.join(tmp_dir.name, 'results')\n        else:\n            tmp_dir = None\n        result_files = self.results2json(results, jsonfile_prefix)\n        return result_files, tmp_dir\n\n    def evaluate(self,\n                 results,\n                 metric='bbox',\n                 logger=None,\n                 jsonfile_prefix=None,\n                 classwise=False,\n                 proposal_nums=(100, 300, 1000),\n                 iou_thrs=np.arange(0.5, 0.96, 0.05)):\n        \"\"\"Evaluation in COCO protocol.\n\n        Args:\n            results (list[list | tuple]): Testing results of the dataset.\n            metric (str | list[str]): Metrics to be evaluated. Options are\n                'bbox', 'segm', 'proposal', 'proposal_fast'.\n            logger (logging.Logger | str | None): Logger used for printing\n                related information during evaluation. Default: None.\n            jsonfile_prefix (str | None): The prefix of json files. It includes\n                the file path and the prefix of filename, e.g., \"a/b/prefix\".\n                If not specified, a temp file will be created. Default: None.\n            classwise (bool): Whether to evaluating the AP for each class.\n            proposal_nums (Sequence[int]): Proposal number used for evaluating\n                recalls, such as recall@100, recall@1000.\n                Default: (100, 300, 1000).\n            iou_thrs (Sequence[float]): IoU threshold used for evaluating\n                recalls. If set to a list, the average recall of all IoUs will\n                also be computed. Default: 0.5.\n\n        Returns:\n            dict[str, float]: COCO style evaluation metric.\n        \"\"\"\n\n        metrics = metric if isinstance(metric, list) else [metric]\n        allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast']\n        for metric in metrics:\n            if metric not in allowed_metrics:\n                raise KeyError(f'metric {metric} is not supported')\n\n        result_files, tmp_dir = self.format_results(results, jsonfile_prefix)\n\n        eval_results = {}\n        cocoGt = self.coco\n        for metric in metrics:\n            msg = f'Evaluating {metric}...'\n            if logger is None:\n                msg = '\\n' + msg\n            print_log(msg, logger=logger)\n\n            if metric == 'proposal_fast':\n                ar = self.fast_eval_recall(\n                    results, proposal_nums, iou_thrs, logger='silent')\n                log_msg = []\n                for i, num in enumerate(proposal_nums):\n                    eval_results[f'AR@{num}'] = ar[i]\n                    log_msg.append(f'\\nAR@{num}\\t{ar[i]:.4f}')\n                log_msg = ''.join(log_msg)\n                print_log(log_msg, logger=logger)\n                continue\n\n            if metric not in result_files:\n                raise KeyError(f'{metric} is not in results')\n            try:\n                cocoDt = cocoGt.loadRes(result_files[metric])\n            except IndexError:\n                print_log(\n                    'The testing results of the whole dataset is empty.',\n                    logger=logger,\n                    level=logging.ERROR)\n                break\n\n            iou_type = 'bbox' if metric == 'proposal' else metric\n            cocoEval = COCOeval(cocoGt, cocoDt, iou_type)\n            cocoEval.params.catIds = self.cat_ids\n            cocoEval.params.imgIds = self.img_ids\n            if metric == 'proposal':\n                cocoEval.params.useCats = 0\n                cocoEval.params.maxDets = list(proposal_nums)\n                cocoEval.evaluate()\n                cocoEval.accumulate()\n                cocoEval.summarize()\n                metric_items = [\n                    'AR@100', 'AR@300', 'AR@1000', 'AR_s@1000', 'AR_m@1000',\n                    'AR_l@1000'\n                ]\n                for i, item in enumerate(metric_items):\n                    val = float(f'{cocoEval.stats[i + 6]:.3f}')\n                    eval_results[item] = val\n            else:\n                cocoEval.evaluate()\n                cocoEval.accumulate()\n                cocoEval.summarize()\n                if classwise:  # Compute per-category AP\n                    # Compute per-category AP\n                    # from https://github.com/facebookresearch/detectron2/\n                    precisions = cocoEval.eval['precision']\n                    # precision: (iou, recall, cls, area range, max dets)\n                    assert len(self.cat_ids) == precisions.shape[2]\n\n                    results_per_category = []\n                    for idx, catId in enumerate(self.cat_ids):\n                        # area range index 0: all area ranges\n                        # max dets index -1: typically 100 per image\n                        nm = self.coco.loadCats(catId)[0]\n                        precision = precisions[:, :, idx, 0, -1]\n                        precision = precision[precision > -1]\n                        if precision.size:\n                            ap = np.mean(precision)\n                        else:\n                            ap = float('nan')\n                        results_per_category.append(\n                            (f'{nm[\"name\"]}', f'{float(ap):0.3f}'))\n\n                    num_columns = min(6, len(results_per_category) * 2)\n                    results_flatten = list(\n                        itertools.chain(*results_per_category))\n                    headers = ['category', 'AP'] * (num_columns // 2)\n                    results_2d = itertools.zip_longest(*[\n                        results_flatten[i::num_columns]\n                        for i in range(num_columns)\n                    ])\n                    table_data = [headers]\n                    table_data += [result for result in results_2d]\n                    table = AsciiTable(table_data)\n                    print_log('\\n' + table.table, logger=logger)\n\n                metric_items = [\n                    'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l'\n                ]\n                for i in range(len(metric_items)):\n                    key = f'{metric}_{metric_items[i]}'\n                    val = float(f'{cocoEval.stats[i]:.3f}')\n                    eval_results[key] = val\n                ap = cocoEval.stats[:6]\n                eval_results[f'{metric}_mAP_copypaste'] = (\n                    f'{ap[0]:.3f} {ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} '\n                    f'{ap[4]:.3f} {ap[5]:.3f}')\n        if tmp_dir is not None:\n            tmp_dir.cleanup()\n        return eval_results\n"
  },
  {
    "path": "code/mmdet/datasets/coco_pose.py",
    "content": "import itertools\nimport logging\nimport os.path as osp\nimport tempfile\n\nimport mmcv\nimport numpy as np\nfrom mmcv.utils import print_log\nfrom pycocotools.coco import COCO\nfrom pycocotools.cocoeval import COCOeval\nfrom terminaltables import AsciiTable\n\nfrom mmdet.core import eval_recalls\nfrom .builder import DATASETS\nfrom .custom import CustomDataset\n\n\n@DATASETS.register_module()\nclass CocoPoseDataset(CustomDataset):\n\n    CLASSES = ('person')\n\n    def load_annotations(self, ann_file):\n        \"\"\"Load annotation from COCO style annotation file.\n\n        Args:\n            ann_file (str): Path of annotation file.\n\n        Returns:\n            list[dict]: Annotation info from COCO api.\n        \"\"\"\n\n        self.coco = COCO(ann_file)\n        self.cat_ids = self.coco.get_cat_ids(cat_names=self.CLASSES)\n        self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)}\n        self.img_ids = self.coco.get_img_ids()\n        data_infos = []\n        for i in self.img_ids:\n            info = self.coco.load_imgs([i])[0]\n            info['filename'] = info['file_name']\n            data_infos.append(info)\n        return data_infos\n\n    def get_ann_info(self, idx):\n        \"\"\"Get COCO annotation by index.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Annotation info of specified index.\n        \"\"\"\n\n        img_id = self.data_infos[idx]['id']\n        ann_ids = self.coco.get_ann_ids(img_ids=[img_id])\n        ann_info = self.coco.load_anns(ann_ids)\n        return self._parse_ann_info(self.data_infos[idx], ann_info)\n\n    def get_cat_ids(self, idx):\n        \"\"\"Get COCO category ids by index.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            list[int]: All categories in the image of specified index.\n        \"\"\"\n\n        img_id = self.data_infos[idx]['id']\n        ann_ids = self.coco.get_ann_ids(img_ids=[img_id])\n        ann_info = self.coco.load_anns(ann_ids)\n        return [ann['category_id'] for ann in ann_info]\n\n    def _filter_imgs(self, min_size=32):\n        \"\"\"Filter images too small or without ground truths.\"\"\"\n        valid_inds = []\n        ids_with_ann = set(_['image_id'] for _ in self.coco.anns.values())\n        for i, img_info in enumerate(self.data_infos):\n            if self.filter_empty_gt and self.img_ids[i] not in ids_with_ann:\n                continue\n            if min(img_info['width'], img_info['height']) >= min_size:\n                valid_inds.append(i)\n        return valid_inds\n\n    def get_subset_by_classes(self):\n        \"\"\"Get img ids that contain any category in class_ids.\n\n        Different from the coco.getImgIds(), this function returns the id if\n        the img contains one of the categories rather than all.\n\n        Args:\n            class_ids (list[int]): list of category ids\n\n        Return:\n            ids (list[int]): integer list of img ids\n        \"\"\"\n\n        ids = set()\n        for i, class_id in enumerate(self.cat_ids):\n            ids |= set(self.coco.cat_img_map[class_id])\n        self.img_ids = list(ids)\n\n        data_infos = []\n        for i in self.img_ids:\n            info = self.coco.load_imgs([i])[0]\n            info['filename'] = info['file_name']\n            data_infos.append(info)\n        return data_infos\n\n    def _parse_ann_info(self, img_info, ann_info):\n        \"\"\"Parse bbox and mask annotation.\n\n        Args:\n            ann_info (list[dict]): Annotation info of an image.\n            with_mask (bool): Whether to parse mask annotations.\n\n        Returns:\n            dict: A dict containing the following keys: bboxes, bboxes_ignore,\n                labels, masks, seg_map. \"masks\" are raw annotations and not\n                decoded into binary masks.\n        \"\"\"\n        gt_bboxes = []\n        gt_labels = []\n        gt_bboxes_ignore = []\n        gt_masks_ann     = []\n        gt_keypoints     = []\n        for i, ann in enumerate(ann_info):\n            if ann.get('ignore', False):\n                continue\n            x1, y1, w, h = ann['bbox']\n            inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0))\n            inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0))\n            if inter_w * inter_h == 0:\n                continue\n            if ann['area'] <= 0 or w < 1 or h < 1:\n                continue\n            if ann['category_id'] not in self.cat_ids:\n                continue\n            bbox = [x1, y1, x1 + w, y1 + h]\n            if ann.get('iscrowd', False):\n                gt_bboxes_ignore.append(bbox)\n            else:\n                gt_bboxes.append(bbox)\n                gt_labels.append(self.cat2label[ann['category_id']])\n                gt_masks_ann.append(ann['segmentation'])\n                gt_keypoints.append(ann['keypoints'])\n\n        if gt_bboxes:\n            gt_bboxes = np.array(gt_bboxes, dtype=np.float32)\n            gt_labels = np.array(gt_labels, dtype=np.int64)\n            gt_keypoints = np.array(gt_keypoints, dtype=np.float32)\n        else:\n            gt_bboxes = np.zeros((0, 4), dtype=np.float32)\n            gt_labels = np.array([], dtype=np.int64)\n            gt_keypoints = np.zeros((0, 51), dtype=np.float32)\n\n        if gt_bboxes_ignore:\n            gt_bboxes_ignore = np.array(gt_bboxes_ignore, dtype=np.float32)\n        else:\n            gt_bboxes_ignore = np.zeros((0, 4), dtype=np.float32)\n\n        seg_map = img_info['filename'].replace('jpg', 'png')\n\n        ann = dict(\n            bboxes=gt_bboxes,\n            labels=gt_labels,\n            bboxes_ignore=gt_bboxes_ignore,\n            masks=gt_masks_ann,\n            keypoints = gt_keypoints,\n            seg_map=seg_map)\n\n        return ann\n\n    def xyxy2xywh(self, bbox):\n        \"\"\"Convert ``xyxy`` style bounding boxes to ``xywh`` style for COCO\n        evaluation.\n\n        Args:\n            bbox (numpy.ndarray): The bounding boxes, shape (4, ), in\n                ``xyxy`` order.\n\n        Returns:\n            list[float]: The converted bounding boxes, in ``xywh`` order.\n        \"\"\"\n\n        _bbox = bbox.tolist()\n        return [\n            _bbox[0],\n            _bbox[1],\n            _bbox[2] - _bbox[0],\n            _bbox[3] - _bbox[1],\n        ]\n\n    def _proposal2json(self, results):\n        \"\"\"Convert proposal results to COCO json style\"\"\"\n        json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            bboxes = results[idx]\n            for i in range(bboxes.shape[0]):\n                data = dict()\n                data['image_id'] = img_id\n                data['bbox'] = self.xyxy2xywh(bboxes[i])\n                data['score'] = float(bboxes[i][4])\n                data['category_id'] = 1\n                json_results.append(data)\n        return json_results\n\n    def _det2json(self, results):\n        \"\"\"Convert detection results to COCO json style\"\"\"\n        json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            result = results[idx][0]\n            for label in range(len(result)):\n                bboxes = result[label]\n                for i in range(bboxes.shape[0]):\n                    data = dict()\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['score'] = float(bboxes[i][4])\n                    data['category_id'] = self.cat_ids[label]\n                    json_results.append(data)\n        return json_results\n\n    def _kps2json(self, results):\n        json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            bbox_result = results[idx][0]\n            kps_result  = results[idx][1]\n            for label in range(len(kps_result)):\n                kps = kps_result[label]\n                bboxes = bbox_result[label]\n                for i in range(kps.shape[0]):\n                    data = dict()\n                    keypoints = np.concatenate([\n                                   kps[i].reshape(-1, 2),\n                                   np.ones((17, 1), dtype=np.float32)], axis=1).reshape(51).tolist()\n\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['keypoints'] = keypoints\n                    data['score'] = float(bboxes[i][4])\n                    data['category_id'] = self.cat_ids[label]\n                    json_results.append(data)\n        return json_results\n\n    def _segm2json(self, results):\n        \"\"\"Convert instance segmentation results to COCO json style\"\"\"\n        bbox_json_results = []\n        segm_json_results = []\n        for idx in range(len(self)):\n            img_id = self.img_ids[idx]\n            det, seg = results[idx]\n            for label in range(len(det)):\n                # bbox results\n                bboxes = det[label]\n                for i in range(bboxes.shape[0]):\n                    data = dict()\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['score'] = float(bboxes[i][4])\n                    data['category_id'] = self.cat_ids[label]\n                    bbox_json_results.append(data)\n\n                # segm results\n                # some detectors use different scores for bbox and mask\n                if isinstance(seg, tuple):\n                    segms = seg[0][label]\n                    mask_score = seg[1][label]\n                else:\n                    segms = seg[label]\n                    mask_score = [bbox[4] for bbox in bboxes]\n                for i in range(bboxes.shape[0]):\n                    data = dict()\n                    data['image_id'] = img_id\n                    data['bbox'] = self.xyxy2xywh(bboxes[i])\n                    data['score'] = float(mask_score[i])\n                    data['category_id'] = self.cat_ids[label]\n                    if isinstance(segms[i]['counts'], bytes):\n                        segms[i]['counts'] = segms[i]['counts'].decode()\n                    data['segmentation'] = segms[i]\n                    segm_json_results.append(data)\n        return bbox_json_results, segm_json_results\n\n    def results2json(self, results, outfile_prefix):\n        \"\"\"Dump the detection results to a COCO style json file.\n\n        There are 3 types of results: proposals, bbox predictions, mask\n        predictions, and they have different data types. This method will\n        automatically recognize the type, and dump them to json files.\n\n        Args:\n            results (list[list | tuple | ndarray]): Testing results of the\n                dataset.\n            outfile_prefix (str): The filename prefix of the json files. If the\n                prefix is \"somepath/xxx\", the json files will be named\n                \"somepath/xxx.bbox.json\", \"somepath/xxx.segm.json\",\n                \"somepath/xxx.proposal.json\".\n\n        Returns:\n            dict[str: str]: Possible keys are \"bbox\", \"segm\", \"proposal\", and\n                values are corresponding filenames.\n        \"\"\"\n        result_files = dict()\n        if isinstance(results[0], list):\n            json_bbox_results = self._det2json(results)\n            json_kps_results  = self._kps2json(results)\n\n            result_files['bbox'] = f'{outfile_prefix}.bbox.json'\n            result_files['proposal'] = f'{outfile_prefix}.bbox.json'\n            result_files['keypoints'] = f'{outfile_prefix}.kps.json'\n            mmcv.dump(json_bbox_results, result_files['bbox'])\n            mmcv.dump(json_kps_results, result_files['keypoints'])\n        elif isinstance(results[0], tuple):\n            json_results = self._segm2json(results)\n            result_files['bbox'] = f'{outfile_prefix}.bbox.json'\n            result_files['proposal'] = f'{outfile_prefix}.bbox.json'\n            result_files['segm'] = f'{outfile_prefix}.segm.json'\n            mmcv.dump(json_results[0], result_files['bbox'])\n            mmcv.dump(json_results[1], result_files['segm'])\n        elif isinstance(results[0], np.ndarray):\n            json_results = self._proposal2json(results)\n            result_files['proposal'] = f'{outfile_prefix}.proposal.json'\n            mmcv.dump(json_results, result_files['proposal'])\n        else:\n            raise TypeError('invalid type of results')\n        return result_files\n\n    def fast_eval_recall(self, results, proposal_nums, iou_thrs, logger=None):\n        gt_bboxes = []\n        for i in range(len(self.img_ids)):\n            ann_ids = self.coco.get_ann_ids(img_ids=self.img_ids[i])\n            ann_info = self.coco.load_anns(ann_ids)\n            if len(ann_info) == 0:\n                gt_bboxes.append(np.zeros((0, 4)))\n                continue\n            bboxes = []\n            for ann in ann_info:\n                if ann.get('ignore', False) or ann['iscrowd']:\n                    continue\n                x1, y1, w, h = ann['bbox']\n                bboxes.append([x1, y1, x1 + w, y1 + h])\n            bboxes = np.array(bboxes, dtype=np.float32)\n            if bboxes.shape[0] == 0:\n                bboxes = np.zeros((0, 4))\n            gt_bboxes.append(bboxes)\n\n        recalls = eval_recalls(\n            gt_bboxes, results, proposal_nums, iou_thrs, logger=logger)\n        ar = recalls.mean(axis=1)\n        return ar\n\n    def format_results(self, results, jsonfile_prefix=None, **kwargs):\n        \"\"\"Format the results to json (standard format for COCO evaluation).\n\n        Args:\n            results (list[tuple | numpy.ndarray]): Testing results of the\n                dataset.\n            jsonfile_prefix (str | None): The prefix of json files. It includes\n                the file path and the prefix of filename, e.g., \"a/b/prefix\".\n                If not specified, a temp file will be created. Default: None.\n\n        Returns:\n            tuple: (result_files, tmp_dir), result_files is a dict containing\n                the json filepaths, tmp_dir is the temporal directory created\n                for saving json files when jsonfile_prefix is not specified.\n        \"\"\"\n        assert isinstance(results, list), 'results must be a list'\n        assert len(results) == len(self), (\n            'The length of results is not equal to the dataset len: {} != {}'.\n            format(len(results), len(self)))\n\n        if jsonfile_prefix is None:\n            tmp_dir = tempfile.TemporaryDirectory()\n            jsonfile_prefix = osp.join(tmp_dir.name, 'results')\n        else:\n            tmp_dir = None\n        result_files = self.results2json(results, jsonfile_prefix)\n        return result_files, tmp_dir\n\n    def evaluate(self,\n                 results,\n                 metric='bbox',\n                 logger=None,\n                 jsonfile_prefix=None,\n                 classwise=False,\n                 proposal_nums=(100, 300, 1000),\n                 iou_thrs=np.arange(0.5, 0.96, 0.05)):\n        \"\"\"Evaluation in COCO protocol.\n\n        Args:\n            results (list[list | tuple]): Testing results of the dataset.\n            metric (str | list[str]): Metrics to be evaluated. Options are\n                'bbox', 'segm', 'proposal', 'proposal_fast'.\n            logger (logging.Logger | str | None): Logger used for printing\n                related information during evaluation. Default: None.\n            jsonfile_prefix (str | None): The prefix of json files. It includes\n                the file path and the prefix of filename, e.g., \"a/b/prefix\".\n                If not specified, a temp file will be created. Default: None.\n            classwise (bool): Whether to evaluating the AP for each class.\n            proposal_nums (Sequence[int]): Proposal number used for evaluating\n                recalls, such as recall@100, recall@1000.\n                Default: (100, 300, 1000).\n            iou_thrs (Sequence[float]): IoU threshold used for evaluating\n                recalls. If set to a list, the average recall of all IoUs will\n                also be computed. Default: 0.5.\n\n        Returns:\n            dict[str, float]: COCO style evaluation metric.\n        \"\"\"\n\n        metrics = metric if isinstance(metric, list) else [metric]\n        allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast', 'keypoints']\n        for metric in metrics:\n            if metric not in allowed_metrics:\n                raise KeyError(f'metric {metric} is not supported')\n\n        result_files, tmp_dir = self.format_results(results, jsonfile_prefix)\n\n        eval_results = {}\n        cocoGt = self.coco\n        for metric in metrics:\n            msg = f'Evaluating {metric}...'\n            if logger is None:\n                msg = '\\n' + msg\n            print_log(msg, logger=logger)\n\n            if metric == 'proposal_fast':\n                ar = self.fast_eval_recall(\n                    results, proposal_nums, iou_thrs, logger='silent')\n                log_msg = []\n                for i, num in enumerate(proposal_nums):\n                    eval_results[f'AR@{num}'] = ar[i]\n                    log_msg.append(f'\\nAR@{num}\\t{ar[i]:.4f}')\n                log_msg = ''.join(log_msg)\n                print_log(log_msg, logger=logger)\n                continue\n\n            if metric not in result_files:\n                raise KeyError(f'{metric} is not in results')\n            try:\n                cocoDt = cocoGt.loadRes(result_files[metric])\n            except IndexError:\n                print_log(\n                    'The testing results of the whole dataset is empty.',\n                    logger=logger,\n                    level=logging.ERROR)\n                break\n\n            iou_type = 'bbox' if metric == 'proposal' else metric\n            cocoEval = COCOeval(cocoGt, cocoDt, iou_type)\n            cocoEval.params.catIds = self.cat_ids\n            cocoEval.params.imgIds = self.img_ids\n            if metric == 'proposal':\n                cocoEval.params.useCats = 0\n                cocoEval.params.maxDets = list(proposal_nums)\n                cocoEval.evaluate()\n                cocoEval.accumulate()\n                cocoEval.summarize()\n                metric_items = [\n                    'AR@100', 'AR@300', 'AR@1000', 'AR_s@1000', 'AR_m@1000',\n                    'AR_l@1000'\n                ]\n                for i, item in enumerate(metric_items):\n                    val = float(f'{cocoEval.stats[i + 6]:.3f}')\n                    eval_results[item] = val\n            else:\n                cocoEval.evaluate()\n                cocoEval.accumulate()\n                cocoEval.summarize()\n                if classwise:  # Compute per-category AP\n                    # Compute per-category AP\n                    # from https://github.com/facebookresearch/detectron2/\n                    precisions = cocoEval.eval['precision']\n                    # precision: (iou, recall, cls, area range, max dets)\n                    assert len(self.cat_ids) == precisions.shape[2]\n\n                    results_per_category = []\n                    for idx, catId in enumerate(self.cat_ids):\n                        # area range index 0: all area ranges\n                        # max dets index -1: typically 100 per image\n                        nm = self.coco.loadCats(catId)[0]\n                        precision = precisions[:, :, idx, 0, -1]\n                        precision = precision[precision > -1]\n                        if precision.size:\n                            ap = np.mean(precision)\n                        else:\n                            ap = float('nan')\n                        results_per_category.append(\n                            (f'{nm[\"name\"]}', f'{float(ap):0.3f}'))\n\n                    num_columns = min(6, len(results_per_category) * 2)\n                    results_flatten = list(\n                        itertools.chain(*results_per_category))\n                    headers = ['category', 'AP'] * (num_columns // 2)\n                    results_2d = itertools.zip_longest(*[\n                        results_flatten[i::num_columns]\n                        for i in range(num_columns)\n                    ])\n                    table_data = [headers]\n                    table_data += [result for result in results_2d]\n                    table = AsciiTable(table_data)\n                    print_log('\\n' + table.table, logger=logger)\n\n                metric_items = [\n                    'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l'\n                ]\n                for i in range(len(metric_items)):\n                    key = f'{metric}_{metric_items[i]}'\n                    val = float(f'{cocoEval.stats[i]:.3f}')\n                    eval_results[key] = val\n                ap = cocoEval.stats[:6]\n                eval_results[f'{metric}_mAP_copypaste'] = (\n                    f'{ap[0]:.3f} {ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} '\n                    f'{ap[4]:.3f} {ap[5]:.3f}')\n        if tmp_dir is not None:\n            tmp_dir.cleanup()\n        return eval_results\n"
  },
  {
    "path": "code/mmdet/datasets/custom.py",
    "content": "import os.path as osp\n\nimport mmcv\nimport numpy as np\nfrom torch.utils.data import Dataset\n\nfrom mmdet.core import eval_map, eval_recalls\nfrom .builder import DATASETS\nfrom .pipelines import Compose\n\n\n@DATASETS.register_module()\nclass CustomDataset(Dataset):\n    \"\"\"Custom dataset for detection.\n\n    The annotation format is shown as follows. The `ann` field is optional for\n    testing.\n\n    .. code-block:: none\n\n        [\n            {\n                'filename': 'a.jpg',\n                'width': 1280,\n                'height': 720,\n                'ann': {\n                    'bboxes': <np.ndarray> (n, 4),\n                    'labels': <np.ndarray> (n, ),\n                    'bboxes_ignore': <np.ndarray> (k, 4), (optional field)\n                    'labels_ignore': <np.ndarray> (k, 4) (optional field)\n                }\n            },\n            ...\n        ]\n\n    Args:\n        ann_file (str): Annotation file path.\n        pipeline (list[dict]): Processing pipeline.\n        classes (str | Sequence[str], optional): Specify classes to load.\n            If is None, ``cls.CLASSES`` will be used. Default: None.\n        data_root (str, optional): Data root for ``ann_file``,\n            ``img_prefix``, ``seg_prefix``, ``proposal_file`` if specified.\n        test_mode (bool, optional): If set True, annotation will not be loaded.\n        filter_empty_gt (bool, optional): If set true, images without bounding\n            boxes will be filtered out.\n    \"\"\"\n\n    CLASSES = None\n\n    def __init__(self,\n                 ann_file,\n                 pipeline,\n                 classes=None,\n                 data_root=None,\n                 img_prefix='',\n                 seg_prefix=None,\n                 proposal_file=None,\n                 test_mode=False,\n                 filter_empty_gt=True):\n        self.ann_file = ann_file\n        self.data_root = data_root\n        self.img_prefix = img_prefix\n        self.seg_prefix = seg_prefix\n        self.proposal_file = proposal_file\n        self.test_mode = test_mode\n        self.filter_empty_gt = filter_empty_gt\n        self.CLASSES = self.get_classes(classes)\n\n        # join paths if data_root is specified\n        if self.data_root is not None:\n            if not osp.isabs(self.ann_file):\n                self.ann_file = osp.join(self.data_root, self.ann_file)\n            if not (self.img_prefix is None or osp.isabs(self.img_prefix)):\n                self.img_prefix = osp.join(self.data_root, self.img_prefix)\n            if not (self.seg_prefix is None or osp.isabs(self.seg_prefix)):\n                self.seg_prefix = osp.join(self.data_root, self.seg_prefix)\n            if not (self.proposal_file is None\n                    or osp.isabs(self.proposal_file)):\n                self.proposal_file = osp.join(self.data_root,\n                                              self.proposal_file)\n        # load annotations (and proposals)\n        self.data_infos = self.load_annotations(self.ann_file)\n        # filter data infos if classes are customized\n        if self.custom_classes:\n            self.data_infos = self.get_subset_by_classes()\n\n        if self.proposal_file is not None:\n            self.proposals = self.load_proposals(self.proposal_file)\n        else:\n            self.proposals = None\n        # filter images too small\n        if not test_mode:\n            valid_inds = self._filter_imgs()\n            self.data_infos = [self.data_infos[i] for i in valid_inds]\n            if self.proposals is not None:\n                self.proposals = [self.proposals[i] for i in valid_inds]\n        # set group flag for the sampler\n        if not self.test_mode:\n            self._set_group_flag()\n        # processing pipeline\n        self.pipeline = Compose(pipeline)\n\n    def __len__(self):\n        \"\"\"Total number of samples of data\"\"\"\n        return len(self.data_infos)\n\n    def load_annotations(self, ann_file):\n        \"\"\"Load annotation from annotation file\"\"\"\n        return mmcv.load(ann_file)\n\n    def load_proposals(self, proposal_file):\n        \"\"\"Load proposal from proposal file\"\"\"\n        return mmcv.load(proposal_file)\n\n    def get_ann_info(self, idx):\n        \"\"\"Get annotation by index\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Annotation info of specified index.\n        \"\"\"\n\n        return self.data_infos[idx]['ann']\n\n    def get_cat_ids(self, idx):\n        \"\"\"Get category ids by index\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            list[int]: All categories in the image of specified index.\n        \"\"\"\n\n        return self.data_infos[idx]['ann']['labels'].astype(np.int).tolist()\n\n    def pre_pipeline(self, results):\n        \"\"\"Prepare results dict for pipeline\"\"\"\n        results['img_prefix'] = self.img_prefix\n        results['seg_prefix'] = self.seg_prefix\n        results['proposal_file'] = self.proposal_file\n        results['bbox_fields'] = []\n        results['extreme_fields'] = []\n        results['mask_fields'] = []\n        results['seg_fields'] = []\n        results['keypoint_fields'] = []\n\n    def _filter_imgs(self, min_size=32):\n        \"\"\"Filter images too small.\"\"\"\n        valid_inds = []\n        for i, img_info in enumerate(self.data_infos):\n            if min(img_info['width'], img_info['height']) >= min_size:\n                valid_inds.append(i)\n        return valid_inds\n\n    def _set_group_flag(self):\n        \"\"\"Set flag according to image aspect ratio.\n\n        Images with aspect ratio greater than 1 will be set as group 1,\n        otherwise group 0.\n        \"\"\"\n        self.flag = np.zeros(len(self), dtype=np.uint8)\n        for i in range(len(self)):\n            img_info = self.data_infos[i]\n            if img_info['width'] / img_info['height'] > 1:\n                self.flag[i] = 1\n\n    def _rand_another(self, idx):\n        \"\"\"Get another random index from the same group as the given index\"\"\"\n        pool = np.where(self.flag == self.flag[idx])[0]\n        return np.random.choice(pool)\n\n    def __getitem__(self, idx):\n        \"\"\"Get training/test data after pipeline\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Training/test data (with annotation if `test_mode` is set\n                True).\n        \"\"\"\n\n        if self.test_mode:\n            return self.prepare_test_img(idx)\n        while True:\n            data = self.prepare_train_img(idx)\n            if data is None:\n                idx = self._rand_another(idx)\n                continue\n            return data\n\n    def prepare_train_img(self, idx):\n        \"\"\"Get training data and annotations after pipeline.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Training data and annotation after pipeline with new keys\n                introduced by pipeline.\n        \"\"\"\n\n        img_info = self.data_infos[idx]\n        ann_info = self.get_ann_info(idx)\n        results = dict(img_info=img_info, ann_info=ann_info)\n        if self.proposals is not None:\n            results['proposals'] = self.proposals[idx]\n        self.pre_pipeline(results)\n        return self.pipeline(results)\n\n    def prepare_test_img(self, idx):\n        \"\"\"Get testing data  after pipeline.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Testing data after pipeline with new keys intorduced by\n                piepline.\n        \"\"\"\n\n        img_info = self.data_infos[idx]\n        results = dict(img_info=img_info)\n        if self.proposals is not None:\n            results['proposals'] = self.proposals[idx]\n        self.pre_pipeline(results)\n        return self.pipeline(results)\n\n    @classmethod\n    def get_classes(cls, classes=None):\n        \"\"\"Get class names of current dataset\n\n        Args:\n            classes (Sequence[str] | str | None): If classes is None, use\n                default CLASSES defined by builtin dataset. If classes is a\n                string, take it as a file name. The file contains the name of\n                classes where each line contains one class name. If classes is\n                a tuple or list, override the CLASSES defined by the dataset.\n\n        \"\"\"\n        if classes is None:\n            cls.custom_classes = False\n            return cls.CLASSES\n\n        cls.custom_classes = True\n        if isinstance(classes, str):\n            # take it as a file path\n            class_names = mmcv.list_from_file(classes)\n        elif isinstance(classes, (tuple, list)):\n            class_names = classes\n        else:\n            raise ValueError(f'Unsupported type {type(classes)} of classes.')\n\n        return class_names\n\n    def get_subset_by_classes(self):\n        return self.data_infos\n\n    def format_results(self, results, **kwargs):\n        \"\"\"Place holder to format result to dataset specific output\"\"\"\n        pass\n\n    def evaluate(self,\n                 results,\n                 metric='mAP',\n                 logger=None,\n                 proposal_nums=(100, 300, 1000),\n                 iou_thr=0.5,\n                 scale_ranges=None):\n        \"\"\"Evaluate the dataset.\n\n        Args:\n            results (list): Testing results of the dataset.\n            metric (str | list[str]): Metrics to be evaluated.\n            logger (logging.Logger | None | str): Logger used for printing\n                related information during evaluation. Default: None.\n            proposal_nums (Sequence[int]): Proposal number used for evaluating\n                recalls, such as recall@100, recall@1000.\n                Default: (100, 300, 1000).\n            iou_thr (float | list[float]): IoU threshold. It must be a float\n                when evaluating mAP, and can be a list when evaluating recall.\n                Default: 0.5.\n            scale_ranges (list[tuple] | None): Scale ranges for evaluating mAP.\n                Default: None.\n        \"\"\"\n\n        if not isinstance(metric, str):\n            assert len(metric) == 1\n            metric = metric[0]\n        allowed_metrics = ['mAP', 'recall']\n        if metric not in allowed_metrics:\n            raise KeyError(f'metric {metric} is not supported')\n        annotations = [self.get_ann_info(i) for i in range(len(self))]\n        eval_results = {}\n        if metric == 'mAP':\n            assert isinstance(iou_thr, float)\n            mean_ap, _ = eval_map(\n                results,\n                annotations,\n                scale_ranges=scale_ranges,\n                iou_thr=iou_thr,\n                dataset=self.CLASSES,\n                logger=logger)\n            eval_results['mAP'] = mean_ap\n        elif metric == 'recall':\n            gt_bboxes = [ann['bboxes'] for ann in annotations]\n            if isinstance(iou_thr, float):\n                iou_thr = [iou_thr]\n            recalls = eval_recalls(\n                gt_bboxes, results, proposal_nums, iou_thr, logger=logger)\n            for i, num in enumerate(proposal_nums):\n                for j, iou in enumerate(iou_thr):\n                    eval_results[f'recall@{num}@{iou}'] = recalls[i, j]\n            if recalls.shape[1] > 1:\n                ar = recalls.mean(axis=1)\n                for i, num in enumerate(proposal_nums):\n                    eval_results[f'AR@{num}'] = ar[i]\n        return eval_results\n"
  },
  {
    "path": "code/mmdet/datasets/dataset_wrappers.py",
    "content": "import bisect\nimport math\nfrom collections import defaultdict\n\nimport numpy as np\nfrom torch.utils.data.dataset import ConcatDataset as _ConcatDataset\n\nfrom .builder import DATASETS\n\n\n@DATASETS.register_module()\nclass ConcatDataset(_ConcatDataset):\n    \"\"\"A wrapper of concatenated dataset.\n\n    Same as :obj:`torch.utils.data.dataset.ConcatDataset`, but\n    concat the group flag for image aspect ratio.\n\n    Args:\n        datasets (list[:obj:`Dataset`]): A list of datasets.\n    \"\"\"\n\n    def __init__(self, datasets):\n        super(ConcatDataset, self).__init__(datasets)\n        self.CLASSES = datasets[0].CLASSES\n        if hasattr(datasets[0], 'flag'):\n            flags = []\n            for i in range(0, len(datasets)):\n                flags.append(datasets[i].flag)\n            self.flag = np.concatenate(flags)\n\n    def get_cat_ids(self, idx):\n        \"\"\"Get category ids of concatenated dataset by index\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            list[int]: All categories in the image of specified index.\n        \"\"\"\n\n        if idx < 0:\n            if -idx > len(self):\n                raise ValueError(\n                    'absolute value of index should not exceed dataset length')\n            idx = len(self) + idx\n        dataset_idx = bisect.bisect_right(self.cumulative_sizes, idx)\n        if dataset_idx == 0:\n            sample_idx = idx\n        else:\n            sample_idx = idx - self.cumulative_sizes[dataset_idx - 1]\n        return self.datasets[dataset_idx].get_cat_ids(sample_idx)\n\n\n@DATASETS.register_module()\nclass RepeatDataset(object):\n    \"\"\"A wrapper of repeated dataset.\n\n    The length of repeated dataset will be `times` larger than the original\n    dataset. This is useful when the data loading time is long but the dataset\n    is small. Using RepeatDataset can reduce the data loading time between\n    epochs.\n\n    Args:\n        dataset (:obj:`Dataset`): The dataset to be repeated.\n        times (int): Repeat times.\n    \"\"\"\n\n    def __init__(self, dataset, times):\n        self.dataset = dataset\n        self.times = times\n        self.CLASSES = dataset.CLASSES\n        if hasattr(self.dataset, 'flag'):\n            self.flag = np.tile(self.dataset.flag, times)\n\n        self._ori_len = len(self.dataset)\n\n    def __getitem__(self, idx):\n        return self.dataset[idx % self._ori_len]\n\n    def get_cat_ids(self, idx):\n        \"\"\"Get category ids of repeat dataset by index\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            list[int]: All categories in the image of specified index.\n        \"\"\"\n\n        return self.dataset.get_cat_ids(idx % self._ori_len)\n\n    def __len__(self):\n        \"\"\"Length after repetition\"\"\"\n        return self.times * self._ori_len\n\n\n# Modified from https://github.com/facebookresearch/detectron2/blob/41d475b75a230221e21d9cac5d69655e3415e3a4/detectron2/data/samplers/distributed_sampler.py#L57 # noqa\n@DATASETS.register_module()\nclass ClassBalancedDataset(object):\n    \"\"\"A wrapper of repeated dataset with repeat factor.\n\n    Suitable for training on class imbalanced datasets like LVIS. Following\n    the sampling strategy in [1], in each epoch, an image may appear multiple\n    times based on its \"repeat factor\".\n    The repeat factor for an image is a function of the frequency the rarest\n    category labeled in that image. The \"frequency of category c\" in [0, 1]\n    is defined by the fraction of images in the training set (without repeats)\n    in which category c appears.\n    The dataset needs to instantiate :func:`self.get_cat_ids(idx)` to support\n    ClassBalancedDataset.\n    The repeat factor is computed as followed.\n    1. For each category c, compute the fraction # of images\n        that contain it: f(c)\n    2. For each category c, compute the category-level repeat factor:\n        r(c) = max(1, sqrt(t/f(c)))\n    3. For each image I, compute the image-level repeat factor:\n        r(I) = max_{c in I} r(c)\n\n    References:\n        .. [1]  https://arxiv.org/pdf/1903.00621v2.pdf\n\n    Args:\n        dataset (:obj:`CustomDataset`): The dataset to be repeated.\n        oversample_thr (float): frequency threshold below which data is\n            repeated. For categories with `f_c` >= `oversample_thr`, there is\n            no oversampling. For categories with `f_c` < `oversample_thr`, the\n            degree of oversampling following the square-root inverse frequency\n            heuristic above.\n    \"\"\"\n\n    def __init__(self, dataset, oversample_thr):\n        self.dataset = dataset\n        self.oversample_thr = oversample_thr\n        self.CLASSES = dataset.CLASSES\n\n        repeat_factors = self._get_repeat_factors(dataset, oversample_thr)\n        repeat_indices = []\n        for dataset_index, repeat_factor in enumerate(repeat_factors):\n            repeat_indices.extend([dataset_index] * math.ceil(repeat_factor))\n        self.repeat_indices = repeat_indices\n\n        flags = []\n        if hasattr(self.dataset, 'flag'):\n            for flag, repeat_factor in zip(self.dataset.flag, repeat_factors):\n                flags.extend([flag] * int(math.ceil(repeat_factor)))\n            assert len(flags) == len(repeat_indices)\n        self.flag = np.asarray(flags, dtype=np.uint8)\n\n    def _get_repeat_factors(self, dataset, repeat_thr):\n        \"\"\"Get repeat factor for each images in the dataset.\n\n        Args:\n            dataset (:obj:`CustomDataset`): The dataset\n            repeat_thr (float): The threshold of frequency. If an image\n                contains the categories whose frequency below the threshold,\n                it would be repeated.\n\n        Returns:\n            list[float]: The repeat factors for each images in the dataset.\n        \"\"\"\n\n        # 1. For each category c, compute the fraction # of images\n        #   that contain it: f(c)\n        category_freq = defaultdict(int)\n        num_images = len(dataset)\n        for idx in range(num_images):\n            cat_ids = set(self.dataset.get_cat_ids(idx))\n            for cat_id in cat_ids:\n                category_freq[cat_id] += 1\n        for k, v in category_freq.items():\n            category_freq[k] = v / num_images\n\n        # 2. For each category c, compute the category-level repeat factor:\n        #    r(c) = max(1, sqrt(t/f(c)))\n        category_repeat = {\n            cat_id: max(1.0, math.sqrt(repeat_thr / cat_freq))\n            for cat_id, cat_freq in category_freq.items()\n        }\n\n        # 3. For each image I, compute the image-level repeat factor:\n        #    r(I) = max_{c in I} r(c)\n        repeat_factors = []\n        for idx in range(num_images):\n            cat_ids = set(self.dataset.get_cat_ids(idx))\n            repeat_factor = max(\n                {category_repeat[cat_id]\n                 for cat_id in cat_ids})\n            repeat_factors.append(repeat_factor)\n\n        return repeat_factors\n\n    def __getitem__(self, idx):\n        ori_index = self.repeat_indices[idx]\n        return self.dataset[ori_index]\n\n    def __len__(self):\n        \"\"\"Length after repetition\"\"\"\n        return len(self.repeat_indices)\n"
  },
  {
    "path": "code/mmdet/datasets/deepfashion.py",
    "content": "from .builder import DATASETS\nfrom .coco import CocoDataset\n\n\n@DATASETS.register_module()\nclass DeepFashionDataset(CocoDataset):\n\n    CLASSES = ('top', 'skirt', 'leggings', 'dress', 'outer', 'pants', 'bag',\n               'neckwear', 'headwear', 'eyeglass', 'belt', 'footwear', 'hair',\n               'skin', 'face')\n"
  },
  {
    "path": "code/mmdet/datasets/lvis.py",
    "content": "import itertools\nimport logging\nimport os.path as osp\nimport tempfile\n\nimport numpy as np\nfrom mmcv.utils import print_log\nfrom terminaltables import AsciiTable\n\nfrom .builder import DATASETS\nfrom .coco import CocoDataset\n\n\n@DATASETS.register_module()\nclass LVISDataset(CocoDataset):\n\n    CLASSES = (\n        'acorn', 'aerosol_can', 'air_conditioner', 'airplane', 'alarm_clock',\n        'alcohol', 'alligator', 'almond', 'ambulance', 'amplifier', 'anklet',\n        'antenna', 'apple', 'apple_juice', 'applesauce', 'apricot', 'apron',\n        'aquarium', 'armband', 'armchair', 'armoire', 'armor', 'artichoke',\n        'trash_can', 'ashtray', 'asparagus', 'atomizer', 'avocado', 'award',\n        'awning', 'ax', 'baby_buggy', 'basketball_backboard', 'backpack',\n        'handbag', 'suitcase', 'bagel', 'bagpipe', 'baguet', 'bait', 'ball',\n        'ballet_skirt', 'balloon', 'bamboo', 'banana', 'Band_Aid', 'bandage',\n        'bandanna', 'banjo', 'banner', 'barbell', 'barge', 'barrel',\n        'barrette', 'barrow', 'baseball_base', 'baseball', 'baseball_bat',\n        'baseball_cap', 'baseball_glove', 'basket', 'basketball_hoop',\n        'basketball', 'bass_horn', 'bat_(animal)', 'bath_mat', 'bath_towel',\n        'bathrobe', 'bathtub', 'batter_(food)', 'battery', 'beachball', 'bead',\n        'beaker', 'bean_curd', 'beanbag', 'beanie', 'bear', 'bed',\n        'bedspread', 'cow', 'beef_(food)', 'beeper', 'beer_bottle', 'beer_can',\n        'beetle', 'bell', 'bell_pepper', 'belt', 'belt_buckle', 'bench',\n        'beret', 'bib', 'Bible', 'bicycle', 'visor', 'binder', 'binoculars',\n        'bird', 'birdfeeder', 'birdbath', 'birdcage', 'birdhouse',\n        'birthday_cake', 'birthday_card', 'biscuit_(bread)', 'pirate_flag',\n        'black_sheep', 'blackboard', 'blanket', 'blazer', 'blender', 'blimp',\n        'blinker', 'blueberry', 'boar', 'gameboard', 'boat', 'bobbin',\n        'bobby_pin', 'boiled_egg', 'bolo_tie', 'deadbolt', 'bolt', 'bonnet',\n        'book', 'book_bag', 'bookcase', 'booklet', 'bookmark',\n        'boom_microphone', 'boot', 'bottle', 'bottle_opener', 'bouquet',\n        'bow_(weapon)', 'bow_(decorative_ribbons)', 'bow-tie', 'bowl',\n        'pipe_bowl', 'bowler_hat', 'bowling_ball', 'bowling_pin',\n        'boxing_glove', 'suspenders', 'bracelet', 'brass_plaque', 'brassiere',\n        'bread-bin', 'breechcloth', 'bridal_gown', 'briefcase',\n        'bristle_brush', 'broccoli', 'broach', 'broom', 'brownie',\n        'brussels_sprouts', 'bubble_gum', 'bucket', 'horse_buggy', 'bull',\n        'bulldog', 'bulldozer', 'bullet_train', 'bulletin_board',\n        'bulletproof_vest', 'bullhorn', 'corned_beef', 'bun', 'bunk_bed',\n        'buoy', 'burrito', 'bus_(vehicle)', 'business_card', 'butcher_knife',\n        'butter', 'butterfly', 'button', 'cab_(taxi)', 'cabana', 'cabin_car',\n        'cabinet', 'locker', 'cake', 'calculator', 'calendar', 'calf',\n        'camcorder', 'camel', 'camera', 'camera_lens', 'camper_(vehicle)',\n        'can', 'can_opener', 'candelabrum', 'candle', 'candle_holder',\n        'candy_bar', 'candy_cane', 'walking_cane', 'canister', 'cannon',\n        'canoe', 'cantaloup', 'canteen', 'cap_(headwear)', 'bottle_cap',\n        'cape', 'cappuccino', 'car_(automobile)', 'railcar_(part_of_a_train)',\n        'elevator_car', 'car_battery', 'identity_card', 'card', 'cardigan',\n        'cargo_ship', 'carnation', 'horse_carriage', 'carrot', 'tote_bag',\n        'cart', 'carton', 'cash_register', 'casserole', 'cassette', 'cast',\n        'cat', 'cauliflower', 'caviar', 'cayenne_(spice)', 'CD_player',\n        'celery', 'cellular_telephone', 'chain_mail', 'chair', 'chaise_longue',\n        'champagne', 'chandelier', 'chap', 'checkbook', 'checkerboard',\n        'cherry', 'chessboard', 'chest_of_drawers_(furniture)',\n        'chicken_(animal)', 'chicken_wire', 'chickpea', 'Chihuahua',\n        'chili_(vegetable)', 'chime', 'chinaware', 'crisp_(potato_chip)',\n        'poker_chip', 'chocolate_bar', 'chocolate_cake', 'chocolate_milk',\n        'chocolate_mousse', 'choker', 'chopping_board', 'chopstick',\n        'Christmas_tree', 'slide', 'cider', 'cigar_box', 'cigarette',\n        'cigarette_case', 'cistern', 'clarinet', 'clasp', 'cleansing_agent',\n        'clementine', 'clip', 'clipboard', 'clock', 'clock_tower',\n        'clothes_hamper', 'clothespin', 'clutch_bag', 'coaster', 'coat',\n        'coat_hanger', 'coatrack', 'cock', 'coconut', 'coffee_filter',\n        'coffee_maker', 'coffee_table', 'coffeepot', 'coil', 'coin',\n        'colander', 'coleslaw', 'coloring_material', 'combination_lock',\n        'pacifier', 'comic_book', 'computer_keyboard', 'concrete_mixer',\n        'cone', 'control', 'convertible_(automobile)', 'sofa_bed', 'cookie',\n        'cookie_jar', 'cooking_utensil', 'cooler_(for_food)',\n        'cork_(bottle_plug)', 'corkboard', 'corkscrew', 'edible_corn',\n        'cornbread', 'cornet', 'cornice', 'cornmeal', 'corset',\n        'romaine_lettuce', 'costume', 'cougar', 'coverall', 'cowbell',\n        'cowboy_hat', 'crab_(animal)', 'cracker', 'crape', 'crate', 'crayon',\n        'cream_pitcher', 'credit_card', 'crescent_roll', 'crib', 'crock_pot',\n        'crossbar', 'crouton', 'crow', 'crown', 'crucifix', 'cruise_ship',\n        'police_cruiser', 'crumb', 'crutch', 'cub_(animal)', 'cube',\n        'cucumber', 'cufflink', 'cup', 'trophy_cup', 'cupcake', 'hair_curler',\n        'curling_iron', 'curtain', 'cushion', 'custard', 'cutting_tool',\n        'cylinder', 'cymbal', 'dachshund', 'dagger', 'dartboard',\n        'date_(fruit)', 'deck_chair', 'deer', 'dental_floss', 'desk',\n        'detergent', 'diaper', 'diary', 'die', 'dinghy', 'dining_table', 'tux',\n        'dish', 'dish_antenna', 'dishrag', 'dishtowel', 'dishwasher',\n        'dishwasher_detergent', 'diskette', 'dispenser', 'Dixie_cup', 'dog',\n        'dog_collar', 'doll', 'dollar', 'dolphin', 'domestic_ass', 'eye_mask',\n        'doorbell', 'doorknob', 'doormat', 'doughnut', 'dove', 'dragonfly',\n        'drawer', 'underdrawers', 'dress', 'dress_hat', 'dress_suit',\n        'dresser', 'drill', 'drinking_fountain', 'drone', 'dropper',\n        'drum_(musical_instrument)', 'drumstick', 'duck', 'duckling',\n        'duct_tape', 'duffel_bag', 'dumbbell', 'dumpster', 'dustpan',\n        'Dutch_oven', 'eagle', 'earphone', 'earplug', 'earring', 'easel',\n        'eclair', 'eel', 'egg', 'egg_roll', 'egg_yolk', 'eggbeater',\n        'eggplant', 'electric_chair', 'refrigerator', 'elephant', 'elk',\n        'envelope', 'eraser', 'escargot', 'eyepatch', 'falcon', 'fan',\n        'faucet', 'fedora', 'ferret', 'Ferris_wheel', 'ferry', 'fig_(fruit)',\n        'fighter_jet', 'figurine', 'file_cabinet', 'file_(tool)', 'fire_alarm',\n        'fire_engine', 'fire_extinguisher', 'fire_hose', 'fireplace',\n        'fireplug', 'fish', 'fish_(food)', 'fishbowl', 'fishing_boat',\n        'fishing_rod', 'flag', 'flagpole', 'flamingo', 'flannel', 'flash',\n        'flashlight', 'fleece', 'flip-flop_(sandal)', 'flipper_(footwear)',\n        'flower_arrangement', 'flute_glass', 'foal', 'folding_chair',\n        'food_processor', 'football_(American)', 'football_helmet',\n        'footstool', 'fork', 'forklift', 'freight_car', 'French_toast',\n        'freshener', 'frisbee', 'frog', 'fruit_juice', 'fruit_salad',\n        'frying_pan', 'fudge', 'funnel', 'futon', 'gag', 'garbage',\n        'garbage_truck', 'garden_hose', 'gargle', 'gargoyle', 'garlic',\n        'gasmask', 'gazelle', 'gelatin', 'gemstone', 'giant_panda',\n        'gift_wrap', 'ginger', 'giraffe', 'cincture',\n        'glass_(drink_container)', 'globe', 'glove', 'goat', 'goggles',\n        'goldfish', 'golf_club', 'golfcart', 'gondola_(boat)', 'goose',\n        'gorilla', 'gourd', 'surgical_gown', 'grape', 'grasshopper', 'grater',\n        'gravestone', 'gravy_boat', 'green_bean', 'green_onion', 'griddle',\n        'grillroom', 'grinder_(tool)', 'grits', 'grizzly', 'grocery_bag',\n        'guacamole', 'guitar', 'gull', 'gun', 'hair_spray', 'hairbrush',\n        'hairnet', 'hairpin', 'ham', 'hamburger', 'hammer', 'hammock',\n        'hamper', 'hamster', 'hair_dryer', 'hand_glass', 'hand_towel',\n        'handcart', 'handcuff', 'handkerchief', 'handle', 'handsaw',\n        'hardback_book', 'harmonium', 'hat', 'hatbox', 'hatch', 'veil',\n        'headband', 'headboard', 'headlight', 'headscarf', 'headset',\n        'headstall_(for_horses)', 'hearing_aid', 'heart', 'heater',\n        'helicopter', 'helmet', 'heron', 'highchair', 'hinge', 'hippopotamus',\n        'hockey_stick', 'hog', 'home_plate_(baseball)', 'honey', 'fume_hood',\n        'hook', 'horse', 'hose', 'hot-air_balloon', 'hotplate', 'hot_sauce',\n        'hourglass', 'houseboat', 'hummingbird', 'hummus', 'polar_bear',\n        'icecream', 'popsicle', 'ice_maker', 'ice_pack', 'ice_skate',\n        'ice_tea', 'igniter', 'incense', 'inhaler', 'iPod',\n        'iron_(for_clothing)', 'ironing_board', 'jacket', 'jam', 'jean',\n        'jeep', 'jelly_bean', 'jersey', 'jet_plane', 'jewelry', 'joystick',\n        'jumpsuit', 'kayak', 'keg', 'kennel', 'kettle', 'key', 'keycard',\n        'kilt', 'kimono', 'kitchen_sink', 'kitchen_table', 'kite', 'kitten',\n        'kiwi_fruit', 'knee_pad', 'knife', 'knight_(chess_piece)',\n        'knitting_needle', 'knob', 'knocker_(on_a_door)', 'koala', 'lab_coat',\n        'ladder', 'ladle', 'ladybug', 'lamb_(animal)', 'lamb-chop', 'lamp',\n        'lamppost', 'lampshade', 'lantern', 'lanyard', 'laptop_computer',\n        'lasagna', 'latch', 'lawn_mower', 'leather', 'legging_(clothing)',\n        'Lego', 'lemon', 'lemonade', 'lettuce', 'license_plate', 'life_buoy',\n        'life_jacket', 'lightbulb', 'lightning_rod', 'lime', 'limousine',\n        'linen_paper', 'lion', 'lip_balm', 'lipstick', 'liquor', 'lizard',\n        'Loafer_(type_of_shoe)', 'log', 'lollipop', 'lotion',\n        'speaker_(stero_equipment)', 'loveseat', 'machine_gun', 'magazine',\n        'magnet', 'mail_slot', 'mailbox_(at_home)', 'mallet', 'mammoth',\n        'mandarin_orange', 'manger', 'manhole', 'map', 'marker', 'martini',\n        'mascot', 'mashed_potato', 'masher', 'mask', 'mast',\n        'mat_(gym_equipment)', 'matchbox', 'mattress', 'measuring_cup',\n        'measuring_stick', 'meatball', 'medicine', 'melon', 'microphone',\n        'microscope', 'microwave_oven', 'milestone', 'milk', 'minivan',\n        'mint_candy', 'mirror', 'mitten', 'mixer_(kitchen_tool)', 'money',\n        'monitor_(computer_equipment) computer_monitor', 'monkey', 'motor',\n        'motor_scooter', 'motor_vehicle', 'motorboat', 'motorcycle',\n        'mound_(baseball)', 'mouse_(animal_rodent)',\n        'mouse_(computer_equipment)', 'mousepad', 'muffin', 'mug', 'mushroom',\n        'music_stool', 'musical_instrument', 'nailfile', 'nameplate', 'napkin',\n        'neckerchief', 'necklace', 'necktie', 'needle', 'nest', 'newsstand',\n        'nightshirt', 'nosebag_(for_animals)', 'noseband_(for_animals)',\n        'notebook', 'notepad', 'nut', 'nutcracker', 'oar', 'octopus_(food)',\n        'octopus_(animal)', 'oil_lamp', 'olive_oil', 'omelet', 'onion',\n        'orange_(fruit)', 'orange_juice', 'oregano', 'ostrich', 'ottoman',\n        'overalls_(clothing)', 'owl', 'packet', 'inkpad', 'pad', 'paddle',\n        'padlock', 'paintbox', 'paintbrush', 'painting', 'pajamas', 'palette',\n        'pan_(for_cooking)', 'pan_(metal_container)', 'pancake', 'pantyhose',\n        'papaya', 'paperclip', 'paper_plate', 'paper_towel', 'paperback_book',\n        'paperweight', 'parachute', 'parakeet', 'parasail_(sports)',\n        'parchment', 'parka', 'parking_meter', 'parrot',\n        'passenger_car_(part_of_a_train)', 'passenger_ship', 'passport',\n        'pastry', 'patty_(food)', 'pea_(food)', 'peach', 'peanut_butter',\n        'pear', 'peeler_(tool_for_fruit_and_vegetables)', 'pegboard',\n        'pelican', 'pen', 'pencil', 'pencil_box', 'pencil_sharpener',\n        'pendulum', 'penguin', 'pennant', 'penny_(coin)', 'pepper',\n        'pepper_mill', 'perfume', 'persimmon', 'baby', 'pet', 'petfood',\n        'pew_(church_bench)', 'phonebook', 'phonograph_record', 'piano',\n        'pickle', 'pickup_truck', 'pie', 'pigeon', 'piggy_bank', 'pillow',\n        'pin_(non_jewelry)', 'pineapple', 'pinecone', 'ping-pong_ball',\n        'pinwheel', 'tobacco_pipe', 'pipe', 'pistol', 'pita_(bread)',\n        'pitcher_(vessel_for_liquid)', 'pitchfork', 'pizza', 'place_mat',\n        'plate', 'platter', 'playing_card', 'playpen', 'pliers',\n        'plow_(farm_equipment)', 'pocket_watch', 'pocketknife',\n        'poker_(fire_stirring_tool)', 'pole', 'police_van', 'polo_shirt',\n        'poncho', 'pony', 'pool_table', 'pop_(soda)', 'portrait',\n        'postbox_(public)', 'postcard', 'poster', 'pot', 'flowerpot', 'potato',\n        'potholder', 'pottery', 'pouch', 'power_shovel', 'prawn', 'printer',\n        'projectile_(weapon)', 'projector', 'propeller', 'prune', 'pudding',\n        'puffer_(fish)', 'puffin', 'pug-dog', 'pumpkin', 'puncher', 'puppet',\n        'puppy', 'quesadilla', 'quiche', 'quilt', 'rabbit', 'race_car',\n        'racket', 'radar', 'radiator', 'radio_receiver', 'radish', 'raft',\n        'rag_doll', 'raincoat', 'ram_(animal)', 'raspberry', 'rat',\n        'razorblade', 'reamer_(juicer)', 'rearview_mirror', 'receipt',\n        'recliner', 'record_player', 'red_cabbage', 'reflector',\n        'remote_control', 'rhinoceros', 'rib_(food)', 'rifle', 'ring',\n        'river_boat', 'road_map', 'robe', 'rocking_chair', 'roller_skate',\n        'Rollerblade', 'rolling_pin', 'root_beer',\n        'router_(computer_equipment)', 'rubber_band', 'runner_(carpet)',\n        'plastic_bag', 'saddle_(on_an_animal)', 'saddle_blanket', 'saddlebag',\n        'safety_pin', 'sail', 'salad', 'salad_plate', 'salami',\n        'salmon_(fish)', 'salmon_(food)', 'salsa', 'saltshaker',\n        'sandal_(type_of_shoe)', 'sandwich', 'satchel', 'saucepan', 'saucer',\n        'sausage', 'sawhorse', 'saxophone', 'scale_(measuring_instrument)',\n        'scarecrow', 'scarf', 'school_bus', 'scissors', 'scoreboard',\n        'scrambled_eggs', 'scraper', 'scratcher', 'screwdriver',\n        'scrubbing_brush', 'sculpture', 'seabird', 'seahorse', 'seaplane',\n        'seashell', 'seedling', 'serving_dish', 'sewing_machine', 'shaker',\n        'shampoo', 'shark', 'sharpener', 'Sharpie', 'shaver_(electric)',\n        'shaving_cream', 'shawl', 'shears', 'sheep', 'shepherd_dog',\n        'sherbert', 'shield', 'shirt', 'shoe', 'shopping_bag', 'shopping_cart',\n        'short_pants', 'shot_glass', 'shoulder_bag', 'shovel', 'shower_head',\n        'shower_curtain', 'shredder_(for_paper)', 'sieve', 'signboard', 'silo',\n        'sink', 'skateboard', 'skewer', 'ski', 'ski_boot', 'ski_parka',\n        'ski_pole', 'skirt', 'sled', 'sleeping_bag', 'sling_(bandage)',\n        'slipper_(footwear)', 'smoothie', 'snake', 'snowboard', 'snowman',\n        'snowmobile', 'soap', 'soccer_ball', 'sock', 'soda_fountain',\n        'carbonated_water', 'sofa', 'softball', 'solar_array', 'sombrero',\n        'soup', 'soup_bowl', 'soupspoon', 'sour_cream', 'soya_milk',\n        'space_shuttle', 'sparkler_(fireworks)', 'spatula', 'spear',\n        'spectacles', 'spice_rack', 'spider', 'sponge', 'spoon', 'sportswear',\n        'spotlight', 'squirrel', 'stapler_(stapling_machine)', 'starfish',\n        'statue_(sculpture)', 'steak_(food)', 'steak_knife',\n        'steamer_(kitchen_appliance)', 'steering_wheel', 'stencil',\n        'stepladder', 'step_stool', 'stereo_(sound_system)', 'stew', 'stirrer',\n        'stirrup', 'stockings_(leg_wear)', 'stool', 'stop_sign', 'brake_light',\n        'stove', 'strainer', 'strap', 'straw_(for_drinking)', 'strawberry',\n        'street_sign', 'streetlight', 'string_cheese', 'stylus', 'subwoofer',\n        'sugar_bowl', 'sugarcane_(plant)', 'suit_(clothing)', 'sunflower',\n        'sunglasses', 'sunhat', 'sunscreen', 'surfboard', 'sushi', 'mop',\n        'sweat_pants', 'sweatband', 'sweater', 'sweatshirt', 'sweet_potato',\n        'swimsuit', 'sword', 'syringe', 'Tabasco_sauce', 'table-tennis_table',\n        'table', 'table_lamp', 'tablecloth', 'tachometer', 'taco', 'tag',\n        'taillight', 'tambourine', 'army_tank', 'tank_(storage_vessel)',\n        'tank_top_(clothing)', 'tape_(sticky_cloth_or_paper)', 'tape_measure',\n        'tapestry', 'tarp', 'tartan', 'tassel', 'tea_bag', 'teacup',\n        'teakettle', 'teapot', 'teddy_bear', 'telephone', 'telephone_booth',\n        'telephone_pole', 'telephoto_lens', 'television_camera',\n        'television_set', 'tennis_ball', 'tennis_racket', 'tequila',\n        'thermometer', 'thermos_bottle', 'thermostat', 'thimble', 'thread',\n        'thumbtack', 'tiara', 'tiger', 'tights_(clothing)', 'timer', 'tinfoil',\n        'tinsel', 'tissue_paper', 'toast_(food)', 'toaster', 'toaster_oven',\n        'toilet', 'toilet_tissue', 'tomato', 'tongs', 'toolbox', 'toothbrush',\n        'toothpaste', 'toothpick', 'cover', 'tortilla', 'tow_truck', 'towel',\n        'towel_rack', 'toy', 'tractor_(farm_equipment)', 'traffic_light',\n        'dirt_bike', 'trailer_truck', 'train_(railroad_vehicle)', 'trampoline',\n        'tray', 'tree_house', 'trench_coat', 'triangle_(musical_instrument)',\n        'tricycle', 'tripod', 'trousers', 'truck', 'truffle_(chocolate)',\n        'trunk', 'vat', 'turban', 'turkey_(bird)', 'turkey_(food)', 'turnip',\n        'turtle', 'turtleneck_(clothing)', 'typewriter', 'umbrella',\n        'underwear', 'unicycle', 'urinal', 'urn', 'vacuum_cleaner', 'valve',\n        'vase', 'vending_machine', 'vent', 'videotape', 'vinegar', 'violin',\n        'vodka', 'volleyball', 'vulture', 'waffle', 'waffle_iron', 'wagon',\n        'wagon_wheel', 'walking_stick', 'wall_clock', 'wall_socket', 'wallet',\n        'walrus', 'wardrobe', 'wasabi', 'automatic_washer', 'watch',\n        'water_bottle', 'water_cooler', 'water_faucet', 'water_filter',\n        'water_heater', 'water_jug', 'water_gun', 'water_scooter', 'water_ski',\n        'water_tower', 'watering_can', 'watermelon', 'weathervane', 'webcam',\n        'wedding_cake', 'wedding_ring', 'wet_suit', 'wheel', 'wheelchair',\n        'whipped_cream', 'whiskey', 'whistle', 'wick', 'wig', 'wind_chime',\n        'windmill', 'window_box_(for_plants)', 'windshield_wiper', 'windsock',\n        'wine_bottle', 'wine_bucket', 'wineglass', 'wing_chair',\n        'blinder_(for_horses)', 'wok', 'wolf', 'wooden_spoon', 'wreath',\n        'wrench', 'wristband', 'wristlet', 'yacht', 'yak', 'yogurt',\n        'yoke_(animal_equipment)', 'zebra', 'zucchini')\n\n    def load_annotations(self, ann_file):\n        \"\"\"Load annotation from lvis style annotation file\n\n        Args:\n            ann_file (str): Path of annotation file.\n\n        Returns:\n            list[dict]: Annotation info from LVIS api.\n        \"\"\"\n\n        try:\n            from lvis import LVIS\n        except ImportError:\n            raise ImportError('Please follow config/lvis/README.md to '\n                              'install open-mmlab forked lvis first.')\n        self.coco = LVIS(ann_file)\n        assert not self.custom_classes, 'LVIS custom classes is not supported'\n        self.cat_ids = self.coco.get_cat_ids()\n        self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)}\n        self.img_ids = self.coco.get_img_ids()\n        data_infos = []\n        for i in self.img_ids:\n            info = self.coco.load_imgs([i])[0]\n            if info['file_name'].startswith('COCO'):\n                # Convert form the COCO 2014 file naming convention of\n                # COCO_[train/val/test]2014_000000000000.jpg to the 2017\n                # naming convention of 000000000000.jpg\n                # (LVIS v1 will fix this naming issue)\n                info['filename'] = info['file_name'][-16:]\n            else:\n                info['filename'] = info['file_name']\n            data_infos.append(info)\n        return data_infos\n\n    def evaluate(self,\n                 results,\n                 metric='bbox',\n                 logger=None,\n                 jsonfile_prefix=None,\n                 classwise=False,\n                 proposal_nums=(100, 300, 1000),\n                 iou_thrs=np.arange(0.5, 0.96, 0.05)):\n        \"\"\"Evaluation in LVIS protocol.\n\n        Args:\n            results (list[list | tuple]): Testing results of the dataset.\n            metric (str | list[str]): Metrics to be evaluated. Options are\n                'bbox', 'segm', 'proposal', 'proposal_fast'.\n            logger (logging.Logger | str | None): Logger used for printing\n                related information during evaluation. Default: None.\n            jsonfile_prefix (str | None):\n            classwise (bool): Whether to evaluating the AP for each class.\n            proposal_nums (Sequence[int]): Proposal number used for evaluating\n                recalls, such as recall@100, recall@1000.\n                Default: (100, 300, 1000).\n            iou_thrs (Sequence[float]): IoU threshold used for evaluating\n                recalls. If set to a list, the average recall of all IoUs will\n                also be computed. Default: 0.5.\n\n        Returns:\n            dict[str, float]: LVIS style metrics.\n        \"\"\"\n\n        try:\n            from lvis import LVISResults, LVISEval\n        except ImportError:\n            raise ImportError('Please follow config/lvis/README.md to '\n                              'install open-mmlab forked lvis first.')\n        assert isinstance(results, list), 'results must be a list'\n        assert len(results) == len(self), (\n            'The length of results is not equal to the dataset len: {} != {}'.\n            format(len(results), len(self)))\n\n        metrics = metric if isinstance(metric, list) else [metric]\n        allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast']\n        for metric in metrics:\n            if metric not in allowed_metrics:\n                raise KeyError('metric {} is not supported'.format(metric))\n\n        if jsonfile_prefix is None:\n            tmp_dir = tempfile.TemporaryDirectory()\n            jsonfile_prefix = osp.join(tmp_dir.name, 'results')\n        else:\n            tmp_dir = None\n        result_files = self.results2json(results, jsonfile_prefix)\n\n        eval_results = {}\n        # get original api\n        lvis_gt = self.coco\n        for metric in metrics:\n            msg = 'Evaluating {}...'.format(metric)\n            if logger is None:\n                msg = '\\n' + msg\n            print_log(msg, logger=logger)\n\n            if metric == 'proposal_fast':\n                ar = self.fast_eval_recall(\n                    results, proposal_nums, iou_thrs, logger='silent')\n                log_msg = []\n                for i, num in enumerate(proposal_nums):\n                    eval_results['AR@{}'.format(num)] = ar[i]\n                    log_msg.append('\\nAR@{}\\t{:.4f}'.format(num, ar[i]))\n                log_msg = ''.join(log_msg)\n                print_log(log_msg, logger=logger)\n                continue\n\n            if metric not in result_files:\n                raise KeyError('{} is not in results'.format(metric))\n            try:\n                lvis_dt = LVISResults(lvis_gt, result_files[metric])\n            except IndexError:\n                print_log(\n                    'The testing results of the whole dataset is empty.',\n                    logger=logger,\n                    level=logging.ERROR)\n                break\n\n            iou_type = 'bbox' if metric == 'proposal' else metric\n            lvis_eval = LVISEval(lvis_gt, lvis_dt, iou_type)\n            lvis_eval.params.imgIds = self.img_ids\n            if metric == 'proposal':\n                lvis_eval.params.useCats = 0\n                lvis_eval.params.maxDets = list(proposal_nums)\n                lvis_eval.evaluate()\n                lvis_eval.accumulate()\n                lvis_eval.summarize()\n                for k, v in lvis_eval.get_results().items():\n                    if k.startswith('AR'):\n                        val = float('{:.3f}'.format(float(v)))\n                        eval_results[k] = val\n            else:\n                lvis_eval.evaluate()\n                lvis_eval.accumulate()\n                lvis_eval.summarize()\n                lvis_results = lvis_eval.get_results()\n                if classwise:  # Compute per-category AP\n                    # Compute per-category AP\n                    # from https://github.com/facebookresearch/detectron2/\n                    precisions = lvis_eval.eval['precision']\n                    # precision: (iou, recall, cls, area range, max dets)\n                    assert len(self.cat_ids) == precisions.shape[2]\n\n                    results_per_category = []\n                    for idx, catId in enumerate(self.cat_ids):\n                        # area range index 0: all area ranges\n                        # max dets index -1: typically 100 per image\n                        nm = self.coco.load_cats(catId)[0]\n                        precision = precisions[:, :, idx, 0, -1]\n                        precision = precision[precision > -1]\n                        if precision.size:\n                            ap = np.mean(precision)\n                        else:\n                            ap = float('nan')\n                        results_per_category.append(\n                            (f'{nm[\"name\"]}', f'{float(ap):0.3f}'))\n\n                    num_columns = min(6, len(results_per_category) * 2)\n                    results_flatten = list(\n                        itertools.chain(*results_per_category))\n                    headers = ['category', 'AP'] * (num_columns // 2)\n                    results_2d = itertools.zip_longest(*[\n                        results_flatten[i::num_columns]\n                        for i in range(num_columns)\n                    ])\n                    table_data = [headers]\n                    table_data += [result for result in results_2d]\n                    table = AsciiTable(table_data)\n                    print_log('\\n' + table.table, logger=logger)\n\n                for k, v in lvis_results.items():\n                    if k.startswith('AP'):\n                        key = '{}_{}'.format(metric, k)\n                        val = float('{:.3f}'.format(float(v)))\n                        eval_results[key] = val\n                ap_summary = ' '.join([\n                    '{}:{:.3f}'.format(k, float(v))\n                    for k, v in lvis_results.items() if k.startswith('AP')\n                ])\n                eval_results['{}_mAP_copypaste'.format(metric)] = ap_summary\n            lvis_eval.print_results()\n        if tmp_dir is not None:\n            tmp_dir.cleanup()\n        return eval_results\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/__init__.py",
    "content": "from .auto_augment import AutoAugment\nfrom .compose import Compose\nfrom .formating import (Collect, ImageToTensor, ToDataContainer, ToTensor,\n                        Transpose, to_tensor)\nfrom .formating_reppointsv2 import RPDV2FormatBundle\nfrom .instaboost import InstaBoost\nfrom .loading import (LoadAnnotations, LoadImageFromFile,\n                      LoadMultiChannelImageFromFiles, LoadProposals)\nfrom .loading_reppointsv2 import LoadRPDV2Annotations, LoadDenseRPDV2Annotations\nfrom .test_time_aug import MultiScaleFlipAug\nfrom .transforms import (Albu, Expand, MinIoURandomCrop, Normalize, Pad,\n                         PhotoMetricDistortion, RandomCenterCropPad,\n                         RandomCrop, RandomFlip, Resize, SegRescale)\n\n__all__ = [\n    'Compose', 'to_tensor', 'ToTensor', 'ImageToTensor', 'ToDataContainer',\n    'Transpose', 'Collect', 'LoadAnnotations', 'LoadImageFromFile',\n    'LoadMultiChannelImageFromFiles', 'LoadProposals', 'MultiScaleFlipAug',\n    'Resize', 'RandomFlip', 'Pad', 'RandomCrop', 'Normalize', 'SegRescale',\n    'MinIoURandomCrop', 'Expand', 'PhotoMetricDistortion', 'Albu',\n    'InstaBoost', 'RandomCenterCropPad', 'AutoAugment', 'LoadRPDV2Annotations', 'RPDV2FormatBundle',\n    'LoadDenseRPDV2Annotations'\n]\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/auto_augment.py",
    "content": "import copy\n\nimport numpy as np\n\nfrom ..builder import PIPELINES\nfrom .compose import Compose\n\n\n@PIPELINES.register_module()\nclass AutoAugment(object):\n    \"\"\"Auto augmentation.\n\n    This data augmentation is proposed in\n    `Learning Data Augmentation Strategies for Object Detection <https://arxiv.org/pdf/1906.11172>`_  # noqa: E501\n\n    Args:\n        policies (list[list[dict]]): The policies of auto augmentation. Each\n            policy in ``policies`` is a specific augmentation policy, and is\n            composed by several augmentations (dict). When AutoAugment is\n            called, a random policy in ``policies`` will be selected to\n            augment images.\n\n    Examples:\n        >>> replace = (104, 116, 124)\n        >>> policies = [\n        >>>     [\n        >>>         dict(type='Sharpness', prob=0.0, level=8),\n        >>>         dict(\n        >>>             type='Shear',\n        >>>             prob=0.4,\n        >>>             level=0,\n        >>>             replace=replace,\n        >>>             axis='x')\n        >>>     ],\n        >>>     [\n        >>>         dict(\n        >>>             type='Rotate',\n        >>>             prob=0.6,\n        >>>             level=10,\n        >>>             replace=replace),\n        >>>         dict(type='Color', prob=1.0, level=6)\n        >>>     ]\n        >>> ]\n        >>> augmentation = AutoAugment(policies)\n        >>> img = np.ones(100, 100, 3)\n        >>> gt_bboxes = np.ones(10, 4)\n        >>> results = dict(img=img, gt_bboxes=gt_bboxes)\n        >>> results = augmentation(results)\n    \"\"\"\n\n    def __init__(self, policies):\n        assert isinstance(policies, list) and len(policies) > 0, \\\n            'Policies must be a non-empty list.'\n        for policy in policies:\n            assert isinstance(policy, list) and len(policy) > 0, \\\n                'Each policy in policies must be a non-empty list.'\n            for augment in policy:\n                assert isinstance(augment, dict) and 'type' in augment, \\\n                    'Each specific augmentation must be a dict with key' \\\n                    ' \"type\".'\n\n        self.policies = copy.deepcopy(policies)\n        self.transforms = [Compose(policy) for policy in self.policies]\n\n    def __call__(self, results):\n        transform = np.random.choice(self.transforms)\n        return transform(results)\n\n    def __repr__(self):\n        return f'{self.__class__.__name__}(policies={self.policies}'\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/compose.py",
    "content": "import collections\n\nfrom mmcv.utils import build_from_cfg\n\nfrom ..builder import PIPELINES\n\n\n@PIPELINES.register_module()\nclass Compose(object):\n    \"\"\"Compose multiple transforms sequentially.\n\n    Args:\n        transforms (Sequence[dict | callable]): Sequence of transform object or\n            config dict to be composed.\n    \"\"\"\n\n    def __init__(self, transforms):\n        assert isinstance(transforms, collections.abc.Sequence)\n        self.transforms = []\n        for transform in transforms:\n            if isinstance(transform, dict):\n                transform = build_from_cfg(transform, PIPELINES)\n                self.transforms.append(transform)\n            elif callable(transform):\n                self.transforms.append(transform)\n            else:\n                raise TypeError('transform must be callable or a dict')\n\n    def __call__(self, data):\n        \"\"\"Call function to apply transforms sequentially.\n\n        Args:\n            data (dict): A result dict contains the data to transform.\n\n        Returns:\n           dict: Transformed data.\n        \"\"\"\n\n        for t in self.transforms:\n            data = t(data)\n            if data is None:\n                return None\n        return data\n\n    def __repr__(self):\n        format_string = self.__class__.__name__ + '('\n        for t in self.transforms:\n            format_string += '\\n'\n            format_string += f'    {t}'\n        format_string += '\\n)'\n        return format_string\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/formating.py",
    "content": "from collections.abc import Sequence\n\nimport mmcv\nimport numpy as np\nimport torch\nfrom mmcv.parallel import DataContainer as DC\n\nfrom ..builder import PIPELINES\n\n\ndef to_tensor(data):\n    \"\"\"Convert objects of various python types to :obj:`torch.Tensor`.\n\n    Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`,\n    :class:`Sequence`, :class:`int` and :class:`float`.\n\n    Args:\n        data (torch.Tensor | numpy.ndarray | Sequence | int | float): Data to\n            be converted.\n    \"\"\"\n\n    if isinstance(data, torch.Tensor):\n        return data\n    elif isinstance(data, np.ndarray):\n        return torch.from_numpy(data)\n    elif isinstance(data, Sequence) and not mmcv.is_str(data):\n        return torch.tensor(data)\n    elif isinstance(data, int):\n        return torch.LongTensor([data])\n    elif isinstance(data, float):\n        return torch.FloatTensor([data])\n    else:\n        raise TypeError(f'type {type(data)} cannot be converted to tensor.')\n\n\n@PIPELINES.register_module()\nclass ToTensor(object):\n    \"\"\"Convert some results to :obj:`torch.Tensor` by given keys.\n\n    Args:\n        keys (Sequence[str]): Keys that need to be converted to Tensor.\n    \"\"\"\n\n    def __init__(self, keys):\n        self.keys = keys\n\n    def __call__(self, results):\n        \"\"\"Call function to convert data in results to :obj:`torch.Tensor`.\n\n        Args:\n            results (dict): Result dict contains the data to convert.\n\n        Returns:\n            dict: The result dict contains the data converted\n                to :obj:`torch.Tensor`.\n        \"\"\"\n        for key in self.keys:\n            results[key] = to_tensor(results[key])\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + f'(keys={self.keys})'\n\n\n@PIPELINES.register_module()\nclass ImageToTensor(object):\n    \"\"\"Convert image to :obj:`torch.Tensor` by given keys.\n\n    The dimension order of input image is (H, W, C). The pipeline will convert\n    it to (C, H, W). If only 2 dimension (H, W) is given, the output would be\n    (1, H, W).\n\n    Args:\n        keys (Sequence[str]): Key of images to be converted to Tensor.\n    \"\"\"\n\n    def __init__(self, keys):\n        self.keys = keys\n\n    def __call__(self, results):\n        \"\"\"Call function to convert image in results to :obj:`torch.Tensor`\n        and transpose the channel order.\n\n        Args:\n            results (dict): Result dict contains the image data to convert.\n\n        Returns:\n            dict: The result dict contains the image converted\n                to :obj:`torch.Tensor` and transposed to (C, H, W) order.\n        \"\"\"\n        for key in self.keys:\n            img = results[key]\n            if len(img.shape) < 3:\n                img = np.expand_dims(img, -1)\n            results[key] = to_tensor(img.transpose(2, 0, 1))\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + f'(keys={self.keys})'\n\n\n@PIPELINES.register_module()\nclass Transpose(object):\n    \"\"\"Transpose some results by given keys.\n\n    Args:\n        keys (Sequence[str]): Keys of results to be transposed.\n        order (Sequence[int]): Order of transpose.\n    \"\"\"\n\n    def __init__(self, keys, order):\n        self.keys = keys\n        self.order = order\n\n    def __call__(self, results):\n        \"\"\"Call function to transpose the channel order of data in results.\n\n        Args:\n            results (dict): Result dict contains the data to transpose.\n\n        Returns:\n            dict: The result dict contains the data transposed to\n                ``self.order``.\n        \"\"\"\n        for key in self.keys:\n            results[key] = results[key].transpose(self.order)\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + \\\n            f'(keys={self.keys}, order={self.order})'\n\n\n@PIPELINES.register_module()\nclass ToDataContainer(object):\n    \"\"\"Convert results to :obj:`mmcv.DataContainer` by given fields.\n\n    Args:\n        fields (Sequence[dict]): Each field is a dict like\n            ``dict(key='xxx', **kwargs)``. The ``key`` in result will\n            be converted to :obj:`mmcv.DataContainer` with ``**kwargs``.\n            Default: ``(dict(key='img', stack=True), dict(key='gt_bboxes'),\n                         dict(key='gt_labels'))``.\n    \"\"\"\n\n    def __init__(self,\n                 fields=(dict(key='img', stack=True), dict(key='gt_bboxes'),\n                         dict(key='gt_labels'))):\n        self.fields = fields\n\n    def __call__(self, results):\n        \"\"\"Call function to convert data in results to\n        :obj:`mmcv.DataContainer`.\n\n        Args:\n            results (dict): Result dict contains the data to convert.\n\n        Returns:\n            dict: The result dict contains the data converted to\n                :obj:`mmcv.DataContainer`.\n        \"\"\"\n\n        for field in self.fields:\n            field = field.copy()\n            key = field.pop('key')\n            results[key] = DC(results[key], **field)\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + f'(fields={self.fields})'\n\n\n@PIPELINES.register_module()\nclass DefaultFormatBundle(object):\n    \"\"\"Default formatting bundle.\n\n    It simplifies the pipeline of formatting common fields, including \"img\",\n    \"proposals\", \"gt_bboxes\", \"gt_labels\", \"gt_masks\" and \"gt_semantic_seg\".\n    These fields are formatted as follows.\n\n    - img: (1)transpose, (2)to tensor, (3)to DataContainer (stack=True)\n    - proposals: (1)to tensor, (2)to DataContainer\n    - gt_bboxes: (1)to tensor, (2)to DataContainer\n    - gt_bboxes_ignore: (1)to tensor, (2)to DataContainer\n    - gt_labels: (1)to tensor, (2)to DataContainer\n    - gt_masks: (1)to tensor, (2)to DataContainer (cpu_only=True)\n    - gt_semantic_seg: (1)unsqueeze dim-0 (2)to tensor,\n                       (3)to DataContainer (stack=True)\n    \"\"\"\n\n    def __call__(self, results):\n        \"\"\"Call function to transform and format common fields in results.\n\n        Args:\n            results (dict): Result dict contains the data to convert.\n\n        Returns:\n            dict: The result dict contains the data that is formatted with\n                default bundle.\n        \"\"\"\n\n        if 'img' in results:\n            img = results['img']\n            # add default meta keys\n            results = self._add_default_meta_keys(results)\n            if len(img.shape) < 3:\n                img = np.expand_dims(img, -1)\n            img = np.ascontiguousarray(img.transpose(2, 0, 1))\n            results['img'] = DC(to_tensor(img), stack=True)\n        for key in ['proposals', 'gt_bboxes', 'gt_bboxes_ignore', 'gt_labels', 'gt_extremes', 'gt_keypoints']:\n            if key not in results:\n                continue\n            results[key] = DC(to_tensor(results[key]))\n        if 'gt_masks' in results:\n            results['gt_masks'] = DC(results['gt_masks'], cpu_only=True)\n        if 'gt_semantic_seg' in results:\n            results['gt_semantic_seg'] = DC(\n                to_tensor(results['gt_semantic_seg'][None, ...]), stack=True)\n        return results\n\n    def _add_default_meta_keys(self, results):\n        \"\"\"Add default meta keys.\n\n        We set default meta keys including `pad_shape`, `scale_factor` and\n        `img_norm_cfg` to avoid the case where no `Resize`, `Normalize` and\n        `Pad` are implemented during the whole pipeline.\n\n        Args:\n            results (dict): Result dict contains the data to convert.\n\n        Returns:\n            results (dict): Updated result dict contains the data to convert.\n        \"\"\"\n        img = results['img']\n        results.setdefault('pad_shape', img.shape)\n        results.setdefault('scale_factor', 1.0)\n        num_channels = 1 if len(img.shape) < 3 else img.shape[2]\n        results.setdefault(\n            'img_norm_cfg',\n            dict(\n                mean=np.zeros(num_channels, dtype=np.float32),\n                std=np.ones(num_channels, dtype=np.float32),\n                to_rgb=False))\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__\n\n\n@PIPELINES.register_module()\nclass Collect(object):\n    \"\"\"\n    Collect data from the loader relevant to the specific task.\n\n    This is usually the last stage of the data loader pipeline. Typically keys\n    is set to some subset of \"img\", \"proposals\", \"gt_bboxes\",\n    \"gt_bboxes_ignore\", \"gt_labels\", and/or \"gt_masks\".\n\n    The \"img_meta\" item is always populated.  The contents of the \"img_meta\"\n    dictionary depends on \"meta_keys\". By default this includes:\n\n        - \"img_shape\": shape of the image input to the network as a tuple\n            (h, w, c).  Note that images may be zero padded on the bottom/right\n            if the batch tensor is larger than this shape.\n\n        - \"scale_factor\": a float indicating the preprocessing scale\n\n        - \"flip\": a boolean indicating if image flip transform was used\n\n        - \"filename\": path to the image file\n\n        - \"ori_shape\": original shape of the image as a tuple (h, w, c)\n\n        - \"pad_shape\": image shape after padding\n\n        - \"img_norm_cfg\": a dict of normalization information:\n            - mean - per channel mean subtraction\n            - std - per channel std divisor\n            - to_rgb - bool indicating if bgr was converted to rgb\n\n    Args:\n        keys (Sequence[str]): Keys of results to be collected in ``data``.\n        meta_keys (Sequence[str], optional): Meta keys to be converted to\n            ``mmcv.DataContainer`` and collected in ``data[img_metas]``.\n            Default: ``('filename', 'ori_filename', 'ori_shape', 'img_shape',\n            'pad_shape', 'scale_factor', 'flip', 'flip_direction',\n            'img_norm_cfg')``\n    \"\"\"\n\n    def __init__(self,\n                 keys,\n                 meta_keys=('filename', 'ori_filename', 'ori_shape',\n                            'img_shape', 'pad_shape', 'scale_factor', 'flip',\n                            'flip_direction', 'img_norm_cfg')):\n        self.keys = keys\n        self.meta_keys = meta_keys\n\n    def __call__(self, results):\n        \"\"\"Call function to collect keys in results. The keys in ``meta_keys``\n        will be converted to :obj:mmcv.DataContainer.\n\n        Args:\n            results (dict): Result dict contains the data to collect.\n\n        Returns:\n            dict: The result dict contains the following keys\n                - keys in``self.keys``\n                - ``img_metas``\n        \"\"\"\n\n        data = {}\n        img_meta = {}\n        for key in self.meta_keys:\n            img_meta[key] = results[key]\n        data['img_metas'] = DC(img_meta, cpu_only=True)\n        for key in self.keys:\n            data[key] = results[key]\n        return data\n\n    def __repr__(self):\n        return self.__class__.__name__ + \\\n            f'(keys={self.keys}, meta_keys={self.meta_keys})'\n\n\n@PIPELINES.register_module()\nclass WrapFieldsToLists(object):\n    \"\"\"\n    Wrap fields of the data dictionary into lists for evaluation.\n\n    This class can be used as a last step of a test or validation\n    pipeline for single image evaluation or inference.\n\n    Example:\n        >>> test_pipeline = [\n        >>>    dict(type='LoadImageFromFile'),\n        >>>    dict(type='Normalize',\n                    mean=[123.675, 116.28, 103.53],\n                    std=[58.395, 57.12, 57.375],\n                    to_rgb=True),\n        >>>    dict(type='Pad', size_divisor=32),\n        >>>    dict(type='ImageToTensor', keys=['img']),\n        >>>    dict(type='Collect', keys=['img']),\n        >>>    dict(type='WrapIntoLists')\n        >>> ]\n    \"\"\"\n\n    def __call__(self, results):\n        \"\"\"Call function to wrap fields into lists.\n\n        Args:\n            results (dict): Result dict contains the data to wrap.\n\n        Returns:\n            dict: The result dict where value of ``self.keys`` are wrapped\n                into list.\n        \"\"\"\n\n        # Wrap dict fields into lists\n        for key, val in results.items():\n            results[key] = [val]\n        return results\n\n    def __repr__(self):\n        return f'{self.__class__.__name__}()'\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/formating_reppointsv2.py",
    "content": "from collections.abc import Sequence\n\nimport mmcv\nimport numpy as np\nimport torch\nfrom mmcv.parallel import DataContainer as DC\n\nfrom ..builder import PIPELINES\nfrom . import to_tensor\n\n\n@PIPELINES.register_module()\nclass RPDV2FormatBundle(object):\n    \"\"\"Default formatting bundle.\n\n    It simplifies the pipeline of formatting common fields, including \"img\",\n    \"proposals\", \"gt_bboxes\", \"gt_labels\", \"gt_masks\" and \"gt_semantic_seg\".\n    These fields are formatted as follows.\n\n    - img: (1)transpose, (2)to tensor, (3)to DataContainer (stack=True)\n    - proposals: (1)to tensor, (2)to DataContainer\n    - gt_bboxes: (1)to tensor, (2)to DataContainer\n    - gt_bboxes_ignore: (1)to tensor, (2)to DataContainer\n    - gt_labels: (1)to tensor, (2)to DataContainer\n    - gt_masks: (1)to tensor, (2)to DataContainer (cpu_only=True)\n    - gt_semantic_seg: (1)unsqueeze dim-0 (2)to tensor,\n                       (3)to DataContainer (stack=True)\n    \"\"\"\n    \n    def __init__(self):\n        super(RPDV2FormatBundle, self).__init__()\n\n    def __call__(self, results):\n        \"\"\"Call function to transform and format common fields in results.\n\n        Args:\n            results (dict): Result dict contains the data to convert.\n\n        Returns:\n            dict: The result dict contains the data that is formatted with\n                default bundle.\n        \"\"\"\n\n        if 'img' in results:\n            img = results['img']\n            # add default meta keys\n            results = self._add_default_meta_keys(results)\n            if len(img.shape) < 3:\n                img = np.expand_dims(img, -1)\n            img = np.ascontiguousarray(img.transpose(2, 0, 1))\n            results['img'] = DC(to_tensor(img), stack=True)\n        for key in ['proposals', 'gt_bboxes', 'gt_bboxes_ignore', 'gt_labels', 'gt_extremes']:\n            if key not in results:\n                continue\n            results[key] = DC(to_tensor(results[key]))\n        if 'gt_masks' in results:\n            results['gt_masks'] = DC(results['gt_masks'], cpu_only=True)\n        if 'gt_semantic_seg' in results:\n            results['gt_semantic_seg'] = DC(\n                to_tensor(results['gt_semantic_seg'][None, ...]), stack=True)\n        if 'gt_sem_map' in results:\n            results['gt_sem_map'] = DC(to_tensor(results['gt_sem_map']), stack=True)\n        if 'gt_sem_weights' in results:\n            results['gt_sem_weights'] = DC(to_tensor(results['gt_sem_weights']), stack=True)\n        if 'gt_contours' in results:\n            results['gt_contours'] = DC(to_tensor(results['gt_contours']))\n\n        return results\n\n    def _add_default_meta_keys(self, results):\n        \"\"\"Add default meta keys.\n\n        We set default meta keys including `pad_shape`, `scale_factor` and\n        `img_norm_cfg` to avoid the case where no `Resize`, `Normalize` and\n        `Pad` are implemented during the whole pipeline.\n\n        Args:\n            results (dict): Result dict contains the data to convert.\n\n        Returns:\n            results (dict): Updated result dict contains the data to convert.\n        \"\"\"\n        img = results['img']\n        results.setdefault('pad_shape', img.shape)\n        results.setdefault('scale_factor', 1.0)\n        num_channels = 1 if len(img.shape) < 3 else img.shape[2]\n        results.setdefault(\n            'img_norm_cfg',\n            dict(\n                mean=np.zeros(num_channels, dtype=np.float32),\n                std=np.ones(num_channels, dtype=np.float32),\n                to_rgb=False))\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/instaboost.py",
    "content": "import numpy as np\n\nfrom ..builder import PIPELINES\n\n\n@PIPELINES.register_module()\nclass InstaBoost(object):\n    \"\"\"\n    Data augmentation method in paper \"InstaBoost: Boosting Instance\n    Segmentation Via Probability Map Guided Copy-Pasting\"\n    Implementation details can refer to https://github.com/GothicAi/Instaboost.\n    \"\"\"\n\n    def __init__(self,\n                 action_candidate=('normal', 'horizontal', 'skip'),\n                 action_prob=(1, 0, 0),\n                 scale=(0.8, 1.2),\n                 dx=15,\n                 dy=15,\n                 theta=(-1, 1),\n                 color_prob=0.5,\n                 hflag=False,\n                 aug_ratio=0.5):\n        try:\n            import instaboostfast as instaboost\n        except ImportError:\n            raise ImportError(\n                'Please run \"pip install instaboostfast\" '\n                'to install instaboostfast first for instaboost augmentation.')\n        self.cfg = instaboost.InstaBoostConfig(action_candidate, action_prob,\n                                               scale, dx, dy, theta,\n                                               color_prob, hflag)\n        self.aug_ratio = aug_ratio\n\n    def _load_anns(self, results):\n        labels = results['ann_info']['labels']\n        masks = results['ann_info']['masks']\n        bboxes = results['ann_info']['bboxes']\n        n = len(labels)\n\n        anns = []\n        for i in range(n):\n            label = labels[i]\n            bbox = bboxes[i]\n            mask = masks[i]\n            x1, y1, x2, y2 = bbox\n            # assert (x2 - x1) >= 1 and (y2 - y1) >= 1\n            bbox = [x1, y1, x2 - x1, y2 - y1]\n            anns.append({\n                'category_id': label,\n                'segmentation': mask,\n                'bbox': bbox\n            })\n\n        return anns\n\n    def _parse_anns(self, results, anns, img):\n        gt_bboxes = []\n        gt_labels = []\n        gt_masks_ann = []\n        for ann in anns:\n            x1, y1, w, h = ann['bbox']\n            # TODO: more essential bug need to be fixed in instaboost\n            if w <= 0 or h <= 0:\n                continue\n            bbox = [x1, y1, x1 + w, y1 + h]\n            gt_bboxes.append(bbox)\n            gt_labels.append(ann['category_id'])\n            gt_masks_ann.append(ann['segmentation'])\n        gt_bboxes = np.array(gt_bboxes, dtype=np.float32)\n        gt_labels = np.array(gt_labels, dtype=np.int64)\n        results['ann_info']['labels'] = gt_labels\n        results['ann_info']['bboxes'] = gt_bboxes\n        results['ann_info']['masks'] = gt_masks_ann\n        results['img'] = img\n        return results\n\n    def __call__(self, results):\n        img = results['img']\n        orig_type = img.dtype\n        anns = self._load_anns(results)\n        if np.random.choice([0, 1], p=[1 - self.aug_ratio, self.aug_ratio]):\n            try:\n                import instaboostfast as instaboost\n            except ImportError:\n                raise ImportError('Please run \"pip install instaboostfast\" '\n                                  'to install instaboostfast first.')\n            anns, img = instaboost.get_new_data(\n                anns, img.astype(np.uint8), self.cfg, background=None)\n\n        results = self._parse_anns(results, anns, img.astype(orig_type))\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(cfg={self.cfg}, aug_ratio={self.aug_ratio})'\n        return repr_str\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/loading.py",
    "content": "import os.path as osp\n\nimport mmcv\nimport numpy as np\nfrom shapely.geometry import Polygon\nimport pycocotools.mask as maskUtils\nfrom mmdet.core import BitmapMasks, PolygonMasks\nfrom ..builder import PIPELINES\n\n\n@PIPELINES.register_module()\nclass LoadImageFromFile(object):\n    \"\"\"Load an image from file.\n\n    Required keys are \"img_prefix\" and \"img_info\" (a dict that must contain the\n    key \"filename\"). Added or updated keys are \"filename\", \"img\", \"img_shape\",\n    \"ori_shape\" (same as `img_shape`), \"pad_shape\" (same as `img_shape`),\n    \"scale_factor\" (1.0) and \"img_norm_cfg\" (means=0 and stds=1).\n\n    Args:\n        to_float32 (bool): Whether to convert the loaded image to a float32\n            numpy array. If set to False, the loaded image is an uint8 array.\n            Defaults to False.\n        color_type (str): The flag argument for :func:`mmcv.imfrombytes()`.\n            Defaults to 'color'.\n        file_client_args (dict): Arguments to instantiate a FileClient.\n            See :class:`mmcv.fileio.FileClient` for details.\n            Defaults to ``dict(backend='disk')``.\n    \"\"\"\n\n    def __init__(self,\n                 to_float32=False,\n                 color_type='color',\n                 file_client_args=dict(backend='disk')):\n        self.to_float32 = to_float32\n        self.color_type = color_type\n        self.file_client_args = file_client_args.copy()\n        self.file_client = None\n\n    def __call__(self, results):\n        \"\"\"Call functions to load image and get image meta information.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded image and meta information.\n        \"\"\"\n\n        if self.file_client is None:\n            self.file_client = mmcv.FileClient(**self.file_client_args)\n\n        if results['img_prefix'] is not None:\n            filename = osp.join(results['img_prefix'],\n                                results['img_info']['filename'])\n        else:\n            filename = results['img_info']['filename']\n\n        img_bytes = self.file_client.get(filename)\n        img = mmcv.imfrombytes(img_bytes, flag=self.color_type)\n        if self.to_float32:\n            img = img.astype(np.float32)\n\n        results['filename'] = filename\n        results['ori_filename'] = results['img_info']['filename']\n        results['img'] = img\n        results['img_shape'] = img.shape\n        results['ori_shape'] = img.shape\n        results['img_fields'] = ['img']\n        return results\n\n    def __repr__(self):\n        repr_str = (f'{self.__class__.__name__}('\n                    f'to_float32={self.to_float32}, '\n                    f\"color_type='{self.color_type}', \"\n                    f'file_client_args={self.file_client_args})')\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass LoadMultiChannelImageFromFiles(object):\n    \"\"\"Load multi-channel images from a list of separate channel files.\n\n    Required keys are \"img_prefix\" and \"img_info\" (a dict that must contain the\n    key \"filename\", which is expected to be a list of filenames).\n    Added or updated keys are \"filename\", \"img\", \"img_shape\",\n    \"ori_shape\" (same as `img_shape`), \"pad_shape\" (same as `img_shape`),\n    \"scale_factor\" (1.0) and \"img_norm_cfg\" (means=0 and stds=1).\n\n    Args:\n        to_float32 (bool): Whether to convert the loaded image to a float32\n            numpy array. If set to False, the loaded image is an uint8 array.\n            Defaults to False.\n        color_type (str): The flag argument for :func:`mmcv.imfrombytes()`.\n            Defaults to 'color'.\n        file_client_args (dict): Arguments to instantiate a FileClient.\n            See :class:`mmcv.fileio.FileClient` for details.\n            Defaults to ``dict(backend='disk')``.\n    \"\"\"\n\n    def __init__(self,\n                 to_float32=False,\n                 color_type='unchanged',\n                 file_client_args=dict(backend='disk')):\n        self.to_float32 = to_float32\n        self.color_type = color_type\n        self.file_client_args = file_client_args.copy()\n        self.file_client = None\n\n    def __call__(self, results):\n        \"\"\"Call functions to load multiple images and get images meta\n        information.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded images and meta information.\n        \"\"\"\n\n        if self.file_client is None:\n            self.file_client = mmcv.FileClient(**self.file_client_args)\n\n        if results['img_prefix'] is not None:\n            filename = [\n                osp.join(results['img_prefix'], fname)\n                for fname in results['img_info']['filename']\n            ]\n        else:\n            filename = results['img_info']['filename']\n\n        img = []\n        for name in filename:\n            img_bytes = self.file_client.get(name)\n            img.append(mmcv.imfrombytes(img_bytes, flag=self.color_type))\n        img = np.stack(img, axis=-1)\n        if self.to_float32:\n            img = img.astype(np.float32)\n\n        results['filename'] = filename\n        results['ori_filename'] = results['img_info']['filename']\n        results['img'] = img\n        results['img_shape'] = img.shape\n        results['ori_shape'] = img.shape\n        # Set initial values for default meta_keys\n        results['pad_shape'] = img.shape\n        results['scale_factor'] = 1.0\n        num_channels = 1 if len(img.shape) < 3 else img.shape[2]\n        results['img_norm_cfg'] = dict(\n            mean=np.zeros(num_channels, dtype=np.float32),\n            std=np.ones(num_channels, dtype=np.float32),\n            to_rgb=False)\n        return results\n\n    def __repr__(self):\n        repr_str = (f'{self.__class__.__name__}('\n                    f'to_float32={self.to_float32}, '\n                    f\"color_type='{self.color_type}', \"\n                    f'file_client_args={self.file_client_args})')\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass LoadAnnotations(object):\n    \"\"\"Load mutiple types of annotations.\n\n    Args:\n        with_bbox (bool): Whether to parse and load the bbox annotation.\n             Default: True.\n        with_label (bool): Whether to parse and load the label annotation.\n            Default: True.\n        with_mask (bool): Whether to parse and load the mask annotation.\n             Default: False.\n        with_seg (bool): Whether to parse and load the semantic segmentation\n            annotation. Default: False.\n        poly2mask (bool): Whether to convert the instance masks from polygons\n            to bitmaps. Default: True.\n        file_client_args (dict): Arguments to instantiate a FileClient.\n            See :class:`mmcv.fileio.FileClient` for details.\n            Defaults to ``dict(backend='disk')``.\n    \"\"\"\n\n    def __init__(self,\n                 with_bbox=True,\n                 with_label=True,\n                 with_mask=False,\n                 with_seg=False,\n                 with_extreme=False,\n                 with_keypoint=False,\n                 poly2mask=True,\n                 file_client_args=dict(backend='disk'),\n                 spline_num=10,\n                 num_contour_points=128):\n        self.with_bbox = with_bbox\n        self.with_extreme = with_extreme\n        self.with_keypoint = with_keypoint\n        self.with_label = with_label\n        self.with_mask = with_mask\n        self.with_seg = with_seg\n        self.poly2mask = poly2mask\n        self.file_client_args = file_client_args.copy()\n        self.file_client = None\n        self.spline_num = spline_num\n        self.num_points = num_contour_points\n        self.spline_poly_num = self.num_points * self.spline_num\n\n    def _load_bboxes(self, results):\n        \"\"\"Private function to load bounding box annotations.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded bounding box annotations.\n        \"\"\"\n\n        ann_info = results['ann_info']\n        results['gt_bboxes'] = ann_info['bboxes'].copy()\n\n        gt_bboxes_ignore = ann_info.get('bboxes_ignore', None)\n        if gt_bboxes_ignore is not None:\n            results['gt_bboxes_ignore'] = gt_bboxes_ignore.copy()\n            results['bbox_fields'].append('gt_bboxes_ignore')\n        results['bbox_fields'].append('gt_bboxes')\n        return results\n    \n    def _load_extremes(self, results):\n        \"\"\"Private function to load bounding box annotations.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded bounding box annotations.\n        \"\"\"\n\n        ann_info = results['ann_info']\n        results['gt_extremes'] = ann_info['extremes'].copy()\n        results['extreme_fields'].append('gt_extremes')\n        return results\n\n    def _load_keypoints(self, results):\n        \"\"\"Private function to load bounding box annotations.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded bounding box annotations.\n        \"\"\"\n\n        ann_info = results['ann_info']\n        results['gt_keypoints'] = ann_info['keypoints'].copy()\n        results['keypoint_fields'].append('gt_keypoints')\n        return results\n\n    def _load_labels(self, results):\n        \"\"\"Private function to load label annotations.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded label annotations.\n        \"\"\"\n\n        results['gt_labels'] = results['ann_info']['labels'].copy()\n        return results\n\n    def _poly2mask(self, mask_ann, img_h, img_w):\n        \"\"\"Private function to convert masks represented with polygon to\n        bitmaps.\n\n        Args:\n            mask_ann (list | dict): Polygon mask annotation input.\n            img_h (int): The height of output mask.\n            img_w (int): The width of output mask.\n\n        Returns:\n            numpy.ndarray: The decode bitmap mask of shape (img_h, img_w).\n        \"\"\"\n\n        if isinstance(mask_ann, list):\n            # polygon -- a single object might consist of multiple parts\n            # we merge all parts into one mask rle code\n            rles = maskUtils.frPyObjects(mask_ann, img_h, img_w)\n            rle = maskUtils.merge(rles)\n        elif isinstance(mask_ann['counts'], list):\n            # uncompressed RLE\n            rle = maskUtils.frPyObjects(mask_ann, img_h, img_w)\n        else:\n            # rle\n            rle = mask_ann\n        mask = maskUtils.decode(rle)\n        return mask\n\n    def process_polygons(self, polygons):\n        \"\"\"Convert polygons to list of ndarray and filter invalid polygons.\n\n        Args:\n            polygons (list[list]): Polygons of one instance.\n\n        Returns:\n            list[numpy.ndarray]: Processed polygons.\n        \"\"\"\n\n        polygons = [np.array(p) for p in polygons]\n        valid_polygons = []\n        for polygon in polygons:\n            if len(polygon) % 2 == 0 and len(polygon) >= 6:\n                valid_polygons.append(polygon)\n        return valid_polygons\n\n    def uniformsample(self, pgtnp_px2, newpnum): # https://github.com/zju3dv/snake\n        pnum, cnum = pgtnp_px2.shape\n        assert cnum == 2\n\n        idxnext_p = (np.arange(pnum, dtype=np.int32) + 1) % pnum\n        pgtnext_px2 = pgtnp_px2[idxnext_p]\n        edgelen_p = np.sqrt(np.sum((pgtnext_px2 - pgtnp_px2) ** 2, axis=1))\n        edgeidxsort_p = np.argsort(edgelen_p)\n\n        # two cases\n        # we need to remove gt points\n        # we simply remove shortest paths\n        if pnum > newpnum:\n            edgeidxkeep_k = edgeidxsort_p[pnum - newpnum:]\n            edgeidxsort_k = np.sort(edgeidxkeep_k)\n            pgtnp_kx2 = pgtnp_px2[edgeidxsort_k]\n            assert pgtnp_kx2.shape[0] == newpnum\n            return pgtnp_kx2\n        # we need to add gt points\n        # we simply add it uniformly\n        else:\n            edgenum = np.round(edgelen_p * newpnum / np.sum(edgelen_p)).astype(np.int32)\n            for i in range(pnum):\n                if edgenum[i] == 0:\n                    edgenum[i] = 1\n\n            # after round, it may has 1 or 2 mismatch\n            edgenumsum = np.sum(edgenum)\n            if edgenumsum != newpnum:\n\n                if edgenumsum > newpnum:\n\n                    id = -1\n                    passnum = edgenumsum - newpnum\n                    while passnum > 0:\n                        edgeid = edgeidxsort_p[id]\n                        if edgenum[edgeid] > passnum:\n                            edgenum[edgeid] -= passnum\n                            passnum -= passnum\n                        else:\n                            passnum -= edgenum[edgeid] - 1\n                            edgenum[edgeid] -= edgenum[edgeid] - 1\n                            id -= 1\n                else:\n                    id = -1\n                    edgeid = edgeidxsort_p[id]\n                    edgenum[edgeid] += newpnum - edgenumsum\n\n            assert np.sum(edgenum) == newpnum\n\n            psample = []\n            for i in range(pnum):\n                pb_1x2 = pgtnp_px2[i:i + 1]\n                pe_1x2 = pgtnext_px2[i:i + 1]\n\n                pnewnum = edgenum[i]\n                wnp_kx1 = np.arange(edgenum[i], dtype=np.float32).reshape(-1, 1) / edgenum[i]\n\n                pmids = pb_1x2 * (1 - wnp_kx1) + pe_1x2 * wnp_kx1\n                psample.append(pmids)\n\n            psamplenp = np.concatenate(psample, axis=0)\n            return psamplenp\n\n    def _polygon_area(self, poly):\n        \"\"\"Compute the area of a component of a polygon.\n\n        Using the shoelace formula:\n        https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates\n\n        Args:\n            x (ndarray): x coordinates of the component\n            y (ndarray): y coordinates of the component\n\n        Return:\n            float: the are of the component\n        \"\"\" \n        x = poly[:,0]\n        y = poly[:,1]\n        return 0.5 * np.abs(\n            np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))\n\n    def filter_tiny_polys(self, polys):\n        polys_ = []\n        for poly in polys:\n            x_min, y_min = np.min(poly[:, 0]), np.min(poly[:, 1])\n            x_max, y_max = np.max(poly[:, 0]), np.max(poly[:, 1])\n            if x_max - x_min >= 1 and y_max - y_min >= 1:\n                polys_.append(poly)\n        return [poly for poly in polys_ if self._polygon_area(poly) > 5]  \n\n    def get_cw_poly(self, poly):\n        return poly[::-1] if Polygon(poly).exterior.is_ccw else poly    \n\n    def unify_origin_polygon(self, poly):\n        new_poly = np.zeros_like(poly)\n        xmin = poly[:,0].min()  \n        xmax = poly[:,0].max()\n        ymin = poly[:,1].min()  \n        ymax = poly[:,1].max() \n        tcx = (xmin + xmax)/2 \n        tcy = ymin\n        dist = (poly[:,0]-tcx)**2 + (poly[:,1]-tcy)**2\n        min_dist_idx = dist.argmin()\n        new_poly[:(poly.shape[0]-min_dist_idx)] = poly[min_dist_idx:]\n        new_poly[(poly.shape[0]-min_dist_idx):] = poly[:min_dist_idx]\n        return new_poly\n\n    def unify_polygons(self, polygons, gt_bbox):\n        polygons = [np.array(p).reshape(-1, 2) for p in polygons]\n        filtered_polygons = self.filter_tiny_polys(polygons)\n        if len(filtered_polygons) == 0:\n            xmin, ymin, xmax, ymax = gt_bbox[0], gt_bbox[1], gt_bbox[2], gt_bbox[3]\n            tl = np.stack([xmin, ymin])\n            bl = np.stack([xmin, ymax])\n            br = np.stack([xmax, ymax])\n            tr = np.stack([xmax, ymin])\n            filtered_polygons = [np.stack([tl, bl, br, tr])]\n\n        valid_polygons = []\n        for polygon in filtered_polygons:\n            sampled_polygon = self.uniformsample(polygon, self.spline_poly_num)\n            tt_idx = np.argmin(np.power(sampled_polygon-sampled_polygon[0], 2).sum(axis=1))\n            valid_polygon = np.roll(sampled_polygon, -tt_idx, axis=0)[::self.spline_num]\n            cw_valid_polygon = self.get_cw_poly(valid_polygon)\n            unify_origin_polygon = self.unify_origin_polygon(cw_valid_polygon)\n            valid_polygons.append(unify_origin_polygon.reshape(-1))\n        return valid_polygons\n\n    def _load_masks(self, results):\n        \"\"\"Private function to load mask annotations.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded mask annotations.\n                If ``self.poly2mask`` is set ``True``, `gt_mask` will contain\n                :obj:`PolygonMasks`. Otherwise, :obj:`BitmapMasks` is used.\n        \"\"\"\n\n        h, w = results['img_info']['height'], results['img_info']['width']\n        gt_masks = results['ann_info']['masks']\n        if self.poly2mask:\n            gt_masks = BitmapMasks(\n                [self._poly2mask(mask, h, w) for mask in gt_masks], h, w)\n        else:\n            gt_bboxes = results['ann_info']['bboxes']\n            gt_masks = PolygonMasks(\n                [self.unify_polygons(polygons, gt_bboxes[i]) for i, polygons in enumerate(gt_masks)],\n                h, w)\n        results['gt_masks'] = gt_masks\n        results['mask_fields'].append('gt_masks')\n        return results\n\n    def _load_semantic_seg(self, results):\n        \"\"\"Private function to load semantic segmentation annotations.\n\n        Args:\n            results (dict): Result dict from :obj:`dataset`.\n\n        Returns:\n            dict: The dict contains loaded semantic segmentation annotations.\n        \"\"\"\n\n        if self.file_client is None:\n            self.file_client = mmcv.FileClient(**self.file_client_args)\n\n        filename = osp.join(results['seg_prefix'],\n                            results['ann_info']['seg_map'])\n        img_bytes = self.file_client.get(filename)\n        results['gt_semantic_seg'] = mmcv.imfrombytes(\n            img_bytes, flag='unchanged').squeeze()\n        results['seg_fields'].append('gt_semantic_seg')\n        return results\n\n    def __call__(self, results):\n        \"\"\"Call function to load multiple types annotations\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded bounding box, label, mask and\n                semantic segmentation annotations.\n        \"\"\"\n\n        if self.with_bbox:\n            results = self._load_bboxes(results)\n            if results is None:\n                return None\n        if self.with_label:\n            results = self._load_labels(results)\n        if self.with_mask:\n            results = self._load_masks(results)\n        if self.with_seg:\n            results = self._load_semantic_seg(results)\n        if self.with_extreme:\n            results = self._load_extremes(results)\n        if self.with_keypoint:\n            results = self._load_keypoints(results)\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(with_bbox={self.with_bbox}, '\n        repr_str += f'(with_extreme={self.with_extreme}, '\n        repr_str += f'(with_keypoint={self.with_keypoint}, '\n        repr_str += f'with_label={self.with_label}, '\n        repr_str += f'with_mask={self.with_mask}, '\n        repr_str += f'with_seg={self.with_seg})'\n        repr_str += f'poly2mask={self.poly2mask})'\n        repr_str += f'poly2mask={self.file_client_args})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass LoadProposals(object):\n    \"\"\"Load proposal pipeline.\n\n    Required key is \"proposals\". Updated keys are \"proposals\", \"bbox_fields\".\n\n    Args:\n        num_max_proposals (int, optional): Maximum number of proposals to load.\n            If not specified, all proposals will be loaded.\n    \"\"\"\n\n    def __init__(self, num_max_proposals=None):\n        self.num_max_proposals = num_max_proposals\n\n    def __call__(self, results):\n        \"\"\"Call function to load proposals from file.\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded proposal annotations.\n        \"\"\"\n\n        proposals = results['proposals']\n        if proposals.shape[1] not in (4, 5):\n            raise AssertionError(\n                'proposals should have shapes (n, 4) or (n, 5), '\n                f'but found {proposals.shape}')\n        proposals = proposals[:, :4]\n\n        if self.num_max_proposals is not None:\n            proposals = proposals[:self.num_max_proposals]\n\n        if len(proposals) == 0:\n            proposals = np.array([[0, 0, 0, 0]], dtype=np.float32)\n        results['proposals'] = proposals\n        results['bbox_fields'].append('proposals')\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + \\\n            f'(num_max_proposals={self.num_max_proposals})'\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/loading_reppointsv2.py",
    "content": "import numpy as np\nimport cv2\nimport mmcv\n\nfrom ..builder import PIPELINES\n\n\n@PIPELINES.register_module()\nclass LoadRPDV2Annotations(object):\n    \"\"\"Load mutiple types of annotations.\n\n    Args:\n        with_bbox (bool): Whether to parse and load the bbox annotation.\n             Default: True.\n        with_label (bool): Whether to parse and load the label annotation.\n            Default: True.\n        with_mask (bool): Whether to parse and load the mask annotation.\n             Default: False.\n        with_seg (bool): Whether to parse and load the semantic segmentation\n            annotation. Default: False.\n        poly2mask (bool): Whether to convert the instance masks from polygons\n            to bitmaps. Default: True.\n        file_client_args (dict): Arguments to instantiate a FileClient.\n            See :class:`mmcv.fileio.FileClient` for details.\n            Defaults to ``dict(backend='disk')``.\n    \"\"\"\n    def __init__(self, num_classes=80):\n        super(LoadRPDV2Annotations, self).__init__()\n        self.num_classes = num_classes\n\n    def _load_semantic_map_from_box(self, results):\n        gt_bboxes = results['gt_bboxes']\n        gt_labels = results['gt_labels']\n        pad_shape = results['pad_shape']\n        gt_areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * (gt_bboxes[:, 3] - gt_bboxes[:, 1])\n        gt_sem_map = np.zeros((self.num_classes, int(pad_shape[0] / 8), int(pad_shape[1] / 8)), dtype=np.float32)\n        gt_sem_weights = np.zeros((self.num_classes, int(pad_shape[0] / 8), int(pad_shape[1] / 8)), dtype=np.float32)\n\n        indexs = np.argsort(gt_areas)\n        for ind in indexs[::-1]:\n            box = gt_bboxes[ind]\n            box_mask = np.zeros((int(pad_shape[0] / 8), int(pad_shape[1] / 8)), dtype=np.int64)\n            box_mask[int(box[1] / 8):int(box[3] / 8) + 1, int(box[0] / 8):int(box[2] / 8) + 1] = 1\n            gt_sem_map[gt_labels[ind]][box_mask > 0] = 1\n            gt_sem_weights[gt_labels[ind]][box_mask > 0] = 1 / gt_areas[ind]\n\n        results['gt_sem_map'] = gt_sem_map\n        results['gt_sem_weights'] = gt_sem_weights\n\n        return results\n\n    def __call__(self, results):\n        \"\"\"Call function to load multiple types annotations\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded bounding box, label, mask and\n                semantic segmentation annotations.\n        \"\"\"\n\n        results = self._load_semantic_map_from_box(results)\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(with_bbox_semantic_map={True}, '\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass LoadDenseRPDV2Annotations(object):\n    \"\"\"Load mutiple types of annotations.\n\n    Args:\n        with_bbox (bool): Whether to parse and load the bbox annotation.\n             Default: True.\n        with_label (bool): Whether to parse and load the label annotation.\n            Default: True.\n        with_mask (bool): Whether to parse and load the mask annotation.\n             Default: False.\n        with_seg (bool): Whether to parse and load the semantic segmentation\n            annotation. Default: False.\n        poly2mask (bool): Whether to convert the instance masks from polygons\n            to bitmaps. Default: True.\n        file_client_args (dict): Arguments to instantiate a FileClient.\n            See :class:`mmcv.fileio.FileClient` for details.\n            Defaults to ``dict(backend='disk')``.\n    \"\"\"\n    def __init__(self):\n        super(LoadDenseRPDV2Annotations, self).__init__()\n\n    def mask_to_poly(self, mask):\n        contours, _ = cv2.findContours(mask.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n        polygons = []\n        for contour in contours:\n            contour = contour.flatten().tolist()\n            if len(contour) > 4:\n                polygons.append(contour)\n        return polygons\n\n    def _load_semantic_map_from_mask(self, results):\n        gt_bboxes = results['gt_bboxes']\n        gt_masks = results['gt_masks'].masks\n        gt_labels = results['gt_labels']\n        pad_shape = results['pad_shape']\n        gt_sem_map = np.zeros((int(pad_shape[0] / 8), int(pad_shape[1] / 8)), dtype=np.int64)\n\n        for i in range(gt_bboxes.shape[0]):\n            mask_rescale = mmcv.imrescale(gt_masks[i], 1. / 8, interpolation='nearest')\n            gt_sem_map = np.maximum(gt_sem_map, mask_rescale * (gt_labels[i] + 1))\n        gt_sem_map = gt_sem_map - 1\n\n        gt_sem_map = gt_sem_map[None, ...]\n        results['gt_sem_map'] = gt_sem_map\n\n        return results\n\n    def _load_contours(self, results):\n        gt_bboxes = results['gt_bboxes']\n        gt_masks = results['gt_masks'].masks\n        gt_contour_map = np.zeros_like(gt_masks[0], dtype=np.uint8)\n\n        for i in range(gt_bboxes.shape[0]):\n            polygons = self.mask_to_poly(gt_masks[i])\n            for poly in polygons:\n                poly = np.array(poly).astype(np.int)\n                for j in range(len(poly) // 2):\n                    x_0, y_0 = poly[2 * j:2 * j + 2]\n                    if j == len(poly) // 2 - 1:\n                        x_1, y_1 = poly[0:2]\n                    else:\n                        x_1, y_1 = poly[2 * j + 2:2 * j + 4]\n                    cv2.line(gt_contour_map, (x_0, y_0), (x_1, y_1), 1, thickness=2)\n\n        gt_contours = np.stack([np.nonzero(gt_contour_map)[1], np.nonzero(gt_contour_map)[0]], axis=1).astype(np.float32)\n\n        results['gt_contours'] = gt_contours\n\n        return results\n\n    def __call__(self, results):\n        \"\"\"Call function to load multiple types annotations\n\n        Args:\n            results (dict): Result dict from :obj:`mmdet.CustomDataset`.\n\n        Returns:\n            dict: The dict contains loaded bounding box, label, mask and\n                semantic segmentation annotations.\n        \"\"\"\n\n        results = self._load_semantic_map_from_mask(results)\n        results = self._load_contours(results)\n\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(with_mask_semantic_map={True}, '\n        repr_str += f'(with_contours={True}, '\n        return repr_str\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/test_time_aug.py",
    "content": "import warnings\n\nimport mmcv\n\nfrom ..builder import PIPELINES\nfrom .compose import Compose\n\n\n@PIPELINES.register_module()\nclass MultiScaleFlipAug(object):\n    \"\"\"Test-time augmentation with multiple scales and flipping\n\n    An example configuration is as followed:\n\n    .. code-block::\n\n        img_scale=[(1333, 400), (1333, 800)],\n        flip=True,\n        transforms=[\n            dict(type='Resize', keep_ratio=True),\n            dict(type='RandomFlip'),\n            dict(type='Normalize', **img_norm_cfg),\n            dict(type='Pad', size_divisor=32),\n            dict(type='ImageToTensor', keys=['img']),\n            dict(type='Collect', keys=['img']),\n        ]\n\n    After MultiScaleFLipAug with above configuration, the results are wrapped\n    into lists of the same length as followed:\n\n    .. code-block::\n\n        dict(\n            img=[...],\n            img_shape=[...],\n            scale=[(1333, 400), (1333, 400), (1333, 800), (1333, 800)]\n            flip=[False, True, False, True]\n            ...\n        )\n\n    Args:\n        transforms (list[dict]): Transforms to apply in each augmentation.\n        img_scale (tuple | list[tuple] | None): Images scales for resizing.\n        scale_factor (float | list[float] | None): Scale factors for resizing.\n        flip (bool): Whether apply flip augmentation. Default: False.\n        flip_direction (str | list[str]): Flip augmentation directions,\n            options are \"horizontal\" and \"vertical\". If flip_direction is list,\n            multiple flip augmentations will be applied.\n            It has no effect when flip == False. Default: \"horizontal\".\n    \"\"\"\n\n    def __init__(self,\n                 transforms,\n                 img_scale=None,\n                 scale_factor=None,\n                 flip=False,\n                 flip_direction='horizontal'):\n        self.transforms = Compose(transforms)\n        assert (img_scale is None) ^ (scale_factor is None), (\n            'Must have but only one variable can be setted')\n        if img_scale is not None:\n            self.img_scale = img_scale if isinstance(img_scale,\n                                                     list) else [img_scale]\n            self.scale_key = 'scale'\n            assert mmcv.is_list_of(self.img_scale, tuple)\n        else:\n            self.img_scale = scale_factor if isinstance(\n                scale_factor, list) else [scale_factor]\n            self.scale_key = 'scale_factor'\n\n        self.flip = flip\n        self.flip_direction = flip_direction if isinstance(\n            flip_direction, list) else [flip_direction]\n        assert mmcv.is_list_of(self.flip_direction, str)\n        if not self.flip and self.flip_direction != ['horizontal']:\n            warnings.warn(\n                'flip_direction has no effect when flip is set to False')\n        if (self.flip\n                and not any([t['type'] == 'RandomFlip' for t in transforms])):\n            warnings.warn(\n                'flip has no effect when RandomFlip is not in transforms')\n\n    def __call__(self, results):\n        \"\"\"Call function to apply test time augment transforms on results.\n\n        Args:\n            results (dict): Result dict contains the data to transform.\n\n        Returns:\n           dict[str: list]: The augmented data, where each value is wrapped\n               into a list.\n        \"\"\"\n\n        aug_data = []\n        flip_aug = [False, True] if self.flip else [False]\n        for scale in self.img_scale:\n            for flip in flip_aug:\n                for direction in self.flip_direction:\n                    _results = results.copy()\n                    _results[self.scale_key] = scale\n                    _results['flip'] = flip\n                    _results['flip_direction'] = direction\n                    data = self.transforms(_results)\n                    aug_data.append(data)\n        # list of dict to dict of list\n        aug_data_dict = {key: [] for key in aug_data[0]}\n        for data in aug_data:\n            for key, val in data.items():\n                aug_data_dict[key].append(val)\n        return aug_data_dict\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(transforms={self.transforms}, '\n        repr_str += f'img_scale={self.img_scale}, flip={self.flip})'\n        repr_str += f'flip_direction={self.flip_direction}'\n        return repr_str\n"
  },
  {
    "path": "code/mmdet/datasets/pipelines/transforms.py",
    "content": "import inspect\n\nimport mmcv\nimport numpy as np\nfrom numpy import random\n\nfrom mmdet.core import PolygonMasks\nfrom mmdet.core.evaluation.bbox_overlaps import bbox_overlaps\nfrom ..builder import PIPELINES\n\ntry:\n    from imagecorruptions import corrupt\nexcept ImportError:\n    corrupt = None\n\ntry:\n    import albumentations\n    from albumentations import Compose\nexcept ImportError:\n    albumentations = None\n    Compose = None\n\n\n@PIPELINES.register_module()\nclass Resize(object):\n    \"\"\"Resize images & bbox & mask.\n\n    This transform resizes the input image to some scale. Bboxes and masks are\n    then resized with the same scale factor. If the input dict contains the key\n    \"scale\", then the scale in the input dict is used, otherwise the specified\n    scale in the init method is used. If the input dict contains the key\n    \"scale_factor\" (if MultiScaleFlipAug does not give img_scale but\n    scale_factor), the actual scale will be computed by image shape and\n    scale_factor.\n\n    `img_scale` can either be a tuple (single-scale) or a list of tuple\n    (multi-scale). There are 3 multiscale modes:\n\n    - ``ratio_range is not None``: randomly sample a ratio from the ratio range\n      and multiply it with the image scale.\n    - ``ratio_range is None`` and ``multiscale_mode == \"range\"``: randomly\n      sample a scale from the multiscale range.\n    - ``ratio_range is None`` and ``multiscale_mode == \"value\"``: randomly\n      sample a scale from multiple scales.\n\n    Args:\n        img_scale (tuple or list[tuple]): Images scales for resizing.\n        multiscale_mode (str): Either \"range\" or \"value\".\n        ratio_range (tuple[float]): (min_ratio, max_ratio)\n        keep_ratio (bool): Whether to keep the aspect ratio when resizing the\n            image.\n    \"\"\"\n\n    def __init__(self,\n                 img_scale=None,\n                 multiscale_mode='range',\n                 ratio_range=None,\n                 keep_ratio=True):\n        if img_scale is None:\n            self.img_scale = None\n        else:\n            if isinstance(img_scale, list):\n                self.img_scale = img_scale\n            else:\n                self.img_scale = [img_scale]\n            assert mmcv.is_list_of(self.img_scale, tuple)\n\n        if ratio_range is not None:\n            # mode 1: given a scale and a range of image ratio\n            assert len(self.img_scale) == 1\n        else:\n            # mode 2: given multiple scales or a range of scales\n            assert multiscale_mode in ['value', 'range']\n\n        self.multiscale_mode = multiscale_mode\n        self.ratio_range = ratio_range\n        self.keep_ratio = keep_ratio\n\n    @staticmethod\n    def random_select(img_scales):\n        \"\"\"Randomly select an img_scale from given candidates.\n\n        Args:\n            img_scales (list[tuple]): Images scales for selection.\n\n        Returns:\n            (tuple, int): Returns a tuple ``(img_scale, scale_dix)``,\n                where ``img_scale`` is the selected image scale and\n                ``scale_idx`` is the selected index in the given candidates.\n        \"\"\"\n\n        assert mmcv.is_list_of(img_scales, tuple)\n        scale_idx = np.random.randint(len(img_scales))\n        img_scale = img_scales[scale_idx]\n        return img_scale, scale_idx\n\n    @staticmethod\n    def random_sample(img_scales):\n        \"\"\"Randomly sample an img_scale when ``multiscale_mode=='range'``.\n\n        Args:\n            img_scales (list[tuple]): Images scale range for sampling.\n                There must be two tuples in img_scales, which specify the lower\n                and uper bound of image scales.\n\n        Returns:\n            (tuple, None): Returns a tuple ``(img_scale, None)``, where\n                ``img_scale`` is sampled scale and None is just a placeholder\n                to be consistent with :func:`random_select`.\n        \"\"\"\n\n        assert mmcv.is_list_of(img_scales, tuple) and len(img_scales) == 2\n        img_scale_long = [max(s) for s in img_scales]\n        img_scale_short = [min(s) for s in img_scales]\n        long_edge = np.random.randint(\n            min(img_scale_long),\n            max(img_scale_long) + 1)\n        short_edge = np.random.randint(\n            min(img_scale_short),\n            max(img_scale_short) + 1)\n        img_scale = (long_edge, short_edge)\n        return img_scale, None\n\n    @staticmethod\n    def random_sample_ratio(img_scale, ratio_range):\n        \"\"\"Randomly sample an img_scale when ``ratio_range`` is specified.\n\n        A ratio will be randomly sampled from the range specified by\n        ``ratio_range``. Then it would be multiplied with ``img_scale`` to\n        generate sampled scale.\n\n        Args:\n            img_scale (tuple): Images scale base to multiply with ratio.\n            ratio_range (tuple[float]): The minimum and maximum ratio to scale\n                the ``img_scale``.\n\n        Returns:\n            (tuple, None): Returns a tuple ``(scale, None)``, where\n                ``scale`` is sampled ratio multiplied with ``img_scale`` and\n                None is just a placeholder to be consistent with\n                :func:`random_select`.\n        \"\"\"\n\n        assert isinstance(img_scale, tuple) and len(img_scale) == 2\n        min_ratio, max_ratio = ratio_range\n        assert min_ratio <= max_ratio\n        ratio = np.random.random_sample() * (max_ratio - min_ratio) + min_ratio\n        scale = int(img_scale[0] * ratio), int(img_scale[1] * ratio)\n        return scale, None\n\n    def _random_scale(self, results):\n        \"\"\"Randomly sample an img_scale according to ``ratio_range`` and\n        ``multiscale_mode``.\n\n        If ``ratio_range`` is specified, a ratio will be sampled and be\n        multiplied with ``img_scale``.\n        If multiple scales are specified by ``img_scale``, a scale will be\n        sampled according to ``multiscale_mode``.\n        Otherwise, single scale will be used.\n\n        Args:\n            results (dict): Result dict from :obj:`dataset`.\n\n        Returns:\n            dict: Two new keys 'scale` and 'scale_idx` are added into\n                ``results``, which would be used by subsequent pipelines.\n        \"\"\"\n\n        if self.ratio_range is not None:\n            scale, scale_idx = self.random_sample_ratio(\n                self.img_scale[0], self.ratio_range)\n        elif len(self.img_scale) == 1:\n            scale, scale_idx = self.img_scale[0], 0\n        elif self.multiscale_mode == 'range':\n            scale, scale_idx = self.random_sample(self.img_scale)\n        elif self.multiscale_mode == 'value':\n            scale, scale_idx = self.random_select(self.img_scale)\n        else:\n            raise NotImplementedError\n\n        results['scale'] = scale\n        results['scale_idx'] = scale_idx\n\n    def _resize_img(self, results):\n        \"\"\"Resize images with ``results['scale']``.\"\"\"\n        for key in results.get('img_fields', ['img']):\n            if self.keep_ratio:\n                img, scale_factor = mmcv.imrescale(\n                    results[key], results['scale'], return_scale=True)\n                # the w_scale and h_scale has minor difference\n                # a real fix should be done in the mmcv.imrescale in the future\n                new_h, new_w = img.shape[:2]\n                h, w = results[key].shape[:2]\n                w_scale = new_w / w\n                h_scale = new_h / h\n            else:\n                img, w_scale, h_scale = mmcv.imresize(\n                    results[key], results['scale'], return_scale=True)\n            results[key] = img\n\n            scale_factor = np.array([w_scale, h_scale, w_scale, h_scale],\n                                    dtype=np.float32)\n            results['img_shape'] = img.shape\n            # in case that there is no padding\n            results['pad_shape'] = img.shape\n            results['scale_factor'] = scale_factor\n            results['keep_ratio'] = self.keep_ratio\n\n    def _resize_bboxes(self, results):\n        \"\"\"Resize bounding boxes with ``results['scale_factor']``.\"\"\"\n        img_shape = results['img_shape']\n        for key in results.get('bbox_fields', []):\n            bboxes = results[key] * results['scale_factor']\n            bboxes[:, 0::2] = np.clip(bboxes[:, 0::2], 0, img_shape[1])\n            bboxes[:, 1::2] = np.clip(bboxes[:, 1::2], 0, img_shape[0])\n            results[key] = bboxes\n\n    def _resize_extremes(self, results):\n        \"\"\"Resize bounding boxes with ``results['scale_factor']``.\"\"\"\n        img_shape = results['img_shape']\n        for key in results.get('extreme_fields', []):\n            scale_factor = np.tile(results['scale_factor'][:2], (1, 5))\n            extremes = results[key] * scale_factor\n            extremes[:, 0::2] = np.clip(extremes[:, 0::2], 0, img_shape[1])\n            extremes[:, 1::2] = np.clip(extremes[:, 1::2], 0, img_shape[0])\n            results[key] = extremes\n\n    def _resize_keypoints(self, results):\n        \"\"\"Resize keypoints with ``results['scale_factor']``.\"\"\"\n        img_shape = results['img_shape']\n        for key in results.get('keypoint_fields', []):\n            keypoints_x = results[key][:, 0::3] * results['scale_factor'][0]\n            keypoints_y = results[key][:, 1::3] * results['scale_factor'][1]\n\n            keypoints_x = np.clip(keypoints_x, 0, img_shape[1])\n            keypoints_y = np.clip(keypoints_y, 0, img_shape[0])\n\n            results[key][:, 0::3] = keypoints_x\n            results[key][:, 1::3] = keypoints_y\n\n    def _resize_masks(self, results):\n        \"\"\"Resize masks with ``results['scale']``\"\"\"\n        for key in results.get('mask_fields', []):\n            if results[key] is None:\n                continue\n            if self.keep_ratio:\n                results[key] = results[key].rescale(results['scale'])\n            else:\n                results[key] = results[key].resize(results['img_shape'][:2])\n\n    def _resize_seg(self, results):\n        \"\"\"Resize semantic segmentation map with ``results['scale']``.\"\"\"\n        for key in results.get('seg_fields', []):\n            if self.keep_ratio:\n                gt_seg = mmcv.imrescale(\n                    results[key], results['scale'], interpolation='nearest')\n            else:\n                gt_seg = mmcv.imresize(\n                    results[key], results['scale'], interpolation='nearest')\n            results['gt_semantic_seg'] = gt_seg\n\n    def __call__(self, results):\n        \"\"\"Call function to resize images, bounding boxes, masks, semantic\n        segmentation map.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Resized results, 'img_shape', 'pad_shape', 'scale_factor',\n                'keep_ratio' keys are added into result dict.\n        \"\"\"\n\n        if 'scale' not in results:\n            if 'scale_factor' in results:\n                img_shape = results['img'].shape[:2]\n                scale_factor = results['scale_factor']\n                assert isinstance(scale_factor, float)\n                results['scale'] = tuple(\n                    [int(x * scale_factor) for x in img_shape][::-1])\n            else:\n                self._random_scale(results)\n        else:\n            assert 'scale_factor' not in results, (\n                \"scale and scale_factor cannot be both set.\")\n\n        self._resize_img(results)\n        self._resize_bboxes(results)\n        self._resize_extremes(results)\n        self._resize_keypoints(results)\n        self._resize_masks(results)\n        self._resize_seg(results)\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(img_scale={self.img_scale}, '\n        repr_str += f'multiscale_mode={self.multiscale_mode}, '\n        repr_str += f'ratio_range={self.ratio_range}, '\n        repr_str += f'keep_ratio={self.keep_ratio})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass RandomFlip(object):\n    \"\"\"Flip the image & bbox & mask.\n\n    If the input dict contains the key \"flip\", then the flag will be used,\n    otherwise it will be randomly decided by a ratio specified in the init\n    method.\n\n    Args:\n        flip_ratio (float, optional): The flipping probability. Default: None.\n        direction(str, optional): The flipping direction. Options are\n            'horizontal' and 'vertical'. Default: 'horizontal'.\n    \"\"\"\n\n    def __init__(self, flip_ratio=None, direction='horizontal', keep_poly_clockwise=True):\n        self.flip_ratio = flip_ratio\n        self.direction = direction\n        self.keep_poly_clockwise = keep_poly_clockwise\n        self.keypoint_flip_idx = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10],\n                                  [11, 12], [13, 14], [15, 16]]\n        if flip_ratio is not None:\n            assert flip_ratio >= 0 and flip_ratio <= 1\n        assert direction in ['horizontal', 'vertical']\n\n    def bbox_flip(self, bboxes, img_shape, direction):\n        \"\"\"Flip bboxes horizontally.\n\n        Args:\n            bboxes (numpy.ndarray): Bounding boxes, shape (..., 4*k)\n            img_shape (tuple[int]): Image shape (height, width)\n            direction (str): Flip direction. Options are 'horizontal',\n                'vertical'.\n\n        Returns:\n            numpy.ndarray: Flipped bounding boxes.\n        \"\"\"\n        assert bboxes.shape[-1] % 4 == 0\n        flipped = bboxes.copy()\n        if direction == 'horizontal':\n            w = img_shape[1]\n            flipped[..., 0::4] = w - bboxes[..., 2::4]\n            flipped[..., 2::4] = w - bboxes[..., 0::4]\n        elif direction == 'vertical':\n            h = img_shape[0]\n            flipped[..., 1::4] = h - bboxes[..., 3::4]\n            flipped[..., 3::4] = h - bboxes[..., 1::4]\n        else:\n            raise ValueError(f\"Invalid flipping direction '{direction}'\")\n        return flipped\n\n    def extreme_flip(self, extremes, img_shape, direction):\n        \"\"\"Flip bboxes horizontally.\n\n        Args:\n            bboxes (numpy.ndarray): Bounding boxes, shape (..., 4*k)\n            img_shape (tuple[int]): Image shape (height, width)\n            direction (str): Flip direction. Options are 'horizontal',\n                'vertical'.\n\n        Returns:\n            numpy.ndarray: Flipped bounding boxes.\n        \"\"\"\n        assert extremes.shape[-1] % 10 == 0\n        flipped = extremes.copy()\n        if direction == 'horizontal':\n            w = img_shape[1]\n            flipped[..., 0::10] = w - extremes[..., 0::10]\n            flipped[..., 2::10] = w - extremes[..., 6::10]\n            flipped[..., 3::10] = extremes[..., 7::10]\n            flipped[..., 4::10] = w - extremes[..., 4::10]\n            flipped[..., 6::10] = w - extremes[..., 2::10]\n            flipped[..., 7::10] = extremes[..., 3::10]\n            flipped[..., 8::10] = w - extremes[..., 8::10]\n        elif direction == 'vertical':\n            h = img_shape[0]\n            flipped[..., 1::10] = h - extremes[..., 5::10]\n            flipped[..., 0::10] = extremes[..., 4::10]\n            flipped[..., 3::10] = h - extremes[..., 3::10]\n            flipped[..., 5::10] = h - extremes[..., 1::10]\n            flipped[..., 4::10] = extremes[..., 0::10]\n            flipped[..., 7::10] = h - extremes[..., 7::10]\n            flipped[..., 9::10] = h - extremes[..., 9::10]\n        else:\n            raise ValueError(f\"Invalid flipping direction '{direction}'\")\n        return flipped\n\n    def keypoint_flip(self, keypoints, img_shape, direction):\n        \"\"\"Flip points horizontally.\n        \"\"\"\n        assert keypoints.shape[-1] % 17 == 0\n        flipped = keypoints.copy()\n        if direction == 'horizontal':\n            w = img_shape[1]\n            flipped[:, 0::3] = w - flipped[:, 0::3]\n            flipped = flipped.reshape(flipped.shape[0], -1, 3)\n            for e in self.keypoint_flip_idx:\n                flipped[:, e[0]], flipped[:, e[1]] = flipped[:, e[1]].copy(), flipped[:, e[0]].copy()\n            flipped = flipped.reshape(flipped.shape[0], -1)\n        elif direction == 'vertical':\n            h = img_shape[0]\n            flipped[:, 1::3] = h - flipped[:, 1::3]\n        else:\n            raise ValueError(f\"Invalid flipping direction '{direction}'\")\n        return flipped\n\n    def __call__(self, results):\n        \"\"\"Call function to flip bounding boxes, masks, semantic segmentation\n        maps.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Flipped results, 'flip', 'flip_direction' keys are added into\n                result dict.\n        \"\"\"\n\n        if 'flip' not in results:\n            flip = True if np.random.rand() < self.flip_ratio else False\n            results['flip'] = flip\n        if 'flip_direction' not in results:\n            results['flip_direction'] = self.direction\n        if results['flip']:\n            # flip image\n            for key in results.get('img_fields', ['img']):\n                results[key] = mmcv.imflip(\n                    results[key], direction=results['flip_direction'])\n            # flip bboxes\n            for key in results.get('bbox_fields', []):\n                results[key] = self.bbox_flip(results[key],\n                                              results['img_shape'],\n                                              results['flip_direction'])\n            # flip extremes\n            for key in results.get('extreme_fields', []):\n                results[key] = self.extreme_flip(results[key],\n                                                 results['img_shape'],\n                                                 results['flip_direction'])\n            # flip keypoints\n            for key in results.get('keypoint_fields', []):\n                results[key] = self.keypoint_flip(results[key],\n                                                  results['img_shape'],\n                                                  results['flip_direction'])      \n            # flip masks\n            for key in results.get('mask_fields', []):\n                results[key] = results[key].flip(results['flip_direction'],\n                                                 self.keep_poly_clockwise)\n\n\n            # flip segs\n            for key in results.get('seg_fields', []):\n                results[key] = mmcv.imflip(\n                    results[key], direction=results['flip_direction'])\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + f'(flip_ratio={self.flip_ratio})'\n\n\n@PIPELINES.register_module()\nclass Pad(object):\n    \"\"\"Pad the image & mask.\n\n    There are two padding modes: (1) pad to a fixed size and (2) pad to the\n    minimum size that is divisible by some number.\n    Added keys are \"pad_shape\", \"pad_fixed_size\", \"pad_size_divisor\",\n\n    Args:\n        size (tuple, optional): Fixed padding size.\n        size_divisor (int, optional): The divisor of padded size.\n        pad_val (float, optional): Padding value, 0 by default.\n    \"\"\"\n\n    def __init__(self, size=None, size_divisor=None, pad_val=0):\n        self.size = size\n        self.size_divisor = size_divisor\n        self.pad_val = pad_val\n        # only one of size and size_divisor should be valid\n        assert size is not None or size_divisor is not None\n        assert size is None or size_divisor is None\n\n    def _pad_img(self, results):\n        \"\"\"Pad images according to ``self.size``.\"\"\"\n        for key in results.get('img_fields', ['img']):\n            if self.size is not None:\n                padded_img = mmcv.impad(results[key], self.size, self.pad_val)\n            elif self.size_divisor is not None:\n                padded_img = mmcv.impad_to_multiple(\n                    results[key], self.size_divisor, pad_val=self.pad_val)\n            results[key] = padded_img\n        results['pad_shape'] = padded_img.shape\n        results['pad_fixed_size'] = self.size\n        results['pad_size_divisor'] = self.size_divisor\n\n    def _pad_masks(self, results):\n        \"\"\"Pad masks according to ``results['pad_shape']``.\"\"\"\n        pad_shape = results['pad_shape'][:2]\n        for key in results.get('mask_fields', []):\n            results[key] = results[key].pad(pad_shape, pad_val=self.pad_val)\n\n    def _pad_seg(self, results):\n        \"\"\"Pad semantic segmentation map according to\n        ``results['pad_shape']``.\"\"\"\n        for key in results.get('seg_fields', []):\n            results[key] = mmcv.impad(results[key], results['pad_shape'][:2])\n\n    def __call__(self, results):\n        \"\"\"Call function to pad images, masks, semantic segmentation maps.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Updated result dict.\n\n        \"\"\"\n        self._pad_img(results)\n        self._pad_masks(results)\n        self._pad_seg(results)\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(size={self.size}, '\n        repr_str += f'size_divisor={self.size_divisor}, '\n        repr_str += f'pad_val={self.pad_val})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass Normalize(object):\n    \"\"\"Normalize the image.\n\n    Added key is \"img_norm_cfg\".\n\n    Args:\n        mean (sequence): Mean values of 3 channels.\n        std (sequence): Std values of 3 channels.\n        to_rgb (bool): Whether to convert the image from BGR to RGB,\n            default is true.\n    \"\"\"\n\n    def __init__(self, mean, std, to_rgb=True):\n        self.mean = np.array(mean, dtype=np.float32)\n        self.std = np.array(std, dtype=np.float32)\n        self.to_rgb = to_rgb\n\n    def __call__(self, results):\n        \"\"\"Call function to normalize images.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Normalized results, 'img_norm_cfg' key is added into\n                result dict.\n\n        \"\"\"\n        for key in results.get('img_fields', ['img']):\n            results[key] = mmcv.imnormalize(results[key], self.mean, self.std,\n                                            self.to_rgb)\n        results['img_norm_cfg'] = dict(\n            mean=self.mean, std=self.std, to_rgb=self.to_rgb)\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(mean={self.mean}, std={self.std}, to_rgb={self.to_rgb})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass RandomCrop(object):\n    \"\"\"Random crop the image & bboxes & masks.\n\n    Args:\n        crop_size (tuple): Expected size after cropping, (h, w).\n\n    Notes:\n        - If the image is smaller than the crop size, return the original image\n        - The keys for bboxes, labels and masks must be aligned. That is,\n          `gt_bboxes` corresponds to `gt_labels` and `gt_masks`, and\n          `gt_bboxes_ignore` corresponds to `gt_labels_ignore` and\n          `gt_masks_ignore`.\n        - If there are gt bboxes in an image and the cropping area does not\n          have intersection with any gt bbox, this image is skipped.\n    \"\"\"\n\n    def __init__(self, crop_size):\n        assert crop_size[0] > 0 and crop_size[1] > 0\n        self.crop_size = crop_size\n        # The key correspondence from bboxes to labels and masks.\n        self.bbox2label = {\n            'gt_bboxes': 'gt_labels',\n            'gt_bboxes_ignore': 'gt_labels_ignore'\n        }\n        self.bbox2mask = {\n            'gt_bboxes': 'gt_masks',\n            'gt_bboxes_ignore': 'gt_masks_ignore'\n        }\n\n    def __call__(self, results):\n        \"\"\"Call function to randomly crop images, bounding boxes, masks,\n        semantic segmentation maps.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Randomly cropped results, 'img_shape' key in result dict is\n                updated according to crop size.\n        \"\"\"\n\n        for key in results.get('img_fields', ['img']):\n            img = results[key]\n            margin_h = max(img.shape[0] - self.crop_size[0], 0)\n            margin_w = max(img.shape[1] - self.crop_size[1], 0)\n            offset_h = np.random.randint(0, margin_h + 1)\n            offset_w = np.random.randint(0, margin_w + 1)\n            crop_y1, crop_y2 = offset_h, offset_h + self.crop_size[0]\n            crop_x1, crop_x2 = offset_w, offset_w + self.crop_size[1]\n\n            # crop the image\n            img = img[crop_y1:crop_y2, crop_x1:crop_x2, ...]\n            img_shape = img.shape\n            results[key] = img\n        results['img_shape'] = img_shape\n\n        valid_flag = False\n        # crop bboxes accordingly and clip to the image boundary\n        for key in results.get('bbox_fields', []):\n            # e.g. gt_bboxes and gt_bboxes_ignore\n            bbox_offset = np.array([offset_w, offset_h, offset_w, offset_h],\n                                   dtype=np.float32)\n            bboxes = results[key] - bbox_offset\n            bboxes[:, 0::2] = np.clip(bboxes[:, 0::2], 0, img_shape[1])\n            bboxes[:, 1::2] = np.clip(bboxes[:, 1::2], 0, img_shape[0])\n            valid_inds = (bboxes[:, 2] > bboxes[:, 0]) & (\n                bboxes[:, 3] > bboxes[:, 1])\n            # When there is no gt bbox, cropping is conducted.\n            # When the crop is valid, cropping is conducted.\n            if len(valid_inds) == 0 or valid_inds.any():\n                valid_flag = True\n            results[key] = bboxes[valid_inds, :]\n            # label fields. e.g. gt_labels and gt_labels_ignore\n            label_key = self.bbox2label.get(key)\n            if label_key in results:\n                results[label_key] = results[label_key][valid_inds]\n\n            # mask fields, e.g. gt_masks and gt_masks_ignore\n            mask_key = self.bbox2mask.get(key)\n            if mask_key in results:\n                results[mask_key] = results[mask_key][\n                    valid_inds.nonzero()[0]].crop(\n                        np.asarray([crop_x1, crop_y1, crop_x2, crop_y2]))\n\n        # if no gt bbox remains after cropping, just skip this image\n        # TODO: check whether we can keep the image regardless of the crop.\n        if 'bbox_fields' in results and not valid_flag:\n            return None\n\n        # crop semantic seg\n        for key in results.get('seg_fields', []):\n            results[key] = results[key][crop_y1:crop_y2, crop_x1:crop_x2]\n\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + f'(crop_size={self.crop_size})'\n\n\n@PIPELINES.register_module()\nclass SegRescale(object):\n    \"\"\"Rescale semantic segmentation maps.\n\n    Args:\n        scale_factor (float): The scale factor of the final output.\n    \"\"\"\n\n    def __init__(self, scale_factor=1):\n        self.scale_factor = scale_factor\n\n    def __call__(self, results):\n        \"\"\"Call function to scale the semantic segmentation map\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Result dict with semantic segmentation map scaled.\n        \"\"\"\n\n        for key in results.get('seg_fields', []):\n            if self.scale_factor != 1:\n                results[key] = mmcv.imrescale(\n                    results[key], self.scale_factor, interpolation='nearest')\n        return results\n\n    def __repr__(self):\n        return self.__class__.__name__ + f'(scale_factor={self.scale_factor})'\n\n\n@PIPELINES.register_module()\nclass PhotoMetricDistortion(object):\n    \"\"\"Apply photometric distortion to image sequentially, every transformation\n    is applied with a probability of 0.5. The position of random contrast is in\n    second or second to last.\n\n    1. random brightness\n    2. random contrast (mode 0)\n    3. convert color from BGR to HSV\n    4. random saturation\n    5. random hue\n    6. convert color from HSV to BGR\n    7. random contrast (mode 1)\n    8. randomly swap channels\n\n    Args:\n        brightness_delta (int): delta of brightness.\n        contrast_range (tuple): range of contrast.\n        saturation_range (tuple): range of saturation.\n        hue_delta (int): delta of hue.\n    \"\"\"\n\n    def __init__(self,\n                 brightness_delta=32,\n                 contrast_range=(0.5, 1.5),\n                 saturation_range=(0.5, 1.5),\n                 hue_delta=18):\n        self.brightness_delta = brightness_delta\n        self.contrast_lower, self.contrast_upper = contrast_range\n        self.saturation_lower, self.saturation_upper = saturation_range\n        self.hue_delta = hue_delta\n\n    def __call__(self, results):\n        \"\"\"Call function to perform photometric distortion on images.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Result dict with images distorted.\n        \"\"\"\n\n        if 'img_fields' in results:\n            assert results['img_fields'] == ['img'], \\\n                'Only single img_fields is allowed'\n        img = results['img']\n        assert img.dtype == np.float32, \\\n            'PhotoMetricDistortion needs the input image of dtype np.float32,'\\\n            ' please set \"to_float32=True\" in \"LoadImageFromFile\" pipeline'\n        # random brightness\n        if random.randint(2):\n            delta = random.uniform(-self.brightness_delta,\n                                   self.brightness_delta)\n            img += delta\n\n        # mode == 0 --> do random contrast first\n        # mode == 1 --> do random contrast last\n        mode = random.randint(2)\n        if mode == 1:\n            if random.randint(2):\n                alpha = random.uniform(self.contrast_lower,\n                                       self.contrast_upper)\n                img *= alpha\n\n        # convert color from BGR to HSV\n        img = mmcv.bgr2hsv(img)\n\n        # random saturation\n        if random.randint(2):\n            img[..., 1] *= random.uniform(self.saturation_lower,\n                                          self.saturation_upper)\n\n        # random hue\n        if random.randint(2):\n            img[..., 0] += random.uniform(-self.hue_delta, self.hue_delta)\n            img[..., 0][img[..., 0] > 360] -= 360\n            img[..., 0][img[..., 0] < 0] += 360\n\n        # convert color from HSV to BGR\n        img = mmcv.hsv2bgr(img)\n\n        # random contrast\n        if mode == 0:\n            if random.randint(2):\n                alpha = random.uniform(self.contrast_lower,\n                                       self.contrast_upper)\n                img *= alpha\n\n        # randomly swap channels\n        if random.randint(2):\n            img = img[..., random.permutation(3)]\n\n        results['img'] = img\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(\\nbrightness_delta={self.brightness_delta},\\n'\n        repr_str += 'contrast_range='\n        repr_str += f'{(self.contrast_lower, self.contrast_upper)},\\n'\n        repr_str += 'saturation_range='\n        repr_str += f'{(self.saturation_lower, self.saturation_upper)},\\n'\n        repr_str += f'hue_delta={self.hue_delta})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass Expand(object):\n    \"\"\"Random expand the image & bboxes.\n\n    Randomly place the original image on a canvas of 'ratio' x original image\n    size filled with mean values. The ratio is in the range of ratio_range.\n\n    Args:\n        mean (tuple): mean value of dataset.\n        to_rgb (bool): if need to convert the order of mean to align with RGB.\n        ratio_range (tuple): range of expand ratio.\n        prob (float): probability of applying this transformation\n    \"\"\"\n\n    def __init__(self,\n                 mean=(0, 0, 0),\n                 to_rgb=True,\n                 ratio_range=(1, 4),\n                 seg_ignore_label=None,\n                 prob=0.5):\n        self.to_rgb = to_rgb\n        self.ratio_range = ratio_range\n        if to_rgb:\n            self.mean = mean[::-1]\n        else:\n            self.mean = mean\n        self.min_ratio, self.max_ratio = ratio_range\n        self.seg_ignore_label = seg_ignore_label\n        self.prob = prob\n\n    def __call__(self, results):\n        \"\"\"Call function to expand images, bounding boxes.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Result dict with images, bounding boxes expanded\n        \"\"\"\n\n        if random.uniform(0, 1) > self.prob:\n            return results\n\n        if 'img_fields' in results:\n            assert results['img_fields'] == ['img'], \\\n                'Only single img_fields is allowed'\n        img = results['img']\n\n        h, w, c = img.shape\n        ratio = random.uniform(self.min_ratio, self.max_ratio)\n        expand_img = np.full((int(h * ratio), int(w * ratio), c),\n                             self.mean,\n                             dtype=img.dtype)\n        left = int(random.uniform(0, w * ratio - w))\n        top = int(random.uniform(0, h * ratio - h))\n        expand_img[top:top + h, left:left + w] = img\n\n        results['img'] = expand_img\n        # expand bboxes\n        for key in results.get('bbox_fields', []):\n            results[key] = results[key] + np.tile(\n                (left, top), 2).astype(results[key].dtype)\n\n        # expand masks\n        for key in results.get('mask_fields', []):\n            results[key] = results[key].expand(\n                int(h * ratio), int(w * ratio), top, left)\n\n        # expand segs\n        for key in results.get('seg_fields', []):\n            gt_seg = results[key]\n            expand_gt_seg = np.full((int(h * ratio), int(w * ratio)),\n                                    self.seg_ignore_label,\n                                    dtype=gt_seg.dtype)\n            expand_gt_seg[top:top + h, left:left + w] = gt_seg\n            results[key] = expand_gt_seg\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(mean={self.mean}, to_rgb={self.to_rgb}, '\n        repr_str += f'ratio_range={self.ratio_range}, '\n        repr_str += f'seg_ignore_label={self.seg_ignore_label})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass MinIoURandomCrop(object):\n    \"\"\"Random crop the image & bboxes, the cropped patches have minimum IoU\n    requirement with original image & bboxes, the IoU threshold is randomly\n    selected from min_ious.\n\n    Args:\n        min_ious (tuple): minimum IoU threshold for all intersections with\n        bounding boxes\n        min_crop_size (float): minimum crop's size (i.e. h,w := a*h, a*w,\n        where a >= min_crop_size).\n\n    Notes:\n        The keys for bboxes, labels and masks should be paired. That is,\n        `gt_bboxes` corresponds to `gt_labels` and `gt_masks`, and\n        `gt_bboxes_ignore` to `gt_labels_ignore` and `gt_masks_ignore`.\n    \"\"\"\n\n    def __init__(self, min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), min_crop_size=0.3):\n        # 1: return ori img\n        self.min_ious = min_ious\n        self.sample_mode = (1, *min_ious, 0)\n        self.min_crop_size = min_crop_size\n        self.bbox2label = {\n            'gt_bboxes': 'gt_labels',\n            'gt_bboxes_ignore': 'gt_labels_ignore'\n        }\n        self.bbox2mask = {\n            'gt_bboxes': 'gt_masks',\n            'gt_bboxes_ignore': 'gt_masks_ignore'\n        }\n\n    def __call__(self, results):\n        \"\"\"Call function to crop images and bounding boxes with minimum IoU\n        constraint.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Result dict with images and bounding boxes cropped,\n                'img_shape' key is updated.\n        \"\"\"\n\n        if 'img_fields' in results:\n            assert results['img_fields'] == ['img'], \\\n                'Only single img_fields is allowed'\n        img = results['img']\n        assert 'bbox_fields' in results\n        boxes = [results[key] for key in results['bbox_fields']]\n        boxes = np.concatenate(boxes, 0)\n        h, w, c = img.shape\n        while True:\n            mode = random.choice(self.sample_mode)\n            self.mode = mode\n            if mode == 1:\n                return results\n\n            min_iou = mode\n            for i in range(50):\n                new_w = random.uniform(self.min_crop_size * w, w)\n                new_h = random.uniform(self.min_crop_size * h, h)\n\n                # h / w in [0.5, 2]\n                if new_h / new_w < 0.5 or new_h / new_w > 2:\n                    continue\n\n                left = random.uniform(w - new_w)\n                top = random.uniform(h - new_h)\n\n                patch = np.array(\n                    (int(left), int(top), int(left + new_w), int(top + new_h)))\n                # Line or point crop is not allowed\n                if patch[2] == patch[0] or patch[3] == patch[1]:\n                    continue\n                overlaps = bbox_overlaps(\n                    patch.reshape(-1, 4), boxes.reshape(-1, 4)).reshape(-1)\n                if len(overlaps) > 0 and overlaps.min() < min_iou:\n                    continue\n\n                # center of boxes should inside the crop img\n                # only adjust boxes and instance masks when the gt is not empty\n                if len(overlaps) > 0:\n                    # adjust boxes\n                    def is_center_of_bboxes_in_patch(boxes, patch):\n                        center = (boxes[:, :2] + boxes[:, 2:]) / 2\n                        mask = ((center[:, 0] > patch[0]) *\n                                (center[:, 1] > patch[1]) *\n                                (center[:, 0] < patch[2]) *\n                                (center[:, 1] < patch[3]))\n                        return mask\n\n                    mask = is_center_of_bboxes_in_patch(boxes, patch)\n                    if not mask.any():\n                        continue\n                    for key in results.get('bbox_fields', []):\n                        boxes = results[key].copy()\n                        mask = is_center_of_bboxes_in_patch(boxes, patch)\n                        boxes = boxes[mask]\n                        boxes[:, 2:] = boxes[:, 2:].clip(max=patch[2:])\n                        boxes[:, :2] = boxes[:, :2].clip(min=patch[:2])\n                        boxes -= np.tile(patch[:2], 2)\n\n                        results[key] = boxes\n                        # labels\n                        label_key = self.bbox2label.get(key)\n                        if label_key in results:\n                            results[label_key] = results[label_key][mask]\n\n                        # mask fields\n                        mask_key = self.bbox2mask.get(key)\n                        if mask_key in results:\n                            results[mask_key] = results[mask_key][\n                                mask.nonzero()[0]].crop(patch)\n                # adjust the img no matter whether the gt is empty before crop\n                img = img[patch[1]:patch[3], patch[0]:patch[2]]\n                results['img'] = img\n                results['img_shape'] = img.shape\n\n                # seg fields\n                for key in results.get('seg_fields', []):\n                    results[key] = results[key][patch[1]:patch[3],\n                                                patch[0]:patch[2]]\n                return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(min_ious={self.min_ious}, '\n        repr_str += f'min_crop_size={self.min_crop_size})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass Corrupt(object):\n    \"\"\"Corruption augmentation.\n\n    Corruption transforms implemented based on\n    `imagecorruptions <https://github.com/bethgelab/imagecorruptions>`_.\n\n    Args:\n        corruption (str): Corruption name.\n        severity (int, optional): The severity of corruption. Default: 1.\n    \"\"\"\n\n    def __init__(self, corruption, severity=1):\n        self.corruption = corruption\n        self.severity = severity\n\n    def __call__(self, results):\n        \"\"\"Call function to corrupt image.\n\n        Args:\n            results (dict): Result dict from loading pipeline.\n\n        Returns:\n            dict: Result dict with images corrupted.\n        \"\"\"\n\n        if corrupt is None:\n            raise RuntimeError('imagecorruptions is not installed')\n        if 'img_fields' in results:\n            assert results['img_fields'] == ['img'], \\\n                'Only single img_fields is allowed'\n        results['img'] = corrupt(\n            results['img'].astype(np.uint8),\n            corruption_name=self.corruption,\n            severity=self.severity)\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(corruption={self.corruption}, '\n        repr_str += f'severity={self.severity})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass Albu(object):\n    \"\"\"Albumentation augmentation.\n\n    Adds custom transformations from Albumentations library.\n    Please, visit `https://albumentations.readthedocs.io`\n    to get more information.\n\n    An example of ``transforms`` is as followed:\n\n    .. code-block::\n\n        [\n            dict(\n                type='ShiftScaleRotate',\n                shift_limit=0.0625,\n                scale_limit=0.0,\n                rotate_limit=0,\n                interpolation=1,\n                p=0.5),\n            dict(\n                type='RandomBrightnessContrast',\n                brightness_limit=[0.1, 0.3],\n                contrast_limit=[0.1, 0.3],\n                p=0.2),\n            dict(type='ChannelShuffle', p=0.1),\n            dict(\n                type='OneOf',\n                transforms=[\n                    dict(type='Blur', blur_limit=3, p=1.0),\n                    dict(type='MedianBlur', blur_limit=3, p=1.0)\n                ],\n                p=0.1),\n        ]\n\n    Args:\n        transforms (list[dict]): A list of albu transformations\n        bbox_params (dict): Bbox_params for albumentation `Compose`\n        keymap (dict): Contains {'input key':'albumentation-style key'}\n        skip_img_without_anno (bool): Whether to skip the image if no ann left\n            after aug\n    \"\"\"\n\n    def __init__(self,\n                 transforms,\n                 bbox_params=None,\n                 keymap=None,\n                 update_pad_shape=False,\n                 skip_img_without_anno=False):\n        if Compose is None:\n            raise RuntimeError('albumentations is not installed')\n\n        self.transforms = transforms\n        self.filter_lost_elements = False\n        self.update_pad_shape = update_pad_shape\n        self.skip_img_without_anno = skip_img_without_anno\n\n        # A simple workaround to remove masks without boxes\n        if (isinstance(bbox_params, dict) and 'label_fields' in bbox_params\n                and 'filter_lost_elements' in bbox_params):\n            self.filter_lost_elements = True\n            self.origin_label_fields = bbox_params['label_fields']\n            bbox_params['label_fields'] = ['idx_mapper']\n            del bbox_params['filter_lost_elements']\n\n        self.bbox_params = (\n            self.albu_builder(bbox_params) if bbox_params else None)\n        self.aug = Compose([self.albu_builder(t) for t in self.transforms],\n                           bbox_params=self.bbox_params)\n\n        if not keymap:\n            self.keymap_to_albu = {\n                'img': 'image',\n                'gt_masks': 'masks',\n                'gt_bboxes': 'bboxes'\n            }\n        else:\n            self.keymap_to_albu = keymap\n        self.keymap_back = {v: k for k, v in self.keymap_to_albu.items()}\n\n    def albu_builder(self, cfg):\n        \"\"\"Import a module from albumentations.\n        Inherits some of `build_from_cfg` logic.\n\n        Args:\n            cfg (dict): Config dict. It should at least contain the key \"type\".\n        Returns:\n            obj: The constructed object.\n        \"\"\"\n\n        assert isinstance(cfg, dict) and 'type' in cfg\n        args = cfg.copy()\n\n        obj_type = args.pop('type')\n        if mmcv.is_str(obj_type):\n            if albumentations is None:\n                raise RuntimeError('albumentations is not installed')\n            obj_cls = getattr(albumentations, obj_type)\n        elif inspect.isclass(obj_type):\n            obj_cls = obj_type\n        else:\n            raise TypeError(\n                f'type must be a str or valid type, but got {type(obj_type)}')\n\n        if 'transforms' in args:\n            args['transforms'] = [\n                self.albu_builder(transform)\n                for transform in args['transforms']\n            ]\n\n        return obj_cls(**args)\n\n    @staticmethod\n    def mapper(d, keymap):\n        \"\"\"\n        Dictionary mapper.\n        Renames keys according to keymap provided.\n\n        Args:\n            d (dict): old dict\n            keymap (dict): {'old_key':'new_key'}\n        Returns:\n            dict: new dict.\n        \"\"\"\n\n        updated_dict = {}\n        for k, v in zip(d.keys(), d.values()):\n            new_k = keymap.get(k, k)\n            updated_dict[new_k] = d[k]\n        return updated_dict\n\n    def __call__(self, results):\n        # dict to albumentations format\n        results = self.mapper(results, self.keymap_to_albu)\n        # TODO: add bbox_fields\n        if 'bboxes' in results:\n            # to list of boxes\n            if isinstance(results['bboxes'], np.ndarray):\n                results['bboxes'] = [x for x in results['bboxes']]\n            # add pseudo-field for filtration\n            if self.filter_lost_elements:\n                results['idx_mapper'] = np.arange(len(results['bboxes']))\n\n        # TODO: Support mask structure in albu\n        if 'masks' in results:\n            if isinstance(results['masks'], PolygonMasks):\n                raise NotImplementedError(\n                    'Albu only supports BitMap masks now')\n            ori_masks = results['masks']\n            results['masks'] = results['masks'].masks\n\n        results = self.aug(**results)\n\n        if 'bboxes' in results:\n            if isinstance(results['bboxes'], list):\n                results['bboxes'] = np.array(\n                    results['bboxes'], dtype=np.float32)\n            results['bboxes'] = results['bboxes'].reshape(-1, 4)\n\n            # filter label_fields\n            if self.filter_lost_elements:\n\n                for label in self.origin_label_fields:\n                    results[label] = np.array(\n                        [results[label][i] for i in results['idx_mapper']])\n                if 'masks' in results:\n                    results['masks'] = np.array(\n                        [results['masks'][i] for i in results['idx_mapper']])\n                    results['masks'] = ori_masks.__class__(\n                        results['masks'], results['image'].shape[0],\n                        results['image'].shape[1])\n\n                if (not len(results['idx_mapper'])\n                        and self.skip_img_without_anno):\n                    return None\n\n        if 'gt_labels' in results:\n            if isinstance(results['gt_labels'], list):\n                results['gt_labels'] = np.array(results['gt_labels'])\n            results['gt_labels'] = results['gt_labels'].astype(np.int64)\n\n        # back to the original format\n        results = self.mapper(results, self.keymap_back)\n\n        # update final shape\n        if self.update_pad_shape:\n            results['pad_shape'] = results['img'].shape\n\n        return results\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__ + f'(transforms={self.transforms})'\n        return repr_str\n\n\n@PIPELINES.register_module()\nclass RandomCenterCropPad(object):\n    \"\"\"Random center crop and random around padding for CornerNet.\n\n    This operation generates randomly cropped image from the original image and\n    pads it simultaneously. Different from `RandomCrop`, the output shape may\n    not equal to `crop_size` strictly. We choose a random value from `ratios`\n    and the output shape could be larger or smaller than `crop_size`. Also the\n    pad in this operation is different from `Pad`, actually we use around\n    padding instead of right-bottom padding.\n\n    The relation between output image (padding image) and original image:\n\n    :code-block:\n                    output image\n           +----------------------------+\n           |          padded area       |\n    +------|----------------------------|----------+\n    |      |         cropped area       |          |\n    |      |         +---------------+  |          |\n    |      |         |    .   center |  |          | original image\n    |      |         |        range  |  |          |\n    |      |         +---------------+  |          |\n    +------|----------------------------|----------+\n           |          padded area       |\n           +----------------------------+\n\n    There are 5 main areas in the figure:\n        - output image: output image of this operation, also called padding\n            image in following instruction.\n        - original image: input image of this operation.\n        - padded area: non-intersect area of output image and original image.\n        - cropped area: the overlap of output image and original image.\n        - center range: a smaller area where random center chosen from.\n            center range is computed by `border` and original image's shape\n            to avoid our random center is too close to original image's border.\n\n    Also this operation act differently in train and test mode, the summary\n    pipeline is listed below.\n\n    Train pipeline:\n        1. Choose a `random_ratio` from `ratios`, the shape of padding image\n            will be `random_ratio * crop_size`.\n        2. Choose a `random_center` in `center range`.\n        3. Generate padding image with center matches the `random_center`.\n        4. Initialize the padding image with pixel value equals to `mean`.\n        5. Copy the `cropped area` to padding image.\n        6. Refine annotations.\n\n    Test pipeline:\n        1. Compute output shape according to `test_pad_mode`.\n        2. Generate padding image with center matches the original image\n            center.\n        3. Initialize the padding image with pixel value equals to `mean`.\n        4. Copy the `cropped area` to padding image.\n\n    Args:\n        crop_size (tuple | None): expected size after crop, final size will\n            computed according to ratio. Requires (h, w) in train mode, and\n            None in test mode.\n        ratios (tuple): random select a ratio from tuple and crop image to\n            (crop_size[0] * ratio) * (crop_size[1] * ratio).\n            Only available in train mode.\n        border (int): max distance from center select area to image border.\n            Only available in train mode.\n        mean (sequence): Mean values of 3 channels.\n        std (sequence): Std values of 3 channels.\n        to_rgb (bool): Whether to convert the image from BGR to RGB.\n        test_mode (bool): whether involve random variables in transform.\n            In train mode, crop_size is fixed, center coords and ratio is\n            random selected from predefined lists. In test mode, crop_size\n            is image's original shape, center coords and ratio is fixed.\n        test_pad_mode (tuple): padding method and padding shape value, only\n            available in test mode. Default is using 'logical_or' with\n            127 as padding shape value.\n\n            - 'logical_or': final_shape = input_shape | padding_shape_value\n            - 'size_divisor': final_shape = int(\n                ceil(input_shape / padding_shape_value) * padding_shape_value)\n    \"\"\"\n\n    def __init__(self,\n                 crop_size=None,\n                 ratios=(0.9, 1.0, 1.1),\n                 border=128,\n                 mean=None,\n                 std=None,\n                 to_rgb=None,\n                 test_mode=False,\n                 test_pad_mode=('logical_or', 127)):\n        if test_mode:\n            assert crop_size is None, 'crop_size must be None in test mode'\n            assert ratios is None, 'ratios must be None in test mode'\n            assert border is None, 'border must be None in test mode'\n            assert isinstance(test_pad_mode, (list, tuple))\n            assert test_pad_mode[0] in ['logical_or', 'size_divisor']\n        else:\n            assert isinstance(crop_size, (list, tuple))\n            assert crop_size[0] > 0 and crop_size[1] > 0, (\n                'crop_size must > 0 in train mode')\n            assert isinstance(ratios, (list, tuple))\n            assert test_pad_mode is None, (\n                'test_pad_mode must be None in train mode')\n\n        self.crop_size = crop_size\n        self.ratios = ratios\n        self.border = border\n        # We do not set default value to mean, std and to_rgb because these\n        # hyper-parameters are easy to forget but could affect the performance.\n        # Please use the same setting as Normalize for performance assurance.\n        assert mean is not None and std is not None and to_rgb is not None\n        self.to_rgb = to_rgb\n        self.input_mean = mean\n        self.input_std = std\n        if to_rgb:\n            self.mean = mean[::-1]\n            self.std = std[::-1]\n        else:\n            self.mean = mean\n            self.std = std\n        self.test_mode = test_mode\n        self.test_pad_mode = test_pad_mode\n\n    def _get_border(self, border, size):\n        \"\"\"Get final border for the target size.\n\n        This function generates a `final_border` according to image's shape.\n        The area between `final_border` and `size - final_border` is the\n        `center range`. We randomly choose center from the `center range`\n        to avoid our random center is too close to original image's border.\n\n        Args:\n            border (int): The initial border, default is 128.\n            size (int): The width or height of original image.\n        Returns:\n            int: The final border.\n        \"\"\"\n        i = pow(2, np.ceil(np.log2(np.ceil(2 * border / size))))\n        return border // i\n\n    def _filter_boxes(self, patch, boxes):\n        \"\"\"Check whether the center of each box is in the patch.\n\n        Args:\n            patch (list[int]): The cropped area, [left, top, right, bottom].\n            boxes (numpy array, (N x 4)): Ground truth boxes.\n        Returns:\n            mask (numpy array, (N,)): Each box is inside or outside the patch.\n        \"\"\"\n        center = (boxes[:, :2] + boxes[:, 2:]) / 2\n        mask = (center[:, 0] > patch[0]) * (center[:, 1] > patch[1]) * (\n            center[:, 0] < patch[2]) * (\n                center[:, 1] < patch[3])\n        return mask\n\n    def _crop_image_and_paste(self, image, center, size):\n        \"\"\"Crop image with a given center and size, then paste the cropped\n        image to a blank image with two centers align.\n\n        This function is equivalent to generating a blank image with `size` as\n        its shape. Then cover it on the original image with two centers (\n        the center of blank image and the random center of original image)\n        aligned. The overlap area is paste from the original image and the\n        outside area is filled with `mean pixel`.\n\n        Args:\n            image (np array, H x W x C): Original image.\n            center (list[int]): Target crop center coord.\n            size (list[int]): Target crop size. [target_h, target_w]\n        Returns:\n            cropped_img (np array, target_h x target_w x C): Cropped image.\n            border (np array, 4): The distance of four border of `cropped_img`\n                to the original image area, [top, bottom, left, right]\n            patch (list[int]): The cropped area, [left, top, right, bottom].\n        \"\"\"\n        center_y, center_x = center\n        target_h, target_w = size\n        img_h, img_w, img_c = image.shape\n\n        x0 = max(0, center_x - target_w // 2)\n        x1 = min(center_x + target_w // 2, img_w)\n        y0 = max(0, center_y - target_h // 2)\n        y1 = min(center_y + target_h // 2, img_h)\n        patch = np.array((int(x0), int(y0), int(x1), int(y1)))\n\n        left, right = center_x - x0, x1 - center_x\n        top, bottom = center_y - y0, y1 - center_y\n\n        cropped_center_y, cropped_center_x = target_h // 2, target_w // 2\n        cropped_img = np.zeros((target_h, target_w, img_c), dtype=image.dtype)\n        for i in range(img_c):\n            cropped_img[:, :, i] += self.mean[i]\n        y_slice = slice(cropped_center_y - top, cropped_center_y + bottom)\n        x_slice = slice(cropped_center_x - left, cropped_center_x + right)\n        cropped_img[y_slice, x_slice, :] = image[y0:y1, x0:x1, :]\n\n        border = np.array([\n            cropped_center_y - top, cropped_center_y + bottom,\n            cropped_center_x - left, cropped_center_x + right\n        ],\n                          dtype=np.float32)\n\n        return cropped_img, border, patch\n\n    def _train_aug(self, results):\n        \"\"\"Random crop and around padding the original image.\n\n        Args:\n            results (dict): Image infomations in the augment pipeline.\n        Returns:\n            results (dict): The updated dict.\n        \"\"\"\n        img = results['img']\n        h, w, c = img.shape\n        boxes = results['gt_bboxes']\n        while True:\n            scale = random.choice(self.ratios)\n            new_h = int(self.crop_size[0] * scale)\n            new_w = int(self.crop_size[1] * scale)\n            h_border = self._get_border(self.border, h)\n            w_border = self._get_border(self.border, w)\n\n            for i in range(50):\n                center_x = random.randint(low=w_border, high=w - w_border)\n                center_y = random.randint(low=h_border, high=h - h_border)\n\n                cropped_img, border, patch = self._crop_image_and_paste(\n                    img, [center_y, center_x], [new_h, new_w])\n\n                mask = self._filter_boxes(patch, boxes)\n                # if image do not have valid bbox, any crop patch is valid.\n                if not mask.any() and len(boxes) > 0:\n                    continue\n\n                results['img'] = cropped_img\n                results['img_shape'] = cropped_img.shape\n                results['pad_shape'] = cropped_img.shape\n\n                x0, y0, x1, y1 = patch\n\n                left_w, top_h = center_x - x0, center_y - y0\n                cropped_center_x, cropped_center_y = new_w // 2, new_h // 2\n\n                # crop bboxes accordingly and clip to the image boundary\n                for key in results.get('bbox_fields', []):\n                    mask = self._filter_boxes(patch, results[key])\n                    bboxes = results[key][mask]\n                    bboxes[:, 0:4:2] += cropped_center_x - left_w - x0\n                    bboxes[:, 1:4:2] += cropped_center_y - top_h - y0\n                    bboxes[:, 0:4:2] = np.clip(bboxes[:, 0:4:2], 0, new_w)\n                    bboxes[:, 1:4:2] = np.clip(bboxes[:, 1:4:2], 0, new_h)\n                    keep = (bboxes[:, 2] > bboxes[:, 0]) & (\n                        bboxes[:, 3] > bboxes[:, 1])\n                    bboxes = bboxes[keep]\n                    results[key] = bboxes\n                    if key in ['gt_bboxes']:\n                        if 'gt_labels' in results:\n                            labels = results['gt_labels'][mask]\n                            labels = labels[keep]\n                            results['gt_labels'] = labels\n                        if 'gt_masks' in results:\n                            raise NotImplementedError(\n                                'RandomCenterCropPad only supports bbox.')\n\n                # crop semantic seg\n                for key in results.get('seg_fields', []):\n                    raise NotImplementedError(\n                        'RandomCenterCropPad only supports bbox.')\n                return results\n\n    def _test_aug(self, results):\n        \"\"\"Around padding the original image without cropping.\n\n        The padding mode and value are from `test_pad_mode`.\n\n        Args:\n            results (dict): Image infomations in the augment pipeline.\n        Returns:\n            results (dict): The updated dict.\n        \"\"\"\n        img = results['img']\n        h, w, c = img.shape\n        results['img_shape'] = img.shape\n        if self.test_pad_mode[0] in ['logical_or']:\n            target_h = h | self.test_pad_mode[1]\n            target_w = w | self.test_pad_mode[1]\n        elif self.test_pad_mode[0] in ['size_divisor']:\n            divisor = self.test_pad_mode[1]\n            target_h = int(np.ceil(h / divisor)) * divisor\n            target_w = int(np.ceil(w / divisor)) * divisor\n        else:\n            raise NotImplementedError(\n                'RandomCenterCropPad only support two testing pad mode:'\n                'logical-or and size_divisor.')\n\n        cropped_img, border, _ = self._crop_image_and_paste(\n            img, [h // 2, w // 2], [target_h, target_w])\n        results['img'] = cropped_img\n        results['pad_shape'] = cropped_img.shape\n        results['border'] = border\n        return results\n\n    def __call__(self, results):\n        img = results['img']\n        assert img.dtype == np.float32, (\n            'RandomCenterCropPad needs the input image of dtype np.float32,'\n            ' please set \"to_float32=True\" in \"LoadImageFromFile\" pipeline')\n        h, w, c = img.shape\n        assert c == len(self.mean)\n        if self.test_mode:\n            return self._test_aug(results)\n        else:\n            return self._train_aug(results)\n\n    def __repr__(self):\n        repr_str = self.__class__.__name__\n        repr_str += f'(crop_size={self.crop_size}, '\n        repr_str += f'ratios={self.ratios}, '\n        repr_str += f'border={self.border}, '\n        repr_str += f'mean={self.input_mean}, '\n        repr_str += f'std={self.input_std}, '\n        repr_str += f'to_rgb={self.to_rgb}, '\n        repr_str += f'test_mode={self.test_mode}, '\n        repr_str += f'test_pad_mode={self.test_pad_mode})'\n        return repr_str\n"
  },
  {
    "path": "code/mmdet/datasets/samplers/__init__.py",
    "content": "from .distributed_sampler import DistributedSampler\nfrom .group_sampler import DistributedGroupSampler, GroupSampler\n\n__all__ = ['DistributedSampler', 'DistributedGroupSampler', 'GroupSampler']\n"
  },
  {
    "path": "code/mmdet/datasets/samplers/distributed_sampler.py",
    "content": "import torch\nfrom torch.utils.data import DistributedSampler as _DistributedSampler\n\n\nclass DistributedSampler(_DistributedSampler):\n\n    def __init__(self, dataset, num_replicas=None, rank=None, shuffle=True):\n        super().__init__(dataset, num_replicas=num_replicas, rank=rank)\n        self.shuffle = shuffle\n\n    def __iter__(self):\n        # deterministically shuffle based on epoch\n        if self.shuffle:\n            g = torch.Generator()\n            g.manual_seed(self.epoch)\n            indices = torch.randperm(len(self.dataset), generator=g).tolist()\n        else:\n            indices = torch.arange(len(self.dataset)).tolist()\n\n        # add extra samples to make it evenly divisible\n        indices += indices[:(self.total_size - len(indices))]\n        assert len(indices) == self.total_size\n\n        # subsample\n        indices = indices[self.rank:self.total_size:self.num_replicas]\n        assert len(indices) == self.num_samples\n\n        return iter(indices)\n"
  },
  {
    "path": "code/mmdet/datasets/samplers/group_sampler.py",
    "content": "from __future__ import division\nimport math\n\nimport numpy as np\nimport torch\nfrom mmcv.runner import get_dist_info\nfrom torch.utils.data import Sampler\n\n\nclass GroupSampler(Sampler):\n\n    def __init__(self, dataset, samples_per_gpu=1):\n        assert hasattr(dataset, 'flag')\n        self.dataset = dataset\n        self.samples_per_gpu = samples_per_gpu\n        self.flag = dataset.flag.astype(np.int64)\n        self.group_sizes = np.bincount(self.flag)\n        self.num_samples = 0\n        for i, size in enumerate(self.group_sizes):\n            self.num_samples += int(np.ceil(\n                size / self.samples_per_gpu)) * self.samples_per_gpu\n\n    def __iter__(self):\n        indices = []\n        for i, size in enumerate(self.group_sizes):\n            if size == 0:\n                continue\n            indice = np.where(self.flag == i)[0]\n            assert len(indice) == size\n            np.random.shuffle(indice)\n            num_extra = int(np.ceil(size / self.samples_per_gpu)\n                            ) * self.samples_per_gpu - len(indice)\n            indice = np.concatenate(\n                [indice, np.random.choice(indice, num_extra)])\n            indices.append(indice)\n        indices = np.concatenate(indices)\n        indices = [\n            indices[i * self.samples_per_gpu:(i + 1) * self.samples_per_gpu]\n            for i in np.random.permutation(\n                range(len(indices) // self.samples_per_gpu))\n        ]\n        indices = np.concatenate(indices)\n        indices = indices.astype(np.int64).tolist()\n        assert len(indices) == self.num_samples\n        return iter(indices)\n\n    def __len__(self):\n        return self.num_samples\n\n\nclass DistributedGroupSampler(Sampler):\n    \"\"\"Sampler that restricts data loading to a subset of the dataset.\n\n    It is especially useful in conjunction with\n    :class:`torch.nn.parallel.DistributedDataParallel`. In such case, each\n    process can pass a DistributedSampler instance as a DataLoader sampler,\n    and load a subset of the original dataset that is exclusive to it.\n\n    .. note::\n        Dataset is assumed to be of constant size.\n\n    Arguments:\n        dataset: Dataset used for sampling.\n        num_replicas (optional): Number of processes participating in\n            distributed training.\n        rank (optional): Rank of the current process within num_replicas.\n    \"\"\"\n\n    def __init__(self,\n                 dataset,\n                 samples_per_gpu=1,\n                 num_replicas=None,\n                 rank=None):\n        _rank, _num_replicas = get_dist_info()\n        if num_replicas is None:\n            num_replicas = _num_replicas\n        if rank is None:\n            rank = _rank\n        self.dataset = dataset\n        self.samples_per_gpu = samples_per_gpu\n        self.num_replicas = num_replicas\n        self.rank = rank\n        self.epoch = 0\n\n        assert hasattr(self.dataset, 'flag')\n        self.flag = self.dataset.flag\n        self.group_sizes = np.bincount(self.flag)\n\n        self.num_samples = 0\n        for i, j in enumerate(self.group_sizes):\n            self.num_samples += int(\n                math.ceil(self.group_sizes[i] * 1.0 / self.samples_per_gpu /\n                          self.num_replicas)) * self.samples_per_gpu\n        self.total_size = self.num_samples * self.num_replicas\n\n    def __iter__(self):\n        # deterministically shuffle based on epoch\n        g = torch.Generator()\n        g.manual_seed(self.epoch)\n\n        indices = []\n        for i, size in enumerate(self.group_sizes):\n            if size > 0:\n                indice = np.where(self.flag == i)[0]\n                assert len(indice) == size\n                indice = indice[list(torch.randperm(int(size),\n                                                    generator=g))].tolist()\n                extra = int(\n                    math.ceil(\n                        size * 1.0 / self.samples_per_gpu / self.num_replicas)\n                ) * self.samples_per_gpu * self.num_replicas - len(indice)\n                # pad indice\n                tmp = indice.copy()\n                for _ in range(extra // size):\n                    indice.extend(tmp)\n                indice.extend(tmp[:extra % size])\n                indices.extend(indice)\n\n        assert len(indices) == self.total_size\n\n        indices = [\n            indices[j] for i in list(\n                torch.randperm(\n                    len(indices) // self.samples_per_gpu, generator=g))\n            for j in range(i * self.samples_per_gpu, (i + 1) *\n                           self.samples_per_gpu)\n        ]\n\n        # subsample\n        offset = self.num_samples * self.rank\n        indices = indices[offset:offset + self.num_samples]\n        assert len(indices) == self.num_samples\n\n        return iter(indices)\n\n    def __len__(self):\n        return self.num_samples\n\n    def set_epoch(self, epoch):\n        self.epoch = epoch\n"
  },
  {
    "path": "code/mmdet/datasets/voc.py",
    "content": "from mmdet.core import eval_map, eval_recalls\nfrom .builder import DATASETS\nfrom .xml_style import XMLDataset\n\n\n@DATASETS.register_module()\nclass VOCDataset(XMLDataset):\n\n    CLASSES = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car',\n               'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',\n               'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train',\n               'tvmonitor')\n\n    def __init__(self, **kwargs):\n        super(VOCDataset, self).__init__(**kwargs)\n        if 'VOC2007' in self.img_prefix:\n            self.year = 2007\n        elif 'VOC2012' in self.img_prefix:\n            self.year = 2012\n        else:\n            raise ValueError('Cannot infer dataset year from img_prefix')\n\n    def evaluate(self,\n                 results,\n                 metric='mAP',\n                 logger=None,\n                 proposal_nums=(100, 300, 1000),\n                 iou_thr=0.5,\n                 scale_ranges=None):\n        \"\"\"Evaluate in VOC protocol.\n\n        Args:\n            results (list[list | tuple]): Testing results of the dataset.\n            metric (str | list[str]): Metrics to be evaluated. Options are\n                'mAP', 'recall'.\n            logger (logging.Logger | str, optional): Logger used for printing\n                related information during evaluation. Default: None.\n            proposal_nums (Sequence[int]): Proposal number used for evaluating\n                recalls, such as recall@100, recall@1000.\n                Default: (100, 300, 1000).\n            iou_thr (float | list[float]): IoU threshold. It must be a float\n                when evaluating mAP, and can be a list when evaluating recall.\n                Default: 0.5.\n            scale_ranges (list[tuple], optional): Scale ranges for evaluating\n                mAP. If not specified, all bounding boxes would be included in\n                evaluation. Default: None.\n\n        Returns:\n            dict[str, float]: AP/recall metrics.\n        \"\"\"\n\n        if not isinstance(metric, str):\n            assert len(metric) == 1\n            metric = metric[0]\n        allowed_metrics = ['mAP', 'recall']\n        if metric not in allowed_metrics:\n            raise KeyError(f'metric {metric} is not supported')\n        annotations = [self.get_ann_info(i) for i in range(len(self))]\n        eval_results = {}\n        if metric == 'mAP':\n            assert isinstance(iou_thr, float)\n            if self.year == 2007:\n                ds_name = 'voc07'\n            else:\n                ds_name = self.dataset.CLASSES\n            mean_ap, _ = eval_map(\n                results,\n                annotations,\n                scale_ranges=None,\n                iou_thr=iou_thr,\n                dataset=ds_name,\n                logger=logger)\n            eval_results['mAP'] = mean_ap\n        elif metric == 'recall':\n            gt_bboxes = [ann['bboxes'] for ann in annotations]\n            if isinstance(iou_thr, float):\n                iou_thr = [iou_thr]\n            recalls = eval_recalls(\n                gt_bboxes, results, proposal_nums, iou_thr, logger=logger)\n            for i, num in enumerate(proposal_nums):\n                for j, iou in enumerate(iou_thr):\n                    eval_results[f'recall@{num}@{iou}'] = recalls[i, j]\n            if recalls.shape[1] > 1:\n                ar = recalls.mean(axis=1)\n                for i, num in enumerate(proposal_nums):\n                    eval_results[f'AR@{num}'] = ar[i]\n        return eval_results\n"
  },
  {
    "path": "code/mmdet/datasets/wider_face.py",
    "content": "import os.path as osp\nimport xml.etree.ElementTree as ET\n\nimport mmcv\n\nfrom .builder import DATASETS\nfrom .xml_style import XMLDataset\n\n\n@DATASETS.register_module()\nclass WIDERFaceDataset(XMLDataset):\n    \"\"\"\n    Reader for the WIDER Face dataset in PASCAL VOC format.\n    Conversion scripts can be found in\n    https://github.com/sovrasov/wider-face-pascal-voc-annotations\n    \"\"\"\n    CLASSES = ('face', )\n\n    def __init__(self, **kwargs):\n        super(WIDERFaceDataset, self).__init__(**kwargs)\n\n    def load_annotations(self, ann_file):\n        \"\"\"Load annotation from WIDERFace XML style annotation file.\n\n        Args:\n            ann_file (str): Path of XML file.\n\n        Returns:\n            list[dict]: Annotation info from XML file.\n        \"\"\"\n\n        data_infos = []\n        img_ids = mmcv.list_from_file(ann_file)\n        for img_id in img_ids:\n            filename = f'{img_id}.jpg'\n            xml_path = osp.join(self.img_prefix, 'Annotations',\n                                f'{img_id}.xml')\n            tree = ET.parse(xml_path)\n            root = tree.getroot()\n            size = root.find('size')\n            width = int(size.find('width').text)\n            height = int(size.find('height').text)\n            folder = root.find('folder').text\n            data_infos.append(\n                dict(\n                    id=img_id,\n                    filename=osp.join(folder, filename),\n                    width=width,\n                    height=height))\n\n        return data_infos\n"
  },
  {
    "path": "code/mmdet/datasets/xml_style.py",
    "content": "import os.path as osp\nimport xml.etree.ElementTree as ET\n\nimport mmcv\nimport numpy as np\nfrom PIL import Image\n\nfrom .builder import DATASETS\nfrom .custom import CustomDataset\n\n\n@DATASETS.register_module()\nclass XMLDataset(CustomDataset):\n    \"\"\"XML dataset for detection.\n\n    Args:\n        min_size (int | float, optional): The minimum size of bounding\n            boxes in the images. If the size of a bounding box is less than\n            ``min_size``, it would be add to ignored field.\n    \"\"\"\n\n    def __init__(self, min_size=None, **kwargs):\n        super(XMLDataset, self).__init__(**kwargs)\n        self.cat2label = {cat: i for i, cat in enumerate(self.CLASSES)}\n        self.min_size = min_size\n\n    def load_annotations(self, ann_file):\n        \"\"\"Load annotation from XML style ann_file.\n\n        Args:\n            ann_file (str): Path of XML file.\n\n        Returns:\n            list[dict]: Annotation info from XML file.\n        \"\"\"\n\n        data_infos = []\n        img_ids = mmcv.list_from_file(ann_file)\n        for img_id in img_ids:\n            filename = f'JPEGImages/{img_id}.jpg'\n            xml_path = osp.join(self.img_prefix, 'Annotations',\n                                f'{img_id}.xml')\n            tree = ET.parse(xml_path)\n            root = tree.getroot()\n            size = root.find('size')\n            width = 0\n            height = 0\n            if size is not None:\n                width = int(size.find('width').text)\n                height = int(size.find('height').text)\n            else:\n                img_path = osp.join(self.img_prefix, 'JPEGImages',\n                                    '{}.jpg'.format(img_id))\n                img = Image.open(img_path)\n                width, height = img.size\n            data_infos.append(\n                dict(id=img_id, filename=filename, width=width, height=height))\n\n        return data_infos\n\n    def get_subset_by_classes(self):\n        \"\"\"Filter imgs by user-defined categories\"\"\"\n        subset_data_infos = []\n        for data_info in self.data_infos:\n            img_id = data_info['id']\n            xml_path = osp.join(self.img_prefix, 'Annotations',\n                                f'{img_id}.xml')\n            tree = ET.parse(xml_path)\n            root = tree.getroot()\n            for obj in root.findall('object'):\n                name = obj.find('name').text\n                if name in self.CLASSES:\n                    subset_data_infos.append(data_info)\n                    break\n\n        return subset_data_infos\n\n    def get_ann_info(self, idx):\n        \"\"\"Get annotation from XML file by index.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            dict: Annotation info of specified index.\n        \"\"\"\n\n        img_id = self.data_infos[idx]['id']\n        xml_path = osp.join(self.img_prefix, 'Annotations', f'{img_id}.xml')\n        tree = ET.parse(xml_path)\n        root = tree.getroot()\n        bboxes = []\n        labels = []\n        bboxes_ignore = []\n        labels_ignore = []\n        for obj in root.findall('object'):\n            name = obj.find('name').text\n            if name not in self.CLASSES:\n                continue\n            label = self.cat2label[name]\n            difficult = int(obj.find('difficult').text)\n            bnd_box = obj.find('bndbox')\n            # TODO: check whether it is necessary to use int\n            # Coordinates may be float type\n            bbox = [\n                int(float(bnd_box.find('xmin').text)),\n                int(float(bnd_box.find('ymin').text)),\n                int(float(bnd_box.find('xmax').text)),\n                int(float(bnd_box.find('ymax').text))\n            ]\n            ignore = False\n            if self.min_size:\n                assert not self.test_mode\n                w = bbox[2] - bbox[0]\n                h = bbox[3] - bbox[1]\n                if w < self.min_size or h < self.min_size:\n                    ignore = True\n            if difficult or ignore:\n                bboxes_ignore.append(bbox)\n                labels_ignore.append(label)\n            else:\n                bboxes.append(bbox)\n                labels.append(label)\n        if not bboxes:\n            bboxes = np.zeros((0, 4))\n            labels = np.zeros((0, ))\n        else:\n            bboxes = np.array(bboxes, ndmin=2) - 1\n            labels = np.array(labels)\n        if not bboxes_ignore:\n            bboxes_ignore = np.zeros((0, 4))\n            labels_ignore = np.zeros((0, ))\n        else:\n            bboxes_ignore = np.array(bboxes_ignore, ndmin=2) - 1\n            labels_ignore = np.array(labels_ignore)\n        ann = dict(\n            bboxes=bboxes.astype(np.float32),\n            labels=labels.astype(np.int64),\n            bboxes_ignore=bboxes_ignore.astype(np.float32),\n            labels_ignore=labels_ignore.astype(np.int64))\n        return ann\n\n    def get_cat_ids(self, idx):\n        \"\"\"Get category ids in XML file by index.\n\n        Args:\n            idx (int): Index of data.\n\n        Returns:\n            list[int]: All categories in the image of specified index.\n        \"\"\"\n\n        cat_ids = []\n        img_id = self.data_infos[idx]['id']\n        xml_path = osp.join(self.img_prefix, 'Annotations', f'{img_id}.xml')\n        tree = ET.parse(xml_path)\n        root = tree.getroot()\n        for obj in root.findall('object'):\n            name = obj.find('name').text\n            if name not in self.CLASSES:\n                continue\n            label = self.cat2label[name]\n            cat_ids.append(label)\n\n        return cat_ids\n"
  },
  {
    "path": "code/mmdet/models/__init__.py",
    "content": "from .backbones import *  # noqa: F401,F403\nfrom .builder import (BACKBONES, DETECTORS, HEADS, LOSSES, NECKS,\n                      ROI_EXTRACTORS, SHARED_HEADS, build_backbone,\n                      build_detector, build_head, build_loss, build_neck,\n                      build_roi_extractor, build_shared_head)\nfrom .dense_heads import *  # noqa: F401,F403\nfrom .detectors import *  # noqa: F401,F403\nfrom .losses import *  # noqa: F401,F403\nfrom .necks import *  # noqa: F401,F403\nfrom .roi_heads import *  # noqa: F401,F403\n\n__all__ = [\n    'BACKBONES', 'NECKS', 'ROI_EXTRACTORS', 'SHARED_HEADS', 'HEADS', 'LOSSES',\n    'DETECTORS', 'build_backbone', 'build_neck', 'build_roi_extractor',\n    'build_shared_head', 'build_head', 'build_loss', 'build_detector'\n]\n"
  },
  {
    "path": "code/mmdet/models/backbones/__init__.py",
    "content": "from .detectors_resnet import DetectoRS_ResNet\nfrom .detectors_resnext import DetectoRS_ResNeXt\nfrom .hourglass import HourglassNet\nfrom .hrnet import HRNet\nfrom .mobilenet import MobileNetV2\nfrom .regnet import RegNet\nfrom .res2net import Res2Net\nfrom .resnet import ResNet, ResNetV1d\nfrom .resnext import ResNeXt\nfrom .ssd_vgg import SSDVGG\n\n__all__ = [\n    'RegNet', 'ResNet', 'ResNetV1d', 'ResNeXt', 'SSDVGG', 'HRNet', 'Res2Net',\n    'HourglassNet', 'DetectoRS_ResNet', 'DetectoRS_ResNeXt', 'MobileNetV2'\n]\n"
  },
  {
    "path": "code/mmdet/models/backbones/detectors_resnet.py",
    "content": "import torch.nn as nn\nimport torch.utils.checkpoint as cp\nfrom mmcv.cnn import build_conv_layer, build_norm_layer, constant_init\n\nfrom ..builder import BACKBONES\nfrom .resnet import Bottleneck as _Bottleneck\nfrom .resnet import ResNet\n\n\nclass Bottleneck(_Bottleneck):\n    \"\"\"Bottleneck for the ResNet backbone in `DetectoRS\n    <https://arxiv.org/pdf/2006.02334.pdf>`_.\n\n    This bottleneck allows the users to specify whether to use\n    SAC (Switchable Atrous Convolution) and RFP (Recursive Feature Pyramid).\n\n    Args:\n         inplanes (int): The number of input channels.\n         planes (int): The number of output channels before expansion.\n         rfp_inplanes (int, optional): The number of channels from RFP.\n             Default: None. If specified, an additional conv layer will be\n             added for ``rfp_feat``. Otherwise, the structure is the same as\n             base class.\n         sac (dict, optional): Dictionary to construct SAC. Default: None.\n    \"\"\"\n    expansion = 4\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 rfp_inplanes=None,\n                 sac=None,\n                 **kwargs):\n        super(Bottleneck, self).__init__(inplanes, planes, **kwargs)\n\n        assert sac is None or isinstance(sac, dict)\n        self.sac = sac\n        self.with_sac = sac is not None\n        if self.with_sac:\n            self.conv2 = build_conv_layer(\n                self.sac,\n                planes,\n                planes,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=self.dilation,\n                dilation=self.dilation,\n                bias=False)\n\n        self.rfp_inplanes = rfp_inplanes\n        if self.rfp_inplanes:\n            self.rfp_conv = build_conv_layer(\n                None,\n                self.rfp_inplanes,\n                planes * self.expansion,\n                1,\n                stride=1,\n                bias=True)\n        self.init_weights()\n\n    def init_weights(self):\n        \"\"\"Initialize the weights.\"\"\"\n        if self.rfp_inplanes:\n            constant_init(self.rfp_conv, 0)\n\n    def rfp_forward(self, x, rfp_feat):\n        \"\"\"The forward function that also takes the RFP features as input.\"\"\"\n\n        def _inner_forward(x):\n            identity = x\n\n            out = self.conv1(x)\n            out = self.norm1(out)\n            out = self.relu(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv1_plugin_names)\n\n            out = self.conv2(out)\n            out = self.norm2(out)\n            out = self.relu(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv2_plugin_names)\n\n            out = self.conv3(out)\n            out = self.norm3(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv3_plugin_names)\n\n            if self.downsample is not None:\n                identity = self.downsample(x)\n\n            out += identity\n\n            return out\n\n        if self.with_cp and x.requires_grad:\n            out = cp.checkpoint(_inner_forward, x)\n        else:\n            out = _inner_forward(x)\n\n        if self.rfp_inplanes:\n            rfp_feat = self.rfp_conv(rfp_feat)\n            out = out + rfp_feat\n\n        out = self.relu(out)\n\n        return out\n\n\nclass ResLayer(nn.Sequential):\n    \"\"\"ResLayer to build ResNet style backbone for RPF in detectoRS.\n\n    The difference between this module and base class is that we pass\n    ``rfp_inplanes`` to the first block.\n\n    Args:\n        block (nn.Module): block used to build ResLayer.\n        inplanes (int): inplanes of block.\n        planes (int): planes of block.\n        num_blocks (int): number of blocks.\n        stride (int): stride of the first block. Default: 1\n        avg_down (bool): Use AvgPool instead of stride conv when\n            downsampling in the bottleneck. Default: False\n        conv_cfg (dict): dictionary to construct and config conv layer.\n            Default: None\n        norm_cfg (dict): dictionary to construct and config norm layer.\n            Default: dict(type='BN')\n        downsample_first (bool): Downsample at the first block or last block.\n            False for Hourglass, True for ResNet. Default: True\n        rfp_inplanes (int, optional): The number of channels from RFP.\n            Default: None. If specified, an additional conv layer will be\n            added for ``rfp_feat``. Otherwise, the structure is the same as\n            base class.\n    \"\"\"\n\n    def __init__(self,\n                 block,\n                 inplanes,\n                 planes,\n                 num_blocks,\n                 stride=1,\n                 avg_down=False,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 downsample_first=True,\n                 rfp_inplanes=None,\n                 **kwargs):\n        self.block = block\n        assert downsample_first, f'downsampel_first={downsample_first} is ' \\\n                                 'not supported in DetectoRS'\n\n        downsample = None\n        if stride != 1 or inplanes != planes * block.expansion:\n            downsample = []\n            conv_stride = stride\n            if avg_down and stride != 1:\n                conv_stride = 1\n                downsample.append(\n                    nn.AvgPool2d(\n                        kernel_size=stride,\n                        stride=stride,\n                        ceil_mode=True,\n                        count_include_pad=False))\n            downsample.extend([\n                build_conv_layer(\n                    conv_cfg,\n                    inplanes,\n                    planes * block.expansion,\n                    kernel_size=1,\n                    stride=conv_stride,\n                    bias=False),\n                build_norm_layer(norm_cfg, planes * block.expansion)[1]\n            ])\n            downsample = nn.Sequential(*downsample)\n\n        layers = []\n        layers.append(\n            block(\n                inplanes=inplanes,\n                planes=planes,\n                stride=stride,\n                downsample=downsample,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                rfp_inplanes=rfp_inplanes,\n                **kwargs))\n        inplanes = planes * block.expansion\n        for _ in range(1, num_blocks):\n            layers.append(\n                block(\n                    inplanes=inplanes,\n                    planes=planes,\n                    stride=1,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=norm_cfg,\n                    **kwargs))\n\n        super(ResLayer, self).__init__(*layers)\n\n\n@BACKBONES.register_module()\nclass DetectoRS_ResNet(ResNet):\n    \"\"\"ResNet backbone for DetectoRS.\n\n    Args:\n        sac (dict, optional): Dictionary to construct SAC (Switchable Atrous\n            Convolution). Default: None.\n        stage_with_sac (list): Which stage to use sac. Default: (False, False,\n            False, False).\n        rfp_inplanes (int, optional): The number of channels from RFP.\n            Default: None. If specified, an additional conv layer will be\n            added for ``rfp_feat``. Otherwise, the structure is the same as\n            base class.\n        output_img (bool): If ``True``, the input image will be inserted into\n            the starting position of output. Default: False.\n        pretrained (str, optional): The pretrained model to load.\n    \"\"\"\n\n    arch_settings = {\n        50: (Bottleneck, (3, 4, 6, 3)),\n        101: (Bottleneck, (3, 4, 23, 3)),\n        152: (Bottleneck, (3, 8, 36, 3))\n    }\n\n    def __init__(self,\n                 sac=None,\n                 stage_with_sac=(False, False, False, False),\n                 rfp_inplanes=None,\n                 output_img=False,\n                 pretrained=None,\n                 **kwargs):\n        self.sac = sac\n        self.stage_with_sac = stage_with_sac\n        self.rfp_inplanes = rfp_inplanes\n        self.output_img = output_img\n        self.pretrained = pretrained\n        super(DetectoRS_ResNet, self).__init__(**kwargs)\n\n        self.inplanes = self.stem_channels\n        self.res_layers = []\n        for i, num_blocks in enumerate(self.stage_blocks):\n            stride = self.strides[i]\n            dilation = self.dilations[i]\n            dcn = self.dcn if self.stage_with_dcn[i] else None\n            sac = self.sac if self.stage_with_sac[i] else None\n            if self.plugins is not None:\n                stage_plugins = self.make_stage_plugins(self.plugins, i)\n            else:\n                stage_plugins = None\n            planes = self.base_channels * 2**i\n            res_layer = self.make_res_layer(\n                block=self.block,\n                inplanes=self.inplanes,\n                planes=planes,\n                num_blocks=num_blocks,\n                stride=stride,\n                dilation=dilation,\n                style=self.style,\n                avg_down=self.avg_down,\n                with_cp=self.with_cp,\n                conv_cfg=self.conv_cfg,\n                norm_cfg=self.norm_cfg,\n                dcn=dcn,\n                sac=sac,\n                rfp_inplanes=rfp_inplanes if i > 0 else None,\n                plugins=stage_plugins)\n            self.inplanes = planes * self.block.expansion\n            layer_name = f'layer{i + 1}'\n            self.add_module(layer_name, res_layer)\n            self.res_layers.append(layer_name)\n\n        self._freeze_stages()\n\n    def make_res_layer(self, **kwargs):\n        \"\"\"Pack all blocks in a stage into a ``ResLayer`` for DetectoRS\"\"\"\n        return ResLayer(**kwargs)\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        outs = list(super(DetectoRS_ResNet, self).forward(x))\n        if self.output_img:\n            outs.insert(0, x)\n        return tuple(outs)\n\n    def rfp_forward(self, x, rfp_feats):\n        \"\"\"Forward function for RFP\"\"\"\n        if self.deep_stem:\n            x = self.stem(x)\n        else:\n            x = self.conv1(x)\n            x = self.norm1(x)\n            x = self.relu(x)\n        x = self.maxpool(x)\n        outs = []\n        for i, layer_name in enumerate(self.res_layers):\n            res_layer = getattr(self, layer_name)\n            rfp_feat = rfp_feats[i] if i > 0 else None\n            for layer in res_layer:\n                x = layer.rfp_forward(x, rfp_feat)\n            if i in self.out_indices:\n                outs.append(x)\n        return tuple(outs)\n"
  },
  {
    "path": "code/mmdet/models/backbones/detectors_resnext.py",
    "content": "import math\n\nfrom mmcv.cnn import build_conv_layer, build_norm_layer\n\nfrom ..builder import BACKBONES\nfrom .detectors_resnet import Bottleneck as _Bottleneck\nfrom .detectors_resnet import DetectoRS_ResNet\n\n\nclass Bottleneck(_Bottleneck):\n    expansion = 4\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 groups=1,\n                 base_width=4,\n                 base_channels=64,\n                 **kwargs):\n        \"\"\"Bottleneck block for ResNeXt.\n        If style is \"pytorch\", the stride-two layer is the 3x3 conv layer,\n        if it is \"caffe\", the stride-two layer is the first 1x1 conv layer.\n        \"\"\"\n        super(Bottleneck, self).__init__(inplanes, planes, **kwargs)\n\n        if groups == 1:\n            width = self.planes\n        else:\n            width = math.floor(self.planes *\n                               (base_width / base_channels)) * groups\n\n        self.norm1_name, norm1 = build_norm_layer(\n            self.norm_cfg, width, postfix=1)\n        self.norm2_name, norm2 = build_norm_layer(\n            self.norm_cfg, width, postfix=2)\n        self.norm3_name, norm3 = build_norm_layer(\n            self.norm_cfg, self.planes * self.expansion, postfix=3)\n\n        self.conv1 = build_conv_layer(\n            self.conv_cfg,\n            self.inplanes,\n            width,\n            kernel_size=1,\n            stride=self.conv1_stride,\n            bias=False)\n        self.add_module(self.norm1_name, norm1)\n        fallback_on_stride = False\n        self.with_modulated_dcn = False\n        if self.with_dcn:\n            fallback_on_stride = self.dcn.pop('fallback_on_stride', False)\n        if self.with_sac:\n            self.conv2 = build_conv_layer(\n                self.sac,\n                width,\n                width,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=self.dilation,\n                dilation=self.dilation,\n                groups=groups,\n                bias=False)\n        elif not self.with_dcn or fallback_on_stride:\n            self.conv2 = build_conv_layer(\n                self.conv_cfg,\n                width,\n                width,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=self.dilation,\n                dilation=self.dilation,\n                groups=groups,\n                bias=False)\n        else:\n            assert self.conv_cfg is None, 'conv_cfg must be None for DCN'\n            self.conv2 = build_conv_layer(\n                self.dcn,\n                width,\n                width,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=self.dilation,\n                dilation=self.dilation,\n                groups=groups,\n                bias=False)\n\n        self.add_module(self.norm2_name, norm2)\n        self.conv3 = build_conv_layer(\n            self.conv_cfg,\n            width,\n            self.planes * self.expansion,\n            kernel_size=1,\n            bias=False)\n        self.add_module(self.norm3_name, norm3)\n\n\n@BACKBONES.register_module()\nclass DetectoRS_ResNeXt(DetectoRS_ResNet):\n    \"\"\"ResNeXt backbone for DetectoRS.\n\n    Args:\n        groups (int): The number of groups in ResNeXt.\n        base_width (int): The base width of ResNeXt.\n    \"\"\"\n\n    arch_settings = {\n        50: (Bottleneck, (3, 4, 6, 3)),\n        101: (Bottleneck, (3, 4, 23, 3)),\n        152: (Bottleneck, (3, 8, 36, 3))\n    }\n\n    def __init__(self, groups=1, base_width=4, **kwargs):\n        self.groups = groups\n        self.base_width = base_width\n        super(DetectoRS_ResNeXt, self).__init__(**kwargs)\n\n    def make_res_layer(self, **kwargs):\n        return super().make_res_layer(\n            groups=self.groups,\n            base_width=self.base_width,\n            base_channels=self.base_channels,\n            **kwargs)\n"
  },
  {
    "path": "code/mmdet/models/backbones/hourglass.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule\n\nfrom ..builder import BACKBONES\nfrom ..utils import ResLayer\nfrom .resnet import BasicBlock\n\n\nclass HourglassModule(nn.Module):\n    \"\"\"Hourglass Module for HourglassNet backbone.\n\n    Generate module recursively and use BasicBlock as the base unit.\n\n    Args:\n        depth (int): Depth of current HourglassModule.\n        stage_channels (list[int]): Feature channels of sub-modules in current\n            and follow-up HourglassModule.\n        stage_blocks (list[int]): Number of sub-modules stacked in current and\n            follow-up HourglassModule.\n        norm_cfg (dict): Dictionary to construct and config norm layer.\n    \"\"\"\n\n    def __init__(self,\n                 depth,\n                 stage_channels,\n                 stage_blocks,\n                 norm_cfg=dict(type='BN', requires_grad=True)):\n        super(HourglassModule, self).__init__()\n\n        self.depth = depth\n\n        cur_block = stage_blocks[0]\n        next_block = stage_blocks[1]\n\n        cur_channel = stage_channels[0]\n        next_channel = stage_channels[1]\n\n        self.up1 = ResLayer(\n            BasicBlock, cur_channel, cur_channel, cur_block, norm_cfg=norm_cfg)\n\n        self.low1 = ResLayer(\n            BasicBlock,\n            cur_channel,\n            next_channel,\n            cur_block,\n            stride=2,\n            norm_cfg=norm_cfg)\n\n        if self.depth > 1:\n            self.low2 = HourglassModule(depth - 1, stage_channels[1:],\n                                        stage_blocks[1:])\n        else:\n            self.low2 = ResLayer(\n                BasicBlock,\n                next_channel,\n                next_channel,\n                next_block,\n                norm_cfg=norm_cfg)\n\n        self.low3 = ResLayer(\n            BasicBlock,\n            next_channel,\n            cur_channel,\n            cur_block,\n            norm_cfg=norm_cfg,\n            downsample_first=False)\n\n        self.up2 = nn.Upsample(scale_factor=2)\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        up1 = self.up1(x)\n        low1 = self.low1(x)\n        low2 = self.low2(low1)\n        low3 = self.low3(low2)\n        up2 = self.up2(low3)\n        return up1 + up2\n\n\n@BACKBONES.register_module()\nclass HourglassNet(nn.Module):\n    \"\"\"HourglassNet backbone.\n\n    Stacked Hourglass Networks for Human Pose Estimation.\n    More details can be found in the `paper\n    <https://arxiv.org/abs/1603.06937>`_ .\n\n    Args:\n        downsample_times (int): Downsample times in a HourglassModule.\n        num_stacks (int): Number of HourglassModule modules stacked,\n            1 for Hourglass-52, 2 for Hourglass-104.\n        stage_channels (list[int]): Feature channel of each sub-module in a\n            HourglassModule.\n        stage_blocks (list[int]): Number of sub-modules stacked in a\n            HourglassModule.\n        feat_channel (int): Feature channel of conv after a HourglassModule.\n        norm_cfg (dict): Dictionary to construct and config norm layer.\n\n    Example:\n        >>> from mmdet.models import HourglassNet\n        >>> import torch\n        >>> self = HourglassNet()\n        >>> self.eval()\n        >>> inputs = torch.rand(1, 3, 511, 511)\n        >>> level_outputs = self.forward(inputs)\n        >>> for level_output in level_outputs:\n        ...     print(tuple(level_output.shape))\n        (1, 256, 128, 128)\n        (1, 256, 128, 128)\n    \"\"\"\n\n    def __init__(self,\n                 downsample_times=5,\n                 num_stacks=2,\n                 stage_channels=(256, 256, 384, 384, 384, 512),\n                 stage_blocks=(2, 2, 2, 2, 2, 4),\n                 feat_channel=256,\n                 norm_cfg=dict(type='BN', requires_grad=True)):\n        super(HourglassNet, self).__init__()\n\n        self.num_stacks = num_stacks\n        assert self.num_stacks >= 1\n        assert len(stage_channels) == len(stage_blocks)\n        assert len(stage_channels) > downsample_times\n\n        cur_channel = stage_channels[0]\n\n        self.stem = nn.Sequential(\n            ConvModule(3, 128, 7, padding=3, stride=2, norm_cfg=norm_cfg),\n            ResLayer(BasicBlock, 128, 256, 1, stride=2, norm_cfg=norm_cfg))\n\n        self.hourglass_modules = nn.ModuleList([\n            HourglassModule(downsample_times, stage_channels, stage_blocks)\n            for _ in range(num_stacks)\n        ])\n\n        self.inters = ResLayer(\n            BasicBlock,\n            cur_channel,\n            cur_channel,\n            num_stacks - 1,\n            norm_cfg=norm_cfg)\n\n        self.conv1x1s = nn.ModuleList([\n            ConvModule(\n                cur_channel, cur_channel, 1, norm_cfg=norm_cfg, act_cfg=None)\n            for _ in range(num_stacks - 1)\n        ])\n\n        self.out_convs = nn.ModuleList([\n            ConvModule(\n                cur_channel, feat_channel, 3, padding=1, norm_cfg=norm_cfg)\n            for _ in range(num_stacks)\n        ])\n\n        self.remap_convs = nn.ModuleList([\n            ConvModule(\n                feat_channel, cur_channel, 1, norm_cfg=norm_cfg, act_cfg=None)\n            for _ in range(num_stacks - 1)\n        ])\n\n        self.relu = nn.ReLU(inplace=True)\n\n    def init_weights(self, pretrained=None):\n        \"\"\"\n        We do nothing in this function because all modules we used (ConvModule,\n        BasicBlock and etc.) have default initialization, and currently\n        we don't provide pretrained model of HourglassNet.\n        Detector's __init__() will call backbone's init_weights() with\n        pretrained as input, so we keep this function.\n        \"\"\"\n        pass\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        inter_feat = self.stem(x)\n        out_feats = []\n\n        for ind in range(self.num_stacks):\n            single_hourglass = self.hourglass_modules[ind]\n            out_conv = self.out_convs[ind]\n\n            hourglass_feat = single_hourglass(inter_feat)\n            out_feat = out_conv(hourglass_feat)\n            out_feats.append(out_feat)\n\n            if ind < self.num_stacks - 1:\n                inter_feat = self.conv1x1s[ind](\n                    inter_feat) + self.remap_convs[ind](\n                        out_feat)\n                inter_feat = self.inters[ind](self.relu(inter_feat))\n\n        return out_feats\n"
  },
  {
    "path": "code/mmdet/models/backbones/hrnet.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init,\n                      kaiming_init)\nfrom mmcv.runner import load_checkpoint\nfrom torch.nn.modules.batchnorm import _BatchNorm\n\nfrom mmdet.utils import get_root_logger\nfrom ..builder import BACKBONES\nfrom .resnet import BasicBlock, Bottleneck\n\n\nclass HRModule(nn.Module):\n    \"\"\" High-Resolution Module for HRNet. In this module, every branch\n    has 4 BasicBlocks/Bottlenecks. Fusion/Exchange is in this module.\n    \"\"\"\n\n    def __init__(self,\n                 num_branches,\n                 blocks,\n                 num_blocks,\n                 in_channels,\n                 num_channels,\n                 multiscale_output=True,\n                 with_cp=False,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN')):\n        super(HRModule, self).__init__()\n        self._check_branches(num_branches, num_blocks, in_channels,\n                             num_channels)\n\n        self.in_channels = in_channels\n        self.num_branches = num_branches\n\n        self.multiscale_output = multiscale_output\n        self.norm_cfg = norm_cfg\n        self.conv_cfg = conv_cfg\n        self.with_cp = with_cp\n        self.branches = self._make_branches(num_branches, blocks, num_blocks,\n                                            num_channels)\n        self.fuse_layers = self._make_fuse_layers()\n        self.relu = nn.ReLU(inplace=False)\n\n    def _check_branches(self, num_branches, num_blocks, in_channels,\n                        num_channels):\n        if num_branches != len(num_blocks):\n            error_msg = f'NUM_BRANCHES({num_branches}) ' \\\n                f'!= NUM_BLOCKS({len(num_blocks)})'\n            raise ValueError(error_msg)\n\n        if num_branches != len(num_channels):\n            error_msg = f'NUM_BRANCHES({num_branches}) ' \\\n                f'!= NUM_CHANNELS({len(num_channels)})'\n            raise ValueError(error_msg)\n\n        if num_branches != len(in_channels):\n            error_msg = f'NUM_BRANCHES({num_branches}) ' \\\n                f'!= NUM_INCHANNELS({len(in_channels)})'\n            raise ValueError(error_msg)\n\n    def _make_one_branch(self,\n                         branch_index,\n                         block,\n                         num_blocks,\n                         num_channels,\n                         stride=1):\n        downsample = None\n        if stride != 1 or \\\n                self.in_channels[branch_index] != \\\n                num_channels[branch_index] * block.expansion:\n            downsample = nn.Sequential(\n                build_conv_layer(\n                    self.conv_cfg,\n                    self.in_channels[branch_index],\n                    num_channels[branch_index] * block.expansion,\n                    kernel_size=1,\n                    stride=stride,\n                    bias=False),\n                build_norm_layer(self.norm_cfg, num_channels[branch_index] *\n                                 block.expansion)[1])\n\n        layers = []\n        layers.append(\n            block(\n                self.in_channels[branch_index],\n                num_channels[branch_index],\n                stride,\n                downsample=downsample,\n                with_cp=self.with_cp,\n                norm_cfg=self.norm_cfg,\n                conv_cfg=self.conv_cfg))\n        self.in_channels[branch_index] = \\\n            num_channels[branch_index] * block.expansion\n        for i in range(1, num_blocks[branch_index]):\n            layers.append(\n                block(\n                    self.in_channels[branch_index],\n                    num_channels[branch_index],\n                    with_cp=self.with_cp,\n                    norm_cfg=self.norm_cfg,\n                    conv_cfg=self.conv_cfg))\n\n        return nn.Sequential(*layers)\n\n    def _make_branches(self, num_branches, block, num_blocks, num_channels):\n        branches = []\n\n        for i in range(num_branches):\n            branches.append(\n                self._make_one_branch(i, block, num_blocks, num_channels))\n\n        return nn.ModuleList(branches)\n\n    def _make_fuse_layers(self):\n        if self.num_branches == 1:\n            return None\n\n        num_branches = self.num_branches\n        in_channels = self.in_channels\n        fuse_layers = []\n        num_out_branches = num_branches if self.multiscale_output else 1\n        for i in range(num_out_branches):\n            fuse_layer = []\n            for j in range(num_branches):\n                if j > i:\n                    fuse_layer.append(\n                        nn.Sequential(\n                            build_conv_layer(\n                                self.conv_cfg,\n                                in_channels[j],\n                                in_channels[i],\n                                kernel_size=1,\n                                stride=1,\n                                padding=0,\n                                bias=False),\n                            build_norm_layer(self.norm_cfg, in_channels[i])[1],\n                            nn.Upsample(\n                                scale_factor=2**(j - i), mode='nearest')))\n                elif j == i:\n                    fuse_layer.append(None)\n                else:\n                    conv_downsamples = []\n                    for k in range(i - j):\n                        if k == i - j - 1:\n                            conv_downsamples.append(\n                                nn.Sequential(\n                                    build_conv_layer(\n                                        self.conv_cfg,\n                                        in_channels[j],\n                                        in_channels[i],\n                                        kernel_size=3,\n                                        stride=2,\n                                        padding=1,\n                                        bias=False),\n                                    build_norm_layer(self.norm_cfg,\n                                                     in_channels[i])[1]))\n                        else:\n                            conv_downsamples.append(\n                                nn.Sequential(\n                                    build_conv_layer(\n                                        self.conv_cfg,\n                                        in_channels[j],\n                                        in_channels[j],\n                                        kernel_size=3,\n                                        stride=2,\n                                        padding=1,\n                                        bias=False),\n                                    build_norm_layer(self.norm_cfg,\n                                                     in_channels[j])[1],\n                                    nn.ReLU(inplace=False)))\n                    fuse_layer.append(nn.Sequential(*conv_downsamples))\n            fuse_layers.append(nn.ModuleList(fuse_layer))\n\n        return nn.ModuleList(fuse_layers)\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        if self.num_branches == 1:\n            return [self.branches[0](x[0])]\n\n        for i in range(self.num_branches):\n            x[i] = self.branches[i](x[i])\n\n        x_fuse = []\n        for i in range(len(self.fuse_layers)):\n            y = 0\n            for j in range(self.num_branches):\n                if i == j:\n                    y += x[j]\n                else:\n                    y += self.fuse_layers[i][j](x[j])\n            x_fuse.append(self.relu(y))\n        return x_fuse\n\n\n@BACKBONES.register_module()\nclass HRNet(nn.Module):\n    \"\"\"HRNet backbone.\n\n    High-Resolution Representations for Labeling Pixels and Regions\n    arXiv: https://arxiv.org/abs/1904.04514\n\n    Args:\n        extra (dict): detailed configuration for each stage of HRNet.\n        in_channels (int): Number of input image channels. Default: 3.\n        conv_cfg (dict): dictionary to construct and config conv layer.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n        norm_eval (bool): Whether to set norm layers to eval mode, namely,\n            freeze running stats (mean and var). Note: Effect on Batch Norm\n            and its variants only.\n        with_cp (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n        zero_init_residual (bool): whether to use zero init for last norm layer\n            in resblocks to let them behave as identity.\n\n    Example:\n        >>> from mmdet.models import HRNet\n        >>> import torch\n        >>> extra = dict(\n        >>>     stage1=dict(\n        >>>         num_modules=1,\n        >>>         num_branches=1,\n        >>>         block='BOTTLENECK',\n        >>>         num_blocks=(4, ),\n        >>>         num_channels=(64, )),\n        >>>     stage2=dict(\n        >>>         num_modules=1,\n        >>>         num_branches=2,\n        >>>         block='BASIC',\n        >>>         num_blocks=(4, 4),\n        >>>         num_channels=(32, 64)),\n        >>>     stage3=dict(\n        >>>         num_modules=4,\n        >>>         num_branches=3,\n        >>>         block='BASIC',\n        >>>         num_blocks=(4, 4, 4),\n        >>>         num_channels=(32, 64, 128)),\n        >>>     stage4=dict(\n        >>>         num_modules=3,\n        >>>         num_branches=4,\n        >>>         block='BASIC',\n        >>>         num_blocks=(4, 4, 4, 4),\n        >>>         num_channels=(32, 64, 128, 256)))\n        >>> self = HRNet(extra, in_channels=1)\n        >>> self.eval()\n        >>> inputs = torch.rand(1, 1, 32, 32)\n        >>> level_outputs = self.forward(inputs)\n        >>> for level_out in level_outputs:\n        ...     print(tuple(level_out.shape))\n        (1, 32, 8, 8)\n        (1, 64, 4, 4)\n        (1, 128, 2, 2)\n        (1, 256, 1, 1)\n    \"\"\"\n\n    blocks_dict = {'BASIC': BasicBlock, 'BOTTLENECK': Bottleneck}\n\n    def __init__(self,\n                 extra,\n                 in_channels=3,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 norm_eval=True,\n                 with_cp=False,\n                 zero_init_residual=False):\n        super(HRNet, self).__init__()\n        self.extra = extra\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.norm_eval = norm_eval\n        self.with_cp = with_cp\n        self.zero_init_residual = zero_init_residual\n\n        # stem net\n        self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1)\n        self.norm2_name, norm2 = build_norm_layer(self.norm_cfg, 64, postfix=2)\n\n        self.conv1 = build_conv_layer(\n            self.conv_cfg,\n            in_channels,\n            64,\n            kernel_size=3,\n            stride=2,\n            padding=1,\n            bias=False)\n\n        self.add_module(self.norm1_name, norm1)\n        self.conv2 = build_conv_layer(\n            self.conv_cfg,\n            64,\n            64,\n            kernel_size=3,\n            stride=2,\n            padding=1,\n            bias=False)\n\n        self.add_module(self.norm2_name, norm2)\n        self.relu = nn.ReLU(inplace=True)\n\n        # stage 1\n        self.stage1_cfg = self.extra['stage1']\n        num_channels = self.stage1_cfg['num_channels'][0]\n        block_type = self.stage1_cfg['block']\n        num_blocks = self.stage1_cfg['num_blocks'][0]\n\n        block = self.blocks_dict[block_type]\n        stage1_out_channels = num_channels * block.expansion\n        self.layer1 = self._make_layer(block, 64, num_channels, num_blocks)\n\n        # stage 2\n        self.stage2_cfg = self.extra['stage2']\n        num_channels = self.stage2_cfg['num_channels']\n        block_type = self.stage2_cfg['block']\n\n        block = self.blocks_dict[block_type]\n        num_channels = [channel * block.expansion for channel in num_channels]\n        self.transition1 = self._make_transition_layer([stage1_out_channels],\n                                                       num_channels)\n        self.stage2, pre_stage_channels = self._make_stage(\n            self.stage2_cfg, num_channels)\n\n        # stage 3\n        self.stage3_cfg = self.extra['stage3']\n        num_channels = self.stage3_cfg['num_channels']\n        block_type = self.stage3_cfg['block']\n\n        block = self.blocks_dict[block_type]\n        num_channels = [channel * block.expansion for channel in num_channels]\n        self.transition2 = self._make_transition_layer(pre_stage_channels,\n                                                       num_channels)\n        self.stage3, pre_stage_channels = self._make_stage(\n            self.stage3_cfg, num_channels)\n\n        # stage 4\n        self.stage4_cfg = self.extra['stage4']\n        num_channels = self.stage4_cfg['num_channels']\n        block_type = self.stage4_cfg['block']\n\n        block = self.blocks_dict[block_type]\n        num_channels = [channel * block.expansion for channel in num_channels]\n        self.transition3 = self._make_transition_layer(pre_stage_channels,\n                                                       num_channels)\n        self.stage4, pre_stage_channels = self._make_stage(\n            self.stage4_cfg, num_channels)\n\n    @property\n    def norm1(self):\n        \"\"\"nn.Module: the normalization layer named \"norm1\" \"\"\"\n        return getattr(self, self.norm1_name)\n\n    @property\n    def norm2(self):\n        \"\"\"nn.Module: the normalization layer named \"norm2\" \"\"\"\n        return getattr(self, self.norm2_name)\n\n    def _make_transition_layer(self, num_channels_pre_layer,\n                               num_channels_cur_layer):\n        num_branches_cur = len(num_channels_cur_layer)\n        num_branches_pre = len(num_channels_pre_layer)\n\n        transition_layers = []\n        for i in range(num_branches_cur):\n            if i < num_branches_pre:\n                if num_channels_cur_layer[i] != num_channels_pre_layer[i]:\n                    transition_layers.append(\n                        nn.Sequential(\n                            build_conv_layer(\n                                self.conv_cfg,\n                                num_channels_pre_layer[i],\n                                num_channels_cur_layer[i],\n                                kernel_size=3,\n                                stride=1,\n                                padding=1,\n                                bias=False),\n                            build_norm_layer(self.norm_cfg,\n                                             num_channels_cur_layer[i])[1],\n                            nn.ReLU(inplace=True)))\n                else:\n                    transition_layers.append(None)\n            else:\n                conv_downsamples = []\n                for j in range(i + 1 - num_branches_pre):\n                    in_channels = num_channels_pre_layer[-1]\n                    out_channels = num_channels_cur_layer[i] \\\n                        if j == i - num_branches_pre else in_channels\n                    conv_downsamples.append(\n                        nn.Sequential(\n                            build_conv_layer(\n                                self.conv_cfg,\n                                in_channels,\n                                out_channels,\n                                kernel_size=3,\n                                stride=2,\n                                padding=1,\n                                bias=False),\n                            build_norm_layer(self.norm_cfg, out_channels)[1],\n                            nn.ReLU(inplace=True)))\n                transition_layers.append(nn.Sequential(*conv_downsamples))\n\n        return nn.ModuleList(transition_layers)\n\n    def _make_layer(self, block, inplanes, planes, blocks, stride=1):\n        downsample = None\n        if stride != 1 or inplanes != planes * block.expansion:\n            downsample = nn.Sequential(\n                build_conv_layer(\n                    self.conv_cfg,\n                    inplanes,\n                    planes * block.expansion,\n                    kernel_size=1,\n                    stride=stride,\n                    bias=False),\n                build_norm_layer(self.norm_cfg, planes * block.expansion)[1])\n\n        layers = []\n        layers.append(\n            block(\n                inplanes,\n                planes,\n                stride,\n                downsample=downsample,\n                with_cp=self.with_cp,\n                norm_cfg=self.norm_cfg,\n                conv_cfg=self.conv_cfg))\n        inplanes = planes * block.expansion\n        for i in range(1, blocks):\n            layers.append(\n                block(\n                    inplanes,\n                    planes,\n                    with_cp=self.with_cp,\n                    norm_cfg=self.norm_cfg,\n                    conv_cfg=self.conv_cfg))\n\n        return nn.Sequential(*layers)\n\n    def _make_stage(self, layer_config, in_channels, multiscale_output=True):\n        num_modules = layer_config['num_modules']\n        num_branches = layer_config['num_branches']\n        num_blocks = layer_config['num_blocks']\n        num_channels = layer_config['num_channels']\n        block = self.blocks_dict[layer_config['block']]\n\n        hr_modules = []\n        for i in range(num_modules):\n            # multi_scale_output is only used for the last module\n            if not multiscale_output and i == num_modules - 1:\n                reset_multiscale_output = False\n            else:\n                reset_multiscale_output = True\n\n            hr_modules.append(\n                HRModule(\n                    num_branches,\n                    block,\n                    num_blocks,\n                    in_channels,\n                    num_channels,\n                    reset_multiscale_output,\n                    with_cp=self.with_cp,\n                    norm_cfg=self.norm_cfg,\n                    conv_cfg=self.conv_cfg))\n\n        return nn.Sequential(*hr_modules), in_channels\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in backbone\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if isinstance(pretrained, str):\n            logger = get_root_logger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, (_BatchNorm, nn.GroupNorm)):\n                    constant_init(m, 1)\n\n            if self.zero_init_residual:\n                for m in self.modules():\n                    if isinstance(m, Bottleneck):\n                        constant_init(m.norm3, 0)\n                    elif isinstance(m, BasicBlock):\n                        constant_init(m.norm2, 0)\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        x = self.conv1(x)\n        x = self.norm1(x)\n        x = self.relu(x)\n        x = self.conv2(x)\n        x = self.norm2(x)\n        x = self.relu(x)\n        x = self.layer1(x)\n\n        x_list = []\n        for i in range(self.stage2_cfg['num_branches']):\n            if self.transition1[i] is not None:\n                x_list.append(self.transition1[i](x))\n            else:\n                x_list.append(x)\n        y_list = self.stage2(x_list)\n\n        x_list = []\n        for i in range(self.stage3_cfg['num_branches']):\n            if self.transition2[i] is not None:\n                x_list.append(self.transition2[i](y_list[-1]))\n            else:\n                x_list.append(y_list[i])\n        y_list = self.stage3(x_list)\n\n        x_list = []\n        for i in range(self.stage4_cfg['num_branches']):\n            if self.transition3[i] is not None:\n                x_list.append(self.transition3[i](y_list[-1]))\n            else:\n                x_list.append(y_list[i])\n        y_list = self.stage4(x_list)\n\n        return y_list\n\n    def train(self, mode=True):\n        \"\"\"Convert the model into training mode whill keeping the normalization\n        layer freezed\"\"\"\n        super(HRNet, self).train(mode)\n        if mode and self.norm_eval:\n            for m in self.modules():\n                # trick: eval have effect on BatchNorm only\n                if isinstance(m, _BatchNorm):\n                    m.eval()\n"
  },
  {
    "path": "code/mmdet/models/backbones/mobilenet.py",
    "content": "# taken from https://github.com/tonylins/pytorch-mobilenet-v2/\n# Published by Ji Lin, tonylins\n# licensed under the  Apache License, Version 2.0, January 2004\nimport warnings\nimport os\nimport os.path as osp\n\nimport torch\nfrom torch import nn\nfrom torch.nn.modules.batchnorm import _BatchNorm\nfrom torch.utils import model_zoo\nfrom mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init)\n#from mmcv.runner import load_checkpoint\nfrom mmcv.runner import load_state_dict\nfrom mmcv.runner.checkpoint import get_torchvision_models, get_external_models, get_deprecated_model_names, _get_mmcv_home\nfrom mmcv.runner.checkpoint import get_dist_info\n\nfrom mmdet.utils import get_root_logger\n\nfrom ..builder import BACKBONES\n\nclass _NewEmptyTensorOp(torch.autograd.Function):\n    @staticmethod\n    def forward(ctx, x, new_shape):\n        ctx.shape = x.shape\n        return x.new_empty(new_shape)\n\n    @staticmethod\n    def backward(ctx, grad):\n        shape = ctx.shape\n        return _NewEmptyTensorOp.apply(grad, shape), None\n\n\nclass Conv2d(torch.nn.Conv2d):\n    def forward(self, x):\n        if x.numel() > 0:\n            return super(Conv2d, self).forward(x)\n        # get output shape\n\n        output_shape = [\n            (i + 2 * p - (di * (k - 1) + 1)) // d + 1\n            for i, p, di, k, d in zip(\n                x.shape[-2:], self.padding, self.dilation, self.kernel_size, self.stride\n            )\n        ]\n        output_shape = [x.shape[0], self.weight.shape[0]] + output_shape\n        return _NewEmptyTensorOp.apply(x, output_shape)\n\n\ndef conv_bn(inp, oup, stride, norm_cfg=dict(type='BN', requires_grad=True)):\n    return nn.Sequential(\n        Conv2d(inp, oup, 3, stride, 1, bias=False),\n        build_norm_layer(norm_cfg, oup)[1],\n        nn.ReLU6(inplace=True)\n    )\n\n\ndef conv_1x1_bn(inp, oup, norm_cfg=dict(type='BN', requires_grad=True)):\n    return nn.Sequential(\n        Conv2d(inp, oup, 1, 1, 0, bias=False),\n        build_norm_layer(norm_cfg, oup)[1],\n        nn.ReLU6(inplace=True)\n    )\n\n\nclass InvertedResidual(nn.Module):\n    def __init__(self, inp, oup, stride, expand_ratio, norm_cfg=dict(type='BN', requires_grad=True)):\n        super(InvertedResidual, self).__init__()\n        self.stride = stride\n        assert stride in [1, 2]\n\n        hidden_dim = int(round(inp * expand_ratio))\n        self.use_res_connect = self.stride == 1 and inp == oup\n\n        if expand_ratio == 1:\n            self.conv = nn.Sequential(\n                # dw\n                Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),\n                build_norm_layer(norm_cfg, hidden_dim)[1],\n                nn.ReLU6(inplace=True),\n                # pw-linear\n                Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),\n                build_norm_layer(norm_cfg, oup)[1],\n            )\n        else:\n            self.conv = nn.Sequential(\n                # pw\n                Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),\n                build_norm_layer(norm_cfg, hidden_dim)[1],\n                nn.ReLU6(inplace=True),\n                # dw\n                Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),\n                build_norm_layer(norm_cfg, hidden_dim)[1],\n                nn.ReLU6(inplace=True),\n                # pw-linear\n                Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),\n                build_norm_layer(norm_cfg, oup)[1],\n            )\n\n    def forward(self, x):\n        if self.use_res_connect:\n            return x + self.conv(x)\n        else:\n            return self.conv(x)\n\n\n@BACKBONES.register_module()\nclass MobileNetV2(nn.Module):\n    \"\"\"\n    Should freeze bn\n    \"\"\"\n    def __init__(self,\n                 width_mult=1.,\n                 frozen_stages=-1,\n                 norm_cfg=dict(type='BN', requires_grad=True)):\n        super(MobileNetV2, self).__init__()\n        block = InvertedResidual\n        input_channel = 32\n        interverted_residual_setting = [\n            # t, c, n, s\n            [1, 16, 1, 1],\n            [6, 24, 2, 2],\n            [6, 32, 3, 2],\n            [6, 64, 4, 2],\n            [6, 96, 3, 1],\n            [6, 160, 3, 2],\n            [6, 320, 1, 1],\n        ]\n\n        # building first layer\n        input_channel = int(input_channel * width_mult)\n        self.return_features_indices = [3, 6, 13, 17]\n        self.return_features_num_channels = []\n        self.features = nn.ModuleList([conv_bn(3, input_channel, 2, norm_cfg=norm_cfg)])\n        # building inverted residual blocks\n        for t, c, n, s in interverted_residual_setting:\n            output_channel = int(c * width_mult)\n            for i in range(n):\n                if i == 0:\n                    self.features.append(block(input_channel, output_channel, s, expand_ratio=t, norm_cfg=norm_cfg))\n                else:\n                    self.features.append(block(input_channel, output_channel, 1, expand_ratio=t, norm_cfg=norm_cfg))\n                input_channel = output_channel\n                if len(self.features) - 1 in self.return_features_indices:\n                    self.return_features_num_channels.append(output_channel)\n\n        # self._init_weights()\n        self._freeze_backbone(frozen_stages)\n\n    def _freeze_backbone(self, freeze_at):\n        for layer_index in range(freeze_at):\n            for p in self.features[layer_index].parameters():\n                p.requires_grad = False\n\n    def forward(self, x):\n        res = []\n        for i, m in enumerate(self.features):\n            x = m(x)\n            if i in self.return_features_indices:\n                res.append(x)\n        return res\n\n    def init_weights(self, pretrained=None):\n        if isinstance(pretrained, str):\n            logger = get_root_logger()\n            load_checkpoint(self, pretrained, map_location='cpu', strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, Conv2d):\n                    n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels\n                    m.weight.data.normal_(0, (2. / n) ** 0.5)\n                    if m.bias is not None:\n                        m.bias.data.zero_()\n                elif isinstance(m, _BatchNorm):\n                    constant_init(m, 1)\n                elif isinstance(m, nn.Linear):\n                    n = m.weight.size(1)\n                    m.weight.data.normal_(0, 0.01)\n                    m.bias.data.zero_()\n\n\ndef load_url_dist(url, model_dir=None, map_location=None):\n    \"\"\" In distributed setting, this function only download checkpoint at\n    local rank 0 \"\"\"\n    rank, world_size = get_dist_info()\n    rank = int(os.environ.get('LOCAL_RANK', rank))\n    if rank == 0:\n        checkpoint = model_zoo.load_url(url, model_dir=model_dir, map_location=map_location)\n    if world_size > 1:\n        torch.distributed.barrier()\n        if rank > 0:\n            checkpoint = model_zoo.load_url(url, model_dir=model_dir, map_location=map_location)\n    return checkpoint\n\n\ndef _load_checkpoint(filename, map_location=None):\n    \"\"\"Load checkpoint from somewhere (modelzoo, file, url).\n\n    Args:\n        filename (str): Accept local filepath, URL, ``torchvision://xxx``,\n            ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for\n            details.\n        map_location (str | None): Same as :func:`torch.load`. Default: None.\n\n    Returns:\n        dict | OrderedDict: The loaded checkpoint. It can be either an\n            OrderedDict storing model weights or a dict containing other\n            information, which depends on the checkpoint.\n    \"\"\"\n    if filename.startswith('modelzoo://'):\n        warnings.warn('The URL scheme of \"modelzoo://\" is deprecated, please '\n                      'use \"torchvision://\" instead')\n        model_urls = get_torchvision_models()\n        model_name = filename[11:]\n        checkpoint = load_url_dist(model_urls[model_name])\n    elif filename.startswith('torchvision://'):\n        model_urls = get_torchvision_models()\n        model_name = filename[14:]\n        checkpoint = load_url_dist(model_urls[model_name])\n    elif filename.startswith('open-mmlab://'):\n        model_urls = get_external_models()\n        model_name = filename[13:]\n        deprecated_urls = get_deprecated_model_names()\n        if model_name in deprecated_urls:\n            warnings.warn(f'open-mmlab://{model_name} is deprecated in favor '\n                          f'of open-mmlab://{deprecated_urls[model_name]}')\n            model_name = deprecated_urls[model_name]\n        model_url = model_urls[model_name]\n        # check if is url\n        if model_url.startswith(('http://', 'https://')):\n            checkpoint = load_url_dist(model_url, map_location=map_location)\n        else:\n            filename = osp.join(_get_mmcv_home(), model_url)\n            if not osp.isfile(filename):\n                raise IOError(f'{filename} is not a checkpoint file')\n            checkpoint = torch.load(filename, map_location=map_location)\n    elif filename.startswith(('http://', 'https://')):\n        checkpoint = load_url_dist(filename, map_location=map_location)\n    else:\n        if not osp.isfile(filename):\n            raise IOError(f'{filename} is not a checkpoint file')\n        checkpoint = torch.load(filename, map_location=map_location)\n    return checkpoint\n\n\ndef load_checkpoint(model,\n                    filename,\n                    map_location=None,\n                    strict=False,\n                    logger=None):\n    \"\"\"Load checkpoint from a file or URI.\n\n    Args:\n        model (Module): Module to load checkpoint.\n        filename (str): Accept local filepath, URL, ``torchvision://xxx``,\n            ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for\n            details.\n        map_location (str): Same as :func:`torch.load`.\n        strict (bool): Whether to allow different params for the model and\n            checkpoint.\n        logger (:mod:`logging.Logger` or None): The logger for error message.\n\n    Returns:\n        dict or OrderedDict: The loaded checkpoint.\n    \"\"\"\n    checkpoint = _load_checkpoint(filename, map_location)\n    # OrderedDict is a subclass of dict\n    if not isinstance(checkpoint, dict):\n        raise RuntimeError(\n            f'No state_dict found in checkpoint file {filename}')\n    # get state_dict from checkpoint\n    if 'state_dict' in checkpoint:\n        state_dict = checkpoint['state_dict']\n    else:\n        state_dict = checkpoint\n    # strip prefix of state_dict\n    if list(state_dict.keys())[0].startswith('module.'):\n        state_dict = {k[7:]: v for k, v in checkpoint['state_dict'].items()}\n    # load state_dict\n    load_state_dict(model, state_dict, strict, logger)\n    return checkpoint"
  },
  {
    "path": "code/mmdet/models/backbones/regnet.py",
    "content": "import numpy as np\nimport torch.nn as nn\nfrom mmcv.cnn import build_conv_layer, build_norm_layer\n\nfrom ..builder import BACKBONES\nfrom .resnet import ResNet\nfrom .resnext import Bottleneck\n\n\n@BACKBONES.register_module()\nclass RegNet(ResNet):\n    \"\"\"RegNet backbone.\n\n    More details can be found in `paper <https://arxiv.org/abs/2003.13678>`_ .\n\n    Args:\n        arch (dict): The parameter of RegNets.\n            - w0 (int): initial width\n            - wa (float): slope of width\n            - wm (float): quantization parameter to quantize the width\n            - depth (int): depth of the backbone\n            - group_w (int): width of group\n            - bot_mul (float): bottleneck ratio, i.e. expansion of bottlneck.\n        strides (Sequence[int]): Strides of the first block of each stage.\n        base_channels (int): Base channels after stem layer.\n        in_channels (int): Number of input image channels. Default: 3.\n        dilations (Sequence[int]): Dilation of each stage.\n        out_indices (Sequence[int]): Output from which stages.\n        style (str): `pytorch` or `caffe`. If set to \"pytorch\", the stride-two\n            layer is the 3x3 conv layer, otherwise the stride-two layer is\n            the first 1x1 conv layer.\n        frozen_stages (int): Stages to be frozen (all param fixed). -1 means\n            not freezing any parameters.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n        norm_eval (bool): Whether to set norm layers to eval mode, namely,\n            freeze running stats (mean and var). Note: Effect on Batch Norm\n            and its variants only.\n        with_cp (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n        zero_init_residual (bool): whether to use zero init for last norm layer\n            in resblocks to let them behave as identity.\n\n    Example:\n        >>> from mmdet.models import RegNet\n        >>> import torch\n        >>> self = RegNet(\n                arch=dict(\n                    w0=88,\n                    wa=26.31,\n                    wm=2.25,\n                    group_w=48,\n                    depth=25,\n                    bot_mul=1.0))\n        >>> self.eval()\n        >>> inputs = torch.rand(1, 3, 32, 32)\n        >>> level_outputs = self.forward(inputs)\n        >>> for level_out in level_outputs:\n        ...     print(tuple(level_out.shape))\n        (1, 96, 8, 8)\n        (1, 192, 4, 4)\n        (1, 432, 2, 2)\n        (1, 1008, 1, 1)\n    \"\"\"\n    arch_settings = {\n        'regnetx_400mf':\n        dict(w0=24, wa=24.48, wm=2.54, group_w=16, depth=22, bot_mul=1.0),\n        'regnetx_800mf':\n        dict(w0=56, wa=35.73, wm=2.28, group_w=16, depth=16, bot_mul=1.0),\n        'regnetx_1.6gf':\n        dict(w0=80, wa=34.01, wm=2.25, group_w=24, depth=18, bot_mul=1.0),\n        'regnetx_3.2gf':\n        dict(w0=88, wa=26.31, wm=2.25, group_w=48, depth=25, bot_mul=1.0),\n        'regnetx_4.0gf':\n        dict(w0=96, wa=38.65, wm=2.43, group_w=40, depth=23, bot_mul=1.0),\n        'regnetx_6.4gf':\n        dict(w0=184, wa=60.83, wm=2.07, group_w=56, depth=17, bot_mul=1.0),\n        'regnetx_8.0gf':\n        dict(w0=80, wa=49.56, wm=2.88, group_w=120, depth=23, bot_mul=1.0),\n        'regnetx_12gf':\n        dict(w0=168, wa=73.36, wm=2.37, group_w=112, depth=19, bot_mul=1.0),\n    }\n\n    def __init__(self,\n                 arch,\n                 in_channels=3,\n                 stem_channels=32,\n                 base_channels=32,\n                 strides=(2, 2, 2, 2),\n                 dilations=(1, 1, 1, 1),\n                 out_indices=(0, 1, 2, 3),\n                 style='pytorch',\n                 deep_stem=False,\n                 avg_down=False,\n                 frozen_stages=-1,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN', requires_grad=True),\n                 norm_eval=True,\n                 dcn=None,\n                 stage_with_dcn=(False, False, False, False),\n                 plugins=None,\n                 with_cp=False,\n                 zero_init_residual=True):\n        super(ResNet, self).__init__()\n\n        # Generate RegNet parameters first\n        if isinstance(arch, str):\n            assert arch in self.arch_settings, \\\n                f'\"arch\": \"{arch}\" is not one of the' \\\n                ' arch_settings'\n            arch = self.arch_settings[arch]\n        elif not isinstance(arch, dict):\n            raise ValueError('Expect \"arch\" to be either a string '\n                             f'or a dict, got {type(arch)}')\n\n        widths, num_stages = self.generate_regnet(\n            arch['w0'],\n            arch['wa'],\n            arch['wm'],\n            arch['depth'],\n        )\n        # Convert to per stage format\n        stage_widths, stage_blocks = self.get_stages_from_blocks(widths)\n        # Generate group widths and bot muls\n        group_widths = [arch['group_w'] for _ in range(num_stages)]\n        self.bottleneck_ratio = [arch['bot_mul'] for _ in range(num_stages)]\n        # Adjust the compatibility of stage_widths and group_widths\n        stage_widths, group_widths = self.adjust_width_group(\n            stage_widths, self.bottleneck_ratio, group_widths)\n\n        # Group params by stage\n        self.stage_widths = stage_widths\n        self.group_widths = group_widths\n        self.depth = sum(stage_blocks)\n        self.stem_channels = stem_channels\n        self.base_channels = base_channels\n        self.num_stages = num_stages\n        assert num_stages >= 1 and num_stages <= 4\n        self.strides = strides\n        self.dilations = dilations\n        assert len(strides) == len(dilations) == num_stages\n        self.out_indices = out_indices\n        assert max(out_indices) < num_stages\n        self.style = style\n        self.deep_stem = deep_stem\n        self.avg_down = avg_down\n        self.frozen_stages = frozen_stages\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.with_cp = with_cp\n        self.norm_eval = norm_eval\n        self.dcn = dcn\n        self.stage_with_dcn = stage_with_dcn\n        if dcn is not None:\n            assert len(stage_with_dcn) == num_stages\n        self.plugins = plugins\n        self.zero_init_residual = zero_init_residual\n        self.block = Bottleneck\n        self.block.expansion = 1\n        self.stage_blocks = stage_blocks[:num_stages]\n\n        self._make_stem_layer(in_channels, stem_channels)\n\n        self.inplanes = stem_channels\n        self.res_layers = []\n        for i, num_blocks in enumerate(self.stage_blocks):\n            stride = self.strides[i]\n            dilation = self.dilations[i]\n            group_width = self.group_widths[i]\n            width = int(round(self.stage_widths[i] * self.bottleneck_ratio[i]))\n            stage_groups = width // group_width\n\n            dcn = self.dcn if self.stage_with_dcn[i] else None\n            if self.plugins is not None:\n                stage_plugins = self.make_stage_plugins(self.plugins, i)\n            else:\n                stage_plugins = None\n\n            res_layer = self.make_res_layer(\n                block=self.block,\n                inplanes=self.inplanes,\n                planes=self.stage_widths[i],\n                num_blocks=num_blocks,\n                stride=stride,\n                dilation=dilation,\n                style=self.style,\n                avg_down=self.avg_down,\n                with_cp=self.with_cp,\n                conv_cfg=self.conv_cfg,\n                norm_cfg=self.norm_cfg,\n                dcn=dcn,\n                plugins=stage_plugins,\n                groups=stage_groups,\n                base_width=group_width,\n                base_channels=self.stage_widths[i])\n            self.inplanes = self.stage_widths[i]\n            layer_name = f'layer{i + 1}'\n            self.add_module(layer_name, res_layer)\n            self.res_layers.append(layer_name)\n\n        self._freeze_stages()\n\n        self.feat_dim = stage_widths[-1]\n\n    def _make_stem_layer(self, in_channels, base_channels):\n        self.conv1 = build_conv_layer(\n            self.conv_cfg,\n            in_channels,\n            base_channels,\n            kernel_size=3,\n            stride=2,\n            padding=1,\n            bias=False)\n        self.norm1_name, norm1 = build_norm_layer(\n            self.norm_cfg, base_channels, postfix=1)\n        self.add_module(self.norm1_name, norm1)\n        self.relu = nn.ReLU(inplace=True)\n\n    def generate_regnet(self,\n                        initial_width,\n                        width_slope,\n                        width_parameter,\n                        depth,\n                        divisor=8):\n        \"\"\"Generates per block width from RegNet parameters.\n\n        Args:\n            initial_width ([int]): Initial width of the backbone\n            width_slope ([float]): Slope of the quantized linear function\n            width_parameter ([int]): Parameter used to quantize the width.\n            depth ([int]): Depth of the backbone.\n            divisor (int, optional): The divisor of channels. Defaults to 8.\n\n        Returns:\n            list, int: return a list of widths of each stage and the number of\n                stages\n        \"\"\"\n        assert width_slope >= 0\n        assert initial_width > 0\n        assert width_parameter > 1\n        assert initial_width % divisor == 0\n        widths_cont = np.arange(depth) * width_slope + initial_width\n        ks = np.round(\n            np.log(widths_cont / initial_width) / np.log(width_parameter))\n        widths = initial_width * np.power(width_parameter, ks)\n        widths = np.round(np.divide(widths, divisor)) * divisor\n        num_stages = len(np.unique(widths))\n        widths, widths_cont = widths.astype(int).tolist(), widths_cont.tolist()\n        return widths, num_stages\n\n    @staticmethod\n    def quantize_float(number, divisor):\n        \"\"\"Converts a float to closest non-zero int divisible by divior.\n\n        Args:\n            number (int): Original number to be quantized.\n            divisor (int): Divisor used to quantize the number.\n\n        Returns:\n            int: quantized number that is divisible by devisor.\n        \"\"\"\n        return int(round(number / divisor) * divisor)\n\n    def adjust_width_group(self, widths, bottleneck_ratio, groups):\n        \"\"\"Adjusts the compatibility of widths and groups.\n\n        Args:\n            widths (list[int]): Width of each stage.\n            bottleneck_ratio (float): Bottleneck ratio.\n            groups (int): number of groups in each stage\n\n        Returns:\n            tuple(list): The adjusted widths and groups of each stage.\n        \"\"\"\n        bottleneck_width = [\n            int(w * b) for w, b in zip(widths, bottleneck_ratio)\n        ]\n        groups = [min(g, w_bot) for g, w_bot in zip(groups, bottleneck_width)]\n        bottleneck_width = [\n            self.quantize_float(w_bot, g)\n            for w_bot, g in zip(bottleneck_width, groups)\n        ]\n        widths = [\n            int(w_bot / b)\n            for w_bot, b in zip(bottleneck_width, bottleneck_ratio)\n        ]\n        return widths, groups\n\n    def get_stages_from_blocks(self, widths):\n        \"\"\"Gets widths/stage_blocks of network at each stage\n\n        Args:\n            widths (list[int]): Width in each stage.\n\n        Returns:\n            tuple(list): width and depth of each stage\n        \"\"\"\n        width_diff = [\n            width != width_prev\n            for width, width_prev in zip(widths + [0], [0] + widths)\n        ]\n        stage_widths = [\n            width for width, diff in zip(widths, width_diff[:-1]) if diff\n        ]\n        stage_blocks = np.diff([\n            depth for depth, diff in zip(range(len(width_diff)), width_diff)\n            if diff\n        ]).tolist()\n        return stage_widths, stage_blocks\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        x = self.conv1(x)\n        x = self.norm1(x)\n        x = self.relu(x)\n\n        outs = []\n        for i, layer_name in enumerate(self.res_layers):\n            res_layer = getattr(self, layer_name)\n            x = res_layer(x)\n            if i in self.out_indices:\n                outs.append(x)\n        return tuple(outs)\n"
  },
  {
    "path": "code/mmdet/models/backbones/res2net.py",
    "content": "import math\n\nimport torch\nimport torch.nn as nn\nimport torch.utils.checkpoint as cp\nfrom mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init,\n                      kaiming_init)\nfrom mmcv.runner import load_checkpoint\nfrom torch.nn.modules.batchnorm import _BatchNorm\n\nfrom mmdet.utils import get_root_logger\nfrom ..builder import BACKBONES\nfrom .resnet import Bottleneck as _Bottleneck\nfrom .resnet import ResNet\n\n\nclass Bottle2neck(_Bottleneck):\n    expansion = 4\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 scales=4,\n                 base_width=26,\n                 base_channels=64,\n                 stage_type='normal',\n                 **kwargs):\n        \"\"\"Bottle2neck block for Res2Net.\n\n        If style is \"pytorch\", the stride-two layer is the 3x3 conv layer, if\n        it is \"caffe\", the stride-two layer is the first 1x1 conv layer.\n        \"\"\"\n        super(Bottle2neck, self).__init__(inplanes, planes, **kwargs)\n        assert scales > 1, 'Res2Net degenerates to ResNet when scales = 1.'\n        width = int(math.floor(self.planes * (base_width / base_channels)))\n\n        self.norm1_name, norm1 = build_norm_layer(\n            self.norm_cfg, width * scales, postfix=1)\n        self.norm3_name, norm3 = build_norm_layer(\n            self.norm_cfg, self.planes * self.expansion, postfix=3)\n\n        self.conv1 = build_conv_layer(\n            self.conv_cfg,\n            self.inplanes,\n            width * scales,\n            kernel_size=1,\n            stride=self.conv1_stride,\n            bias=False)\n        self.add_module(self.norm1_name, norm1)\n\n        if stage_type == 'stage' and self.conv2_stride != 1:\n            self.pool = nn.AvgPool2d(\n                kernel_size=3, stride=self.conv2_stride, padding=1)\n        convs = []\n        bns = []\n\n        fallback_on_stride = False\n        if self.with_dcn:\n            fallback_on_stride = self.dcn.pop('fallback_on_stride', False)\n        if not self.with_dcn or fallback_on_stride:\n            for i in range(scales - 1):\n                convs.append(\n                    build_conv_layer(\n                        self.conv_cfg,\n                        width,\n                        width,\n                        kernel_size=3,\n                        stride=self.conv2_stride,\n                        padding=self.dilation,\n                        dilation=self.dilation,\n                        bias=False))\n                bns.append(\n                    build_norm_layer(self.norm_cfg, width, postfix=i + 1)[1])\n            self.convs = nn.ModuleList(convs)\n            self.bns = nn.ModuleList(bns)\n        else:\n            assert self.conv_cfg is None, 'conv_cfg must be None for DCN'\n            for i in range(scales - 1):\n                convs.append(\n                    build_conv_layer(\n                        self.dcn,\n                        width,\n                        width,\n                        kernel_size=3,\n                        stride=self.conv2_stride,\n                        padding=self.dilation,\n                        dilation=self.dilation,\n                        bias=False))\n                bns.append(\n                    build_norm_layer(self.norm_cfg, width, postfix=i + 1)[1])\n            self.convs = nn.ModuleList(convs)\n            self.bns = nn.ModuleList(bns)\n\n        self.conv3 = build_conv_layer(\n            self.conv_cfg,\n            width * scales,\n            self.planes * self.expansion,\n            kernel_size=1,\n            bias=False)\n        self.add_module(self.norm3_name, norm3)\n\n        self.stage_type = stage_type\n        self.scales = scales\n        self.width = width\n        delattr(self, 'conv2')\n        delattr(self, self.norm2_name)\n\n    def forward(self, x):\n        \"\"\"Forward function.\"\"\"\n\n        def _inner_forward(x):\n            identity = x\n\n            out = self.conv1(x)\n            out = self.norm1(out)\n            out = self.relu(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv1_plugin_names)\n\n            spx = torch.split(out, self.width, 1)\n            sp = self.convs[0](spx[0].contiguous())\n            sp = self.relu(self.bns[0](sp))\n            out = sp\n            for i in range(1, self.scales - 1):\n                if self.stage_type == 'stage':\n                    sp = spx[i]\n                else:\n                    sp = sp + spx[i]\n                sp = self.convs[i](sp.contiguous())\n                sp = self.relu(self.bns[i](sp))\n                out = torch.cat((out, sp), 1)\n\n            if self.stage_type == 'normal' or self.conv2_stride == 1:\n                out = torch.cat((out, spx[self.scales - 1]), 1)\n            elif self.stage_type == 'stage':\n                out = torch.cat((out, self.pool(spx[self.scales - 1])), 1)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv2_plugin_names)\n\n            out = self.conv3(out)\n            out = self.norm3(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv3_plugin_names)\n\n            if self.downsample is not None:\n                identity = self.downsample(x)\n\n            out += identity\n\n            return out\n\n        if self.with_cp and x.requires_grad:\n            out = cp.checkpoint(_inner_forward, x)\n        else:\n            out = _inner_forward(x)\n\n        out = self.relu(out)\n\n        return out\n\n\nclass Res2Layer(nn.Sequential):\n    \"\"\"Res2Layer to build Res2Net style backbone.\n\n    Args:\n        block (nn.Module): block used to build ResLayer.\n        inplanes (int): inplanes of block.\n        planes (int): planes of block.\n        num_blocks (int): number of blocks.\n        stride (int): stride of the first block. Default: 1\n        avg_down (bool): Use AvgPool instead of stride conv when\n            downsampling in the bottle2neck. Default: False\n        conv_cfg (dict): dictionary to construct and config conv layer.\n            Default: None\n        norm_cfg (dict): dictionary to construct and config norm layer.\n            Default: dict(type='BN')\n        scales (int): Scales used in Res2Net. Default: 4\n        base_width (int): Basic width of each scale. Default: 26\n    \"\"\"\n\n    def __init__(self,\n                 block,\n                 inplanes,\n                 planes,\n                 num_blocks,\n                 stride=1,\n                 avg_down=True,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 scales=4,\n                 base_width=26,\n                 **kwargs):\n        self.block = block\n\n        downsample = None\n        if stride != 1 or inplanes != planes * block.expansion:\n            downsample = nn.Sequential(\n                nn.AvgPool2d(\n                    kernel_size=stride,\n                    stride=stride,\n                    ceil_mode=True,\n                    count_include_pad=False),\n                build_conv_layer(\n                    conv_cfg,\n                    inplanes,\n                    planes * block.expansion,\n                    kernel_size=1,\n                    stride=1,\n                    bias=False),\n                build_norm_layer(norm_cfg, planes * block.expansion)[1],\n            )\n\n        layers = []\n        layers.append(\n            block(\n                inplanes=inplanes,\n                planes=planes,\n                stride=stride,\n                downsample=downsample,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                scales=scales,\n                base_width=base_width,\n                stage_type='stage',\n                **kwargs))\n        inplanes = planes * block.expansion\n        for i in range(1, num_blocks):\n            layers.append(\n                block(\n                    inplanes=inplanes,\n                    planes=planes,\n                    stride=1,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=norm_cfg,\n                    scales=scales,\n                    base_width=base_width,\n                    **kwargs))\n        super(Res2Layer, self).__init__(*layers)\n\n\n@BACKBONES.register_module()\nclass Res2Net(ResNet):\n    \"\"\"Res2Net backbone.\n\n    Args:\n        scales (int): Scales used in Res2Net. Default: 4\n        base_width (int): Basic width of each scale. Default: 26\n        depth (int): Depth of res2net, from {50, 101, 152}.\n        in_channels (int): Number of input image channels. Default: 3.\n        num_stages (int): Res2net stages. Default: 4.\n        strides (Sequence[int]): Strides of the first block of each stage.\n        dilations (Sequence[int]): Dilation of each stage.\n        out_indices (Sequence[int]): Output from which stages.\n        style (str): `pytorch` or `caffe`. If set to \"pytorch\", the stride-two\n            layer is the 3x3 conv layer, otherwise the stride-two layer is\n            the first 1x1 conv layer.\n        deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv\n        avg_down (bool): Use AvgPool instead of stride conv when\n            downsampling in the bottle2neck.\n        frozen_stages (int): Stages to be frozen (stop grad and set eval mode).\n            -1 means not freezing any parameters.\n        norm_cfg (dict): Dictionary to construct and config norm layer.\n        norm_eval (bool): Whether to set norm layers to eval mode, namely,\n            freeze running stats (mean and var). Note: Effect on Batch Norm\n            and its variants only.\n        plugins (list[dict]): List of plugins for stages, each dict contains:\n\n            - cfg (dict, required): Cfg dict to build plugin.\n            - position (str, required): Position inside block to insert\n              plugin, options are 'after_conv1', 'after_conv2', 'after_conv3'.\n            - stages (tuple[bool], optional): Stages to apply plugin, length\n              should be same as 'num_stages'.\n        with_cp (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n        zero_init_residual (bool): Whether to use zero init for last norm layer\n            in resblocks to let them behave as identity.\n\n    Example:\n        >>> from mmdet.models import Res2Net\n        >>> import torch\n        >>> self = Res2Net(depth=50, scales=4, base_width=26)\n        >>> self.eval()\n        >>> inputs = torch.rand(1, 3, 32, 32)\n        >>> level_outputs = self.forward(inputs)\n        >>> for level_out in level_outputs:\n        ...     print(tuple(level_out.shape))\n        (1, 256, 8, 8)\n        (1, 512, 4, 4)\n        (1, 1024, 2, 2)\n        (1, 2048, 1, 1)\n    \"\"\"\n\n    arch_settings = {\n        50: (Bottle2neck, (3, 4, 6, 3)),\n        101: (Bottle2neck, (3, 4, 23, 3)),\n        152: (Bottle2neck, (3, 8, 36, 3))\n    }\n\n    def __init__(self,\n                 scales=4,\n                 base_width=26,\n                 style='pytorch',\n                 deep_stem=True,\n                 avg_down=True,\n                 **kwargs):\n        self.scales = scales\n        self.base_width = base_width\n        super(Res2Net, self).__init__(\n            style='pytorch', deep_stem=True, avg_down=True, **kwargs)\n\n    def make_res_layer(self, **kwargs):\n        return Res2Layer(\n            scales=self.scales,\n            base_width=self.base_width,\n            base_channels=self.base_channels,\n            **kwargs)\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in backbone.\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if isinstance(pretrained, str):\n            logger = get_root_logger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, (_BatchNorm, nn.GroupNorm)):\n                    constant_init(m, 1)\n\n            if self.dcn is not None:\n                for m in self.modules():\n                    if isinstance(m, Bottle2neck):\n                        # dcn in Res2Net bottle2neck is in ModuleList\n                        for n in m.convs:\n                            if hasattr(n, 'conv_offset'):\n                                constant_init(n.conv_offset, 0)\n\n            if self.zero_init_residual:\n                for m in self.modules():\n                    if isinstance(m, Bottle2neck):\n                        constant_init(m.norm3, 0)\n        else:\n            raise TypeError('pretrained must be a str or None')"
  },
  {
    "path": "code/mmdet/models/backbones/resnet.py",
    "content": "import torch.nn as nn\nimport torch.utils.checkpoint as cp\nfrom mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init,\n                      kaiming_init)\nfrom mmcv.runner import load_checkpoint\nfrom torch.nn.modules.batchnorm import _BatchNorm\n\nfrom mmdet.ops import build_plugin_layer\nfrom mmdet.utils import get_root_logger\nfrom ..builder import BACKBONES\nfrom ..utils import ResLayer\n\n\nclass BasicBlock(nn.Module):\n    expansion = 1\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 stride=1,\n                 dilation=1,\n                 downsample=None,\n                 style='pytorch',\n                 with_cp=False,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 dcn=None,\n                 plugins=None):\n        super(BasicBlock, self).__init__()\n        assert dcn is None, 'Not implemented yet.'\n        assert plugins is None, 'Not implemented yet.'\n\n        self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1)\n        self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2)\n\n        self.conv1 = build_conv_layer(\n            conv_cfg,\n            inplanes,\n            planes,\n            3,\n            stride=stride,\n            padding=dilation,\n            dilation=dilation,\n            bias=False)\n        self.add_module(self.norm1_name, norm1)\n        self.conv2 = build_conv_layer(\n            conv_cfg, planes, planes, 3, padding=1, bias=False)\n        self.add_module(self.norm2_name, norm2)\n\n        self.relu = nn.ReLU(inplace=True)\n        self.downsample = downsample\n        self.stride = stride\n        self.dilation = dilation\n        self.with_cp = with_cp\n\n    @property\n    def norm1(self):\n        \"\"\"nn.Module: normalization layer after the first convolution layer\"\"\"\n        return getattr(self, self.norm1_name)\n\n    @property\n    def norm2(self):\n        \"\"\"nn.Module: normalization layer after the second convolution layer\"\"\"\n        return getattr(self, self.norm2_name)\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n\n        def _inner_forward(x):\n            identity = x\n\n            out = self.conv1(x)\n            out = self.norm1(out)\n            out = self.relu(out)\n\n            out = self.conv2(out)\n            out = self.norm2(out)\n\n            if self.downsample is not None:\n                identity = self.downsample(x)\n\n            out += identity\n\n            return out\n\n        if self.with_cp and x.requires_grad:\n            out = cp.checkpoint(_inner_forward, x)\n        else:\n            out = _inner_forward(x)\n\n        out = self.relu(out)\n\n        return out\n\n\nclass Bottleneck(nn.Module):\n    expansion = 4\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 stride=1,\n                 dilation=1,\n                 downsample=None,\n                 style='pytorch',\n                 with_cp=False,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 dcn=None,\n                 plugins=None):\n        \"\"\"Bottleneck block for ResNet.\n        If style is \"pytorch\", the stride-two layer is the 3x3 conv layer,\n        if it is \"caffe\", the stride-two layer is the first 1x1 conv layer.\n        \"\"\"\n        super(Bottleneck, self).__init__()\n        assert style in ['pytorch', 'caffe']\n        assert dcn is None or isinstance(dcn, dict)\n        assert plugins is None or isinstance(plugins, list)\n        if plugins is not None:\n            allowed_position = ['after_conv1', 'after_conv2', 'after_conv3']\n            assert all(p['position'] in allowed_position for p in plugins)\n\n        self.inplanes = inplanes\n        self.planes = planes\n        self.stride = stride\n        self.dilation = dilation\n        self.style = style\n        self.with_cp = with_cp\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.dcn = dcn\n        self.with_dcn = dcn is not None\n        self.plugins = plugins\n        self.with_plugins = plugins is not None\n\n        if self.with_plugins:\n            # collect plugins for conv1/conv2/conv3\n            self.after_conv1_plugins = [\n                plugin['cfg'] for plugin in plugins\n                if plugin['position'] == 'after_conv1'\n            ]\n            self.after_conv2_plugins = [\n                plugin['cfg'] for plugin in plugins\n                if plugin['position'] == 'after_conv2'\n            ]\n            self.after_conv3_plugins = [\n                plugin['cfg'] for plugin in plugins\n                if plugin['position'] == 'after_conv3'\n            ]\n\n        if self.style == 'pytorch':\n            self.conv1_stride = 1\n            self.conv2_stride = stride\n        else:\n            self.conv1_stride = stride\n            self.conv2_stride = 1\n\n        self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1)\n        self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2)\n        self.norm3_name, norm3 = build_norm_layer(\n            norm_cfg, planes * self.expansion, postfix=3)\n\n        self.conv1 = build_conv_layer(\n            conv_cfg,\n            inplanes,\n            planes,\n            kernel_size=1,\n            stride=self.conv1_stride,\n            bias=False)\n        self.add_module(self.norm1_name, norm1)\n        fallback_on_stride = False\n        if self.with_dcn:\n            fallback_on_stride = dcn.pop('fallback_on_stride', False)\n        if not self.with_dcn or fallback_on_stride:\n            self.conv2 = build_conv_layer(\n                conv_cfg,\n                planes,\n                planes,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=dilation,\n                dilation=dilation,\n                bias=False)\n        else:\n            assert self.conv_cfg is None, 'conv_cfg must be None for DCN'\n            self.conv2 = build_conv_layer(\n                dcn,\n                planes,\n                planes,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=dilation,\n                dilation=dilation,\n                bias=False)\n\n        self.add_module(self.norm2_name, norm2)\n        self.conv3 = build_conv_layer(\n            conv_cfg,\n            planes,\n            planes * self.expansion,\n            kernel_size=1,\n            bias=False)\n        self.add_module(self.norm3_name, norm3)\n\n        self.relu = nn.ReLU(inplace=True)\n        self.downsample = downsample\n\n        if self.with_plugins:\n            self.after_conv1_plugin_names = self.make_block_plugins(\n                planes, self.after_conv1_plugins)\n            self.after_conv2_plugin_names = self.make_block_plugins(\n                planes, self.after_conv2_plugins)\n            self.after_conv3_plugin_names = self.make_block_plugins(\n                planes * self.expansion, self.after_conv3_plugins)\n\n    def make_block_plugins(self, in_channels, plugins):\n        \"\"\" make plugins for block\n\n        Args:\n            in_channels (int): Input channels of plugin.\n            plugins (list[dict]): List of plugins cfg to build.\n\n        Returns:\n            list[str]: List of the names of plugin.\n\n        \"\"\"\n        assert isinstance(plugins, list)\n        plugin_names = []\n        for plugin in plugins:\n            plugin = plugin.copy()\n            name, layer = build_plugin_layer(\n                plugin,\n                in_channels=in_channels,\n                postfix=plugin.pop('postfix', ''))\n            assert not hasattr(self, name), f'duplicate plugin {name}'\n            self.add_module(name, layer)\n            plugin_names.append(name)\n        return plugin_names\n\n    def forward_plugin(self, x, plugin_names):\n        out = x\n        for name in plugin_names:\n            out = getattr(self, name)(x)\n        return out\n\n    @property\n    def norm1(self):\n        \"\"\"nn.Module: normalization layer after the first convolution layer\"\"\"\n        return getattr(self, self.norm1_name)\n\n    @property\n    def norm2(self):\n        \"\"\"nn.Module: normalization layer after the second convolution layer\"\"\"\n        return getattr(self, self.norm2_name)\n\n    @property\n    def norm3(self):\n        \"\"\"nn.Module: normalization layer after the third convolution layer\"\"\"\n        return getattr(self, self.norm3_name)\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n\n        def _inner_forward(x):\n            identity = x\n\n            out = self.conv1(x)\n            out = self.norm1(out)\n            out = self.relu(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv1_plugin_names)\n\n            out = self.conv2(out)\n            out = self.norm2(out)\n            out = self.relu(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv2_plugin_names)\n\n            out = self.conv3(out)\n            out = self.norm3(out)\n\n            if self.with_plugins:\n                out = self.forward_plugin(out, self.after_conv3_plugin_names)\n\n            if self.downsample is not None:\n                identity = self.downsample(x)\n\n            out += identity\n\n            return out\n\n        if self.with_cp and x.requires_grad:\n            out = cp.checkpoint(_inner_forward, x)\n        else:\n            out = _inner_forward(x)\n\n        out = self.relu(out)\n\n        return out\n\n\n@BACKBONES.register_module()\nclass ResNet(nn.Module):\n    \"\"\"ResNet backbone.\n\n    Args:\n        depth (int): Depth of resnet, from {18, 34, 50, 101, 152}.\n        stem_channels (int): Number of stem channels. Default: 64.\n        base_channels (int): Number of base channels of res layer. Default: 64.\n        in_channels (int): Number of input image channels. Default: 3.\n        num_stages (int): Resnet stages. Default: 4.\n        strides (Sequence[int]): Strides of the first block of each stage.\n        dilations (Sequence[int]): Dilation of each stage.\n        out_indices (Sequence[int]): Output from which stages.\n        style (str): `pytorch` or `caffe`. If set to \"pytorch\", the stride-two\n            layer is the 3x3 conv layer, otherwise the stride-two layer is\n            the first 1x1 conv layer.\n        deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv\n        avg_down (bool): Use AvgPool instead of stride conv when\n            downsampling in the bottleneck.\n        frozen_stages (int): Stages to be frozen (stop grad and set eval mode).\n            -1 means not freezing any parameters.\n        norm_cfg (dict): Dictionary to construct and config norm layer.\n        norm_eval (bool): Whether to set norm layers to eval mode, namely,\n            freeze running stats (mean and var). Note: Effect on Batch Norm\n            and its variants only.\n        plugins (list[dict]): List of plugins for stages, each dict contains:\n\n            - cfg (dict, required): Cfg dict to build plugin.\n            - position (str, required): Position inside block to insert\n              plugin, options are 'after_conv1', 'after_conv2', 'after_conv3'.\n            - stages (tuple[bool], optional): Stages to apply plugin, length\n              should be same as 'num_stages'.\n        with_cp (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n        zero_init_residual (bool): Whether to use zero init for last norm layer\n            in resblocks to let them behave as identity.\n\n    Example:\n        >>> from mmdet.models import ResNet\n        >>> import torch\n        >>> self = ResNet(depth=18)\n        >>> self.eval()\n        >>> inputs = torch.rand(1, 3, 32, 32)\n        >>> level_outputs = self.forward(inputs)\n        >>> for level_out in level_outputs:\n        ...     print(tuple(level_out.shape))\n        (1, 64, 8, 8)\n        (1, 128, 4, 4)\n        (1, 256, 2, 2)\n        (1, 512, 1, 1)\n    \"\"\"\n\n    arch_settings = {\n        18: (BasicBlock, (2, 2, 2, 2)),\n        34: (BasicBlock, (3, 4, 6, 3)),\n        50: (Bottleneck, (3, 4, 6, 3)),\n        101: (Bottleneck, (3, 4, 23, 3)),\n        152: (Bottleneck, (3, 8, 36, 3))\n    }\n\n    def __init__(self,\n                 depth,\n                 in_channels=3,\n                 stem_channels=64,\n                 base_channels=64,\n                 num_stages=4,\n                 strides=(1, 2, 2, 2),\n                 dilations=(1, 1, 1, 1),\n                 out_indices=(0, 1, 2, 3),\n                 style='pytorch',\n                 deep_stem=False,\n                 avg_down=False,\n                 frozen_stages=-1,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN', requires_grad=True),\n                 norm_eval=True,\n                 dcn=None,\n                 stage_with_dcn=(False, False, False, False),\n                 plugins=None,\n                 with_cp=False,\n                 zero_init_residual=True):\n        super(ResNet, self).__init__()\n        if depth not in self.arch_settings:\n            raise KeyError(f'invalid depth {depth} for resnet')\n        self.depth = depth\n        self.stem_channels = stem_channels\n        self.base_channels = base_channels\n        self.num_stages = num_stages\n        assert num_stages >= 1 and num_stages <= 4\n        self.strides = strides\n        self.dilations = dilations\n        assert len(strides) == len(dilations) == num_stages\n        self.out_indices = out_indices\n        assert max(out_indices) < num_stages\n        self.style = style\n        self.deep_stem = deep_stem\n        self.avg_down = avg_down\n        self.frozen_stages = frozen_stages\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.with_cp = with_cp\n        self.norm_eval = norm_eval\n        self.dcn = dcn\n        self.stage_with_dcn = stage_with_dcn\n        if dcn is not None:\n            assert len(stage_with_dcn) == num_stages\n        self.plugins = plugins\n        self.zero_init_residual = zero_init_residual\n        self.block, stage_blocks = self.arch_settings[depth]\n        self.stage_blocks = stage_blocks[:num_stages]\n        self.inplanes = stem_channels\n\n        self._make_stem_layer(in_channels, stem_channels)\n\n        self.res_layers = []\n        for i, num_blocks in enumerate(self.stage_blocks):\n            stride = strides[i]\n            dilation = dilations[i]\n            dcn = self.dcn if self.stage_with_dcn[i] else None\n            if plugins is not None:\n                stage_plugins = self.make_stage_plugins(plugins, i)\n            else:\n                stage_plugins = None\n            planes = base_channels * 2**i\n            res_layer = self.make_res_layer(\n                block=self.block,\n                inplanes=self.inplanes,\n                planes=planes,\n                num_blocks=num_blocks,\n                stride=stride,\n                dilation=dilation,\n                style=self.style,\n                avg_down=self.avg_down,\n                with_cp=with_cp,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                dcn=dcn,\n                plugins=stage_plugins)\n            self.inplanes = planes * self.block.expansion\n            layer_name = f'layer{i + 1}'\n            self.add_module(layer_name, res_layer)\n            self.res_layers.append(layer_name)\n\n        self._freeze_stages()\n\n        self.feat_dim = self.block.expansion * base_channels * 2**(\n            len(self.stage_blocks) - 1)\n\n    def make_stage_plugins(self, plugins, stage_idx):\n        \"\"\" make plugins for ResNet 'stage_idx'th stage .\n\n        Currently we support to insert 'context_block',\n        'empirical_attention_block', 'nonlocal_block' into the backbone like\n        ResNet/ResNeXt. They could be inserted after conv1/conv2/conv3 of\n        Bottleneck.\n        An example of plugins format could be:\n\n        >>> plugins=[\n        ...     dict(cfg=dict(type='xxx', arg1='xxx'),\n        ...          stages=(False, True, True, True),\n        ...          position='after_conv2'),\n        ...     dict(cfg=dict(type='yyy'),\n        ...          stages=(True, True, True, True),\n        ...          position='after_conv3'),\n        ...     dict(cfg=dict(type='zzz', postfix='1'),\n        ...          stages=(True, True, True, True),\n        ...          position='after_conv3'),\n        ...     dict(cfg=dict(type='zzz', postfix='2'),\n        ...          stages=(True, True, True, True),\n        ...          position='after_conv3')\n        ... ]\n        >>> self = ResNet(depth=18)\n        >>> stage_plugins = self.make_stage_plugins(plugins, 0)\n        >>> assert len(stage_plugins) == 3\n\n        Suppose 'stage_idx=0', the structure of blocks in the stage would be:\n\n        .. code-block:: none\n\n            conv1-> conv2->conv3->yyy->zzz1->zzz2\n\n        Suppose 'stage_idx=1', the structure of blocks in the stage would be:\n\n        .. code-block:: none\n\n            conv1-> conv2->xxx->conv3->yyy->zzz1->zzz2\n\n        If stages is missing, the plugin would be applied to all stages.\n\n        Args:\n            plugins (list[dict]): List of plugins cfg to build. The postfix is\n                required if multiple same type plugins are inserted.\n            stage_idx (int): Index of stage to build\n\n        Returns:\n            list[dict]: Plugins for current stage\n        \"\"\"\n        stage_plugins = []\n        for plugin in plugins:\n            plugin = plugin.copy()\n            stages = plugin.pop('stages', None)\n            assert stages is None or len(stages) == self.num_stages\n            # whether to insert plugin into current stage\n            if stages is None or stages[stage_idx]:\n                stage_plugins.append(plugin)\n\n        return stage_plugins\n\n    def make_res_layer(self, **kwargs):\n        \"\"\"Pack all blocks in a stage into a ``ResLayer``\"\"\"\n        return ResLayer(**kwargs)\n\n    @property\n    def norm1(self):\n        \"\"\"nn.Module: the normalization layer named \"norm1\" \"\"\"\n        return getattr(self, self.norm1_name)\n\n    def _make_stem_layer(self, in_channels, stem_channels):\n        if self.deep_stem:\n            self.stem = nn.Sequential(\n                build_conv_layer(\n                    self.conv_cfg,\n                    in_channels,\n                    stem_channels // 2,\n                    kernel_size=3,\n                    stride=2,\n                    padding=1,\n                    bias=False),\n                build_norm_layer(self.norm_cfg, stem_channels // 2)[1],\n                nn.ReLU(inplace=True),\n                build_conv_layer(\n                    self.conv_cfg,\n                    stem_channels // 2,\n                    stem_channels // 2,\n                    kernel_size=3,\n                    stride=1,\n                    padding=1,\n                    bias=False),\n                build_norm_layer(self.norm_cfg, stem_channels // 2)[1],\n                nn.ReLU(inplace=True),\n                build_conv_layer(\n                    self.conv_cfg,\n                    stem_channels // 2,\n                    stem_channels,\n                    kernel_size=3,\n                    stride=1,\n                    padding=1,\n                    bias=False),\n                build_norm_layer(self.norm_cfg, stem_channels)[1],\n                nn.ReLU(inplace=True))\n        else:\n            self.conv1 = build_conv_layer(\n                self.conv_cfg,\n                in_channels,\n                stem_channels,\n                kernel_size=7,\n                stride=2,\n                padding=3,\n                bias=False)\n            self.norm1_name, norm1 = build_norm_layer(\n                self.norm_cfg, stem_channels, postfix=1)\n            self.add_module(self.norm1_name, norm1)\n            self.relu = nn.ReLU(inplace=True)\n        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n\n    def _freeze_stages(self):\n        if self.frozen_stages >= 0:\n            if self.deep_stem:\n                self.stem.eval()\n                for param in self.stem.parameters():\n                    param.requires_grad = False\n            else:\n                self.norm1.eval()\n                for m in [self.conv1, self.norm1]:\n                    for param in m.parameters():\n                        param.requires_grad = False\n\n        for i in range(1, self.frozen_stages + 1):\n            m = getattr(self, f'layer{i}')\n            m.eval()\n            for param in m.parameters():\n                param.requires_grad = False\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in backbone\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if isinstance(pretrained, str):\n            logger = get_root_logger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, (_BatchNorm, nn.GroupNorm)):\n                    constant_init(m, 1)\n\n            if self.dcn is not None:\n                for m in self.modules():\n                    if isinstance(m, Bottleneck) and hasattr(\n                            m.conv2, 'conv_offset'):\n                        constant_init(m.conv2.conv_offset, 0)\n\n            if self.zero_init_residual:\n                for m in self.modules():\n                    if isinstance(m, Bottleneck):\n                        constant_init(m.norm3, 0)\n                    elif isinstance(m, BasicBlock):\n                        constant_init(m.norm2, 0)\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        if self.deep_stem:\n            x = self.stem(x)\n        else:\n            x = self.conv1(x)\n            x = self.norm1(x)\n            x = self.relu(x)\n        x = self.maxpool(x)\n        outs = []\n        for i, layer_name in enumerate(self.res_layers):\n            res_layer = getattr(self, layer_name)\n            x = res_layer(x)\n            if i in self.out_indices:\n                outs.append(x)\n        return tuple(outs)\n\n    def train(self, mode=True):\n        \"\"\"Convert the model into training mode while keep normalization layer\n        freezed\"\"\"\n        super(ResNet, self).train(mode)\n        self._freeze_stages()\n        if mode and self.norm_eval:\n            for m in self.modules():\n                # trick: eval have effect on BatchNorm only\n                if isinstance(m, _BatchNorm):\n                    m.eval()\n\n\n@BACKBONES.register_module()\nclass ResNetV1d(ResNet):\n    \"\"\"ResNetV1d variant described in\n    `Bag of Tricks <https://arxiv.org/pdf/1812.01187.pdf>`_.\n\n    Compared with default ResNet(ResNetV1b), ResNetV1d replaces the 7x7 conv\n    in the input stem with three 3x3 convs. And in the downsampling block,\n    a 2x2 avg_pool with stride 2 is added before conv, whose stride is\n    changed to 1.\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        super(ResNetV1d, self).__init__(\n            deep_stem=True, avg_down=True, **kwargs)\n"
  },
  {
    "path": "code/mmdet/models/backbones/resnext.py",
    "content": "import math\n\nfrom mmcv.cnn import build_conv_layer, build_norm_layer\n\nfrom ..builder import BACKBONES\nfrom ..utils import ResLayer\nfrom .resnet import Bottleneck as _Bottleneck\nfrom .resnet import ResNet\n\n\nclass Bottleneck(_Bottleneck):\n    expansion = 4\n\n    def __init__(self,\n                 inplanes,\n                 planes,\n                 groups=1,\n                 base_width=4,\n                 base_channels=64,\n                 **kwargs):\n        \"\"\"Bottleneck block for ResNeXt.\n        If style is \"pytorch\", the stride-two layer is the 3x3 conv layer,\n        if it is \"caffe\", the stride-two layer is the first 1x1 conv layer.\n        \"\"\"\n        super(Bottleneck, self).__init__(inplanes, planes, **kwargs)\n\n        if groups == 1:\n            width = self.planes\n        else:\n            width = math.floor(self.planes *\n                               (base_width / base_channels)) * groups\n\n        self.norm1_name, norm1 = build_norm_layer(\n            self.norm_cfg, width, postfix=1)\n        self.norm2_name, norm2 = build_norm_layer(\n            self.norm_cfg, width, postfix=2)\n        self.norm3_name, norm3 = build_norm_layer(\n            self.norm_cfg, self.planes * self.expansion, postfix=3)\n\n        self.conv1 = build_conv_layer(\n            self.conv_cfg,\n            self.inplanes,\n            width,\n            kernel_size=1,\n            stride=self.conv1_stride,\n            bias=False)\n        self.add_module(self.norm1_name, norm1)\n        fallback_on_stride = False\n        self.with_modulated_dcn = False\n        if self.with_dcn:\n            fallback_on_stride = self.dcn.pop('fallback_on_stride', False)\n        if not self.with_dcn or fallback_on_stride:\n            self.conv2 = build_conv_layer(\n                self.conv_cfg,\n                width,\n                width,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=self.dilation,\n                dilation=self.dilation,\n                groups=groups,\n                bias=False)\n        else:\n            assert self.conv_cfg is None, 'conv_cfg must be None for DCN'\n            self.conv2 = build_conv_layer(\n                self.dcn,\n                width,\n                width,\n                kernel_size=3,\n                stride=self.conv2_stride,\n                padding=self.dilation,\n                dilation=self.dilation,\n                groups=groups,\n                bias=False)\n\n        self.add_module(self.norm2_name, norm2)\n        self.conv3 = build_conv_layer(\n            self.conv_cfg,\n            width,\n            self.planes * self.expansion,\n            kernel_size=1,\n            bias=False)\n        self.add_module(self.norm3_name, norm3)\n\n\n@BACKBONES.register_module()\nclass ResNeXt(ResNet):\n    \"\"\"ResNeXt backbone.\n\n    Args:\n        depth (int): Depth of resnet, from {18, 34, 50, 101, 152}.\n        in_channels (int): Number of input image channels. Default: 3.\n        num_stages (int): Resnet stages. Default: 4.\n        groups (int): Group of resnext.\n        base_width (int): Base width of resnext.\n        strides (Sequence[int]): Strides of the first block of each stage.\n        dilations (Sequence[int]): Dilation of each stage.\n        out_indices (Sequence[int]): Output from which stages.\n        style (str): `pytorch` or `caffe`. If set to \"pytorch\", the stride-two\n            layer is the 3x3 conv layer, otherwise the stride-two layer is\n            the first 1x1 conv layer.\n        frozen_stages (int): Stages to be frozen (all param fixed). -1 means\n            not freezing any parameters.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n        norm_eval (bool): Whether to set norm layers to eval mode, namely,\n            freeze running stats (mean and var). Note: Effect on Batch Norm\n            and its variants only.\n        with_cp (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n        zero_init_residual (bool): whether to use zero init for last norm layer\n            in resblocks to let them behave as identity.\n    \"\"\"\n\n    arch_settings = {\n        50: (Bottleneck, (3, 4, 6, 3)),\n        101: (Bottleneck, (3, 4, 23, 3)),\n        152: (Bottleneck, (3, 8, 36, 3))\n    }\n\n    def __init__(self, groups=1, base_width=4, **kwargs):\n        self.groups = groups\n        self.base_width = base_width\n        super(ResNeXt, self).__init__(**kwargs)\n\n    def make_res_layer(self, **kwargs):\n        \"\"\"Pack all blocks in a stage into a ``ResLayer``\"\"\"\n        return ResLayer(\n            groups=self.groups,\n            base_width=self.base_width,\n            base_channels=self.base_channels,\n            **kwargs)\n"
  },
  {
    "path": "code/mmdet/models/backbones/ssd_vgg.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import VGG, constant_init, kaiming_init, normal_init, xavier_init\nfrom mmcv.runner import load_checkpoint\n\nfrom mmdet.utils import get_root_logger\nfrom ..builder import BACKBONES\n\n\n@BACKBONES.register_module()\nclass SSDVGG(VGG):\n    \"\"\"VGG Backbone network for single-shot-detection\n\n    Args:\n        input_size (int): width and height of input, from {300, 512}.\n        depth (int): Depth of vgg, from {11, 13, 16, 19}.\n        out_indices (Sequence[int]): Output from which stages.\n\n    Example:\n        >>> self = SSDVGG(input_size=300, depth=11)\n        >>> self.eval()\n        >>> inputs = torch.rand(1, 3, 300, 300)\n        >>> level_outputs = self.forward(inputs)\n        >>> for level_out in level_outputs:\n        ...     print(tuple(level_out.shape))\n        (1, 1024, 19, 19)\n        (1, 512, 10, 10)\n        (1, 256, 5, 5)\n        (1, 256, 3, 3)\n        (1, 256, 1, 1)\n    \"\"\"\n    extra_setting = {\n        300: (256, 'S', 512, 128, 'S', 256, 128, 256, 128, 256),\n        512: (256, 'S', 512, 128, 'S', 256, 128, 'S', 256, 128, 'S', 256, 128),\n    }\n\n    def __init__(self,\n                 input_size,\n                 depth,\n                 with_last_pool=False,\n                 ceil_mode=True,\n                 out_indices=(3, 4),\n                 out_feature_indices=(22, 34),\n                 l2_norm_scale=20.):\n        # TODO: in_channels for mmcv.VGG\n        super(SSDVGG, self).__init__(\n            depth,\n            with_last_pool=with_last_pool,\n            ceil_mode=ceil_mode,\n            out_indices=out_indices)\n        assert input_size in (300, 512)\n        self.input_size = input_size\n\n        self.features.add_module(\n            str(len(self.features)),\n            nn.MaxPool2d(kernel_size=3, stride=1, padding=1))\n        self.features.add_module(\n            str(len(self.features)),\n            nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6))\n        self.features.add_module(\n            str(len(self.features)), nn.ReLU(inplace=True))\n        self.features.add_module(\n            str(len(self.features)), nn.Conv2d(1024, 1024, kernel_size=1))\n        self.features.add_module(\n            str(len(self.features)), nn.ReLU(inplace=True))\n        self.out_feature_indices = out_feature_indices\n\n        self.inplanes = 1024\n        self.extra = self._make_extra_layers(self.extra_setting[input_size])\n        self.l2_norm = L2Norm(\n            self.features[out_feature_indices[0] - 1].out_channels,\n            l2_norm_scale)\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in backbone\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if isinstance(pretrained, str):\n            logger = get_root_logger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.features.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, nn.BatchNorm2d):\n                    constant_init(m, 1)\n                elif isinstance(m, nn.Linear):\n                    normal_init(m, std=0.01)\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n        for m in self.extra.modules():\n            if isinstance(m, nn.Conv2d):\n                xavier_init(m, distribution='uniform')\n\n        constant_init(self.l2_norm, self.l2_norm.scale)\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        outs = []\n        for i, layer in enumerate(self.features):\n            x = layer(x)\n            if i in self.out_feature_indices:\n                outs.append(x)\n        for i, layer in enumerate(self.extra):\n            x = F.relu(layer(x), inplace=True)\n            if i % 2 == 1:\n                outs.append(x)\n        outs[0] = self.l2_norm(outs[0])\n        if len(outs) == 1:\n            return outs[0]\n        else:\n            return tuple(outs)\n\n    def _make_extra_layers(self, outplanes):\n        layers = []\n        kernel_sizes = (1, 3)\n        num_layers = 0\n        outplane = None\n        for i in range(len(outplanes)):\n            if self.inplanes == 'S':\n                self.inplanes = outplane\n                continue\n            k = kernel_sizes[num_layers % 2]\n            if outplanes[i] == 'S':\n                outplane = outplanes[i + 1]\n                conv = nn.Conv2d(\n                    self.inplanes, outplane, k, stride=2, padding=1)\n            else:\n                outplane = outplanes[i]\n                conv = nn.Conv2d(\n                    self.inplanes, outplane, k, stride=1, padding=0)\n            layers.append(conv)\n            self.inplanes = outplanes[i]\n            num_layers += 1\n        if self.input_size == 512:\n            layers.append(nn.Conv2d(self.inplanes, 256, 4, padding=1))\n\n        return nn.Sequential(*layers)\n\n\nclass L2Norm(nn.Module):\n\n    def __init__(self, n_dims, scale=20., eps=1e-10):\n        \"\"\"L2 normalization layer\n\n        Args:\n            n_dims (int): Number of dimensions to be normalized\n            scale (float, optional): Defaults to 20..\n            eps (float, optional): Used to avoid division by zero.\n                Defaults to 1e-10.\n        \"\"\"\n        super(L2Norm, self).__init__()\n        self.n_dims = n_dims\n        self.weight = nn.Parameter(torch.Tensor(self.n_dims))\n        self.eps = eps\n        self.scale = scale\n\n    def forward(self, x):\n        \"\"\"Forward function\"\"\"\n        # normalization layer convert to FP32 in FP16 training\n        x_float = x.float()\n        norm = x_float.pow(2).sum(1, keepdim=True).sqrt() + self.eps\n        return (self.weight[None, :, None, None].float().expand_as(x_float) *\n                x_float / norm).type_as(x)\n"
  },
  {
    "path": "code/mmdet/models/builder.py",
    "content": "from mmcv.utils import Registry, build_from_cfg\nfrom torch import nn\n\nBACKBONES = Registry('backbone')\nNECKS = Registry('neck')\nROI_EXTRACTORS = Registry('roi_extractor')\nSHARED_HEADS = Registry('shared_head')\nHEADS = Registry('head')\nLOSSES = Registry('loss')\nDETECTORS = Registry('detector')\n\n\ndef build(cfg, registry, default_args=None):\n    \"\"\"Build a module\n\n    Args:\n        cfg (dict, list[dict]): The config of modules, is is either a dict\n            or a list of configs.\n        registry (:obj:`Registry`): A registry the module belongs to.\n        default_args (dict, optional): Default arguments to build the module.\n            Defaults to None.\n\n    Returns:\n        nn.Module: A built nn module.\n    \"\"\"\n    if isinstance(cfg, list):\n        modules = [\n            build_from_cfg(cfg_, registry, default_args) for cfg_ in cfg\n        ]\n        return nn.Sequential(*modules)\n    else:\n        return build_from_cfg(cfg, registry, default_args)\n\n\ndef build_backbone(cfg):\n    \"\"\"Build backbone\"\"\"\n    return build(cfg, BACKBONES)\n\n\ndef build_neck(cfg):\n    \"\"\"Build neck\"\"\"\n    return build(cfg, NECKS)\n\n\ndef build_roi_extractor(cfg):\n    \"\"\"Build roi extractor\"\"\"\n    return build(cfg, ROI_EXTRACTORS)\n\n\ndef build_shared_head(cfg):\n    \"\"\"Build shared head\"\"\"\n    return build(cfg, SHARED_HEADS)\n\n\ndef build_head(cfg):\n    \"\"\"Build head\"\"\"\n    return build(cfg, HEADS)\n\n\ndef build_loss(cfg):\n    \"\"\"Build loss\"\"\"\n    return build(cfg, LOSSES)\n\n\ndef build_detector(cfg, train_cfg=None, test_cfg=None):\n    \"\"\"Build detector\"\"\"\n    return build(cfg, DETECTORS, dict(train_cfg=train_cfg, test_cfg=test_cfg))\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/__init__.py",
    "content": "from .anchor_free_head import AnchorFreeHead\nfrom .anchor_head import AnchorHead\nfrom .atss_head import ATSSHead\nfrom .dense_reppoints_head import DenseRepPointsHead\nfrom .dense_reppoints_v2_head import DenseRepPointsV2Head\nfrom .fcos_head import FCOSHead\nfrom .fovea_head import FoveaHead\nfrom .free_anchor_retina_head import FreeAnchorRetinaHead\nfrom .fsaf_head import FSAFHead\nfrom .ga_retina_head import GARetinaHead\nfrom .ga_rpn_head import GARPNHead\nfrom .gfl_head import GFLHead\nfrom .guided_anchor_head import FeatureAdaption, GuidedAnchorHead\nfrom .nasfcos_head import NASFCOSHead\nfrom .pisa_retinanet_head import PISARetinaHead\nfrom .pisa_ssd_head import PISASSDHead\nfrom .reppoints_head import RepPointsHead\nfrom .reppoints_v2_head import RepPointsV2Head\nfrom .lsnet_head import LSHead\nfrom .lscpvnet_head import LSCPVHead\nfrom .retina_head import RetinaHead\nfrom .retina_sepbn_head import RetinaSepBNHead\nfrom .rpn_head import RPNHead\nfrom .ssd_head import SSDHead\n\n__all__ = [\n    'AnchorFreeHead', 'AnchorHead', 'GuidedAnchorHead', 'FeatureAdaption',\n    'RPNHead', 'GARPNHead', 'RetinaHead', 'RetinaSepBNHead', 'GARetinaHead',\n    'SSDHead', 'FCOSHead', 'RepPointsHead', 'FoveaHead',\n    'FreeAnchorRetinaHead', 'ATSSHead', 'FSAFHead', 'NASFCOSHead',\n    'PISARetinaHead', 'PISASSDHead', 'GFLHead', 'RepPointsV2Head',\n    'DenseRepPointsHead', 'DenseRepPointsV2Head', 'LSHead', 'LSCPVHead'\n]\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/anchor_free_head.py",
    "content": "from abc import abstractmethod\n\nimport pdb\nimport torch\nimport torch.nn as nn\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.core import force_fp32, multi_apply\nfrom ..builder import HEADS, build_loss\nfrom .base_dense_head import BaseDenseHead\n\n\n@HEADS.register_module()\nclass AnchorFreeHead(BaseDenseHead):\n    \"\"\"Anchor-free head (FCOS, Fovea, RepPoints, etc.).\n\n    Args:\n        num_classes (int): Number of categories excluding the background\n            category.\n        in_channels (int): Number of channels in the input feature map.\n        feat_channels (int): Number of hidden channels. Used in child classes.\n        stacked_convs (int): Number of stacking convs of the head.\n        strides (tuple): Downsample factor of each feature map.\n        dcn_on_last_conv (bool): If true, use dcn in the last layer of\n            towers. Default: False.\n        conv_bias (bool | str): If specified as `auto`, it will be decided by\n            the norm_cfg. Bias of conv will be set as True if `norm_cfg` is\n            None, otherwise False. Default: \"auto\".\n        background_label (int | None): Label ID of background, set as 0 for\n            RPN and num_classes for other heads. It will automatically set as\n            num_classes if None is given.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox (dict): Config of localization loss.\n        conv_cfg (dict): Config dict for convolution layer. Default: None.\n        norm_cfg (dict): Config dict for normalization layer. Default: None.\n        train_cfg (dict): Training config of anchor head.\n        test_cfg (dict): Testing config of anchor head.\n    \"\"\"  # noqa: W605\n\n    _version = 1\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 feat_channels=256,\n                 stacked_convs=4,\n                 strides=(4, 8, 16, 32, 64),\n                 dcn_on_last_conv=False,\n                 conv_bias='auto',\n                 background_label=None,\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox=dict(type='IoULoss', loss_weight=1.0),\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 train_cfg=None,\n                 test_cfg=None):\n        super(AnchorFreeHead, self).__init__()\n        self.num_classes = num_classes\n        self.cls_out_channels = num_classes\n        self.in_channels = in_channels\n        self.feat_channels = feat_channels\n        self.stacked_convs = stacked_convs\n        self.strides = strides\n        self.dcn_on_last_conv = dcn_on_last_conv\n        assert conv_bias == 'auto' or isinstance(conv_bias, bool)\n        self.conv_bias = conv_bias\n        self.loss_cls = build_loss(loss_cls)\n        self.loss_bbox = build_loss(loss_bbox)\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.fp16_enabled = False\n        self.background_label = (\n            num_classes if background_label is None else background_label)\n        # background_label should be either 0 or num_classes\n        assert (self.background_label == 0\n                or self.background_label == num_classes)\n\n        self._init_layers()\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self._init_cls_convs()\n        self._init_reg_convs()\n        self._init_predictor()\n\n    def _init_cls_convs(self):\n        \"\"\"Initialize classification conv layers of the head.\"\"\"\n        self.cls_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            if self.dcn_on_last_conv and i == self.stacked_convs - 1:\n                conv_cfg = dict(type='DCNv2')\n            else:\n                conv_cfg = self.conv_cfg\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=self.norm_cfg,\n                    bias=self.conv_bias))\n\n    def _init_reg_convs(self):\n        \"\"\"Initialize bbox regression conv layers of the head.\"\"\"\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            if self.dcn_on_last_conv and i == self.stacked_convs - 1:\n                conv_cfg = dict(type='DCNv2')\n            else:\n                conv_cfg = self.conv_cfg\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=self.norm_cfg,\n                    bias=self.conv_bias))\n\n    def _init_predictor(self):\n        \"\"\"Initialize predictor layers of the head.\"\"\"\n        self.conv_cls = nn.Conv2d(\n            self.feat_channels, self.cls_out_channels, 3, padding=1)\n        self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            if isinstance(m.conv, nn.Conv2d):\n                normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            if isinstance(m.conv, nn.Conv2d):\n                normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.conv_cls, std=0.01, bias=bias_cls)\n        normal_init(self.conv_reg, std=0.01)\n\n    def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,\n                              missing_keys, unexpected_keys, error_msgs):\n        \"\"\"Hack some keys of the model state dict so that can load checkpoints\n        of previous version.\"\"\"\n        version = local_metadata.get('version', None)\n        if version is None:\n            # the key is different in early versions\n            # for example, 'fcos_cls' become 'conv_cls' now\n            bbox_head_keys = [\n                k for k in state_dict.keys() if k.startswith(prefix)\n            ]\n            ori_predictor_keys = []\n            new_predictor_keys = []\n            # e.g. 'fcos_cls' or 'fcos_reg'\n            for key in bbox_head_keys:\n                ori_predictor_keys.append(key)\n                key = key.split('.')\n                conv_name = None\n                if key[1].endswith('cls'):\n                    conv_name = 'conv_cls'\n                elif key[1].endswith('reg'):\n                    conv_name = 'conv_reg'\n                elif key[1].endswith('centerness'):\n                    conv_name = 'conv_centerness'\n                else:\n                    assert NotImplementedError\n                if conv_name is not None:\n                    key[1] = conv_name\n                    new_predictor_keys.append('.'.join(key))\n                else:\n                    ori_predictor_keys.pop(-1)\n            for i in range(len(new_predictor_keys)):\n                state_dict[new_predictor_keys[i]] = state_dict.pop(\n                    ori_predictor_keys[i])\n        super()._load_from_state_dict(state_dict, prefix, local_metadata,\n                                      strict, missing_keys, unexpected_keys,\n                                      error_msgs)\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple: Usually contain classification scores and bbox predictions.\n                cls_scores (list[Tensor]): Box scores for each scale level,\n                    each is a 4D-tensor, the channel number is\n                    num_points * num_classes.\n                bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                    level, each is a 4D-tensor, the channel number is\n                    num_points * 4.\n        \"\"\"\n        return multi_apply(self.forward_single, feats)[:2]\n\n    def forward_single(self, x):\n        \"\"\"Forward features of a single scale levle.\n\n        Args:\n            x (Tensor): FPN feature maps of the specified stride.\n\n        Returns:\n            tuple: Scores for each class, bbox predictions, features\n                after classification and regression conv layers, some\n                models needs these features like FCOS.\n        \"\"\"\n        cls_feat = x\n        reg_feat = x\n\n        for cls_layer in self.cls_convs:\n            cls_feat = cls_layer(cls_feat)\n        cls_score = self.conv_cls(cls_feat)\n\n        for reg_layer in self.reg_convs:\n            reg_feat = reg_layer(reg_feat)\n        bbox_pred = self.conv_reg(reg_feat)\n        return cls_score, bbox_pred, cls_feat, reg_feat\n\n    @abstractmethod\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute loss of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level,\n                each is a 4D-tensor, the channel number is\n                num_points * num_classes.\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level, each is a 4D-tensor, the channel number is\n                num_points * 4.\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n        \"\"\"\n\n        raise NotImplementedError\n\n    @abstractmethod\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_preds,\n                   img_metas,\n                   cfg=None,\n                   rescale=None):\n        \"\"\" Transform network output for a batch into bbox predictions.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_points * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_points * 4, H, W)\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used\n            rescale (bool): If True, return boxes in original image space\n        \"\"\"\n\n        raise NotImplementedError\n\n    @abstractmethod\n    def get_targets(self, points, gt_bboxes_list, gt_labels_list):\n        \"\"\"Compute regression, classification and centerss targets for points\n            in multiple images.\n\n        Args:\n            points (list[Tensor]): Points of each fpn level, each has shape\n                (num_points, 2).\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image,\n                each has shape (num_gt, 4).\n            gt_labels_list (list[Tensor]): Ground truth labels of each box,\n                each has shape (num_gt,).\n        \"\"\"\n        raise NotImplementedError\n\n    def _get_points_single(self,\n                           featmap_size,\n                           stride,\n                           dtype,\n                           device,\n                           flatten=False):\n        \"\"\"Get points of a single scale level.\"\"\"\n        h, w = featmap_size\n        x_range = torch.arange(w, dtype=dtype, device=device)\n        y_range = torch.arange(h, dtype=dtype, device=device)\n        y, x = torch.meshgrid(y_range, x_range)\n        if flatten:\n            y = y.flatten()\n            x = x.flatten()\n        return y, x\n\n    def get_points(self, featmap_sizes, dtype, device, flatten=False):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            dtype (torch.dtype): Type of points.\n            device (torch.device): Device of points.\n\n        Returns:\n            tuple: points of each image.\n        \"\"\"\n        mlvl_points = []\n        for i in range(len(featmap_sizes)):\n            mlvl_points.append(\n                self._get_points_single(featmap_sizes[i], self.strides[i],\n                                        dtype, device, flatten))\n        return mlvl_points\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/anchor_head.py",
    "content": "import torch\nimport torch.nn as nn\nfrom mmcv.cnn import normal_init\n\nfrom mmdet.core import (anchor_inside_flags, build_anchor_generator,\n                        build_assigner, build_bbox_coder, build_sampler,\n                        force_fp32, images_to_levels, multi_apply,\n                        multiclass_nms, unmap)\nfrom ..builder import HEADS, build_loss\nfrom .base_dense_head import BaseDenseHead\n\n\n@HEADS.register_module()\nclass AnchorHead(BaseDenseHead):\n    \"\"\"Anchor-based head (RPN, RetinaNet, SSD, etc.).\n\n    Args:\n        num_classes (int): Number of categories excluding the background\n            category.\n        in_channels (int): Number of channels in the input feature map.\n        feat_channels (int): Number of hidden channels. Used in child classes.\n        anchor_generator (dict): Config dict for anchor generator\n        bbox_coder (dict): Config of bounding box coder.\n        reg_decoded_bbox (bool): If true, the regression loss would be\n            applied on decoded bounding boxes. Default: False\n        background_label (int | None): Label ID of background, set as 0 for\n            RPN and num_classes for other heads. It will automatically set as\n            num_classes if None is given.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox (dict): Config of localization loss.\n        train_cfg (dict): Training config of anchor head.\n        test_cfg (dict): Testing config of anchor head.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 feat_channels=256,\n                 anchor_generator=dict(\n                     type='AnchorGenerator',\n                     scales=[8, 16, 32],\n                     ratios=[0.5, 1.0, 2.0],\n                     strides=[4, 8, 16, 32, 64]),\n                 bbox_coder=dict(\n                     type='DeltaXYWHBBoxCoder',\n                     target_means=(.0, .0, .0, .0),\n                     target_stds=(1.0, 1.0, 1.0, 1.0)),\n                 reg_decoded_bbox=False,\n                 background_label=None,\n                 loss_cls=dict(\n                     type='CrossEntropyLoss',\n                     use_sigmoid=True,\n                     loss_weight=1.0),\n                 loss_bbox=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 train_cfg=None,\n                 test_cfg=None):\n        super(AnchorHead, self).__init__()\n        self.in_channels = in_channels\n        self.num_classes = num_classes\n        self.feat_channels = feat_channels\n        self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False)\n        # TODO better way to determine whether sample or not\n        self.sampling = loss_cls['type'] not in [\n            'FocalLoss', 'GHMC', 'QualityFocalLoss'\n        ]\n        if self.use_sigmoid_cls:\n            self.cls_out_channels = num_classes\n        else:\n            self.cls_out_channels = num_classes + 1\n\n        if self.cls_out_channels <= 0:\n            raise ValueError(f'num_classes={num_classes} is too small')\n        self.reg_decoded_bbox = reg_decoded_bbox\n\n        self.background_label = (\n            num_classes if background_label is None else background_label)\n        # background_label should be either 0 or num_classes\n        assert (self.background_label == 0\n                or self.background_label == num_classes)\n\n        self.bbox_coder = build_bbox_coder(bbox_coder)\n        self.loss_cls = build_loss(loss_cls)\n        self.loss_bbox = build_loss(loss_bbox)\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n        if self.train_cfg:\n            self.assigner = build_assigner(self.train_cfg.assigner)\n            # use PseudoSampler when sampling is False\n            if self.sampling and hasattr(self.train_cfg, 'sampler'):\n                sampler_cfg = self.train_cfg.sampler\n            else:\n                sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.fp16_enabled = False\n\n        self.anchor_generator = build_anchor_generator(anchor_generator)\n        # usually the numbers of anchors for each level are the same\n        # except SSD detectors\n        self.num_anchors = self.anchor_generator.num_base_anchors[0]\n        self._init_layers()\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.conv_cls = nn.Conv2d(self.in_channels,\n                                  self.num_anchors * self.cls_out_channels, 1)\n        self.conv_reg = nn.Conv2d(self.in_channels, self.num_anchors * 4, 1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        normal_init(self.conv_cls, std=0.01)\n        normal_init(self.conv_reg, std=0.01)\n\n    def forward_single(self, x):\n        \"\"\"Forward feature of a single scale level.\n\n        Args:\n            x (Tensor): Features of a single scale level.\n\n        Returns:\n            tuple:\n                cls_score (Tensor): Cls scores for a single scale level\n                    the channels number is num_anchors * num_classes.\n                bbox_pred (Tensor): Box energies / deltas for a single scale\n                    level, the channels number is num_anchors * 4.\n        \"\"\"\n        cls_score = self.conv_cls(x)\n        bbox_pred = self.conv_reg(x)\n        return cls_score, bbox_pred\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple: Usually a tuple of classification scores and bbox prediction\n                cls_scores (list[Tensor]): Classification scores for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * num_classes.\n                bbox_preds (list[Tensor]): Box energies / deltas for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * 4.\n        \"\"\"\n        return multi_apply(self.forward_single, feats)\n\n    def get_anchors(self, featmap_sizes, img_metas, device='cuda'):\n        \"\"\"Get anchors according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n            device (torch.device | str): Device for returned tensors\n\n        Returns:\n            tuple:\n                anchor_list (list[Tensor]): Anchors of each image\n                valid_flag_list (list[Tensor]): Valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n\n        # since feature map sizes of all images are the same, we only compute\n        # anchors for one time\n        multi_level_anchors = self.anchor_generator.grid_anchors(\n            featmap_sizes, device)\n        anchor_list = [multi_level_anchors for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level anchors\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = self.anchor_generator.valid_flags(\n                featmap_sizes, img_meta['pad_shape'], device)\n            valid_flag_list.append(multi_level_flags)\n\n        return anchor_list, valid_flag_list\n\n    def _get_targets_single(self,\n                            flat_anchors,\n                            valid_flags,\n                            gt_bboxes,\n                            gt_bboxes_ignore,\n                            gt_labels,\n                            img_meta,\n                            label_channels=1,\n                            unmap_outputs=True):\n        \"\"\"Compute regression and classification targets for anchors in\n            a single image.\n\n        Args:\n            flat_anchors (Tensor): Multi-level anchors of the image, which are\n                concatenated into a single tensor of shape (num_anchors ,4)\n            valid_flags (Tensor): Multi level valid flags of the image,\n                which are concatenated into a single tensor of\n                    shape (num_anchors,).\n            gt_bboxes (Tensor): Ground truth bboxes of the image,\n                shape (num_gts, 4).\n            img_meta (dict): Meta info of the image.\n            gt_bboxes_ignore (Tensor): Ground truth bboxes to be\n                ignored, shape (num_ignored_gts, 4).\n            img_meta (dict): Meta info of the image.\n            gt_labels (Tensor): Ground truth labels of each box,\n                shape (num_gts,).\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple:\n                labels_list (list[Tensor]): Labels of each level\n                label_weights_list (list[Tensor]): Label weights of each level\n                bbox_targets_list (list[Tensor]): BBox targets of each level\n                bbox_weights_list (list[Tensor]): BBox weights of each level\n                num_total_pos (int): Number of positive samples in all images\n                num_total_neg (int): Number of negative samples in all images\n        \"\"\"\n        inside_flags = anchor_inside_flags(flat_anchors, valid_flags,\n                                           img_meta['img_shape'][:2],\n                                           self.train_cfg.allowed_border)\n        if not inside_flags.any():\n            return (None, ) * 6\n        # assign gt and sample anchors\n        anchors = flat_anchors[inside_flags, :]\n\n        assign_result = self.assigner.assign(\n            anchors, gt_bboxes, gt_bboxes_ignore,\n            None if self.sampling else gt_labels)\n        sampling_result = self.sampler.sample(assign_result, anchors,\n                                              gt_bboxes)\n\n        num_valid_anchors = anchors.shape[0]\n        bbox_targets = torch.zeros_like(anchors)\n        bbox_weights = torch.zeros_like(anchors)\n        labels = anchors.new_full((num_valid_anchors, ),\n                                  self.background_label,\n                                  dtype=torch.long)\n        label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            if not self.reg_decoded_bbox:\n                pos_bbox_targets = self.bbox_coder.encode(\n                    sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes)\n            else:\n                pos_bbox_targets = sampling_result.pos_gt_bboxes\n            bbox_targets[pos_inds, :] = pos_bbox_targets\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                # only rpn gives gt_labels as None, this time FG is 1\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[\n                    sampling_result.pos_assigned_gt_inds]\n            if self.train_cfg.pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = self.train_cfg.pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of anchors\n        if unmap_outputs:\n            num_total_anchors = flat_anchors.size(0)\n            labels = unmap(\n                labels,\n                num_total_anchors,\n                inside_flags,\n                fill=self.background_label)  # fill bg label\n            label_weights = unmap(label_weights, num_total_anchors,\n                                  inside_flags)\n            bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)\n\n        return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,\n                neg_inds, sampling_result)\n\n    def get_targets(self,\n                    anchor_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    label_channels=1,\n                    unmap_outputs=True,\n                    return_sampling_results=False):\n        \"\"\"Compute regression and classification targets for anchors in\n            multiple images.\n\n        Args:\n            anchor_list (list[list[Tensor]]): Multi level anchors of each\n                image. The outer list indicates images, and the inner list\n                corresponds to feature levels of the image. Each element of\n                the inner list is a tensor of shape (num_anchors, 4).\n            valid_flag_list (list[list[Tensor]]): Multi level valid flags of\n                each image. The outer list indicates images, and the inner list\n                corresponds to feature levels of the image. Each element of\n                the inner list is a tensor of shape (num_anchors, )\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            gt_bboxes_ignore_list (list[Tensor]): Ground truth bboxes to be\n                ignored.\n            gt_labels_list (list[Tensor]): Ground truth labels of each box.\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple:\n                labels_list (list[Tensor]): Labels of each level\n                label_weights_list (list[Tensor]): Label weights of each level\n                bbox_targets_list (list[Tensor]): BBox targets of each level\n                bbox_weights_list (list[Tensor]): BBox weights of each level\n                num_total_pos (int): Number of positive samples in all images\n                num_total_neg (int): Number of negative samples in all images\n            additional_returns: This function enables user-defined returns from\n                `self._get_targets_single`. These returns are currently refined\n                to properties at each feature map (i.e. having HxW dimension).\n                The results will be concatenated after the end\n\n        \"\"\"\n        num_imgs = len(img_metas)\n        assert len(anchor_list) == len(valid_flag_list) == num_imgs\n\n        # anchor number of multi levels\n        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]\n        # concat all level anchors to a single tensor\n        concat_anchor_list = []\n        concat_valid_flag_list = []\n        for i in range(num_imgs):\n            assert len(anchor_list[i]) == len(valid_flag_list[i])\n            concat_anchor_list.append(torch.cat(anchor_list[i]))\n            concat_valid_flag_list.append(torch.cat(valid_flag_list[i]))\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        results = multi_apply(\n            self._get_targets_single,\n            concat_anchor_list,\n            concat_valid_flag_list,\n            gt_bboxes_list,\n            gt_bboxes_ignore_list,\n            gt_labels_list,\n            img_metas,\n            label_channels=label_channels,\n            unmap_outputs=unmap_outputs)\n        (all_labels, all_label_weights, all_bbox_targets, all_bbox_weights,\n         pos_inds_list, neg_inds_list, sampling_results_list) = results[:7]\n        rest_results = list(results[7:])  # user-added return values\n        # no valid anchors\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled anchors of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        # split targets to a list w.r.t. multiple levels\n        labels_list = images_to_levels(all_labels, num_level_anchors)\n        label_weights_list = images_to_levels(all_label_weights,\n                                              num_level_anchors)\n        bbox_targets_list = images_to_levels(all_bbox_targets,\n                                             num_level_anchors)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                             num_level_anchors)\n        res = (labels_list, label_weights_list, bbox_targets_list,\n               bbox_weights_list, num_total_pos, num_total_neg)\n        if return_sampling_results:\n            res = res + (sampling_results_list, )\n        for i, r in enumerate(rest_results):  # user-added return values\n            rest_results[i] = images_to_levels(r, num_level_anchors)\n\n        return res + tuple(rest_results)\n\n    def loss_single(self, cls_score, bbox_pred, anchors, labels, label_weights,\n                    bbox_targets, bbox_weights, num_total_samples):\n        \"\"\"Compute loss of a single scale level.\n\n        Args:\n            cls_score (Tensor): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W).\n            bbox_pred (Tensor): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W).\n            anchors (Tensor): Box reference for each scale level with shape\n                (N, num_total_anchors, 4).\n            labels (Tensor): Labels of each anchors with shape\n                (N, num_total_anchors).\n            label_weights (Tensor): Label weights of each anchor with shape\n                (N, num_total_anchors)\n            bbox_targets (Tensor): BBox regression targets of each anchor wight\n                shape (N, num_total_anchors, 4).\n            bbox_weights (Tensor): BBox regression loss weights of each anchor\n                with shape (N, num_total_anchors, 4).\n            num_total_samples (int): If sampling, num total samples equal to\n                the number of total anchors; Otherwise, it is the number of\n                positive anchors.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n        cls_score = cls_score.permute(0, 2, 3,\n                                      1).reshape(-1, self.cls_out_channels)\n        loss_cls = self.loss_cls(\n            cls_score, labels, label_weights, avg_factor=num_total_samples)\n        # regression loss\n        bbox_targets = bbox_targets.reshape(-1, 4)\n        bbox_weights = bbox_weights.reshape(-1, 4)\n        bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4)\n        if self.reg_decoded_bbox:\n            anchors = anchors.reshape(-1, 4)\n            bbox_pred = self.bbox_coder.decode(anchors, bbox_pred)\n        loss_bbox = self.loss_bbox(\n            bbox_pred,\n            bbox_targets,\n            bbox_weights,\n            avg_factor=num_total_samples)\n        return loss_cls, loss_bbox\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss. Default: None\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n\n        device = cls_scores[0].device\n\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=label_channels)\n        if cls_reg_targets is None:\n            return None\n        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,\n         num_total_pos, num_total_neg) = cls_reg_targets\n        num_total_samples = (\n            num_total_pos + num_total_neg if self.sampling else num_total_pos)\n\n        # anchor number of multi levels\n        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]\n        # concat all level anchors and flags to a single tensor\n        concat_anchor_list = []\n        for i in range(len(anchor_list)):\n            concat_anchor_list.append(torch.cat(anchor_list[i]))\n        all_anchor_list = images_to_levels(concat_anchor_list,\n                                           num_level_anchors)\n\n        losses_cls, losses_bbox = multi_apply(\n            self.loss_single,\n            cls_scores,\n            bbox_preds,\n            all_anchor_list,\n            labels_list,\n            label_weights_list,\n            bbox_targets_list,\n            bbox_weights_list,\n            num_total_samples=num_total_samples)\n        return dict(loss_cls=losses_cls, loss_bbox=losses_bbox)\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_preds,\n                   img_metas,\n                   cfg=None,\n                   rescale=False):\n        \"\"\"Transform network output for a batch into bbox predictions.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            cfg (mmcv.Config | None): Test / postprocessing configuration,\n                if None, test_cfg would be used\n            rescale (bool): If True, return boxes in original image space.\n                Default: False.\n\n        Returns:\n            list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.\n                The first item is an (n, 5) tensor, where the first 4 columns\n                are bounding box positions (tl_x, tl_y, br_x, br_y) and the\n                5-th column is a score between 0 and 1. The second item is a\n                (n,) tensor where each item is the predicted class labelof the\n                corresponding box.\n\n        Example:\n            >>> import mmcv\n            >>> self = AnchorHead(\n            >>>     num_classes=9,\n            >>>     in_channels=1,\n            >>>     anchor_generator=dict(\n            >>>         type='AnchorGenerator',\n            >>>         scales=[8],\n            >>>         ratios=[0.5, 1.0, 2.0],\n            >>>         strides=[4,]))\n            >>> img_metas = [{'img_shape': (32, 32, 3), 'scale_factor': 1}]\n            >>> cfg = mmcv.Config(dict(\n            >>>     score_thr=0.00,\n            >>>     nms=dict(type='nms', iou_thr=1.0),\n            >>>     max_per_img=10))\n            >>> feat = torch.rand(1, 1, 3, 3)\n            >>> cls_score, bbox_pred = self.forward_single(feat)\n            >>> # note the input lists are over different levels, not images\n            >>> cls_scores, bbox_preds = [cls_score], [bbox_pred]\n            >>> result_list = self.get_bboxes(cls_scores, bbox_preds,\n            >>>                               img_metas, cfg)\n            >>> det_bboxes, det_labels = result_list[0]\n            >>> assert len(result_list) == 1\n            >>> assert det_bboxes.shape[1] == 5\n            >>> assert len(det_bboxes) == len(det_labels) == cfg.max_per_img\n        \"\"\"\n        assert len(cls_scores) == len(bbox_preds)\n        num_levels = len(cls_scores)\n\n        device = cls_scores[0].device\n        featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)]\n        mlvl_anchors = self.anchor_generator.grid_anchors(\n            featmap_sizes, device=device)\n\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list,\n                                                mlvl_anchors, img_shape,\n                                                scale_factor, cfg, rescale)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_score_list,\n                           bbox_pred_list,\n                           mlvl_anchors,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        \"\"\"Transform outputs for a single batch item into bbox predictions.\n\n        Args:\n            cls_score_list (list[Tensor]): Box scores for a single scale level\n                Has shape (num_anchors * num_classes, H, W).\n            bbox_pred_list (list[Tensor]): Box energies / deltas for a single\n                scale level with shape (num_anchors * 4, H, W).\n            mlvl_anchors (list[Tensor]): Box reference for a single scale level\n                with shape (num_total_anchors, 4).\n            img_shape (tuple[int]): Shape of the input image,\n                (height, width, 3).\n            scale_factor (ndarray): Scale factor of the image arange as\n                (w_scale, h_scale, w_scale, h_scale).\n            cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used.\n            rescale (bool): If True, return boxes in original image space.\n\n        Returns:\n            Tensor: Labeled boxes in shape (n, 5), where the first 4 columns\n                are bounding box positions (tl_x, tl_y, br_x, br_y) and the\n                5-th column is a score between 0 and 1.\n        \"\"\"\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_score_list) == len(bbox_pred_list) == len(mlvl_anchors)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        for cls_score, bbox_pred, anchors in zip(cls_score_list,\n                                                 bbox_pred_list, mlvl_anchors):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            cls_score = cls_score.permute(1, 2,\n                                          0).reshape(-1, self.cls_out_channels)\n            if self.use_sigmoid_cls:\n                scores = cls_score.sigmoid()\n            else:\n                scores = cls_score.softmax(-1)\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                # Get maximum scores for foreground classes.\n                if self.use_sigmoid_cls:\n                    max_scores, _ = scores.max(dim=1)\n                else:\n                    # remind that we set FG labels to [0, num_class-1]\n                    # since mmdet v2.0\n                    # BG cat_id: num_class\n                    max_scores, _ = scores[:, :-1].max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                anchors = anchors[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n            bboxes = self.bbox_coder.decode(\n                anchors, bbox_pred, max_shape=img_shape)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n        mlvl_scores = torch.cat(mlvl_scores)\n        if self.use_sigmoid_cls:\n            # Add a dummy background class to the backend when using sigmoid\n            # remind that we set FG labels to [0, num_class-1] since mmdet v2.0\n            # BG cat_id: num_class\n            padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n            mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores,\n                                                cfg.score_thr, cfg.nms,\n                                                cfg.max_per_img)\n        return det_bboxes, det_labels\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/atss_head.py",
    "content": "import torch\nimport torch.distributed as dist\nimport torch.nn as nn\nfrom mmcv.cnn import ConvModule, Scale, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (anchor_inside_flags, build_assigner, build_sampler,\n                        force_fp32, images_to_levels, multi_apply,\n                        multiclass_nms, unmap)\nfrom ..builder import HEADS, build_loss\nfrom .anchor_head import AnchorHead\n\n\ndef reduce_mean(tensor):\n    if not (dist.is_available() and dist.is_initialized()):\n        return tensor\n    tensor = tensor.clone()\n    dist.all_reduce(tensor.div_(dist.get_world_size()), op=dist.ReduceOp.SUM)\n    return tensor\n\n\n@HEADS.register_module()\nclass ATSSHead(AnchorHead):\n    \"\"\"\n    Bridging the Gap Between Anchor-based and Anchor-free Detection via\n    Adaptive Training Sample Selection\n\n    ATSS head structure is similar with FCOS, however ATSS use anchor boxes\n    and assign label by Adaptive Training Sample Selection instead max-iou.\n\n    https://arxiv.org/abs/1912.02424\n    \"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 stacked_convs=4,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='GN', num_groups=32, requires_grad=True),\n                 loss_centerness=dict(\n                     type='CrossEntropyLoss',\n                     use_sigmoid=True,\n                     loss_weight=1.0),\n                 **kwargs):\n        self.stacked_convs = stacked_convs\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        super(ATSSHead, self).__init__(num_classes, in_channels, **kwargs)\n\n        self.sampling = False\n        if self.train_cfg:\n            self.assigner = build_assigner(self.train_cfg.assigner)\n            # SSD sampling=False so use PseudoSampler\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.loss_centerness = build_loss(loss_centerness)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        self.atss_cls = nn.Conv2d(\n            self.feat_channels,\n            self.num_anchors * self.cls_out_channels,\n            3,\n            padding=1)\n        self.atss_reg = nn.Conv2d(\n            self.feat_channels, self.num_anchors * 4, 3, padding=1)\n        self.atss_centerness = nn.Conv2d(\n            self.feat_channels, self.num_anchors * 1, 3, padding=1)\n        self.scales = nn.ModuleList(\n            [Scale(1.0) for _ in self.anchor_generator.strides])\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.atss_cls, std=0.01, bias=bias_cls)\n        normal_init(self.atss_reg, std=0.01)\n        normal_init(self.atss_centerness, std=0.01)\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple: Usually a tuple of classification scores and bbox prediction\n                cls_scores (list[Tensor]): Classification scores for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * num_classes.\n                bbox_preds (list[Tensor]): Box energies / deltas for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * 4.\n        \"\"\"\n        return multi_apply(self.forward_single, feats, self.scales)\n\n    def forward_single(self, x, scale):\n        \"\"\"Forward feature of a single scale level.\n\n        Args:\n            x (Tensor): Features of a single scale level.\n            scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize\n                the bbox prediction.\n\n        Returns:\n            tuple:\n                cls_score (Tensor): Cls scores for a single scale level\n                    the channels number is num_anchors * num_classes.\n                bbox_pred (Tensor): Box energies / deltas for a single scale\n                    level, the channels number is num_anchors * 4.\n                centerness (Tensor): Centerness for a single scale level, the\n                    channel number is (N, num_anchors * 1, H, W).\n        \"\"\"\n        cls_feat = x\n        reg_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            reg_feat = reg_conv(reg_feat)\n        cls_score = self.atss_cls(cls_feat)\n        # we just follow atss, not apply exp in bbox_pred\n        bbox_pred = scale(self.atss_reg(reg_feat)).float()\n        centerness = self.atss_centerness(reg_feat)\n        return cls_score, bbox_pred, centerness\n\n    def loss_single(self, anchors, cls_score, bbox_pred, centerness, labels,\n                    label_weights, bbox_targets, num_total_samples):\n        \"\"\"Compute loss of a single scale level.\n\n        Args:\n            cls_score (Tensor): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W).\n            bbox_pred (Tensor): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W).\n            anchors (Tensor): Box reference for each scale level with shape\n                (N, num_total_anchors, 4).\n            labels (Tensor): Labels of each anchors with shape\n                (N, num_total_anchors).\n            label_weights (Tensor): Label weights of each anchor with shape\n                (N, num_total_anchors)\n            bbox_targets (Tensor): BBox regression targets of each anchor wight\n                shape (N, num_total_anchors, 4).\n            num_total_samples (int): Number os positive samples that is\n                reduced over all GPUs.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n\n        anchors = anchors.reshape(-1, 4)\n        cls_score = cls_score.permute(0, 2, 3,\n                                      1).reshape(-1, self.cls_out_channels)\n        bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4)\n        centerness = centerness.permute(0, 2, 3, 1).reshape(-1)\n        bbox_targets = bbox_targets.reshape(-1, 4)\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n\n        # classification loss\n        loss_cls = self.loss_cls(\n            cls_score, labels, label_weights, avg_factor=num_total_samples)\n\n        # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n        bg_class_ind = self.num_classes\n        pos_inds = ((labels >= 0)\n                    & (labels < bg_class_ind)).nonzero().squeeze(1)\n\n        if len(pos_inds) > 0:\n            pos_bbox_targets = bbox_targets[pos_inds]\n            pos_bbox_pred = bbox_pred[pos_inds]\n            pos_anchors = anchors[pos_inds]\n            pos_centerness = centerness[pos_inds]\n\n            centerness_targets = self.centerness_target(\n                pos_anchors, pos_bbox_targets)\n            pos_decode_bbox_pred = self.bbox_coder.decode(\n                pos_anchors, pos_bbox_pred)\n            pos_decode_bbox_targets = self.bbox_coder.decode(\n                pos_anchors, pos_bbox_targets)\n\n            # regression loss\n            loss_bbox = self.loss_bbox(\n                pos_decode_bbox_pred,\n                pos_decode_bbox_targets,\n                weight=centerness_targets,\n                avg_factor=1.0)\n\n            # centerness loss\n            loss_centerness = self.loss_centerness(\n                pos_centerness,\n                centerness_targets,\n                avg_factor=num_total_samples)\n\n        else:\n            loss_bbox = bbox_pred.sum() * 0\n            loss_centerness = centerness.sum() * 0\n            centerness_targets = torch.tensor(0).cuda()\n\n        return loss_cls, loss_bbox, loss_centerness, centerness_targets.sum()\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds', 'centernesses'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             centernesses,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            centernesses (list[Tensor]): Centerness for each scale\n                level with shape (N, num_anchors * 1, H, W)\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (list[Tensor] | None): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n\n        device = cls_scores[0].device\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1\n\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=label_channels)\n        if cls_reg_targets is None:\n            return None\n\n        (anchor_list, labels_list, label_weights_list, bbox_targets_list,\n         bbox_weights_list, num_total_pos, num_total_neg) = cls_reg_targets\n\n        num_total_samples = reduce_mean(\n            torch.tensor(num_total_pos).cuda()).item()\n        num_total_samples = max(num_total_samples, 1.0)\n\n        losses_cls, losses_bbox, loss_centerness,\\\n            bbox_avg_factor = multi_apply(\n                self.loss_single,\n                anchor_list,\n                cls_scores,\n                bbox_preds,\n                centernesses,\n                labels_list,\n                label_weights_list,\n                bbox_targets_list,\n                num_total_samples=num_total_samples)\n\n        bbox_avg_factor = sum(bbox_avg_factor)\n        bbox_avg_factor = reduce_mean(bbox_avg_factor).item()\n        losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox))\n        return dict(\n            loss_cls=losses_cls,\n            loss_bbox=losses_bbox,\n            loss_centerness=loss_centerness)\n\n    def centerness_target(self, anchors, bbox_targets):\n        # only calculate pos centerness targets, otherwise there may be nan\n        gts = self.bbox_coder.decode(anchors, bbox_targets)\n        anchors_cx = (anchors[:, 2] + anchors[:, 0]) / 2\n        anchors_cy = (anchors[:, 3] + anchors[:, 1]) / 2\n        l_ = anchors_cx - gts[:, 0]\n        t_ = anchors_cy - gts[:, 1]\n        r_ = gts[:, 2] - anchors_cx\n        b_ = gts[:, 3] - anchors_cy\n\n        left_right = torch.stack([l_, r_], dim=1)\n        top_bottom = torch.stack([t_, b_], dim=1)\n        centerness = torch.sqrt(\n            (left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) *\n            (top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0]))\n        assert not torch.isnan(centerness).any()\n        return centerness\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds', 'centernesses'))\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_preds,\n                   centernesses,\n                   img_metas,\n                   cfg=None,\n                   rescale=False):\n        \"\"\"Transform network output for a batch into bbox predictions.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            centernesses (list[Tensor]): Centerness for each scale\n                level with shape (N, num_anchors * 1, H, W)\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used. Default: None.\n            rescale (bool): If True, return boxes in original image space.\n                Default: False.\n\n        Returns:\n            list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.\n                The first item is an (n, 5) tensor, where the first 4 columns\n                are bounding box positions (tl_x, tl_y, br_x, br_y) and the\n                5-th column is a score between 0 and 1. The second item is a\n                (n,) tensor where each item is the predicted class label of the\n                corresponding box.\n        \"\"\"\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds)\n        num_levels = len(cls_scores)\n        device = cls_scores[0].device\n        featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)]\n        mlvl_anchors = self.anchor_generator.grid_anchors(\n            featmap_sizes, device=device)\n\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds[i][img_id].detach() for i in range(num_levels)\n            ]\n            centerness_pred_list = [\n                centernesses[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list,\n                                                centerness_pred_list,\n                                                mlvl_anchors, img_shape,\n                                                scale_factor, cfg, rescale)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           centernesses,\n                           mlvl_anchors,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        \"\"\"Transform outputs for a single batch item into labeled boxes.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for a single scale level\n                Has shape (num_anchors * num_classes, H, W).\n            bbox_preds (list[Tensor]): Box energies / deltas for a single\n                scale level with shape (num_anchors * 4, H, W).\n            centernesses (list[Tensor]): Centerness for a single scale level\n                Has shape (num_anchors * 1, H, W).\n            mlvl_anchors (list[Tensor]): Box reference for a single scale level\n                with shape (num_total_anchors, 4).\n            img_shape (tuple[int]): Shape of the input image,\n                (height, width, 3).\n            scale_factor (ndarray): Scale factor of the image arange as\n                (w_scale, h_scale, w_scale, h_scale).\n            cfg (mmcv.Config | None): Test / postprocessing configuration,\n                if None, test_cfg would be used.\n            rescale (bool): If True, return boxes in original image space.\n                Default: False.\n\n        Returns:\n            tuple(Tensor):\n                det_bboxes (Tensor): BBox predictions in shape (n, 5), where\n                    the first 4 columns are bounding box positions\n                    (tl_x, tl_y, br_x, br_y) and the 5-th column is a score\n                    between 0 and 1.\n                det_labels (Tensor): A (n,) tensor where each item is the\n                    predicted class label of the corresponding box.\n        \"\"\"\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_anchors)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        mlvl_centerness = []\n        for cls_score, bbox_pred, centerness, anchors in zip(\n                cls_scores, bbox_preds, centernesses, mlvl_anchors):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n\n            scores = cls_score.permute(1, 2, 0).reshape(\n                -1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            centerness = centerness.permute(1, 2, 0).reshape(-1).sigmoid()\n\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = (scores * centerness[:, None]).max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                anchors = anchors[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n                centerness = centerness[topk_inds]\n\n            bboxes = self.bbox_coder.decode(\n                anchors, bbox_pred, max_shape=img_shape)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n            mlvl_centerness.append(centerness)\n\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n\n        mlvl_scores = torch.cat(mlvl_scores)\n        # Add a dummy background class to the backend when using sigmoid\n        # remind that we set FG labels to [0, num_class-1] since mmdet v2.0\n        # BG cat_id: num_class\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        mlvl_centerness = torch.cat(mlvl_centerness)\n\n        det_bboxes, det_labels = multiclass_nms(\n            mlvl_bboxes,\n            mlvl_scores,\n            cfg.score_thr,\n            cfg.nms,\n            cfg.max_per_img,\n            score_factors=mlvl_centerness)\n        return det_bboxes, det_labels\n\n    def get_targets(self,\n                    anchor_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    label_channels=1,\n                    unmap_outputs=True):\n        \"\"\"Get targets for ATSS head.\n\n        This method is almost the same as `AnchorHead.get_targets()`. Besides\n        returning the targets as the parent method does, it also returns the\n        anchors as the first element of the returned tuple.\n        \"\"\"\n        num_imgs = len(img_metas)\n        assert len(anchor_list) == len(valid_flag_list) == num_imgs\n\n        # anchor number of multi levels\n        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]\n        num_level_anchors_list = [num_level_anchors] * num_imgs\n\n        # concat all level anchors and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(anchor_list[i]) == len(valid_flag_list[i])\n            anchor_list[i] = torch.cat(anchor_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_anchors, all_labels, all_label_weights, all_bbox_targets,\n         all_bbox_weights, pos_inds_list, neg_inds_list) = multi_apply(\n             self._get_target_single,\n             anchor_list,\n             valid_flag_list,\n             num_level_anchors_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             gt_labels_list,\n             img_metas,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid anchors\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled anchors of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        # split targets to a list w.r.t. multiple levels\n        anchors_list = images_to_levels(all_anchors, num_level_anchors)\n        labels_list = images_to_levels(all_labels, num_level_anchors)\n        label_weights_list = images_to_levels(all_label_weights,\n                                              num_level_anchors)\n        bbox_targets_list = images_to_levels(all_bbox_targets,\n                                             num_level_anchors)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                             num_level_anchors)\n        return (anchors_list, labels_list, label_weights_list,\n                bbox_targets_list, bbox_weights_list, num_total_pos,\n                num_total_neg)\n\n    def _get_target_single(self,\n                           flat_anchors,\n                           valid_flags,\n                           num_level_anchors,\n                           gt_bboxes,\n                           gt_bboxes_ignore,\n                           gt_labels,\n                           img_meta,\n                           label_channels=1,\n                           unmap_outputs=True):\n        \"\"\"Compute regression, classification targets for anchors in a single\n            image.\n\n        Args:\n            flat_anchors (Tensor): Multi-level anchors of the image, which are\n                concatenated into a single tensor of shape (num_anchors ,4)\n            valid_flags (Tensor): Multi level valid flags of the image,\n                which are concatenated into a single tensor of\n                    shape (num_anchors,).\n            num_level_anchors Tensor): Number of anchors of each scale level.\n            gt_bboxes (Tensor): Ground truth bboxes of the image,\n                shape (num_gts, 4).\n            gt_bboxes_ignore (Tensor): Ground truth bboxes to be\n                ignored, shape (num_ignored_gts, 4).\n            gt_labels (Tensor): Ground truth labels of each box,\n                shape (num_gts,).\n            img_meta (dict): Meta info of the image.\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple: N is the number of total anchors in the image.\n                labels (Tensor): Labels of all anchors in the image with shape\n                    (N,).\n                label_weights (Tensor): Label weights of all anchor in the\n                    image with shape (N,).\n                bbox_targets (Tensor): BBox targets of all anchors in the\n                    image with shape (N, 4).\n                bbox_weights (Tensor): BBox weights of all anchors in the\n                    image with shape (N, 4)\n                pos_inds (Tensor): Indices of postive anchor with shape\n                    (num_pos,).\n                neg_inds (Tensor): Indices of negative anchor with shape\n                    (num_neg,).\n        \"\"\"\n        inside_flags = anchor_inside_flags(flat_anchors, valid_flags,\n                                           img_meta['img_shape'][:2],\n                                           self.train_cfg.allowed_border)\n        if not inside_flags.any():\n            return (None, ) * 6\n        # assign gt and sample anchors\n        anchors = flat_anchors[inside_flags, :]\n\n        num_level_anchors_inside = self.get_num_level_anchors_inside(\n            num_level_anchors, inside_flags)\n        assign_result = self.assigner.assign(anchors, num_level_anchors_inside,\n                                             gt_bboxes, gt_bboxes_ignore,\n                                             gt_labels)\n\n        sampling_result = self.sampler.sample(assign_result, anchors,\n                                              gt_bboxes)\n\n        num_valid_anchors = anchors.shape[0]\n        bbox_targets = torch.zeros_like(anchors)\n        bbox_weights = torch.zeros_like(anchors)\n        labels = anchors.new_full((num_valid_anchors, ),\n                                  self.background_label,\n                                  dtype=torch.long)\n        label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_bbox_targets = self.bbox_coder.encode(\n                sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes)\n            bbox_targets[pos_inds, :] = pos_bbox_targets\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[\n                    sampling_result.pos_assigned_gt_inds]\n            if self.train_cfg.pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = self.train_cfg.pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of anchors\n        if unmap_outputs:\n            num_total_anchors = flat_anchors.size(0)\n            anchors = unmap(anchors, num_total_anchors, inside_flags)\n            labels = unmap(\n                labels, num_total_anchors, inside_flags, fill=self.num_classes)\n            label_weights = unmap(label_weights, num_total_anchors,\n                                  inside_flags)\n            bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)\n\n        return (anchors, labels, label_weights, bbox_targets, bbox_weights,\n                pos_inds, neg_inds)\n\n    def get_num_level_anchors_inside(self, num_level_anchors, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_anchors)\n        num_level_anchors_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_anchors_inside\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/base_dense_head.py",
    "content": "from abc import ABCMeta, abstractmethod\n\nimport torch.nn as nn\n\n\nclass BaseDenseHead(nn.Module, metaclass=ABCMeta):\n    \"\"\"Base class for DenseHeads\"\"\"\n\n    def __init__(self):\n        super(BaseDenseHead, self).__init__()\n\n    @abstractmethod\n    def loss(self, **kwargs):\n        \"\"\"Compute losses of the head.\"\"\"\n        pass\n\n    @abstractmethod\n    def get_bboxes(self, **kwargs):\n        \"\"\"Transform network output for a batch into bbox predictions.\"\"\"\n        pass\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels=None,\n                      gt_bboxes_ignore=None,\n                      proposal_cfg=None,\n                      **kwargs):\n        \"\"\"\n        Args:\n            x (list[Tensor]): Features from FPN.\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes (Tensor): Ground truth bboxes of the image,\n                shape (num_gts, 4).\n            gt_labels (Tensor): Ground truth labels of each box,\n                shape (num_gts,).\n            gt_bboxes_ignore (Tensor): Ground truth bboxes to be\n                ignored, shape (num_ignored_gts, 4).\n            proposal_cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used\n\n        Returns:\n            tuple:\n                losses: (dict[str, Tensor]): A dictionary of loss components.\n                proposal_list (list[Tensor]): Proposals of each image.\n        \"\"\"\n        outs = self(x)\n        if gt_labels is None:\n            loss_inputs = outs + (gt_bboxes, img_metas)\n        else:\n            loss_inputs = outs + (gt_bboxes, gt_labels, img_metas)\n        losses = self.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)\n        if proposal_cfg is None:\n            return losses\n        else:\n            proposal_list = self.get_bboxes(*outs, img_metas, cfg=proposal_cfg)\n            return losses, proposal_list\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/dense_reppoints_head.py",
    "content": "import numpy as np\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nimport mmcv\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (PointGenerator, build_assigner, build_sampler,\n                        images_to_levels, multi_apply, multiclass_nms_pts, unmap)\nfrom mmdet.ops import DeformConv\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\n\n@HEADS.register_module()\nclass DenseRepPointsHead(AnchorFreeHead):\n    \"\"\"RepPoint head.\n\n    Args:\n        point_feat_channels (int): Number of channels of points features.\n        gradient_mul (float): The multiplier to gradients from\n            points refinement and recognition.\n        point_strides (Iterable): points strides.\n        point_base_scale (int): bbox scale for assigning labels.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox_init (dict): Config of initial points loss.\n        loss_bbox_refine (dict): Config of points loss in refinement.\n        use_grid_points (bool): If we use bounding box representation, the\n        reppoints is represented as grid points on the bounding box.\n        center_init (bool): Whether to use center point assignment.\n        transform_method (str): The methods to transform RepPoints to bbox.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 point_feat_channels=256,\n                 stacked_mask_convs=3,\n                 num_group=9,\n                 num_points=729,\n                 num_score_group=121,\n                 gradient_mul=0.1,\n                 point_strides=[8, 16, 32, 64, 128],\n                 point_base_scale=4,\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox_init=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=0.5),\n                 loss_bbox_refine=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_pts_init=dict(type='ChamferLoss2D', use_cuda=True, loss_weight=0.5, eps=1e-12),\n                 loss_pts_refine=dict(type='ChamferLoss2D', use_cuda=True, loss_weight=1.0, eps=1e-12),\n                 loss_mask_score_init=dict(type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n                 transform_method='minmax',\n                 sample_padding_mode='border',\n                 fuse_mask_feat=False,\n                 **kwargs):\n        self.num_group = num_group\n        self.num_points = num_points\n        self.num_score_group = num_score_group\n        self.point_feat_channels = point_feat_channels\n        self.stacked_mask_convs = stacked_mask_convs\n        self.fuse_mask_feat = fuse_mask_feat\n        self.sample_padding_mode = sample_padding_mode\n\n        # we use deformable conv to extract points features\n        self.dcn_kernel = int(np.sqrt(num_points))\n        self.dcn_pad = int((self.dcn_kernel - 1) / 2)\n        assert self.dcn_kernel * self.dcn_kernel == num_points, \\\n            'The points number should be a square number.'\n        assert self.dcn_kernel % 2 == 1, \\\n            'The points number should be an odd square number.'\n        dcn_base = np.arange(-self.dcn_pad,\n                             self.dcn_pad + 1).astype(np.float64)\n        dcn_base_y = np.repeat(dcn_base, self.dcn_kernel)\n        dcn_base_x = np.tile(dcn_base, self.dcn_kernel)\n        dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape(\n            (-1))\n        self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1)\n\n        super().__init__(num_classes, in_channels, loss_cls=loss_cls, **kwargs)\n\n        self.gradient_mul = gradient_mul\n        self.point_base_scale = point_base_scale\n        self.point_strides = point_strides\n        self.point_generators = [PointGenerator() for _ in self.point_strides]\n\n        if self.train_cfg:\n            self.init_assigner = build_assigner(self.train_cfg.init.assigner)\n            self.refine_assigner = build_assigner(self.train_cfg.refine.assigner)\n            # use PseudoSampler when sampling is False\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.transform_method = transform_method\n\n\n        self.cls_out_channels = self.num_classes\n        self.loss_bbox_init = build_loss(loss_bbox_init)\n        self.loss_bbox_refine = build_loss(loss_bbox_refine)\n        self.loss_pts_init = build_loss(loss_pts_init)\n        self.loss_pts_refine = build_loss(loss_pts_refine)\n        self.loss_mask_score_init = build_loss(loss_mask_score_init)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        self.mask_convs = nn.ModuleList()\n\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        for i in range(self.stacked_mask_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.mask_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n\n        pts_out_dim = 2 * self.num_points\n        self.reppoints_cls_conv = nn.Conv2d(self.feat_channels * self.num_group, self.point_feat_channels, 1, 1, 0)\n        self.reppoints_cls_out = nn.Conv2d(self.point_feat_channels, self.cls_out_channels, 1, 1, 0)\n\n        self.reppoints_pts_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        self.reppoints_pts_init_out = nn.Conv2d(self.point_feat_channels, pts_out_dim, 1, 1, 0)\n\n        self.reppoints_pts_refine_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        self.reppoints_pts_refine_out = nn.Conv2d(self.point_feat_channels, pts_out_dim, 1, 1, 0)\n\n        self.reppoints_mask_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        self.reppoints_mask_init_out = nn.Conv2d(self.point_feat_channels, self.num_score_group, 1, 1, 0)\n\n        if self.fuse_mask_feat:\n            self.mask_fuse_conv = nn.Conv2d(self.feat_channels, self.feat_channels, 3, 1, 1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.mask_convs:\n            normal_init(m.conv, std=0.01)\n        if self.fuse_mask_feat:\n            normal_init(self.mask_fuse_conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.reppoints_cls_conv, std=0.01)\n        normal_init(self.reppoints_cls_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_pts_init_conv, std=0.01)\n        normal_init(self.reppoints_pts_init_out, std=0.01)\n        normal_init(self.reppoints_pts_refine_conv, std=0.01)\n        normal_init(self.reppoints_pts_refine_out, std=0.01)\n        normal_init(self.reppoints_mask_init_conv, std=0.01)\n        normal_init(self.reppoints_mask_init_out, std=0.01)\n\n    def points2bbox(self, pts):\n        \"\"\"Converting the points set into bounding box.\n\n        :param pts: the input points sets (fields), each points\n            set (fields) is represented as 2n scalar.\n        :param y_first: if y_fisrt=True, the point set is represented as\n            [y1, x1, y2, x2 ... yn, xn], otherwise the point set is\n            represented as [x1, y1, x2, y2 ... xn, yn].\n        :return: each points set is converting to a bbox [x1, y1, x2, y2].\n        \"\"\"\n        pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n        pts_x = pts_reshape[:, :, 0, ...]\n        pts_y = pts_reshape[:, :, 1, ...]\n        if self.transform_method == 'minmax':\n            bbox_left = pts_x.min(dim=1, keepdim=True)[0]\n            bbox_right = pts_x.max(dim=1, keepdim=True)[0]\n            bbox_up = pts_y.min(dim=1, keepdim=True)[0]\n            bbox_bottom = pts_y.max(dim=1, keepdim=True)[0]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n        else:\n            raise NotImplementedError\n        return bbox\n\n    def sample_offset(self, x, flow, padding_mode):\n        \"\"\"\n        sample feature based on offset\n\n            Args:\n                x (Tensor): input feature, size (n, c, h, w)\n                flow (Tensor): flow fields, size(n, 2, h', w')\n                padding_mode (str): grid sample padding mode, 'zeros' or 'border'\n            Returns:\n                Tensor: warped feature map (n, c, h', w')\n        \"\"\"\n        # assert x.size()[-2:] == flow.size()[-2:]\n        n, _, h, w = flow.size()\n        x_ = torch.arange(w).view(1, -1).expand(h, -1)\n        y_ = torch.arange(h).view(-1, 1).expand(-1, w)\n        grid = torch.stack([x_, y_], dim=0).float().cuda()\n        grid = grid.unsqueeze(0).expand(n, -1, -1, -1)\n        grid = grid + flow\n        gx = 2 * grid[:, 0, :, :] / (w - 1) - 1\n        gy = 2 * grid[:, 1, :, :] / (h - 1) - 1\n        grid = torch.stack([gx, gy], dim=1)\n        grid = grid.permute(0, 2, 3, 1)\n        return F.grid_sample(x, grid, padding_mode=padding_mode, align_corners=True)\n\n    def compute_offset_feature(self, x, offset, padding_mode):\n        \"\"\"\n        sample feature based on offset\n\n            Args:\n                x (Tensor) : feature map, size (n, C, h, w),  x first\n                offset (Tensor) : offset, size (n, sample_pts*2, h, w), x first\n                padding_mode (str): 'zeros' or 'border' or 'relection'\n            Returns:\n                Tensor: the warped feature generated by the offset and the input feature map, size (n, sample_pts, C, h, w)\n        \"\"\"\n        offset_reshape = offset.view(offset.shape[0], -1, 2, offset.shape[2], offset.shape[3])  # (n, sample_pts, 2, h, w)\n        num_pts = offset_reshape.shape[1]\n        offset_reshape = offset_reshape.contiguous().view(-1, 2, offset.shape[2],\n                                                          offset.shape[3])  # (n*sample_pts, 2, h, w)\n        x_repeat = x.unsqueeze(1).repeat(1, num_pts, 1, 1, 1)  # (n, sample_pts, C, h, w)\n        x_repeat = x_repeat.view(-1, x_repeat.shape[2], x_repeat.shape[3], x_repeat.shape[4])  # (n*sample_pts, C, h, w)\n        sampled_feat = self.sample_offset(x_repeat, offset_reshape, padding_mode)  # (n*sample_pts, C, h, w)\n        sampled_feat = sampled_feat.view(-1, num_pts, sampled_feat.shape[1], sampled_feat.shape[2],\n                                         sampled_feat.shape[3])  # (n, sample_pts, C, h, w)\n        return sampled_feat\n\n    def sample_offset_3d(self, x, flow, padding_mode):\n        \"\"\"\n        sample feature based on 2D offset(x, y) + 1-D index(z)\n\n            Args:\n                x (Tensor): size (n, c, d', h', w')\n                flow (Tensor): size(n, 3, d, h, w)\n                padding_mode (str): 'zeros' or 'border'\n            Returns:\n                warped feature map generated by the offset and the input feature map, size(n, c, d, h, w)\n        \"\"\"\n        n, _, d, h, w = flow.size()\n        num_group = x.shape[2]\n        device = flow.get_device()\n        x_ = torch.arange(w, device=device).view(1, 1, -1).expand(d, h, -1).float()  # (d, h, w)\n        y_ = torch.arange(h, device=device).view(1, -1, 1).expand(d, -1, w).float()  # (d, h, w)\n        z_ = torch.zeros(d, h, w, device=device)  # (d, h, w)\n        grid = torch.stack([x_, y_, z_], dim=0).float()  # (3, d, h, w)\n        del x_, y_, z_\n        grid = grid.unsqueeze(0).expand(n, -1, -1, -1, -1)  # (n, 3, d, h, w)\n        grid = grid + flow  # (n, 3, d, h, w)\n        gx = 2 * grid[:, 0, :, :, :] / (w - 1) - 1  # (n, d, h, w)\n        gy = 2 * grid[:, 1, :, :, :] / (h - 1) - 1  # (n, d, h, w)\n        gz = 2 * grid[:, 2, :, :, :] / (num_group - 1) - 1  # (n, d, h, w)\n        grid = torch.stack([gx, gy, gz], dim=1)  # (n, 3, d, h, w)\n        del gx, gy, gz\n        grid = grid.permute(0, 2, 3, 4, 1)  # (n, d, h, w, 3)\n        return F.grid_sample(x, grid, padding_mode=padding_mode, align_corners=True)\n\n    def compute_offset_feature_5d(self, x, offset, padding_mode):\n        \"\"\"\n        sample 5D feature based on offset\n\n            Args:\n                x (Tensor) : input feature, size (n, C, d', h', w'), x first\n                offset (Tensor) : flow field, size (n, 3, sample_pts, h, w), x first\n                padding_mode (str): 'zeros' or 'border'\n            Returns:\n                Tensor: offset_feature, size (n, sample_pts, C, h, w)\n        \"\"\"\n        sampled_feat = self.sample_offset_3d(x, offset, padding_mode)  # (n, C, sample_pts, h, w)\n        sampled_feat = sampled_feat.transpose(1, 2)  # (n, sample_pts, C, h, w)\n        return sampled_feat\n\n    def forward(self, feats, test=False):\n        cls_out_list, pts_out_init_list, pts_out_refine_list = multi_apply(self.forward_pts_head_single, feats)\n        if test:\n            pts_out_list = pts_out_refine_list\n        else:\n            pts_out_list = [(1 - self.gradient_mul) * pts_out_init.detach()\n                            + self.gradient_mul * pts_out_init for pts_out_init in pts_out_init_list]\n\n        pts_score_out = self.forward_mask_head(feats, pts_out_list)\n        return cls_out_list, pts_out_init_list, pts_out_refine_list, pts_score_out\n\n    def forward_pts_head_single(self, x):\n        b, _, h, w = x.shape\n        dcn_base_offset = self.dcn_base_offset.type_as(x)\n        scale = self.point_base_scale / 2\n        points_init = dcn_base_offset / dcn_base_offset.max() * scale\n\n        cls_feat = x\n        pts_feat = x\n\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            pts_feat = reg_conv(pts_feat)\n\n        # generate points_init\n        pts_out_init = self.reppoints_pts_init_out(self.relu(self.reppoints_pts_init_conv(pts_feat)))\n        pts_out_init = pts_out_init + points_init  # (b, 2n, h, w)\n        pts_out_init_detach = (1 - self.gradient_mul) * pts_out_init.detach() + self.gradient_mul * pts_out_init\n\n        # classify dense reppoints based on group pooling\n        cls_offset = pts_out_init_detach.view(b, self.num_group, -1, 2, h, w)\n        cls_offset = cls_offset[:, :, 0, ...].reshape(b, -1, h, w)\n\n        cls_pts_feature = self.compute_offset_feature(cls_feat, cls_offset, padding_mode=self.sample_padding_mode)\n        cls_pts_feature = cls_pts_feature.contiguous().view(b, -1, h, w)\n\n        cls_out = self.reppoints_cls_out(self.relu(self.reppoints_cls_conv(cls_pts_feature)))\n\n        # generate offset field\n        pts_refine_field = self.reppoints_pts_refine_out(self.relu(self.reppoints_pts_refine_conv(pts_feat)))  # (b, n*2, h, w)\n        pts_refine_field = pts_refine_field.view(b * self.num_points, -1, h, w)  # (b*n, 2, h, w)\n        pts_out_init_detach_reshape = pts_out_init_detach.view(b, -1, 2, h, w).view(-1, 2, h, w)  # (b*n, 2, h, w)\n        pts_out_refine = self.compute_offset_feature(pts_refine_field, pts_out_init_detach_reshape,padding_mode=self.sample_padding_mode)  # (b*n, 2, h, w)\n        pts_out_refine = pts_out_refine.view(b, -1, h, w)  # (b, n*2, h, w)\n        # generate points_refine\n        pts_out_refine = pts_out_refine + pts_out_init_detach\n\n        return cls_out, pts_out_init, pts_out_refine\n\n    def forward_mask_head(self, mask_feat_list, pts_out_list):\n        for mask_conv in self.mask_convs:\n            mask_feat_list = [mask_conv(mask_feat) for mask_feat in mask_feat_list]\n        if self.fuse_mask_feat:\n            mask_feat_high_res = mask_feat_list[0]\n            H, W = mask_feat_high_res.shape[-2:]\n            mask_feat_up_list = []\n            for lvl, mask_feat in enumerate(mask_feat_list):\n                mask_feat_up = mask_feat\n                if lvl > 0:\n                    mask_feat_up = F.interpolate(\n                        mask_feat, size=(H, W), mode=\"bilinear\", align_corners=False\n                    )\n                    del mask_feat\n                mask_feat_up_list.append(\n                    self.mask_fuse_conv(mask_feat_up + mask_feat_high_res)\n                )\n                del mask_feat_up\n            del mask_feat_high_res\n            del mask_feat_list\n            mask_feat_list = mask_feat_up_list\n\n        pts_score_out = multi_apply(self.forward_mask_head_single, pts_out_list, mask_feat_list)[0]\n        return pts_score_out\n\n    def forward_mask_head_single(self, pts, mask_feat):\n        b, _, h, w = mask_feat.shape\n        h_pts, w_pts = pts.shape[-2:]\n        score_map = self.reppoints_mask_init_out(\n            self.relu(self.reppoints_mask_init_conv(mask_feat)))  # (b, G*1, h, w)\n        # position sensitive group partition based on grids\n        pts_reshape_detach = pts.detach().view(b, -1, 2, h_pts, w_pts)  # (b, n, 2, h_pts, w_pts)\n        group_inds = self.grid_position_sensitive_group_partition(\n            pts_reshape_detach, self.num_score_group)  # (b, 1, n, h_pts, w_pts)\n        del pts_reshape_detach\n        score_map = score_map.unsqueeze(1)  # (b, 1, G, h, w)\n\n        pts_reshape = pts.view(b, -1, 2, h_pts, w_pts).transpose(1, 2)  # (b, 2, n, h_pts, w_pts)\n        pts_reshape = pts_reshape.detach()\n        _pts_inds_cat = torch.cat([pts_reshape, group_inds], dim=1)  # (b, 3, n, h_pts, w_pts)\n        del group_inds, pts_reshape\n        # position sensitive sampling on score maps\n        pts_score_out = self.compute_offset_feature_5d(\n            score_map, _pts_inds_cat, padding_mode=self.sample_padding_mode)  # (b, n, 1, h_pts, w_pts)\n\n        pts_score_out = pts_score_out.view(b, -1, h_pts, w_pts)  # (b, n, h_pts, w_pts)\n        return pts_score_out, _\n\n    @staticmethod\n    def normalize_pts_within_bboxes(pts):\n        \"\"\"\n        Normalize pts offset within bboxes(instance level)\n\n            Args:\n                pts(Tensor): input points, size (b, n, 2, h_pts, w_pts)\n\n            Returns:\n                Tensor: normalized_pts, size (b, n, 2, h_pts, w_pts)\n        \"\"\"\n        b, _, _, h_pts, w_pts = pts.shape\n        _pts_x = pts[:, :, 0, :, :]  # (b, n, h_pts, w_pts)\n        _pts_y = pts[:, :, 1, :, :]  # (b, n, h_pts, w_pts)\n        _bbox_left = torch.min(_pts_x, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_right = torch.max(_pts_x, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_bottom = torch.max(_pts_y, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_up = torch.min(_pts_y, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_w = _bbox_right - _bbox_left  # (b, 1, h_pts, w_pts)\n        _bbox_h = _bbox_bottom - _bbox_up  # (b, 1, h_pts, w_pts)\n\n        normalized_x = (_pts_x - _bbox_left) / (_bbox_w + 1e-6)  # (b, n, h_pts, w_pts)\n        normalized_y = (_pts_y - _bbox_up) / (_bbox_h + 1e-6)  # (b, n, h_pts, w_pts)\n        normalized_pts = torch.stack([normalized_x, normalized_y], dim=2)  # (b, n, 2, h_pts, w_pts)\n        return normalized_pts\n\n    def grid_position_sensitive_group_partition(self, pts, num_group):\n        \"\"\"\n        Position-sensitive group partition based on grids.\n\n            Args:\n                pts(Tensor): input points, size (b, n, 2, h_pts, w_pts)\n                num_group(int): the number of groups\n\n            Returs:\n                Tensor: group_inds, size (b, 1, n, h_pts, w_pts)\n        \"\"\"\n        normalized_pts = self.normalize_pts_within_bboxes(pts)  # (b, n, 2, h_pts, w_pts)\n        normalized_x = normalized_pts[:, :, 0, :, :]  # (b, n, h_pts, w_pts)\n        normalized_y = normalized_pts[:, :, 1, :, :]  # (b, n, h_pts, w_pts)\n\n        num_group_kernel = int(np.sqrt(num_group))\n        grid_x_inds = (normalized_x * num_group_kernel).long()  # (b, n, h_pts, w_pts)\n        grid_y_inds = (normalized_y * num_group_kernel).long()  # (b, n, h_pts, w_pts)\n        group_inds = grid_y_inds * num_group_kernel + grid_x_inds  # (b, n, h_pts, w_pts)\n        group_inds = group_inds.unsqueeze(1).float()  # (b, 1, n, h_pts, w_pts)\n        return group_inds\n\n    def get_points(self, featmap_sizes, img_metas):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n\n        Returns:\n            tuple: points of each image, valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # points center for one time\n        multi_level_points = []\n        for i in range(num_levels):\n            points = self.point_generators[i].grid_points(\n                featmap_sizes[i], self.point_strides[i])\n            multi_level_points.append(points)\n        points_list = [[point.clone() for point in multi_level_points] for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level grids\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            for i in range(num_levels):\n                point_stride = self.point_strides[i]\n                feat_h, feat_w = featmap_sizes[i]\n                h, w = img_meta['pad_shape'][:2]\n                valid_feat_h = min(int(np.ceil(h / point_stride)), feat_h)\n                valid_feat_w = min(int(np.ceil(w / point_stride)), feat_w)\n                flags = self.point_generators[i].valid_flags(\n                    (feat_h, feat_w), (valid_feat_h, valid_feat_w))\n                multi_level_flags.append(flags)\n            valid_flag_list.append(multi_level_flags)\n\n        return points_list, valid_flag_list\n\n    def offset_to_pts(self, center_list, pred_list):\n        \"\"\"Change from point offset to point coordinate.\"\"\"\n        pts_list = []\n        for i_lvl in range(len(self.point_strides)):\n            pts_lvl = []\n            for i_img in range(len(center_list)):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                xy_pts_shift = pts_shift.permute(1, 2, 0).view( -1, 2 * self.num_points)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_lvl.append(pts)\n            pts_lvl = torch.stack(pts_lvl, 0)\n            pts_list.append(pts_lvl)\n        return pts_list\n\n    # pts_to_img_lvl\n    def offset_to_pts_img_lvl(self, center_list, pred_list):\n        \"\"\"\n        Project points offset based on center point to image scale and organized in image-level order\n\n            Args:\n                center_list(list(Tensor)): Multi image center list with different level\n                pred_list: Multi image pred points offset with different level\n            Returns:\n                list(Tensor): multi-image points in image scale with different level\n        \"\"\"\n        pts_list = []\n        for i_img, point in enumerate(center_list):\n            pts_img = []\n            for i_lvl in range(len(center_list[0])):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                xy_pts_shift = pts_shift.permute(1, 2, 0).view(-1, 2 * self.num_points)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_img.append(pts)\n            pts_list.append(pts_img)\n        return pts_list\n\n    def _dense_point_target_single(self,\n                                   flat_proposals,\n                                   flat_proposals_pts,\n                                   valid_flags,\n                                   num_level_proposals,\n                                   gt_bboxes,\n                                   gt_bboxes_ignore,\n                                   gt_masks,\n                                   gt_labels,\n                                   num_pts,\n                                   label_channels=1,\n                                   stage='init',\n                                   unmap_outputs=True):\n        inside_flags = valid_flags\n        if not inside_flags.any():\n            return (None, ) * 9\n        # assign gt and sample proposals\n        proposals = flat_proposals[inside_flags, :]\n        proposals_pts = flat_proposals_pts[inside_flags, :]\n\n        num_level_proposals_inside = self.get_num_level_proposals_inside(num_level_proposals, inside_flags)\n        if stage == 'init':\n            assigner = self.init_assigner\n            assigner_type = self.train_cfg.init.assigner.type\n            pos_weight = self.train_cfg.init.pos_weight\n        else:\n            assigner = self.refine_assigner\n            assigner_type = self.train_cfg.refine.assigner.type\n            pos_weight = self.train_cfg.refine.pos_weight\n        if assigner_type != \"ATSSAssigner\":\n            assign_result = assigner.assign(proposals, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        else:\n            assign_result = assigner.assign(proposals, num_level_proposals_inside, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        sampling_result = self.sampler.sample(assign_result, proposals, gt_bboxes)\n\n        gt_ind = sampling_result.pos_assigned_gt_inds.cpu().numpy()\n        gt_pts_numpy = distance_sample_pts(gt_bboxes, gt_masks, self.train_cfg.get(stage), num_pts)\n\n        pts_label_list = []\n        proposals_pos_pts = proposals_pts[sampling_result.pos_inds, :].detach().cpu().numpy().round().astype(np.long)\n        for i in range(len(gt_ind)):\n            gt_mask = gt_masks.masks[gt_ind[i]]\n            h, w = gt_mask.shape\n            pts_long = proposals_pos_pts[i]\n            _pts_label = gt_mask[pts_long[1::2].clip(0, h - 1), pts_long[0::2].clip(0, w - 1)]\n            pts_label_list.append(_pts_label)\n        del proposals_pos_pts\n\n        if len(gt_ind) != 0:\n            gt_pts = gt_bboxes.new_tensor(gt_pts_numpy)\n            pos_gt_pts = gt_pts[gt_ind]\n            pts_label = np.stack(pts_label_list, 0)\n            pos_gt_pts_label = gt_bboxes.new_tensor(pts_label)\n        else:\n            pos_gt_pts = None\n            pos_gt_pts_label = None\n\n        num_valid_proposals = proposals.shape[0]\n        bbox_gt = proposals.new_zeros([num_valid_proposals, 4])\n        bbox_weights = proposals.new_zeros([num_valid_proposals, 4])\n        mask_gt = proposals.new_zeros([0, num_pts * 2])\n        mask_gt_label = proposals.new_zeros([0, int(flat_proposals_pts.shape[1] / 2)]).long()\n        mask_gt_index = proposals.new_zeros([num_valid_proposals, ], dtype=torch.long)\n        labels = proposals.new_full((num_valid_proposals, ), self.background_label, dtype=torch.long)\n        label_weights = proposals.new_zeros(num_valid_proposals, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_gt_bboxes = sampling_result.pos_gt_bboxes\n            bbox_gt[pos_inds, :] = pos_gt_bboxes\n            bbox_weights[pos_inds, :] = 1.0\n            if pos_gt_pts is not None:\n                mask_gt = pos_gt_pts.type(bbox_gt.type())\n                mask_gt_index[pos_inds] = torch.arange(len(pos_inds)).long().cuda() + 1\n            if pos_gt_pts_label is not None:\n                mask_gt_label = pos_gt_pts_label.long()\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]\n            if pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of proposals\n        if unmap_outputs:\n            num_total_proposals = flat_proposals.size(0)\n            labels = unmap(labels, num_total_proposals, inside_flags)\n            label_weights = unmap(label_weights, num_total_proposals, inside_flags)\n            bbox_gt = unmap(bbox_gt, num_total_proposals, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_proposals, inside_flags)\n            mask_gt_index = unmap(mask_gt_index, num_total_proposals, inside_flags)\n\n        return labels, label_weights, bbox_gt, bbox_weights, mask_gt_index, mask_gt, mask_gt_label, pos_inds, neg_inds\n\n    def get_targets(self,\n                    proposals_list,\n                    proposals_pts_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    gt_masks_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    num_pts=729,\n                    stage='init',\n                    label_channels=1,\n                    unmap_outputs=True):\n        \"\"\"Compute corresponding GT box and classification targets for\n        proposals.\n\n        Args:\n            proposals_list (list[list]): Multi level points/bboxes of each\n                image.\n            valid_flag_list (list[list]): Multi level valid flags of each\n                image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            gt_bboxes_ignore_list (list[Tensor]): Ground truth bboxes to be\n                ignored.\n            gt_bboxes_list (list[Tensor]): Ground truth labels of each box.\n            stage (str): `init` or `refine`. Generate target for init stage or\n                refine stage\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple:\n                - labels_list (list[Tensor]): Labels of each level.\n                - label_weights_list (list[Tensor]): Label weights of each level.  # noqa: E501\n                - bbox_gt_list (list[Tensor]): Ground truth bbox of each level.\n                - proposal_list (list[Tensor]): Proposals(points/bboxes) of each level.  # noqa: E501\n                - proposal_weights_list (list[Tensor]): Proposal weights of each level.  # noqa: E501\n                - num_total_pos (int): Number of positive samples in all images.  # noqa: E501\n                - num_total_neg (int): Number of negative samples in all images.  # noqa: E501\n        \"\"\"\n        assert stage in ['init', 'refine']\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n        num_level_proposals_list = [num_level_proposals] * num_imgs\n\n        # concat all level points and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n            proposals_pts_list[i] = torch.cat(proposals_pts_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_labels, all_label_weights, all_bbox_gt, all_bbox_weights,\n         all_mask_gt_index, all_mask_gt, all_mask_gt_label,\n         pos_inds_list, neg_inds_list) = multi_apply(\n             self._dense_point_target_single,\n             proposals_list,\n             proposals_pts_list,\n             valid_flag_list,\n             num_level_proposals_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             gt_masks_list,\n             gt_labels_list,\n             num_pts=num_pts,\n             stage=stage,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        labels_list = images_to_levels(all_labels, num_level_proposals)\n        label_weights_list = images_to_levels(all_label_weights, num_level_proposals)\n        bbox_gt_list = images_to_levels(all_bbox_gt, num_level_proposals)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                                 num_level_proposals)\n        mask_gt_index_list = images_to_levels(all_mask_gt_index, num_level_proposals)\n        mask_gt_list = mask_to_levels(all_mask_gt, mask_gt_index_list)\n        mask_gt_label_list = mask_to_levels(all_mask_gt_label, mask_gt_index_list)\n\n        return (labels_list, label_weights_list, bbox_gt_list, bbox_weights_list,\n                mask_gt_list, mask_gt_label_list,\n                num_total_pos, num_total_neg)\n\n    def loss_single(self, cls_score, pts_pred_init, pts_pred_refine, pts_score_pred_init,\n                    labels, label_weights,\n                    bbox_gt_init, pts_gt_init, bbox_weights_init,\n                    bbox_gt_refine, pts_gt_refine, pts_score_gt_label, bbox_weights_refine,\n                    stride, num_total_samples_init, num_total_samples_refine):\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n        cls_score = cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n        loss_cls = self.loss_cls(\n            cls_score, labels, label_weights, avg_factor=num_total_samples_refine)\n\n        # bbox loss\n        bbox_gt_init = bbox_gt_init.reshape(-1, 4)\n        bbox_weights_init = bbox_weights_init.reshape(-1, 4)\n        bbox_pred_init = self.points2bbox(pts_pred_init.reshape(-1, 2 * self.num_points))\n        bbox_gt_refine = bbox_gt_refine.reshape(-1, 4)\n        bbox_weights_refine = bbox_weights_refine.reshape(-1, 4)\n        bbox_pred_refine = self.points2bbox(pts_pred_refine.reshape(-1, 2 * self.num_points))\n        normalize_term = self.point_base_scale * stride\n\n        loss_bbox_init = self.loss_bbox_init(\n            bbox_pred_init / normalize_term,\n            bbox_gt_init / normalize_term,\n            bbox_weights_init,\n            avg_factor=num_total_samples_init)\n        loss_bbox_refine = self.loss_bbox_refine(\n            bbox_pred_refine / normalize_term,\n            bbox_gt_refine / normalize_term,\n            bbox_weights_refine,\n            avg_factor=num_total_samples_refine)\n\n        # pts_loss_init\n        valid_pts_gt_init = torch.cat(pts_gt_init, 0)\n        valid_pts_gt_init = valid_pts_gt_init.view(-1, self.num_points, 2)\n        mask_pred_init = pts_pred_init.reshape(-1, 2 * self.num_points)\n        valid_pts_pred_init = mask_pred_init[bbox_weights_init[:, 0] > 0]\n        valid_pts_pred_init = valid_pts_pred_init.view(-1, self.num_points, 2)\n        valid_pts = valid_pts_gt_init.sum(-1).sum(-1) > 0\n        num_total_samples = max(num_total_samples_init, 1)\n        loss_pts_init = self.loss_pts_init(\n            valid_pts_gt_init[valid_pts] / normalize_term,\n            valid_pts_pred_init[valid_pts] / normalize_term).sum() / num_total_samples\n        # pts_loss_refine\n        valid_pts_gt_refine = torch.cat(pts_gt_refine, 0)\n        valid_pts_gt_refine = valid_pts_gt_refine.view(-1, self.num_points, 2)\n        pts_pred_refine = pts_pred_refine.reshape(-1, 2 * self.num_points)\n        valid_pts_pred_refine = pts_pred_refine[bbox_weights_refine[:, 0] > 0]\n        valid_pts_pred_refine = valid_pts_pred_refine.view(-1, self.num_points, 2)\n        valid_pts = valid_pts_gt_refine.sum(-1).sum(-1) > 0\n        num_total_samples = max(num_total_samples_refine, 1)\n        loss_pts_refine = self.loss_pts_refine(\n            valid_pts_gt_refine[valid_pts] / normalize_term,\n            valid_pts_pred_refine[valid_pts] / normalize_term).sum() / num_total_samples\n        # mask score loss\n        valid_pts_score_gt_label = torch.cat(pts_score_gt_label, 0)\n        valid_pts_score_gt_label = valid_pts_score_gt_label.view(-1, self.num_points, 1)\n        pts_score_pred_init = pts_score_pred_init.reshape(-1, self.num_points)\n        valid_pts_score_pred_init = pts_score_pred_init[bbox_weights_refine[:, 0] > 0]\n        valid_pts_score_pred_init = valid_pts_score_pred_init.view(-1, self.num_points, 1)\n        valid_pts_score_inds = (valid_pts_score_gt_label.sum(-1).sum(-1) > 0)\n        num_total_samples = max(num_total_samples_refine, 1)\n        loss_mask_score_init = self.loss_mask_score_init(\n            valid_pts_score_pred_init[valid_pts_score_inds],\n            valid_pts_score_gt_label[valid_pts_score_inds],\n            weight=bbox_weights_init.new_ones(*valid_pts_score_pred_init[valid_pts_score_inds].shape),\n            avg_factor=num_total_samples\n        ) / self.num_points\n\n        return loss_cls, loss_bbox_init, loss_pts_init, loss_bbox_refine, loss_pts_refine, loss_mask_score_init\n\n    def loss(self,\n             cls_scores,\n             pts_preds_init,\n             pts_preds_refine,\n             pts_preds_score_init,\n             gt_bboxes,\n             gt_masks,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.point_generators)\n        label_channels = self.cls_out_channels\n\n        # target for initial stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        real_pts_preds_init = self.offset_to_pts(center_list, pts_preds_init)\n        proposal_pts_list = self.offset_to_pts_img_lvl(center_list, pts_preds_init)\n        real_pts_preds_score_init = []\n        for lvl_pts_score in pts_preds_score_init:\n            b = lvl_pts_score.shape[0]\n            real_pts_preds_score_init.append(lvl_pts_score.permute(0, 2, 3, 1).view(b, -1, self.num_points))\n\n        cls_reg_targets_init = self.get_targets(\n            center_list,\n            proposal_pts_list,\n            valid_flag_list,\n            gt_bboxes,\n            gt_masks,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            num_pts=self.num_points,\n            stage='init',\n            label_channels=label_channels)\n        (*_, bbox_gt_list_init, bbox_weights_list_init, pts_gt_list_init, _,\n         num_total_pos_init, num_total_neg_init) = cls_reg_targets_init\n\n        # target for refinement stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        real_pts_preds_refine = self.offset_to_pts(center_list, pts_preds_refine)\n        bbox_pts_list = self.offset_to_pts_img_lvl(center_list, pts_preds_init)\n\n        bbox_list = []\n        for i_img, center in enumerate(center_list):\n            bbox = []\n            for i_lvl in range(len(pts_preds_refine)):\n                bbox_preds_init = self.points2bbox(pts_preds_init[i_lvl].detach())\n                bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                bbox_center = torch.cat([center[i_lvl][:, :2], center[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n            bbox_list.append(bbox)\n        cls_reg_targets_refine = self.get_targets(\n            bbox_list,\n            bbox_pts_list,\n            valid_flag_list,\n            gt_bboxes,\n            gt_masks,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            num_pts=self.num_points,\n            stage='refine',\n            label_channels=label_channels)\n        (labels_list, label_weights_list,\n         bbox_gt_list_refine, bbox_weights_list_refine, pts_gt_list_refine, pts_score_gt_label_list,\n         num_total_pos_refine, num_total_neg_refine) = cls_reg_targets_refine\n\n        # compute loss\n        losses_cls, losses_bbox_init, losses_pts_init, losses_bbox_refine, losses_pts_refine, losses_mask_score_init = multi_apply(\n            self.loss_single,\n            cls_scores,\n            real_pts_preds_init,\n            real_pts_preds_refine,\n            real_pts_preds_score_init,\n            labels_list,\n            label_weights_list,\n            bbox_gt_list_init,\n            pts_gt_list_init,\n            bbox_weights_list_init,\n            bbox_gt_list_refine,\n            pts_gt_list_refine,\n            pts_score_gt_label_list,\n            bbox_weights_list_refine,\n            self.point_strides,\n            num_total_samples_init=num_total_pos_init,\n            num_total_samples_refine=num_total_pos_refine)\n\n        loss_dict_all = {'loss_cls': losses_cls,\n                         'loss_bbox_init': losses_bbox_init,\n                         'losses_pts_init': losses_pts_init,\n                         'losses_bbox_refine': losses_bbox_refine,\n                         'losses_pts_refine': losses_pts_refine,\n                         'losses_mask_score_init': losses_mask_score_init,\n                         }\n        return loss_dict_all\n\n    def get_bboxes(self,\n                   cls_scores,\n                   pts_preds_init,\n                   pts_preds_refine,\n                   pts_preds_score_refine,\n                   img_metas,\n                   cfg=None,\n                   rescale=False,\n                   nms=True):\n        assert len(cls_scores) == len(pts_preds_refine)\n        bbox_preds_refine = [self.points2bbox(pts_pred_refine) for pts_pred_refine in pts_preds_refine]\n        num_levels = len(cls_scores)\n        mlvl_points = [\n            self.point_generators[i].grid_points(cls_scores[i].size()[-2:],\n                                                 self.point_strides[i])\n            for i in range(num_levels)\n        ]\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds_refine[i][img_id].detach() for i in range(num_levels)\n            ]\n            pts_pred_list = [\n                pts_preds_refine[i][img_id].detach() for i in range(num_levels)\n            ]\n            mask_pred_list = [\n                pts_preds_score_refine[i][img_id].sigmoid().detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list, pts_pred_list, mask_pred_list,\n                                                mlvl_points, img_shape,\n                                                scale_factor, cfg, rescale,\n                                                nms)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           pts_preds,\n                           mask_preds,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False,\n                           nms=True):\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_points)\n        mlvl_pts = []\n        mlvl_bboxes = []\n        mlvl_scores = []\n        mlvl_masks = []\n        for i_lvl, (cls_score, bbox_pred, pts_pred, mask_pred, points) in enumerate(zip(cls_scores, bbox_preds, pts_preds, mask_preds, mlvl_points)):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(-1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            pts_pred = pts_pred.permute(1, 2, 0).reshape(-1, 2 * self.num_points)\n            mask_pred = mask_pred.permute(1, 2, 0).reshape(-1, self.num_points)\n\n            # mask scoring\n            mask_sum = (mask_pred > 0.5).sum(1).float()\n            mask_score = ((mask_pred > 0.5).float() * mask_pred).sum(1) / (mask_sum + 1e-6)\n            scores = scores * mask_score.unsqueeze(1)\n\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                pts_pred = pts_pred[topk_inds, :]\n                mask_pred = mask_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n\n            pts_pos_center = points[:, :2].repeat(1, self.num_points)\n            pts = pts_pred * self.point_strides[i_lvl] + pts_pos_center\n            pts[:, 0::2] = pts[:, 0::2].clamp(min=0, max=img_shape[1] - 1)\n            pts[:, 1::2] = pts[:, 1::2].clamp(min=0, max=img_shape[0] - 1)\n\n            bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1)\n            bboxes = bbox_pred * self.point_strides[i_lvl] + bbox_pos_center\n            x1 = bboxes[:, 0].clamp(min=0, max=img_shape[1])\n            y1 = bboxes[:, 1].clamp(min=0, max=img_shape[0])\n            x2 = bboxes[:, 2].clamp(min=0, max=img_shape[1])\n            y2 = bboxes[:, 3].clamp(min=0, max=img_shape[0])\n            bboxes = torch.stack([x1, y1, x2, y2], dim=-1)\n\n            mlvl_pts.append(pts)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n            mlvl_masks.append(mask_pred)\n        mlvl_pts = torch.cat(mlvl_pts)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n            mlvl_pts /= mlvl_pts.new_tensor(scale_factor[:2]).repeat(mlvl_pts.shape[1] // 2)\n        mlvl_scores = torch.cat(mlvl_scores)\n        mlvl_masks = torch.cat(mlvl_masks)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        if nms:\n            det_bboxes, det_pts, det_masks, det_labels = multiclass_nms_pts(\n                mlvl_bboxes, mlvl_pts, mlvl_scores, mlvl_masks, cfg.score_thr, cfg.nms, cfg.max_per_img)\n            return det_bboxes, det_pts, det_masks, det_labels\n        else:\n            return mlvl_bboxes, mlvl_scores\n\n    def get_num_level_proposals_inside(self, num_level_proposals, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_proposals)\n        num_level_proposals_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_proposals_inside\n\n\ndef mask_to_levels(target, mask_index_list):\n    \"\"\"\n    Convert target by mask_index_list\n    \"\"\"\n    target_gt_list = []\n    for lvl in range(len(mask_index_list)):\n        mask_gt_lvl_list = []\n        for i in range(mask_index_list[lvl].shape[0]):\n            index = mask_index_list[lvl][i]\n            index = index[index > 0]\n            mask_gt_lvl = target[i][index - 1]\n            mask_gt_lvl_list.append(mask_gt_lvl)\n        target_gt_list.append(mask_gt_lvl_list)\n    return target_gt_list\n\n\ndef mask_to_poly(mask):\n    contours, _ = cv2.findContours(mask.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n    polygons = []\n    for contour in contours:\n        contour = contour.flatten().tolist()\n        if len(contour) > 4:\n            polygons.append(contour)\n    return polygons\n\n\ndef distance_sample_pts(gt_bboxes, gt_masks, cfg, num_pts):\n    \"\"\"\n    Sample pts based on distance transformation map.\n\n    Args:\n        gt_bboxes(list(Tensor)): groud-truth bounding box\n        gt_masks(list(Mask)): ground-truth mask\n        cfg(dict): sampling config\n        num_pts(int): number of points\n\n    Returns:\n        numpy: the sampling points based on distance transform map\n    \"\"\"\n    dist_sample_thr = cfg.get('dist_sample_thr', 2)\n    pts_list = []\n    pts_label_list = []\n    for i in range(len(gt_bboxes)):\n        x1, y1, x2, y2 = gt_bboxes[i].cpu().numpy().astype(np.int32)\n        w = np.maximum(x2 - x1, 1)\n        h = np.maximum(y2 - y1, 1)\n        mask = mmcv.imresize(gt_masks.masks[i][y1:y1 + h, x1:x1 + w],\n                             (cfg.get('mask_size', 56), cfg.get('mask_size', 56)))\n        polygons = mask_to_poly(mask)\n        distance_map = np.ones(mask.shape).astype(np.uint8)\n        for poly in polygons:\n            poly = np.array(poly).astype(np.int)\n            for j in range(len(poly) // 2):\n                x_0, y_0 = poly[2 * j:2 * j + 2]\n                if j == len(poly) // 2 - 1:\n                    x_1, y_1 = poly[0:2]\n                else:\n                    x_1, y_1 = poly[2 * j + 2:2 * j + 4]\n                cv2.line(distance_map, (x_0, y_0), (x_1, y_1), 0, thickness=2)\n        roi_dist_map = cv2.distanceTransform(distance_map, cv2.DIST_L2, 3)\n        con_index = np.stack(np.nonzero(roi_dist_map == 0)[::-1], axis=-1)\n        roi_dist_map[roi_dist_map == 0] = 1\n        roi_dist_map[roi_dist_map > dist_sample_thr] = 0\n\n        index_y, index_x = np.nonzero(roi_dist_map > 0)\n        index = np.stack([index_x, index_y], axis=-1)\n        _len = index.shape[0]\n        if len(con_index) == 0:\n            pts = np.zeros([2 * num_pts])\n        else:\n            repeat = num_pts // _len\n            mod = num_pts % _len\n            perm = np.random.choice(_len, mod, replace=False)\n            draw = [index.copy() for i in range(repeat)]\n            draw.append(index[perm])\n            draw = np.concatenate(draw, 0)\n            draw = np.random.permutation(draw)\n            draw = draw + np.random.rand(*draw.shape)\n            x_scale = float(w) / cfg.get('mask_size', 56)\n            y_scale = float(h) / cfg.get('mask_size', 56)\n            draw[:, 0] = draw[:, 0] * x_scale + x1\n            draw[:, 1] = draw[:, 1] * y_scale + y1\n            pts = draw.reshape(2 * num_pts)\n\n        pts_list.append(pts)\n        pts_long = pts.astype(np.long)\n        pts_label = gt_masks.masks[i][pts_long[1::2], pts_long[0::2]]\n        pts_label_list.append(pts_label)\n    pts_list = np.stack(pts_list, 0)\n    return pts_list"
  },
  {
    "path": "code/mmdet/models/dense_heads/dense_reppoints_v2_head.py",
    "content": "import numpy as np\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nimport mmcv\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (PointGenerator, build_assigner, build_sampler,\n                        images_to_levels, multi_apply, multiclass_nms_pts_refine, unmap)\nfrom mmdet.ops import DeformConv\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\n\n@HEADS.register_module()\nclass DenseRepPointsV2Head(AnchorFreeHead):\n    \"\"\"RepPoint head.\n\n    Args:\n        point_feat_channels (int): Number of channels of points features.\n        gradient_mul (float): The multiplier to gradients from\n            points refinement and recognition.\n        point_strides (Iterable): points strides.\n        point_base_scale (int): bbox scale for assigning labels.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox_init (dict): Config of initial points loss.\n        loss_bbox_refine (dict): Config of points loss in refinement.\n        use_grid_points (bool): If we use bounding box representation, the\n        reppoints is represented as grid points on the bounding box.\n        center_init (bool): Whether to use center point assignment.\n        transform_method (str): The methods to transform RepPoints to bbox.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 point_feat_channels=256,\n                 stacked_mask_convs=3,\n                 shared_stacked_convs=1,\n                 num_group=9,\n                 num_points=729,\n                 num_score_group=121,\n                 gradient_mul=0.1,\n                 point_strides=[8, 16, 32, 64, 128],\n                 point_base_scale=4,\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox_init=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=0.5),\n                 loss_bbox_refine=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_pts_init=dict(type='ChamferLoss2D', use_cuda=True, loss_weight=0.5, eps=1e-12),\n                 loss_pts_refine=dict(type='ChamferLoss2D', use_cuda=True, loss_weight=1.0, eps=1e-12),\n                 loss_mask_score_init=dict(type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n                 loss_ct_heatmap=dict(type='FocalLoss', use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=1.0),\n                 loss_ct_offset=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_sem=dict(type='FocalLoss', use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=0.1),\n                 transform_method='minmax',\n                 sample_padding_mode='border',\n                 fuse_mask_feat=False,\n                 **kwargs):\n        self.num_group = num_group\n        self.num_points = num_points\n        self.num_score_group = num_score_group\n        self.point_feat_channels = point_feat_channels\n        self.stacked_mask_convs = stacked_mask_convs\n        self.shared_stacked_convs = shared_stacked_convs\n        self.fuse_mask_feat = fuse_mask_feat\n        self.sample_padding_mode = sample_padding_mode\n\n        # we use deformable conv to extract points features\n        self.dcn_kernel = int(np.sqrt(num_points))\n        self.dcn_pad = int((self.dcn_kernel - 1) / 2)\n        assert self.dcn_kernel * self.dcn_kernel == num_points, \\\n            'The points number should be a square number.'\n        assert self.dcn_kernel % 2 == 1, \\\n            'The points number should be an odd square number.'\n        dcn_base = np.arange(-self.dcn_pad,\n                             self.dcn_pad + 1).astype(np.float64)\n        dcn_base_y = np.repeat(dcn_base, self.dcn_kernel)\n        dcn_base_x = np.tile(dcn_base, self.dcn_kernel)\n        dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape(\n            (-1))\n        self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1)\n\n        super().__init__(num_classes, in_channels, loss_cls=loss_cls, **kwargs)\n\n        self.gradient_mul = gradient_mul\n        self.point_base_scale = point_base_scale\n        self.point_strides = point_strides\n        self.point_generators = [PointGenerator() for _ in self.point_strides]\n\n        if self.train_cfg:\n            self.init_assigner = build_assigner(self.train_cfg.init.assigner)\n            self.refine_assigner = build_assigner(self.train_cfg.refine.assigner)\n            self.cont_assigner = build_assigner(self.train_cfg.contour.assigner)\n            # use PseudoSampler when sampling is False\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.transform_method = transform_method\n\n\n        self.cls_out_channels = self.num_classes\n        self.loss_bbox_init = build_loss(loss_bbox_init)\n        self.loss_bbox_refine = build_loss(loss_bbox_refine)\n        self.loss_pts_init = build_loss(loss_pts_init)\n        self.loss_pts_refine = build_loss(loss_pts_refine)\n        self.loss_mask_score_init = build_loss(loss_mask_score_init)\n        self.loss_ct_heatmap = build_loss(loss_ct_heatmap)\n        self.loss_ct_offset = build_loss(loss_ct_offset)\n        self.loss_sem = build_loss(loss_sem)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        self.mask_convs = nn.ModuleList()\n        self.shared_convs = nn.ModuleList()\n\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        for i in range(self.stacked_mask_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.mask_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        for i in range(self.shared_stacked_convs):\n            self.shared_convs.append(\n                ConvModule(\n                    self.feat_channels,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n\n        pts_out_dim = 2 * self.num_points\n\n        cls_in_channels = self.feat_channels + 3\n        self.reppoints_cls_conv = nn.Conv2d(cls_in_channels * self.num_group, self.point_feat_channels, 1, 1, 0)\n        self.reppoints_cls_out = nn.Conv2d(self.point_feat_channels, self.cls_out_channels, 1, 1, 0)\n\n        self.reppoints_pts_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        self.reppoints_pts_init_out = nn.Conv2d(self.point_feat_channels, pts_out_dim, 1, 1, 0)\n\n        pts_in_channels = self.feat_channels + 3\n\n        self.reppoints_pts_refine_conv = nn.Conv2d(pts_in_channels, self.point_feat_channels, 3, 1, 1)\n        self.reppoints_pts_refine_out = nn.Conv2d(self.point_feat_channels, pts_out_dim, 1, 1, 0)\n\n        self.reppoints_mask_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        self.reppoints_mask_init_out = nn.Conv2d(self.point_feat_channels, self.num_score_group, 1, 1, 0)\n\n        if self.fuse_mask_feat:\n            self.mask_fuse_conv = nn.Conv2d(self.feat_channels, self.feat_channels, 3, 1, 1)\n\n        self.reppoints_cont_score_out = nn.Conv2d(self.feat_channels, 1, 3, 1, 1)\n        self.reppoints_cont_offset_out = nn.Conv2d(self.feat_channels, 2, 3, 1, 1)\n\n        self.reppoints_sem_out = nn.Conv2d(self.feat_channels, self.cls_out_channels, 1, 1, 0)\n        self.reppoints_sem_embedding = ConvModule(\n            self.feat_channels,\n            self.feat_channels,\n            1,\n            conv_cfg=self.conv_cfg,\n            norm_cfg=self.norm_cfg)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.mask_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.shared_convs:\n            normal_init(m.conv, std=0.01)\n        if self.fuse_mask_feat:\n            normal_init(self.mask_fuse_conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.reppoints_cls_conv, std=0.01)\n        normal_init(self.reppoints_cls_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_pts_init_conv, std=0.01)\n        normal_init(self.reppoints_pts_init_out, std=0.01)\n        normal_init(self.reppoints_pts_refine_conv, std=0.01)\n        normal_init(self.reppoints_pts_refine_out, std=0.01)\n        normal_init(self.reppoints_mask_init_conv, std=0.01)\n        normal_init(self.reppoints_mask_init_out, std=0.01)\n        normal_init(self.reppoints_cont_score_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_cont_offset_out, std=0.01)\n        normal_init(self.reppoints_sem_out, std=0.01, bias=bias_cls)\n\n    def points2bbox(self, pts):\n        \"\"\"Converting the points set into bounding box.\n\n        :param pts: the input points sets (fields), each points\n            set (fields) is represented as 2n scalar.\n        :param y_first: if y_fisrt=True, the point set is represented as\n            [y1, x1, y2, x2 ... yn, xn], otherwise the point set is\n            represented as [x1, y1, x2, y2 ... xn, yn].\n        :return: each points set is converting to a bbox [x1, y1, x2, y2].\n        \"\"\"\n        pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n        pts_x = pts_reshape[:, :, 0, ...]\n        pts_y = pts_reshape[:, :, 1, ...]\n        if self.transform_method == 'minmax':\n            bbox_left = pts_x.min(dim=1, keepdim=True)[0]\n            bbox_right = pts_x.max(dim=1, keepdim=True)[0]\n            bbox_up = pts_y.min(dim=1, keepdim=True)[0]\n            bbox_bottom = pts_y.max(dim=1, keepdim=True)[0]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n        else:\n            raise NotImplementedError\n        return bbox\n\n    def sample_offset(self, x, flow, padding_mode):\n        \"\"\"\n        sample feature based on offset\n\n            Args:\n                x (Tensor): input feature, size (n, c, h, w)\n                flow (Tensor): flow fields, size(n, 2, h', w')\n                padding_mode (str): grid sample padding mode, 'zeros' or 'border'\n            Returns:\n                Tensor: warped feature map (n, c, h', w')\n        \"\"\"\n        # assert x.size()[-2:] == flow.size()[-2:]\n        n, _, h, w = flow.size()\n        x_ = torch.arange(w).view(1, -1).expand(h, -1)\n        y_ = torch.arange(h).view(-1, 1).expand(-1, w)\n        grid = torch.stack([x_, y_], dim=0).float().cuda()\n        grid = grid.unsqueeze(0).expand(n, -1, -1, -1)\n        grid = grid + flow\n        gx = 2 * grid[:, 0, :, :] / (w - 1) - 1\n        gy = 2 * grid[:, 1, :, :] / (h - 1) - 1\n        grid = torch.stack([gx, gy], dim=1)\n        grid = grid.permute(0, 2, 3, 1)\n        return F.grid_sample(x, grid, padding_mode=padding_mode, align_corners=True)\n\n    def compute_offset_feature(self, x, offset, padding_mode):\n        \"\"\"\n        sample feature based on offset\n\n            Args:\n                x (Tensor) : feature map, size (n, C, h, w),  x first\n                offset (Tensor) : offset, size (n, sample_pts*2, h, w), x first\n                padding_mode (str): 'zeros' or 'border' or 'relection'\n            Returns:\n                Tensor: the warped feature generated by the offset and the input feature map, size (n, sample_pts, C, h, w)\n        \"\"\"\n        offset_reshape = offset.view(offset.shape[0], -1, 2, offset.shape[2], offset.shape[3])  # (n, sample_pts, 2, h, w)\n        num_pts = offset_reshape.shape[1]\n        offset_reshape = offset_reshape.contiguous().view(-1, 2, offset.shape[2],\n                                                          offset.shape[3])  # (n*sample_pts, 2, h, w)\n        x_repeat = x.unsqueeze(1).repeat(1, num_pts, 1, 1, 1)  # (n, sample_pts, C, h, w)\n        x_repeat = x_repeat.view(-1, x_repeat.shape[2], x_repeat.shape[3], x_repeat.shape[4])  # (n*sample_pts, C, h, w)\n        sampled_feat = self.sample_offset(x_repeat, offset_reshape, padding_mode)  # (n*sample_pts, C, h, w)\n        sampled_feat = sampled_feat.view(-1, num_pts, sampled_feat.shape[1], sampled_feat.shape[2],\n                                         sampled_feat.shape[3])  # (n, sample_pts, C, h, w)\n        return sampled_feat\n\n    def sample_offset_3d(self, x, flow, padding_mode):\n        \"\"\"\n        sample feature based on 2D offset(x, y) + 1-D index(z)\n\n            Args:\n                x (Tensor): size (n, c, d', h', w')\n                flow (Tensor): size(n, 3, d, h, w)\n                padding_mode (str): 'zeros' or 'border'\n            Returns:\n                warped feature map generated by the offset and the input feature map, size(n, c, d, h, w)\n        \"\"\"\n        n, _, d, h, w = flow.size()\n        num_group = x.shape[2]\n        device = flow.get_device()\n        x_ = torch.arange(w, device=device).view(1, 1, -1).expand(d, h, -1).float()  # (d, h, w)\n        y_ = torch.arange(h, device=device).view(1, -1, 1).expand(d, -1, w).float()  # (d, h, w)\n        z_ = torch.zeros(d, h, w, device=device)  # (d, h, w)\n        grid = torch.stack([x_, y_, z_], dim=0).float()  # (3, d, h, w)\n        del x_, y_, z_\n        grid = grid.unsqueeze(0).expand(n, -1, -1, -1, -1)  # (n, 3, d, h, w)\n        grid = grid + flow  # (n, 3, d, h, w)\n        gx = 2 * grid[:, 0, :, :, :] / (w - 1) - 1  # (n, d, h, w)\n        gy = 2 * grid[:, 1, :, :, :] / (h - 1) - 1  # (n, d, h, w)\n        gz = 2 * grid[:, 2, :, :, :] / (num_group - 1) - 1  # (n, d, h, w)\n        grid = torch.stack([gx, gy, gz], dim=1)  # (n, 3, d, h, w)\n        del gx, gy, gz\n        grid = grid.permute(0, 2, 3, 4, 1)  # (n, d, h, w, 3)\n        return F.grid_sample(x, grid, padding_mode=padding_mode, align_corners=True)\n\n    def compute_offset_feature_5d(self, x, offset, padding_mode):\n        \"\"\"\n        sample 5D feature based on offset\n\n            Args:\n                x (Tensor) : input feature, size (n, C, d', h', w'), x first\n                offset (Tensor) : flow field, size (n, 3, sample_pts, h, w), x first\n                padding_mode (str): 'zeros' or 'border'\n            Returns:\n                Tensor: offset_feature, size (n, sample_pts, C, h, w)\n        \"\"\"\n        sampled_feat = self.sample_offset_3d(x, offset, padding_mode)  # (n, C, sample_pts, h, w)\n        sampled_feat = sampled_feat.transpose(1, 2)  # (n, sample_pts, C, h, w)\n        return sampled_feat\n\n    def forward(self, feats, test=False):\n        cls_out_list, pts_out_init_list, pts_out_refine_list, \\\n        cont_score_out_list, cont_offset_out_list, sem_scores_out_list, \\\n        sem_feat_list = multi_apply(self.forward_pts_head_single, feats)\n        if test:\n            pts_out_list = pts_out_refine_list\n        else:\n            pts_out_list = [(1 - self.gradient_mul) * pts_out_init.detach()\n                            + self.gradient_mul * pts_out_init for pts_out_init in pts_out_init_list]\n\n        pts_score_out = self.forward_mask_head(feats, pts_out_list, sem_feat_list=sem_feat_list)\n        return cls_out_list, pts_out_init_list, pts_out_refine_list, pts_score_out, \\\n               cont_score_out_list, cont_offset_out_list, sem_scores_out_list\n\n    def forward_pts_head_single(self, x):\n        b, _, h, w = x.shape\n        dcn_base_offset = self.dcn_base_offset.type_as(x)\n        scale = self.point_base_scale / 2\n        points_init = dcn_base_offset / dcn_base_offset.max() * scale\n\n        cls_feat = x\n        pts_feat = x\n\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            pts_feat = reg_conv(pts_feat)\n\n        shared_feat = pts_feat\n        for shared_conv in self.shared_convs:\n            shared_feat = shared_conv(shared_feat)\n\n        sem_feat = shared_feat\n        cont_feat = shared_feat\n\n        sem_scores_out = self.reppoints_sem_out(sem_feat)\n        sem_feat = self.reppoints_sem_embedding(sem_feat)\n\n        cls_feat = cls_feat + sem_feat\n        pts_feat = pts_feat + sem_feat\n        cont_feat = cont_feat + sem_feat\n\n        # generate contours and offset\n        cont_score_out = self.reppoints_cont_score_out(cont_feat)\n        cont_offset_out = self.reppoints_cont_offset_out(cont_feat)\n\n        # generate points_init\n        pts_out_init = self.reppoints_pts_init_out(self.relu(self.reppoints_pts_init_conv(pts_feat)))\n        pts_out_init = pts_out_init + points_init  # (b, 2n, h, w)\n        pts_out_init_detach = (1 - self.gradient_mul) * pts_out_init.detach() + self.gradient_mul * pts_out_init\n\n        cont_feat = torch.cat([cont_score_out, cont_offset_out], dim=1)\n        cls_feat = torch.cat([cls_feat, cont_feat], dim=1)\n        pts_feat = torch.cat([pts_feat, cont_feat], dim=1)\n\n        # classify dense reppoints based on group pooling\n        cls_offset = pts_out_init_detach.view(b, self.num_group, -1, 2, h, w)\n        cls_offset = cls_offset[:, :, 0, ...].reshape(b, -1, h, w)\n\n        cls_pts_feature = self.compute_offset_feature(cls_feat, cls_offset, padding_mode=self.sample_padding_mode)\n        cls_pts_feature = cls_pts_feature.contiguous().view(b, -1, h, w)\n\n        cls_out = self.reppoints_cls_out(self.relu(self.reppoints_cls_conv(cls_pts_feature)))\n\n        # generate offset field\n        pts_refine_field = self.reppoints_pts_refine_out(self.relu(self.reppoints_pts_refine_conv(pts_feat)))  # (b, n*2, h, w)\n        pts_refine_field = pts_refine_field.view(b * self.num_points, -1, h, w)  # (b*n, 2, h, w)\n        pts_out_init_detach_reshape = pts_out_init_detach.view(b, -1, 2, h, w).view(-1, 2, h, w)  # (b*n, 2, h, w)\n        pts_out_refine = self.compute_offset_feature(pts_refine_field, pts_out_init_detach_reshape,padding_mode=self.sample_padding_mode)  # (b*n, 2, h, w)\n        pts_out_refine = pts_out_refine.view(b, -1, h, w)  # (b, n*2, h, w)\n        # generate points_refine\n        pts_out_refine = pts_out_refine + pts_out_init_detach\n\n        return cls_out, pts_out_init, pts_out_refine, cont_score_out, cont_offset_out, sem_scores_out, sem_feat\n\n    def forward_mask_head(self, mask_feat_list, pts_out_list, sem_feat_list):\n        for mask_conv in self.mask_convs:\n            mask_feat_list = [mask_conv(mask_feat) for mask_feat in mask_feat_list]\n        if self.fuse_mask_feat:\n            mask_feat_high_res = mask_feat_list[0] + sem_feat_list[0]\n            H, W = mask_feat_high_res.shape[-2:]\n            mask_feat_up_list = []\n            for lvl, mask_feat in enumerate(mask_feat_list):\n                mask_feat_up = mask_feat + sem_feat_list[lvl]\n                if lvl > 0:\n                    mask_feat_up = F.interpolate(\n                        mask_feat, size=(H, W), mode=\"bilinear\", align_corners=False\n                    )\n                    del mask_feat\n                mask_feat_up_list.append(\n                    self.mask_fuse_conv(mask_feat_up + mask_feat_high_res)\n                )\n                del mask_feat_up\n            del mask_feat_high_res\n            del mask_feat_list\n            mask_feat_list = mask_feat_up_list\n\n        pts_score_out = multi_apply(self.forward_mask_head_single, pts_out_list, mask_feat_list)[0]\n        return pts_score_out\n\n    def forward_mask_head_single(self, pts, mask_feat):\n        b, _, h, w = mask_feat.shape\n        h_pts, w_pts = pts.shape[-2:]\n        score_map = self.reppoints_mask_init_out(\n            self.relu(self.reppoints_mask_init_conv(mask_feat)))  # (b, G*1, h, w)\n        # position sensitive group partition based on grids\n        pts_reshape_detach = pts.detach().view(b, -1, 2, h_pts, w_pts)  # (b, n, 2, h_pts, w_pts)\n        group_inds = self.grid_position_sensitive_group_partition(\n            pts_reshape_detach, self.num_score_group)  # (b, 1, n, h_pts, w_pts)\n        del pts_reshape_detach\n        score_map = score_map.unsqueeze(1)  # (b, 1, G, h, w)\n\n        pts_reshape = pts.view(b, -1, 2, h_pts, w_pts).transpose(1, 2)  # (b, 2, n, h_pts, w_pts)\n        pts_reshape = pts_reshape.detach()\n        _pts_inds_cat = torch.cat([pts_reshape, group_inds], dim=1)  # (b, 3, n, h_pts, w_pts)\n        del group_inds, pts_reshape\n        # position sensitive sampling on score maps\n        pts_score_out = self.compute_offset_feature_5d(\n            score_map, _pts_inds_cat, padding_mode=self.sample_padding_mode)  # (b, n, 1, h_pts, w_pts)\n\n        pts_score_out = pts_score_out.view(b, -1, h_pts, w_pts)  # (b, n, h_pts, w_pts)\n        return pts_score_out, _\n\n    @staticmethod\n    def normalize_pts_within_bboxes(pts):\n        \"\"\"\n        Normalize pts offset within bboxes(instance level)\n\n            Args:\n                pts(Tensor): input points, size (b, n, 2, h_pts, w_pts)\n\n            Returns:\n                Tensor: normalized_pts, size (b, n, 2, h_pts, w_pts)\n        \"\"\"\n        b, _, _, h_pts, w_pts = pts.shape\n        _pts_x = pts[:, :, 0, :, :]  # (b, n, h_pts, w_pts)\n        _pts_y = pts[:, :, 1, :, :]  # (b, n, h_pts, w_pts)\n        _bbox_left = torch.min(_pts_x, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_right = torch.max(_pts_x, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_bottom = torch.max(_pts_y, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_up = torch.min(_pts_y, dim=1, keepdim=True)[0]  # (b, 1, h_pts, w_pts)\n        _bbox_w = _bbox_right - _bbox_left  # (b, 1, h_pts, w_pts)\n        _bbox_h = _bbox_bottom - _bbox_up  # (b, 1, h_pts, w_pts)\n\n        normalized_x = (_pts_x - _bbox_left) / (_bbox_w + 1e-6)  # (b, n, h_pts, w_pts)\n        normalized_y = (_pts_y - _bbox_up) / (_bbox_h + 1e-6)  # (b, n, h_pts, w_pts)\n        normalized_pts = torch.stack([normalized_x, normalized_y], dim=2)  # (b, n, 2, h_pts, w_pts)\n        return normalized_pts\n\n    def grid_position_sensitive_group_partition(self, pts, num_group):\n        \"\"\"\n        Position-sensitive group partition based on grids.\n\n            Args:\n                pts(Tensor): input points, size (b, n, 2, h_pts, w_pts)\n                num_group(int): the number of groups\n\n            Returs:\n                Tensor: group_inds, size (b, 1, n, h_pts, w_pts)\n        \"\"\"\n        normalized_pts = self.normalize_pts_within_bboxes(pts)  # (b, n, 2, h_pts, w_pts)\n        normalized_x = normalized_pts[:, :, 0, :, :]  # (b, n, h_pts, w_pts)\n        normalized_y = normalized_pts[:, :, 1, :, :]  # (b, n, h_pts, w_pts)\n\n        num_group_kernel = int(np.sqrt(num_group))\n        grid_x_inds = (normalized_x * num_group_kernel).long()  # (b, n, h_pts, w_pts)\n        grid_y_inds = (normalized_y * num_group_kernel).long()  # (b, n, h_pts, w_pts)\n        group_inds = grid_y_inds * num_group_kernel + grid_x_inds  # (b, n, h_pts, w_pts)\n        group_inds = group_inds.unsqueeze(1).float()  # (b, 1, n, h_pts, w_pts)\n        return group_inds\n\n    def get_points(self, featmap_sizes, img_metas):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n\n        Returns:\n            tuple: points of each image, valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # points center for one time\n        multi_level_points = []\n        for i in range(num_levels):\n            points = self.point_generators[i].grid_points(\n                featmap_sizes[i], self.point_strides[i])\n            multi_level_points.append(points)\n        points_list = [[point.clone() for point in multi_level_points] for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level grids\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            for i in range(num_levels):\n                point_stride = self.point_strides[i]\n                feat_h, feat_w = featmap_sizes[i]\n                h, w = img_meta['pad_shape'][:2]\n                valid_feat_h = min(int(np.ceil(h / point_stride)), feat_h)\n                valid_feat_w = min(int(np.ceil(w / point_stride)), feat_w)\n                flags = self.point_generators[i].valid_flags(\n                    (feat_h, feat_w), (valid_feat_h, valid_feat_w))\n                multi_level_flags.append(flags)\n            valid_flag_list.append(multi_level_flags)\n\n        return points_list, valid_flag_list\n\n    def offset_to_pts(self, center_list, pred_list):\n        \"\"\"Change from point offset to point coordinate.\"\"\"\n        pts_list = []\n        for i_lvl in range(len(self.point_strides)):\n            pts_lvl = []\n            for i_img in range(len(center_list)):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                xy_pts_shift = pts_shift.permute(1, 2, 0).view( -1, 2 * self.num_points)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_lvl.append(pts)\n            pts_lvl = torch.stack(pts_lvl, 0)\n            pts_list.append(pts_lvl)\n        return pts_list\n\n    # pts_to_img_lvl\n    def offset_to_pts_img_lvl(self, center_list, pred_list):\n        \"\"\"\n        Project points offset based on center point to image scale and organized in image-level order\n\n            Args:\n                center_list(list(Tensor)): Multi image center list with different level\n                pred_list: Multi image pred points offset with different level\n            Returns:\n                list(Tensor): multi-image points in image scale with different level\n        \"\"\"\n        pts_list = []\n        for i_img, point in enumerate(center_list):\n            pts_img = []\n            for i_lvl in range(len(center_list[0])):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                xy_pts_shift = pts_shift.permute(1, 2, 0).view(-1, 2 * self.num_points)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_img.append(pts)\n            pts_list.append(pts_img)\n        return pts_list\n\n    def _dense_point_target_single(self,\n                                   flat_proposals,\n                                   flat_proposals_pts,\n                                   valid_flags,\n                                   num_level_proposals,\n                                   gt_bboxes,\n                                   gt_bboxes_ignore,\n                                   gt_masks,\n                                   gt_labels,\n                                   num_pts,\n                                   label_channels=1,\n                                   stage='init',\n                                   unmap_outputs=True):\n        inside_flags = valid_flags\n        if not inside_flags.any():\n            return (None, ) * 9\n        # assign gt and sample proposals\n        proposals = flat_proposals[inside_flags, :]\n        proposals_pts = flat_proposals_pts[inside_flags, :]\n\n        num_level_proposals_inside = self.get_num_level_proposals_inside(num_level_proposals, inside_flags)\n        if stage == 'init':\n            assigner = self.init_assigner\n            assigner_type = self.train_cfg.init.assigner.type\n            pos_weight = self.train_cfg.init.pos_weight\n        else:\n            assigner = self.refine_assigner\n            assigner_type = self.train_cfg.refine.assigner.type\n            pos_weight = self.train_cfg.refine.pos_weight\n        if assigner_type != \"ATSSAssigner\":\n            assign_result = assigner.assign(proposals, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        else:\n            assign_result = assigner.assign(proposals, num_level_proposals_inside, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        sampling_result = self.sampler.sample(assign_result, proposals, gt_bboxes)\n\n        gt_ind = sampling_result.pos_assigned_gt_inds.cpu().numpy()\n        gt_pts_numpy = distance_sample_pts(gt_bboxes, gt_masks, self.train_cfg.get(stage), num_pts)\n\n        pts_label_list = []\n        proposals_pos_pts = proposals_pts[sampling_result.pos_inds, :].detach().cpu().numpy().round().astype(np.long)\n        for i in range(len(gt_ind)):\n            gt_mask = gt_masks.masks[gt_ind[i]]\n            h, w = gt_mask.shape\n            pts_long = proposals_pos_pts[i]\n            _pts_label = gt_mask[pts_long[1::2].clip(0, h - 1), pts_long[0::2].clip(0, w - 1)]\n            pts_label_list.append(_pts_label)\n        del proposals_pos_pts\n\n        if len(gt_ind) != 0:\n            gt_pts = gt_bboxes.new_tensor(gt_pts_numpy)\n            pos_gt_pts = gt_pts[gt_ind]\n            pts_label = np.stack(pts_label_list, 0)\n            pos_gt_pts_label = gt_bboxes.new_tensor(pts_label)\n        else:\n            pos_gt_pts = None\n            pos_gt_pts_label = None\n\n        num_valid_proposals = proposals.shape[0]\n        bbox_gt = proposals.new_zeros([num_valid_proposals, 4])\n        bbox_weights = proposals.new_zeros([num_valid_proposals, 4])\n        mask_gt = proposals.new_zeros([0, num_pts * 2])\n        mask_gt_label = proposals.new_zeros([0, int(flat_proposals_pts.shape[1] / 2)]).long()\n        mask_gt_index = proposals.new_zeros([num_valid_proposals, ], dtype=torch.long)\n        labels = proposals.new_full((num_valid_proposals, ), self.background_label, dtype=torch.long)\n        label_weights = proposals.new_zeros(num_valid_proposals, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_gt_bboxes = sampling_result.pos_gt_bboxes\n            bbox_gt[pos_inds, :] = pos_gt_bboxes\n            bbox_weights[pos_inds, :] = 1.0\n            if pos_gt_pts is not None:\n                mask_gt = pos_gt_pts.type(bbox_gt.type())\n                mask_gt_index[pos_inds] = torch.arange(len(pos_inds)).long().cuda() + 1\n            if pos_gt_pts_label is not None:\n                mask_gt_label = pos_gt_pts_label.long()\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]\n            if pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of proposals\n        if unmap_outputs:\n            num_total_proposals = flat_proposals.size(0)\n            labels = unmap(labels, num_total_proposals, inside_flags)\n            label_weights = unmap(label_weights, num_total_proposals, inside_flags)\n            bbox_gt = unmap(bbox_gt, num_total_proposals, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_proposals, inside_flags)\n            mask_gt_index = unmap(mask_gt_index, num_total_proposals, inside_flags)\n\n        return labels, label_weights, bbox_gt, bbox_weights, mask_gt_index, mask_gt, mask_gt_label, pos_inds, neg_inds\n\n    def get_targets(self,\n                    proposals_list,\n                    proposals_pts_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    gt_masks_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    num_pts=729,\n                    stage='init',\n                    label_channels=1,\n                    unmap_outputs=True):\n        \"\"\"Compute corresponding GT box and classification targets for\n        proposals.\n\n        Args:\n            proposals_list (list[list]): Multi level points/bboxes of each\n                image.\n            valid_flag_list (list[list]): Multi level valid flags of each\n                image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            gt_bboxes_ignore_list (list[Tensor]): Ground truth bboxes to be\n                ignored.\n            gt_bboxes_list (list[Tensor]): Ground truth labels of each box.\n            stage (str): `init` or `refine`. Generate target for init stage or\n                refine stage\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple:\n                - labels_list (list[Tensor]): Labels of each level.\n                - label_weights_list (list[Tensor]): Label weights of each level.  # noqa: E501\n                - bbox_gt_list (list[Tensor]): Ground truth bbox of each level.\n                - proposal_list (list[Tensor]): Proposals(points/bboxes) of each level.  # noqa: E501\n                - proposal_weights_list (list[Tensor]): Proposal weights of each level.  # noqa: E501\n                - num_total_pos (int): Number of positive samples in all images.  # noqa: E501\n                - num_total_neg (int): Number of negative samples in all images.  # noqa: E501\n        \"\"\"\n        assert stage in ['init', 'refine']\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n        num_level_proposals_list = [num_level_proposals] * num_imgs\n\n        # concat all level points and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n            proposals_pts_list[i] = torch.cat(proposals_pts_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_labels, all_label_weights, all_bbox_gt, all_bbox_weights,\n         all_mask_gt_index, all_mask_gt, all_mask_gt_label,\n         pos_inds_list, neg_inds_list) = multi_apply(\n             self._dense_point_target_single,\n             proposals_list,\n             proposals_pts_list,\n             valid_flag_list,\n             num_level_proposals_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             gt_masks_list,\n             gt_labels_list,\n             num_pts=num_pts,\n             stage=stage,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        labels_list = images_to_levels(all_labels, num_level_proposals)\n        label_weights_list = images_to_levels(all_label_weights, num_level_proposals)\n        bbox_gt_list = images_to_levels(all_bbox_gt, num_level_proposals)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                                 num_level_proposals)\n        mask_gt_index_list = images_to_levels(all_mask_gt_index, num_level_proposals)\n        mask_gt_list = mask_to_levels(all_mask_gt, mask_gt_index_list)\n        mask_gt_label_list = mask_to_levels(all_mask_gt_label, mask_gt_index_list)\n\n        return (labels_list, label_weights_list, bbox_gt_list, bbox_weights_list,\n                mask_gt_list, mask_gt_label_list,\n                num_total_pos, num_total_neg)\n\n    def _cont_target_single(self,\n                            flat_points,\n                            inside_flags,\n                            gt_bboxes,\n                            gt_contours,\n                            sizes,\n                            unmap_outputs=True):\n        # assign gt and sample points\n        if not inside_flags.any():\n            return (None, ) * 5\n        points = flat_points\n\n        assigner = self.cont_assigner\n        gt_contour, gt_offsets, pos_inds, neg_inds = \\\n            assigner.assign(points, gt_bboxes, gt_contours, sizes)\n\n        num_valid_points = points.shape[0]\n        offsets_weights = points.new_zeros([num_valid_points, 2], dtype=torch.float)\n\n        offsets_weights[pos_inds, :] = 1.0\n\n        return gt_contour, gt_offsets, offsets_weights, pos_inds, neg_inds\n\n    def get_cont_target(self,\n                        proposals_list,\n                        valid_flag_list,\n                        gt_bboxes_list,\n                        gt_contours_list,\n                        sizes_list,\n                        img_metas,\n                        unmap_outputs=True):\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n\n        # concat all level points and flags to a single tensor\n        for i in range(len(proposals_list)):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        all_gt_contour, all_gt_offsets, all_offset_weights, pos_inds_list, neg_inds_list = multi_apply(\n            self._cont_target_single,\n            proposals_list,\n            valid_flag_list,\n            gt_bboxes_list,\n            gt_contours_list,\n            sizes=sizes_list,\n            unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([gt_contour is None for gt_contour in all_gt_contour]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n\n        gt_contours_list = images_to_levels(all_gt_contour, num_level_proposals)\n        gt_offsets_list = images_to_levels(all_gt_offsets, num_level_proposals)\n        offset_weight_list = images_to_levels(all_offset_weights, num_level_proposals)\n\n        return (gt_contours_list, gt_offsets_list, offset_weight_list,\n                num_total_pos, num_total_neg)\n\n    def loss_single(self, cls_score, pts_pred_init, pts_pred_refine, pts_score_pred_init, ct_score, ct_offset,\n                    labels, label_weights,\n                    bbox_gt_init, pts_gt_init, bbox_weights_init,\n                    bbox_gt_refine, pts_gt_refine, pts_score_gt_label, bbox_weights_refine,\n                    gt_ct, gt_offset, gt_offset_weight,\n                    stride, num_total_samples_init, num_total_samples_refine,\n                    num_total_samples_ct):\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n        cls_score = cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n        loss_cls = self.loss_cls(\n            cls_score, labels, label_weights, avg_factor=num_total_samples_refine)\n\n        # bbox loss\n        bbox_gt_init = bbox_gt_init.reshape(-1, 4)\n        bbox_weights_init = bbox_weights_init.reshape(-1, 4)\n        bbox_pred_init = self.points2bbox(pts_pred_init.reshape(-1, 2 * self.num_points))\n        bbox_gt_refine = bbox_gt_refine.reshape(-1, 4)\n        bbox_weights_refine = bbox_weights_refine.reshape(-1, 4)\n        bbox_pred_refine = self.points2bbox(pts_pred_refine.reshape(-1, 2 * self.num_points))\n        normalize_term = self.point_base_scale * stride\n\n        loss_bbox_init = self.loss_bbox_init(\n            bbox_pred_init / normalize_term,\n            bbox_gt_init / normalize_term,\n            bbox_weights_init,\n            avg_factor=num_total_samples_init)\n        loss_bbox_refine = self.loss_bbox_refine(\n            bbox_pred_refine / normalize_term,\n            bbox_gt_refine / normalize_term,\n            bbox_weights_refine,\n            avg_factor=num_total_samples_refine)\n\n        # pts_loss_init\n        valid_pts_gt_init = torch.cat(pts_gt_init, 0)\n        valid_pts_gt_init = valid_pts_gt_init.view(-1, self.num_points, 2)\n        mask_pred_init = pts_pred_init.reshape(-1, 2 * self.num_points)\n        valid_pts_pred_init = mask_pred_init[bbox_weights_init[:, 0] > 0]\n        valid_pts_pred_init = valid_pts_pred_init.view(-1, self.num_points, 2)\n        valid_pts = valid_pts_gt_init.sum(-1).sum(-1) > 0\n        num_total_samples = max(num_total_samples_init, 1)\n        loss_pts_init = self.loss_pts_init(\n            valid_pts_gt_init[valid_pts] / normalize_term,\n            valid_pts_pred_init[valid_pts] / normalize_term).sum() / num_total_samples\n        # pts_loss_refine\n        valid_pts_gt_refine = torch.cat(pts_gt_refine, 0)\n        valid_pts_gt_refine = valid_pts_gt_refine.view(-1, self.num_points, 2)\n        pts_pred_refine = pts_pred_refine.reshape(-1, 2 * self.num_points)\n        valid_pts_pred_refine = pts_pred_refine[bbox_weights_refine[:, 0] > 0]\n        valid_pts_pred_refine = valid_pts_pred_refine.view(-1, self.num_points, 2)\n        valid_pts = valid_pts_gt_refine.sum(-1).sum(-1) > 0\n        num_total_samples = max(num_total_samples_refine, 1)\n        loss_pts_refine = self.loss_pts_refine(\n            valid_pts_gt_refine[valid_pts] / normalize_term,\n            valid_pts_pred_refine[valid_pts] / normalize_term).sum() / num_total_samples\n        # mask score loss\n        valid_pts_score_gt_label = torch.cat(pts_score_gt_label, 0)\n        valid_pts_score_gt_label = valid_pts_score_gt_label.view(-1, self.num_points, 1)\n        pts_score_pred_init = pts_score_pred_init.reshape(-1, self.num_points)\n        valid_pts_score_pred_init = pts_score_pred_init[bbox_weights_refine[:, 0] > 0]\n        valid_pts_score_pred_init = valid_pts_score_pred_init.view(-1, self.num_points, 1)\n        valid_pts_score_inds = (valid_pts_score_gt_label.sum(-1).sum(-1) > 0)\n        num_total_samples = max(num_total_samples_refine, 1)\n        loss_mask_score_init = self.loss_mask_score_init(\n            valid_pts_score_pred_init[valid_pts_score_inds],\n            valid_pts_score_gt_label[valid_pts_score_inds],\n            weight=bbox_weights_init.new_ones(*valid_pts_score_pred_init[valid_pts_score_inds].shape),\n            avg_factor=num_total_samples\n        ) / self.num_points\n\n        # contour cls loss\n        ct_score = ct_score.permute(0, 2, 3, 1).reshape(-1, 1)\n        gt_ct = gt_ct.reshape(-1)\n        loss_ct_heatmap = self.loss_ct_heatmap(ct_score, gt_ct, avg_factor=num_total_samples_ct)\n\n        # contour offset loss\n        ct_offset = ct_offset.permute(0, 2, 3, 1).reshape(-1, 2)\n        gt_offset = gt_offset.reshape(-1, 2)\n        gt_offset_weight = gt_offset_weight.reshape(-1, 2)\n\n        loss_ct_offset = self.loss_ct_offset(ct_offset, gt_offset, gt_offset_weight, avg_factor=num_total_samples_ct)\n\n        return loss_cls, loss_bbox_init, loss_pts_init, loss_bbox_refine, loss_pts_refine, loss_mask_score_init, loss_ct_heatmap, loss_ct_offset\n\n    def loss(self,\n             cls_scores,\n             pts_preds_init,\n             pts_preds_refine,\n             pts_preds_score_init,\n             ct_scores,\n             ct_offsets,\n             sem_scores,\n             gt_bboxes,\n             gt_masks,\n             gt_sem_map,\n             gt_contours,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.point_generators)\n        label_channels = self.cls_out_channels\n\n        # target for initial stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        real_pts_preds_init = self.offset_to_pts(center_list, pts_preds_init)\n        proposal_pts_list = self.offset_to_pts_img_lvl(center_list, pts_preds_init)\n        real_pts_preds_score_init = []\n        for lvl_pts_score in pts_preds_score_init:\n            b = lvl_pts_score.shape[0]\n            real_pts_preds_score_init.append(lvl_pts_score.permute(0, 2, 3, 1).view(b, -1, self.num_points))\n\n        cls_reg_targets_init = self.get_targets(\n            center_list,\n            proposal_pts_list,\n            valid_flag_list,\n            gt_bboxes,\n            gt_masks,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            num_pts=self.num_points,\n            stage='init',\n            label_channels=label_channels)\n        (*_, bbox_gt_list_init, bbox_weights_list_init, pts_gt_list_init, _,\n         num_total_pos_init, num_total_neg_init) = cls_reg_targets_init\n\n        # target for contours\n        proposal_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        contours_targets = self.get_cont_target(\n            proposal_list,\n            valid_flag_list,\n            gt_bboxes,\n            gt_contours,\n            featmap_sizes,\n            img_metas\n        )\n        (gt_ct_list, gt_offsets_list, gt_offset_weight_list, num_total_pos_ct, num_total_neg_ct) = contours_targets\n\n        # target for refinement stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        real_pts_preds_refine = self.offset_to_pts(center_list, pts_preds_refine)\n        bbox_pts_list = self.offset_to_pts_img_lvl(center_list, pts_preds_init)\n\n        bbox_list = []\n        for i_img, center in enumerate(center_list):\n            bbox = []\n            for i_lvl in range(len(pts_preds_refine)):\n                bbox_preds_init = self.points2bbox(pts_preds_init[i_lvl].detach())\n                bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                bbox_center = torch.cat([center[i_lvl][:, :2], center[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n            bbox_list.append(bbox)\n        cls_reg_targets_refine = self.get_targets(\n            bbox_list,\n            bbox_pts_list,\n            valid_flag_list,\n            gt_bboxes,\n            gt_masks,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            num_pts=self.num_points,\n            stage='refine',\n            label_channels=label_channels)\n        (labels_list, label_weights_list,\n         bbox_gt_list_refine, bbox_weights_list_refine, pts_gt_list_refine, pts_score_gt_label_list,\n         num_total_pos_refine, num_total_neg_refine) = cls_reg_targets_refine\n\n        # compute loss\n        losses_cls, losses_bbox_init, losses_pts_init, losses_bbox_refine, losses_pts_refine, losses_mask_score_init, \\\n        losses_ct_heatmap, losses_ct_offset = multi_apply(\n            self.loss_single,\n            cls_scores,\n            real_pts_preds_init,\n            real_pts_preds_refine,\n            real_pts_preds_score_init,\n            ct_scores,\n            ct_offsets,\n            labels_list,\n            label_weights_list,\n            bbox_gt_list_init,\n            pts_gt_list_init,\n            bbox_weights_list_init,\n            bbox_gt_list_refine,\n            pts_gt_list_refine,\n            pts_score_gt_label_list,\n            bbox_weights_list_refine,\n            gt_ct_list,\n            gt_offsets_list,\n            gt_offset_weight_list,\n            self.point_strides,\n            num_total_samples_init=num_total_pos_init,\n            num_total_samples_refine=num_total_pos_refine,\n            num_total_samples_ct=num_total_pos_ct)\n\n        # sem loss\n        concat_sem_scores = []\n        concat_gt_sem_map = []\n\n        gt_sem_map[gt_sem_map == -1] = self.background_label\n        for i in range(5):\n            sem_score = sem_scores[i]\n            gt_lvl_sem_map = F.interpolate(gt_sem_map.to(torch.float32), sem_score.shape[-2:]).to(torch.long).reshape(-1)\n            sem_score = sem_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n\n            try:\n                concat_sem_scores = torch.cat([concat_sem_scores, sem_score])\n                concat_gt_sem_map = torch.cat([concat_gt_sem_map, gt_lvl_sem_map])\n            except:\n                concat_sem_scores = sem_score\n                concat_gt_sem_map = gt_lvl_sem_map\n\n        loss_sem = self.loss_sem(concat_sem_scores, concat_gt_sem_map, avg_factor=(concat_gt_sem_map < self.background_label).sum())\n\n        loss_dict_all = {'loss_cls': losses_cls,\n                         'loss_bbox_init': losses_bbox_init,\n                         'losses_pts_init': losses_pts_init,\n                         'losses_bbox_refine': losses_bbox_refine,\n                         'losses_pts_refine': losses_pts_refine,\n                         'losses_mask_score_init': losses_mask_score_init,\n                         \"loss_ct_heatmap\": losses_ct_heatmap,\n                         \"loss_ct_offset\": losses_ct_offset,\n                         'loss_sem': loss_sem\n                         }\n        return loss_dict_all\n\n    def get_bboxes(self,\n                   cls_scores,\n                   pts_preds_init,\n                   pts_preds_refine,\n                   pts_preds_score_refine,\n                   ct_scores,\n                   ct_offsets,\n                   sem_scores,\n                   img_metas,\n                   cfg=None,\n                   rescale=False,\n                   nms=True):\n        assert len(cls_scores) == len(pts_preds_refine)\n        bbox_preds_refine = [self.points2bbox(pts_pred_refine) for pts_pred_refine in pts_preds_refine]\n        num_levels = len(cls_scores)\n        mlvl_points = [\n            self.point_generators[i].grid_points(cls_scores[i].size()[-2:],\n                                                 self.point_strides[i])\n            for i in range(num_levels)\n        ]\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds_refine[i][img_id].detach() for i in range(num_levels)\n            ]\n            pts_pred_list = [\n                pts_preds_refine[i][img_id].detach() for i in range(num_levels)\n            ]\n            mask_pred_list = [\n                pts_preds_score_refine[i][img_id].sigmoid().detach() for i in range(num_levels)\n            ]\n            ct_scores_list = [\n                ct_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            ct_offsets_list = [\n                ct_offsets[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list, pts_pred_list, mask_pred_list, ct_scores_list, ct_offsets_list,\n                                                mlvl_points, img_shape,\n                                                scale_factor, cfg, rescale,\n                                                nms)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           pts_preds,\n                           mask_preds,\n                           ct_scores,\n                           ct_offsets,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False,\n                           nms=True):\n        def select(score_map, x, y, ks=2, i=0):\n            H, W = score_map.shape[-2], score_map.shape[-1]\n            score_map = score_map.sigmoid()\n            score_map_original = score_map.clone()\n\n            score_map, indices = F.max_pool2d_with_indices(score_map.unsqueeze(0), kernel_size=ks, stride=1, padding=(ks - 1) // 2)\n\n            indices = indices.squeeze(0).squeeze(0)\n\n            if ks % 2 == 0:\n                round_func = torch.floor\n            else:\n                round_func = torch.round\n\n            x_round = round_func((x / self.point_strides[i]).clamp(min=0, max=score_map.shape[-1] - 1))\n            y_round = round_func((y / self.point_strides[i]).clamp(min=0, max=score_map.shape[-2] - 1))\n\n            select_indices = indices[y_round.to(torch.long), x_round.to(torch.long)]\n            new_x = select_indices % W\n            new_y = select_indices // W\n\n            score_map_squeeze = score_map_original.squeeze(0)\n            score = score_map_squeeze[new_y, new_x]\n\n            new_x, new_y = new_x.to(torch.float), new_y.to(torch.float)\n\n            return new_x, new_y, score\n\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_points)\n        mlvl_pts = []\n        mlvl_bboxes = []\n        mlvl_scores = []\n        mlvl_masks = []\n        mlvl_pts_refine = []\n        mlvl_masks_refine = []\n        for i_lvl, (cls_score, bbox_pred, pts_pred, mask_pred, points) in enumerate(zip(cls_scores, bbox_preds, pts_preds, mask_preds, mlvl_points)):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(-1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            pts_pred = pts_pred.permute(1, 2, 0).reshape(-1, 2 * self.num_points)\n            mask_pred = mask_pred.permute(1, 2, 0).reshape(-1, self.num_points)\n\n            # mask scoring\n            mask_sum = (mask_pred > 0.5).sum(1).float()\n            mask_score = ((mask_pred > 0.5).float() * mask_pred).sum(1) / (mask_sum + 1e-6)\n            scores = scores * mask_score.unsqueeze(1)\n\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                pts_pred = pts_pred[topk_inds, :]\n                mask_pred = mask_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n\n            pts_pos_center = points[:, :2].repeat(1, self.num_points)\n            pts = pts_pred * self.point_strides[i_lvl] + pts_pos_center\n            pts[:, 0::2] = pts[:, 0::2].clamp(min=0, max=img_shape[1] - 1)\n            pts[:, 1::2] = pts[:, 1::2].clamp(min=0, max=img_shape[0] - 1)\n\n            bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1)\n            bboxes = bbox_pred * self.point_strides[i_lvl] + bbox_pos_center\n            x1 = bboxes[:, 0].clamp(min=0, max=img_shape[1])\n            y1 = bboxes[:, 1].clamp(min=0, max=img_shape[0])\n            x2 = bboxes[:, 2].clamp(min=0, max=img_shape[1])\n            y2 = bboxes[:, 3].clamp(min=0, max=img_shape[0])\n            bboxes = torch.stack([x1, y1, x2, y2], dim=-1)\n\n            if i_lvl > 0:\n                i = 0 if i_lvl in (1, 2) else 1\n\n                pts_x = pts[:, 0::2]\n                pts_y = pts[:, 1::2]\n                N, _ = pts_x.shape\n\n                pts_x = pts_x.reshape(-1)\n                pts_y = pts_y.reshape(-1)\n\n                pts_x_new, pts_y_new, ct_score = select(ct_scores[i], pts_x, pts_y, 2, i)\n\n                ct_offset = ct_offsets[i].permute(1, 2, 0)\n                point_stride = self.point_strides[i]\n\n                pts_x_refine = ((pts_x_new + ct_offset[pts_y_new.to(torch.long), pts_x_new.to(torch.long), 0]) * point_stride).clamp(min=0, max=img_shape[1] - 1)\n                pts_y_refine = ((pts_y_new + ct_offset[pts_y_new.to(torch.long), pts_x_new.to(torch.long), 1]) * point_stride).clamp(min=0, max=img_shape[0] - 1)\n                pts_refine = torch.stack([pts_x_refine, pts_y_refine], dim=1)\n\n                pts_refine = pts_refine.reshape(N, -1)\n                ct_score = ct_score.reshape(N, -1)\n\n                mask_refine = torch.zeros_like(mask_pred)\n                keep_inds = ct_score > 0.4\n                mask_refine[keep_inds] = 0.5\n            else:\n                pts_refine = pts\n                mask_refine = torch.zeros_like(mask_pred)\n\n            mlvl_pts.append(pts)\n            mlvl_pts_refine.append(pts_refine)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n            mlvl_masks.append(mask_pred)\n            mlvl_masks_refine.append(mask_refine)\n        mlvl_pts = torch.cat(mlvl_pts)\n        mlvl_pts_refine = torch.cat(mlvl_pts_refine)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n            mlvl_pts /= mlvl_pts.new_tensor(scale_factor[:2]).repeat(mlvl_pts.shape[1] // 2)\n            mlvl_pts_refine /= mlvl_pts_refine.new_tensor(scale_factor[:2]).repeat(mlvl_pts.shape[1] // 2)\n        mlvl_scores = torch.cat(mlvl_scores)\n        mlvl_masks = torch.cat(mlvl_masks)\n        mlvl_masks_refine = torch.cat(mlvl_masks_refine)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        if nms:\n            det_bboxes, det_pts, det_pts_refine, det_masks, det_masks_refine, det_labels = multiclass_nms_pts_refine(\n                mlvl_bboxes, mlvl_pts, mlvl_pts_refine, mlvl_scores, mlvl_masks, mlvl_masks_refine, cfg.score_thr, cfg.nms, cfg.max_per_img)\n            return det_bboxes, det_pts, det_pts_refine, det_masks, det_masks_refine, det_labels\n        else:\n            return mlvl_bboxes, mlvl_scores\n\n    def get_num_level_proposals_inside(self, num_level_proposals, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_proposals)\n        num_level_proposals_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_proposals_inside\n\n\ndef mask_to_levels(target, mask_index_list):\n    \"\"\"\n    Convert target by mask_index_list\n    \"\"\"\n    target_gt_list = []\n    for lvl in range(len(mask_index_list)):\n        mask_gt_lvl_list = []\n        for i in range(mask_index_list[lvl].shape[0]):\n            index = mask_index_list[lvl][i]\n            index = index[index > 0]\n            mask_gt_lvl = target[i][index - 1]\n            mask_gt_lvl_list.append(mask_gt_lvl)\n        target_gt_list.append(mask_gt_lvl_list)\n    return target_gt_list\n\n\ndef mask_to_poly(mask):\n    contours, _ = cv2.findContours(mask.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n    polygons = []\n    for contour in contours:\n        contour = contour.flatten().tolist()\n        if len(contour) > 4:\n            polygons.append(contour)\n    return polygons\n\n\ndef distance_sample_pts(gt_bboxes, gt_masks, cfg, num_pts):\n    \"\"\"\n    Sample pts based on distance transformation map.\n\n    Args:\n        gt_bboxes(list(Tensor)): groud-truth bounding box\n        gt_masks(list(Mask)): ground-truth mask\n        cfg(dict): sampling config\n        num_pts(int): number of points\n\n    Returns:\n        numpy: the sampling points based on distance transform map\n    \"\"\"\n    dist_sample_thr = cfg.get('dist_sample_thr', 2)\n    pts_list = []\n    pts_label_list = []\n    for i in range(len(gt_bboxes)):\n        x1, y1, x2, y2 = gt_bboxes[i].cpu().numpy().astype(np.int32)\n        w = np.maximum(x2 - x1, 1)\n        h = np.maximum(y2 - y1, 1)\n        mask = mmcv.imresize(gt_masks.masks[i][y1:y1 + h, x1:x1 + w],\n                             (cfg.get('mask_size', 56), cfg.get('mask_size', 56)))\n        polygons = mask_to_poly(mask)\n        distance_map = np.ones(mask.shape).astype(np.uint8)\n        for poly in polygons:\n            poly = np.array(poly).astype(np.int)\n            for j in range(len(poly) // 2):\n                x_0, y_0 = poly[2 * j:2 * j + 2]\n                if j == len(poly) // 2 - 1:\n                    x_1, y_1 = poly[0:2]\n                else:\n                    x_1, y_1 = poly[2 * j + 2:2 * j + 4]\n                cv2.line(distance_map, (x_0, y_0), (x_1, y_1), 0, thickness=2)\n        roi_dist_map = cv2.distanceTransform(distance_map, cv2.DIST_L2, 3)\n        con_index = np.stack(np.nonzero(roi_dist_map == 0)[::-1], axis=-1)\n        roi_dist_map[roi_dist_map == 0] = 1\n        roi_dist_map[roi_dist_map > dist_sample_thr] = 0\n\n        index_y, index_x = np.nonzero(roi_dist_map > 0)\n        index = np.stack([index_x, index_y], axis=-1)\n        _len = index.shape[0]\n        if len(con_index) == 0:\n            pts = np.zeros([2 * num_pts])\n        else:\n            repeat = num_pts // _len\n            mod = num_pts % _len\n            perm = np.random.choice(_len, mod, replace=False)\n            draw = [index.copy() for i in range(repeat)]\n            draw.append(index[perm])\n            draw = np.concatenate(draw, 0)\n            draw = np.random.permutation(draw)\n            draw = draw + np.random.rand(*draw.shape)\n            x_scale = float(w) / cfg.get('mask_size', 56)\n            y_scale = float(h) / cfg.get('mask_size', 56)\n            draw[:, 0] = draw[:, 0] * x_scale + x1\n            draw[:, 1] = draw[:, 1] * y_scale + y1\n            pts = draw.reshape(2 * num_pts)\n\n        pts_list.append(pts)\n        pts_long = pts.astype(np.long)\n        pts_label = gt_masks.masks[i][pts_long[1::2], pts_long[0::2]]\n        pts_label_list.append(pts_label)\n    pts_list = np.stack(pts_list, 0)\n    return pts_list"
  },
  {
    "path": "code/mmdet/models/dense_heads/fcos_head.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import Scale, normal_init\n\nfrom mmdet.core import distance2bbox, force_fp32, multi_apply, multiclass_nms\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\nINF = 1e8\n\n\n@HEADS.register_module()\nclass FCOSHead(AnchorFreeHead):\n    \"\"\"Anchor-free head used in `FCOS <https://arxiv.org/abs/1904.01355>`_.\n\n    The FCOS head does not use anchor boxes. Instead bounding boxes are\n    predicted at each pixel and a centerness measure is used to supress\n    low-quality predictions.\n    Here norm_on_bbox, centerness_on_reg, dcn_on_last_conv are training\n    tricks used in official repo, which will bring remarkable mAP gains\n    of up to 4.9. Please see https://github.com/tianzhi0549/FCOS for\n    more detail.\n\n    Args:\n        num_classes (int): Number of categories excluding the background\n            category.\n        in_channels (int): Number of channels in the input feature map.\n        strides (list[int] | list[tuple[int, int]]): Strides of points\n            in multiple feature levels. Default: (4, 8, 16, 32, 64).\n        regress_ranges (tuple[tuple[int, int]]): Regress range of multiple\n            level points.\n        center_sampling (bool): If true, use center sampling. Default: False.\n        center_sample_radius (float): Radius of center sampling. Default: 1.5.\n        norm_on_bbox (bool): If true, normalize the regression targets\n            with FPN strides. Default: False.\n        centerness_on_reg (bool): If true, position centerness on the\n            regress branch. Please refer to https://github.com/tianzhi0549/FCOS/issues/89#issuecomment-516877042.\n            Default: False.\n        conv_bias (bool | str): If specified as `auto`, it will be decided by the\n            norm_cfg. Bias of conv will be set as True if `norm_cfg` is None, otherwise\n            False. Default: \"auto\".\n        loss_cls (dict): Config of classification loss.\n        loss_bbox (dict): Config of localization loss.\n        loss_centerness (dict): Config of centerness loss.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n            Default: norm_cfg=dict(type='GN', num_groups=32, requires_grad=True).\n    Example:\n        >>> self = FCOSHead(11, 7)\n        >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]]\n        >>> cls_score, bbox_pred, centerness = self.forward(feats)\n        >>> assert len(cls_score) == len(self.scales)\n    \"\"\"  # noqa: E501\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 regress_ranges=((-1, 64), (64, 128), (128, 256), (256, 512),\n                                 (512, INF)),\n                 center_sampling=False,\n                 center_sample_radius=1.5,\n                 norm_on_bbox=False,\n                 centerness_on_reg=False,\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox=dict(type='IoULoss', loss_weight=1.0),\n                 loss_centerness=dict(\n                     type='CrossEntropyLoss',\n                     use_sigmoid=True,\n                     loss_weight=1.0),\n                 norm_cfg=dict(type='GN', num_groups=32, requires_grad=True),\n                 **kwargs):\n        self.regress_ranges = regress_ranges\n        self.center_sampling = center_sampling\n        self.center_sample_radius = center_sample_radius\n        self.norm_on_bbox = norm_on_bbox\n        self.centerness_on_reg = centerness_on_reg\n        super().__init__(\n            num_classes,\n            in_channels,\n            loss_cls=loss_cls,\n            loss_bbox=loss_bbox,\n            norm_cfg=norm_cfg,\n            **kwargs)\n        self.loss_centerness = build_loss(loss_centerness)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        super()._init_layers()\n        self.conv_centerness = nn.Conv2d(self.feat_channels, 1, 3, padding=1)\n        self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides])\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        super().init_weights()\n        normal_init(self.conv_centerness, std=0.01)\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple:\n                cls_scores (list[Tensor]): Box scores for each scale level,\n                    each is a 4D-tensor, the channel number is\n                    num_points * num_classes.\n                bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                    level, each is a 4D-tensor, the channel number is\n                    num_points * 4.\n                centernesses (list[Tensor]): Centerss for each scale level,\n                    each is a 4D-tensor, the channel number is num_points * 1.\n        \"\"\"\n        return multi_apply(self.forward_single, feats, self.scales,\n                           self.strides)\n\n    def forward_single(self, x, scale, stride):\n        \"\"\"Forward features of a single scale levle.\n\n        Args:\n            x (Tensor): FPN feature maps of the specified stride.\n            scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize\n                the bbox prediction.\n            stride (int): The corresponding stride for feature maps, only\n                used to normalize the bbox prediction when self.norm_on_bbox\n                is True.\n\n        Returns:\n            tuple: scores for each class, bbox predictions and centerness\n                predictions of input feature maps.\n        \"\"\"\n        cls_score, bbox_pred, cls_feat, reg_feat = super().forward_single(x)\n        if self.centerness_on_reg:\n            centerness = self.conv_centerness(reg_feat)\n        else:\n            centerness = self.conv_centerness(cls_feat)\n        # scale the bbox_pred of different level\n        # float to avoid overflow when enabling FP16\n        bbox_pred = scale(bbox_pred).float()\n        if self.norm_on_bbox:\n            bbox_pred = F.relu(bbox_pred)\n            if not self.training:\n                bbox_pred *= stride\n        else:\n            bbox_pred = bbox_pred.exp()\n        return cls_score, bbox_pred, centerness\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds', 'centernesses'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             centernesses,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute loss of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level,\n                each is a 4D-tensor, the channel number is\n                num_points * num_classes.\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level, each is a 4D-tensor, the channel number is\n                num_points * 4.\n            centernesses (list[Tensor]): Centerss for each scale level, each\n                is a 4D-tensor, the channel number is num_points * 1.\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        assert len(cls_scores) == len(bbox_preds) == len(centernesses)\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        all_level_points = self.get_points(featmap_sizes, bbox_preds[0].dtype,\n                                           bbox_preds[0].device)\n        labels, bbox_targets = self.get_targets(all_level_points, gt_bboxes,\n                                                gt_labels)\n\n        num_imgs = cls_scores[0].size(0)\n        # flatten cls_scores, bbox_preds and centerness\n        flatten_cls_scores = [\n            cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n            for cls_score in cls_scores\n        ]\n        flatten_bbox_preds = [\n            bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4)\n            for bbox_pred in bbox_preds\n        ]\n        flatten_centerness = [\n            centerness.permute(0, 2, 3, 1).reshape(-1)\n            for centerness in centernesses\n        ]\n        flatten_cls_scores = torch.cat(flatten_cls_scores)\n        flatten_bbox_preds = torch.cat(flatten_bbox_preds)\n        flatten_centerness = torch.cat(flatten_centerness)\n        flatten_labels = torch.cat(labels)\n        flatten_bbox_targets = torch.cat(bbox_targets)\n        # repeat points to align with bbox_preds\n        flatten_points = torch.cat(\n            [points.repeat(num_imgs, 1) for points in all_level_points])\n\n        # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n        bg_class_ind = self.num_classes\n        pos_inds = ((flatten_labels >= 0)\n                    & (flatten_labels < bg_class_ind)).nonzero().reshape(-1)\n        num_pos = len(pos_inds)\n        loss_cls = self.loss_cls(\n            flatten_cls_scores, flatten_labels,\n            avg_factor=num_pos + num_imgs)  # avoid num_pos is 0\n\n        pos_bbox_preds = flatten_bbox_preds[pos_inds]\n        pos_centerness = flatten_centerness[pos_inds]\n\n        if num_pos > 0:\n            pos_bbox_targets = flatten_bbox_targets[pos_inds]\n            pos_centerness_targets = self.centerness_target(pos_bbox_targets)\n            pos_points = flatten_points[pos_inds]\n            pos_decoded_bbox_preds = distance2bbox(pos_points, pos_bbox_preds)\n            pos_decoded_target_preds = distance2bbox(pos_points,\n                                                     pos_bbox_targets)\n            # centerness weighted iou loss\n            loss_bbox = self.loss_bbox(\n                pos_decoded_bbox_preds,\n                pos_decoded_target_preds,\n                weight=pos_centerness_targets,\n                avg_factor=pos_centerness_targets.sum())\n            loss_centerness = self.loss_centerness(pos_centerness,\n                                                   pos_centerness_targets)\n        else:\n            loss_bbox = pos_bbox_preds.sum()\n            loss_centerness = pos_centerness.sum()\n\n        return dict(\n            loss_cls=loss_cls,\n            loss_bbox=loss_bbox,\n            loss_centerness=loss_centerness)\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds', 'centernesses'))\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_preds,\n                   centernesses,\n                   img_metas,\n                   cfg=None,\n                   rescale=None):\n        \"\"\" Transform network output for a batch into bbox predictions.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_points * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_points * 4, H, W)\n            centernesses (list[Tensor]): Centerness for each scale level with\n                shape (N, num_points * 1, H, W)\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used\n            rescale (bool): If True, return boxes in original image space\n\n        Returns:\n            list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.\n                The first item is an (n, 5) tensor, where the first 4 columns\n                are bounding box positions (tl_x, tl_y, br_x, br_y) and the\n                5-th column is a score between 0 and 1. The second item is a\n                (n,) tensor where each item is the predicted class label of the\n                corresponding box.\n        \"\"\"\n        assert len(cls_scores) == len(bbox_preds)\n        num_levels = len(cls_scores)\n\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        mlvl_points = self.get_points(featmap_sizes, bbox_preds[0].dtype,\n                                      bbox_preds[0].device)\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds[i][img_id].detach() for i in range(num_levels)\n            ]\n            centerness_pred_list = [\n                centernesses[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            det_bboxes = self._get_bboxes_single(cls_score_list,\n                                                 bbox_pred_list,\n                                                 centerness_pred_list,\n                                                 mlvl_points, img_shape,\n                                                 scale_factor, cfg, rescale)\n            result_list.append(det_bboxes)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           centernesses,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        \"\"\"Transform outputs for a single batch item into bbox predictions.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for a single scale level\n                Has shape (num_points * num_classes, H, W).\n            bbox_preds (list[Tensor]): Box energies / deltas for a single scale\n                level with shape (num_points * 4, H, W).\n            centernesses (list[Tensor]): Centerness for a single scale level\n                with shape (num_points * 4, H, W).\n            mlvl_points (list[Tensor]): Box reference for a single scale level\n                with shape (num_total_points, 4).\n            img_shape (tuple[int]): Shape of the input image,\n                (height, width, 3).\n            scale_factor (ndarray): Scale factor of the image arrange as\n                (w_scale, h_scale, w_scale, h_scale).\n            cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used.\n            rescale (bool): If True, return boxes in original image space.\n\n        Returns:\n            Tensor: Labeled boxes in shape (n, 5), where the first 4 columns\n                are bounding box positions (tl_x, tl_y, br_x, br_y) and the\n                5-th column is a score between 0 and 1.\n        \"\"\"\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_points)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        mlvl_centerness = []\n        for cls_score, bbox_pred, centerness, points in zip(\n                cls_scores, bbox_preds, centernesses, mlvl_points):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(\n                -1, self.cls_out_channels).sigmoid()\n            centerness = centerness.permute(1, 2, 0).reshape(-1).sigmoid()\n\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = (scores * centerness[:, None]).max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n                centerness = centerness[topk_inds]\n            bboxes = distance2bbox(points, bbox_pred, max_shape=img_shape)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n            mlvl_centerness.append(centerness)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n        mlvl_scores = torch.cat(mlvl_scores)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        # remind that we set FG labels to [0, num_class-1] since mmdet v2.0\n        # BG cat_id: num_class\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        mlvl_centerness = torch.cat(mlvl_centerness)\n        det_bboxes, det_labels = multiclass_nms(\n            mlvl_bboxes,\n            mlvl_scores,\n            cfg.score_thr,\n            cfg.nms,\n            cfg.max_per_img,\n            score_factors=mlvl_centerness)\n        return det_bboxes, det_labels\n\n    def _get_points_single(self,\n                           featmap_size,\n                           stride,\n                           dtype,\n                           device,\n                           flatten=False):\n        \"\"\"Get points according to feature map sizes.\"\"\"\n        y, x = super()._get_points_single(featmap_size, stride, dtype, device)\n        points = torch.stack((x.reshape(-1) * stride, y.reshape(-1) * stride),\n                             dim=-1) + stride // 2\n        return points\n\n    def get_targets(self, points, gt_bboxes_list, gt_labels_list):\n        \"\"\"Compute regression, classification and centerss targets for points\n            in multiple images.\n\n        Args:\n            points (list[Tensor]): Points of each fpn level, each has shape\n                (num_points, 2).\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image,\n                each has shape (num_gt, 4).\n            gt_labels_list (list[Tensor]): Ground truth labels of each box,\n                each has shape (num_gt,).\n\n        Returns:\n            tuple:\n                concat_lvl_labels (list[Tensor]): Labels of each level.\n                concat_lvl_bbox_targets (list[Tensor]): BBox targets of each\n                    level.\n        \"\"\"\n        assert len(points) == len(self.regress_ranges)\n        num_levels = len(points)\n        # expand regress ranges to align with points\n        expanded_regress_ranges = [\n            points[i].new_tensor(self.regress_ranges[i])[None].expand_as(\n                points[i]) for i in range(num_levels)\n        ]\n        # concat all levels points and regress ranges\n        concat_regress_ranges = torch.cat(expanded_regress_ranges, dim=0)\n        concat_points = torch.cat(points, dim=0)\n\n        # the number of points per img, per lvl\n        num_points = [center.size(0) for center in points]\n\n        # get labels and bbox_targets of each image\n        labels_list, bbox_targets_list = multi_apply(\n            self._get_target_single,\n            gt_bboxes_list,\n            gt_labels_list,\n            points=concat_points,\n            regress_ranges=concat_regress_ranges,\n            num_points_per_lvl=num_points)\n\n        # split to per img, per level\n        labels_list = [labels.split(num_points, 0) for labels in labels_list]\n        bbox_targets_list = [\n            bbox_targets.split(num_points, 0)\n            for bbox_targets in bbox_targets_list\n        ]\n\n        # concat per level image\n        concat_lvl_labels = []\n        concat_lvl_bbox_targets = []\n        for i in range(num_levels):\n            concat_lvl_labels.append(\n                torch.cat([labels[i] for labels in labels_list]))\n            bbox_targets = torch.cat(\n                [bbox_targets[i] for bbox_targets in bbox_targets_list])\n            if self.norm_on_bbox:\n                bbox_targets = bbox_targets / self.strides[i]\n            concat_lvl_bbox_targets.append(bbox_targets)\n        return concat_lvl_labels, concat_lvl_bbox_targets\n\n    def _get_target_single(self, gt_bboxes, gt_labels, points, regress_ranges,\n                           num_points_per_lvl):\n        \"\"\"Compute regression and classification targets for a single image.\"\"\"\n        num_points = points.size(0)\n        num_gts = gt_labels.size(0)\n        if num_gts == 0:\n            return gt_labels.new_full((num_points,), self.background_label), \\\n                   gt_bboxes.new_zeros((num_points, 4))\n\n        areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * (\n            gt_bboxes[:, 3] - gt_bboxes[:, 1])\n        # TODO: figure out why these two are different\n        # areas = areas[None].expand(num_points, num_gts)\n        areas = areas[None].repeat(num_points, 1)\n        regress_ranges = regress_ranges[:, None, :].expand(\n            num_points, num_gts, 2)\n        gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4)\n        xs, ys = points[:, 0], points[:, 1]\n        xs = xs[:, None].expand(num_points, num_gts)\n        ys = ys[:, None].expand(num_points, num_gts)\n\n        left = xs - gt_bboxes[..., 0]\n        right = gt_bboxes[..., 2] - xs\n        top = ys - gt_bboxes[..., 1]\n        bottom = gt_bboxes[..., 3] - ys\n        bbox_targets = torch.stack((left, top, right, bottom), -1)\n\n        if self.center_sampling:\n            # condition1: inside a `center bbox`\n            radius = self.center_sample_radius\n            center_xs = (gt_bboxes[..., 0] + gt_bboxes[..., 2]) / 2\n            center_ys = (gt_bboxes[..., 1] + gt_bboxes[..., 3]) / 2\n            center_gts = torch.zeros_like(gt_bboxes)\n            stride = center_xs.new_zeros(center_xs.shape)\n\n            # project the points on current lvl back to the `original` sizes\n            lvl_begin = 0\n            for lvl_idx, num_points_lvl in enumerate(num_points_per_lvl):\n                lvl_end = lvl_begin + num_points_lvl\n                stride[lvl_begin:lvl_end] = self.strides[lvl_idx] * radius\n                lvl_begin = lvl_end\n\n            x_mins = center_xs - stride\n            y_mins = center_ys - stride\n            x_maxs = center_xs + stride\n            y_maxs = center_ys + stride\n            center_gts[..., 0] = torch.where(x_mins > gt_bboxes[..., 0],\n                                             x_mins, gt_bboxes[..., 0])\n            center_gts[..., 1] = torch.where(y_mins > gt_bboxes[..., 1],\n                                             y_mins, gt_bboxes[..., 1])\n            center_gts[..., 2] = torch.where(x_maxs > gt_bboxes[..., 2],\n                                             gt_bboxes[..., 2], x_maxs)\n            center_gts[..., 3] = torch.where(y_maxs > gt_bboxes[..., 3],\n                                             gt_bboxes[..., 3], y_maxs)\n\n            cb_dist_left = xs - center_gts[..., 0]\n            cb_dist_right = center_gts[..., 2] - xs\n            cb_dist_top = ys - center_gts[..., 1]\n            cb_dist_bottom = center_gts[..., 3] - ys\n            center_bbox = torch.stack(\n                (cb_dist_left, cb_dist_top, cb_dist_right, cb_dist_bottom), -1)\n            inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0\n        else:\n            # condition1: inside a gt bbox\n            inside_gt_bbox_mask = bbox_targets.min(-1)[0] > 0\n\n        # condition2: limit the regression range for each location\n        max_regress_distance = bbox_targets.max(-1)[0]\n        inside_regress_range = (\n            (max_regress_distance >= regress_ranges[..., 0])\n            & (max_regress_distance <= regress_ranges[..., 1]))\n\n        # if there are still more than one objects for a location,\n        # we choose the one with minimal area\n        areas[inside_gt_bbox_mask == 0] = INF\n        areas[inside_regress_range == 0] = INF\n        min_area, min_area_inds = areas.min(dim=1)\n\n        labels = gt_labels[min_area_inds]\n        labels[min_area == INF] = self.background_label  # set as BG\n        bbox_targets = bbox_targets[range(num_points), min_area_inds]\n\n        return labels, bbox_targets\n\n    def centerness_target(self, pos_bbox_targets):\n        \"\"\"Compute centerness targets.\n\n        Args:\n            pos_bbox_targets (Tensor): BBox targets of positive bboxes in shape\n                (num_pos, 4)\n\n        Returns:\n            Tensor: Centerness target.\n        \"\"\"\n        # only calculate pos centerness targets, otherwise there may be nan\n        left_right = pos_bbox_targets[:, [0, 2]]\n        top_bottom = pos_bbox_targets[:, [1, 3]]\n        centerness_targets = (\n            left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * (\n                top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])\n        return torch.sqrt(centerness_targets)\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/fovea_head.py",
    "content": "import torch\nimport torch.nn as nn\nfrom mmcv.cnn import ConvModule, normal_init\n\nfrom mmdet.core import multi_apply, multiclass_nms\nfrom mmdet.ops import DeformConv\nfrom ..builder import HEADS\nfrom .anchor_free_head import AnchorFreeHead\n\nINF = 1e8\n\n\nclass FeatureAlign(nn.Module):\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size=3,\n                 deformable_groups=4):\n        super(FeatureAlign, self).__init__()\n        offset_channels = kernel_size * kernel_size * 2\n        self.conv_offset = nn.Conv2d(\n            4, deformable_groups * offset_channels, 1, bias=False)\n        self.conv_adaption = DeformConv(\n            in_channels,\n            out_channels,\n            kernel_size=kernel_size,\n            padding=(kernel_size - 1) // 2,\n            deformable_groups=deformable_groups)\n        self.relu = nn.ReLU(inplace=True)\n\n    def init_weights(self):\n        normal_init(self.conv_offset, std=0.1)\n        normal_init(self.conv_adaption, std=0.01)\n\n    def forward(self, x, shape):\n        offset = self.conv_offset(shape)\n        x = self.relu(self.conv_adaption(x, offset))\n        return x\n\n\n@HEADS.register_module()\nclass FoveaHead(AnchorFreeHead):\n    \"\"\"FoveaBox: Beyond Anchor-based Object Detector\n    https://arxiv.org/abs/1904.03797\n    \"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 base_edge_list=(16, 32, 64, 128, 256),\n                 scale_ranges=((8, 32), (16, 64), (32, 128), (64, 256), (128,\n                                                                         512)),\n                 sigma=0.4,\n                 with_deform=False,\n                 deformable_groups=4,\n                 **kwargs):\n        self.base_edge_list = base_edge_list\n        self.scale_ranges = scale_ranges\n        self.sigma = sigma\n        self.with_deform = with_deform\n        self.deformable_groups = deformable_groups\n        super().__init__(num_classes, in_channels, **kwargs)\n\n    def _init_layers(self):\n        # box branch\n        super()._init_reg_convs()\n        self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1)\n\n        # cls branch\n        if not self.with_deform:\n            super()._init_cls_convs()\n            self.conv_cls = nn.Conv2d(\n                self.feat_channels, self.cls_out_channels, 3, padding=1)\n        else:\n            self.cls_convs = nn.ModuleList()\n            self.cls_convs.append(\n                ConvModule(\n                    self.feat_channels, (self.feat_channels * 4),\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg,\n                    bias=self.norm_cfg is None))\n            self.cls_convs.append(\n                ConvModule((self.feat_channels * 4), (self.feat_channels * 4),\n                           1,\n                           stride=1,\n                           padding=0,\n                           conv_cfg=self.conv_cfg,\n                           norm_cfg=self.norm_cfg,\n                           bias=self.norm_cfg is None))\n            self.feature_adaption = FeatureAlign(\n                self.feat_channels,\n                self.feat_channels,\n                kernel_size=3,\n                deformable_groups=self.deformable_groups)\n            self.conv_cls = nn.Conv2d(\n                int(self.feat_channels * 4),\n                self.cls_out_channels,\n                3,\n                padding=1)\n\n    def init_weights(self):\n        super().init_weights()\n        if self.with_deform:\n            self.feature_adaption.init_weights()\n\n    def forward_single(self, x):\n        cls_feat = x\n        reg_feat = x\n        for reg_layer in self.reg_convs:\n            reg_feat = reg_layer(reg_feat)\n        bbox_pred = self.conv_reg(reg_feat)\n        if self.with_deform:\n            cls_feat = self.feature_adaption(cls_feat, bbox_pred.exp())\n        for cls_layer in self.cls_convs:\n            cls_feat = cls_layer(cls_feat)\n        cls_score = self.conv_cls(cls_feat)\n        return cls_score, bbox_pred\n\n    def _get_points_single(self, *args, **kwargs):\n        y, x = super()._get_points_single(*args, **kwargs)\n        return y + 0.5, x + 0.5\n\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bbox_list,\n             gt_label_list,\n             img_metas,\n             gt_bboxes_ignore=None):\n        assert len(cls_scores) == len(bbox_preds)\n\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        points = self.get_points(featmap_sizes, bbox_preds[0].dtype,\n                                 bbox_preds[0].device)\n        num_imgs = cls_scores[0].size(0)\n        flatten_cls_scores = [\n            cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n            for cls_score in cls_scores\n        ]\n        flatten_bbox_preds = [\n            bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4)\n            for bbox_pred in bbox_preds\n        ]\n        flatten_cls_scores = torch.cat(flatten_cls_scores)\n        flatten_bbox_preds = torch.cat(flatten_bbox_preds)\n        flatten_labels, flatten_bbox_targets = self.get_targets(\n            gt_bbox_list, gt_label_list, featmap_sizes, points)\n\n        # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n        pos_inds = (\n            (flatten_labels >= 0)\n            & (flatten_labels < self.background_label)).nonzero().view(-1)\n        num_pos = len(pos_inds)\n\n        loss_cls = self.loss_cls(\n            flatten_cls_scores, flatten_labels, avg_factor=num_pos + num_imgs)\n        if num_pos > 0:\n            pos_bbox_preds = flatten_bbox_preds[pos_inds]\n            pos_bbox_targets = flatten_bbox_targets[pos_inds]\n            pos_weights = pos_bbox_targets.new_zeros(\n                pos_bbox_targets.size()) + 1.0\n            loss_bbox = self.loss_bbox(\n                pos_bbox_preds,\n                pos_bbox_targets,\n                pos_weights,\n                avg_factor=num_pos)\n        else:\n            loss_bbox = torch.tensor(\n                0,\n                dtype=flatten_bbox_preds.dtype,\n                device=flatten_bbox_preds.device)\n        return dict(loss_cls=loss_cls, loss_bbox=loss_bbox)\n\n    def get_targets(self, gt_bbox_list, gt_label_list, featmap_sizes, points):\n        label_list, bbox_target_list = multi_apply(\n            self._get_target_single,\n            gt_bbox_list,\n            gt_label_list,\n            featmap_size_list=featmap_sizes,\n            point_list=points)\n        flatten_labels = [\n            torch.cat([\n                labels_level_img.flatten() for labels_level_img in labels_level\n            ]) for labels_level in zip(*label_list)\n        ]\n        flatten_bbox_targets = [\n            torch.cat([\n                bbox_targets_level_img.reshape(-1, 4)\n                for bbox_targets_level_img in bbox_targets_level\n            ]) for bbox_targets_level in zip(*bbox_target_list)\n        ]\n        flatten_labels = torch.cat(flatten_labels)\n        flatten_bbox_targets = torch.cat(flatten_bbox_targets)\n        return flatten_labels, flatten_bbox_targets\n\n    def _get_target_single(self,\n                           gt_bboxes_raw,\n                           gt_labels_raw,\n                           featmap_size_list=None,\n                           point_list=None):\n\n        gt_areas = torch.sqrt((gt_bboxes_raw[:, 2] - gt_bboxes_raw[:, 0]) *\n                              (gt_bboxes_raw[:, 3] - gt_bboxes_raw[:, 1]))\n        label_list = []\n        bbox_target_list = []\n        # for each pyramid, find the cls and box target\n        for base_len, (lower_bound, upper_bound), stride, featmap_size, \\\n            (y, x) in zip(self.base_edge_list, self.scale_ranges,\n                          self.strides, featmap_size_list, point_list):\n            # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n            labels = gt_labels_raw.new_zeros(featmap_size) + self.num_classes\n            bbox_targets = gt_bboxes_raw.new(featmap_size[0], featmap_size[1],\n                                             4) + 1\n            # scale assignment\n            hit_indices = ((gt_areas >= lower_bound) &\n                           (gt_areas <= upper_bound)).nonzero().flatten()\n            if len(hit_indices) == 0:\n                label_list.append(labels)\n                bbox_target_list.append(torch.log(bbox_targets))\n                continue\n            _, hit_index_order = torch.sort(-gt_areas[hit_indices])\n            hit_indices = hit_indices[hit_index_order]\n            gt_bboxes = gt_bboxes_raw[hit_indices, :] / stride\n            gt_labels = gt_labels_raw[hit_indices]\n            half_w = 0.5 * (gt_bboxes[:, 2] - gt_bboxes[:, 0])\n            half_h = 0.5 * (gt_bboxes[:, 3] - gt_bboxes[:, 1])\n            # valid fovea area: left, right, top, down\n            pos_left = torch.ceil(\n                gt_bboxes[:, 0] + (1 - self.sigma) * half_w - 0.5).long().\\\n                clamp(0, featmap_size[1] - 1)\n            pos_right = torch.floor(\n                gt_bboxes[:, 0] + (1 + self.sigma) * half_w - 0.5).long().\\\n                clamp(0, featmap_size[1] - 1)\n            pos_top = torch.ceil(\n                gt_bboxes[:, 1] + (1 - self.sigma) * half_h - 0.5).long().\\\n                clamp(0, featmap_size[0] - 1)\n            pos_down = torch.floor(\n                gt_bboxes[:, 1] + (1 + self.sigma) * half_h - 0.5).long().\\\n                clamp(0, featmap_size[0] - 1)\n            for px1, py1, px2, py2, label, (gt_x1, gt_y1, gt_x2, gt_y2) in \\\n                    zip(pos_left, pos_top, pos_right, pos_down, gt_labels,\n                        gt_bboxes_raw[hit_indices, :]):\n                labels[py1:py2 + 1, px1:px2 + 1] = label\n                bbox_targets[py1:py2 + 1, px1:px2 + 1, 0] = \\\n                    (stride * x[py1:py2 + 1, px1:px2 + 1] - gt_x1) / base_len\n                bbox_targets[py1:py2 + 1, px1:px2 + 1, 1] = \\\n                    (stride * y[py1:py2 + 1, px1:px2 + 1] - gt_y1) / base_len\n                bbox_targets[py1:py2 + 1, px1:px2 + 1, 2] = \\\n                    (gt_x2 - stride * x[py1:py2 + 1, px1:px2 + 1]) / base_len\n                bbox_targets[py1:py2 + 1, px1:px2 + 1, 3] = \\\n                    (gt_y2 - stride * y[py1:py2 + 1, px1:px2 + 1]) / base_len\n            bbox_targets = bbox_targets.clamp(min=1. / 16, max=16.)\n            label_list.append(labels)\n            bbox_target_list.append(torch.log(bbox_targets))\n        return label_list, bbox_target_list\n\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_preds,\n                   img_metas,\n                   cfg=None,\n                   rescale=None):\n        assert len(cls_scores) == len(bbox_preds)\n        num_levels = len(cls_scores)\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        points = self.get_points(\n            featmap_sizes,\n            bbox_preds[0].dtype,\n            bbox_preds[0].device,\n            flatten=True)\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            det_bboxes = self._get_bboxes_single(cls_score_list,\n                                                 bbox_pred_list, featmap_sizes,\n                                                 points, img_shape,\n                                                 scale_factor, cfg, rescale)\n            result_list.append(det_bboxes)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           featmap_sizes,\n                           point_list,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(point_list)\n        det_bboxes = []\n        det_scores = []\n        for cls_score, bbox_pred, featmap_size, stride, base_len, (y, x) \\\n                in zip(cls_scores, bbox_preds, featmap_sizes, self.strides,\n                       self.base_edge_list, point_list):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(\n                -1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4).exp()\n            nms_pre = cfg.get('nms_pre', -1)\n            if (nms_pre > 0) and (scores.shape[0] > nms_pre):\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n                y = y[topk_inds]\n                x = x[topk_inds]\n            x1 = (stride * x - base_len * bbox_pred[:, 0]).\\\n                clamp(min=0, max=img_shape[1] - 1)\n            y1 = (stride * y - base_len * bbox_pred[:, 1]).\\\n                clamp(min=0, max=img_shape[0] - 1)\n            x2 = (stride * x + base_len * bbox_pred[:, 2]).\\\n                clamp(min=0, max=img_shape[1] - 1)\n            y2 = (stride * y + base_len * bbox_pred[:, 3]).\\\n                clamp(min=0, max=img_shape[0] - 1)\n            bboxes = torch.stack([x1, y1, x2, y2], -1)\n            det_bboxes.append(bboxes)\n            det_scores.append(scores)\n        det_bboxes = torch.cat(det_bboxes)\n        if rescale:\n            det_bboxes /= det_bboxes.new_tensor(scale_factor)\n        det_scores = torch.cat(det_scores)\n        padding = det_scores.new_zeros(det_scores.shape[0], 1)\n        # remind that we set FG labels to [0, num_class-1] since mmdet v2.0\n        # BG cat_id: num_class\n        det_scores = torch.cat([det_scores, padding], dim=1)\n        det_bboxes, det_labels = multiclass_nms(det_bboxes, det_scores,\n                                                cfg.score_thr, cfg.nms,\n                                                cfg.max_per_img)\n        return det_bboxes, det_labels\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/free_anchor_retina_head.py",
    "content": "import torch\nimport torch.nn.functional as F\n\nfrom mmdet.core import bbox_overlaps\nfrom ..builder import HEADS\nfrom .retina_head import RetinaHead\n\n\n@HEADS.register_module()\nclass FreeAnchorRetinaHead(RetinaHead):\n    \"\"\"FreeAnchor RetinaHead used in https://arxiv.org/abs/1909.02466.\n\n    Args:\n        num_classes (int): Number of categories excluding the background\n            category.\n        in_channels (int): Number of channels in the input feature map.\n        stacked_convs (int): Number of conv layers in cls and reg tower.\n            Default: 4.\n        conv_cfg (dict): dictionary to construct and config conv layer.\n            Default: None.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n            Default: norm_cfg=dict(type='GN', num_groups=32,\n            requires_grad=True).\n        pre_anchor_topk (int): Number of boxes that be token in each bag.\n        bbox_thr (float): The threshold of the saturated linear function. It is\n            usually the same with the IoU threshold used in NMS.\n        gamma (float): Gamma parameter in focal loss.\n        alpha (float): Alpha parameter in focal loss.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 stacked_convs=4,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 pre_anchor_topk=50,\n                 bbox_thr=0.6,\n                 gamma=2.0,\n                 alpha=0.5,\n                 **kwargs):\n        super(FreeAnchorRetinaHead,\n              self).__init__(num_classes, in_channels, stacked_convs, conv_cfg,\n                             norm_cfg, **kwargs)\n\n        self.pre_anchor_topk = pre_anchor_topk\n        self.bbox_thr = bbox_thr\n        self.gamma = gamma\n        self.alpha = alpha\n\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            gt_bboxes (list[Tensor]): each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.anchor_generator.base_anchors)\n\n        anchor_list, _ = self.get_anchors(featmap_sizes, img_metas)\n        anchors = [torch.cat(anchor) for anchor in anchor_list]\n\n        # concatenate each level\n        cls_scores = [\n            cls.permute(0, 2, 3,\n                        1).reshape(cls.size(0), -1, self.cls_out_channels)\n            for cls in cls_scores\n        ]\n        bbox_preds = [\n            bbox_pred.permute(0, 2, 3, 1).reshape(bbox_pred.size(0), -1, 4)\n            for bbox_pred in bbox_preds\n        ]\n        cls_scores = torch.cat(cls_scores, dim=1)\n        bbox_preds = torch.cat(bbox_preds, dim=1)\n\n        cls_prob = torch.sigmoid(cls_scores)\n        box_prob = []\n        num_pos = 0\n        positive_losses = []\n        for _, (anchors_, gt_labels_, gt_bboxes_, cls_prob_,\n                bbox_preds_) in enumerate(\n                    zip(anchors, gt_labels, gt_bboxes, cls_prob, bbox_preds)):\n\n            with torch.no_grad():\n                if len(gt_bboxes_) == 0:\n                    image_box_prob = torch.zeros(\n                        anchors_.size(0),\n                        self.cls_out_channels).type_as(bbox_preds_)\n                else:\n                    # box_localization: a_{j}^{loc}, shape: [j, 4]\n                    pred_boxes = self.bbox_coder.decode(anchors_, bbox_preds_)\n\n                    # object_box_iou: IoU_{ij}^{loc}, shape: [i, j]\n                    object_box_iou = bbox_overlaps(gt_bboxes_, pred_boxes)\n\n                    # object_box_prob: P{a_{j} -> b_{i}}, shape: [i, j]\n                    t1 = self.bbox_thr\n                    t2 = object_box_iou.max(\n                        dim=1, keepdim=True).values.clamp(min=t1 + 1e-12)\n                    object_box_prob = ((object_box_iou - t1) /\n                                       (t2 - t1)).clamp(\n                                           min=0, max=1)\n\n                    # object_cls_box_prob: P{a_{j} -> b_{i}}, shape: [i, c, j]\n                    num_obj = gt_labels_.size(0)\n                    indices = torch.stack([\n                        torch.arange(num_obj).type_as(gt_labels_), gt_labels_\n                    ],\n                                          dim=0)\n                    object_cls_box_prob = torch.sparse_coo_tensor(\n                        indices, object_box_prob)\n\n                    # image_box_iou: P{a_{j} \\in A_{+}}, shape: [c, j]\n                    \"\"\"\n                    from \"start\" to \"end\" implement:\n                    image_box_iou = torch.sparse.max(object_cls_box_prob,\n                                                     dim=0).t()\n\n                    \"\"\"\n                    # start\n                    box_cls_prob = torch.sparse.sum(\n                        object_cls_box_prob, dim=0).to_dense()\n\n                    indices = torch.nonzero(box_cls_prob, as_tuple=False).t_()\n                    if indices.numel() == 0:\n                        image_box_prob = torch.zeros(\n                            anchors_.size(0),\n                            self.cls_out_channels).type_as(object_box_prob)\n                    else:\n                        nonzero_box_prob = torch.where(\n                            (gt_labels_.unsqueeze(dim=-1) == indices[0]),\n                            object_box_prob[:, indices[1]],\n                            torch.tensor([\n                                0\n                            ]).type_as(object_box_prob)).max(dim=0).values\n\n                        # upmap to shape [j, c]\n                        image_box_prob = torch.sparse_coo_tensor(\n                            indices.flip([0]),\n                            nonzero_box_prob,\n                            size=(anchors_.size(0),\n                                  self.cls_out_channels)).to_dense()\n                    # end\n\n                box_prob.append(image_box_prob)\n\n            # construct bags for objects\n            match_quality_matrix = bbox_overlaps(gt_bboxes_, anchors_)\n            _, matched = torch.topk(\n                match_quality_matrix,\n                self.pre_anchor_topk,\n                dim=1,\n                sorted=False)\n            del match_quality_matrix\n\n            # matched_cls_prob: P_{ij}^{cls}\n            matched_cls_prob = torch.gather(\n                cls_prob_[matched], 2,\n                gt_labels_.view(-1, 1, 1).repeat(1, self.pre_anchor_topk,\n                                                 1)).squeeze(2)\n\n            # matched_box_prob: P_{ij}^{loc}\n            matched_anchors = anchors_[matched]\n            matched_object_targets = self.bbox_coder.encode(\n                matched_anchors,\n                gt_bboxes_.unsqueeze(dim=1).expand_as(matched_anchors))\n            loss_bbox = self.loss_bbox(\n                bbox_preds_[matched],\n                matched_object_targets,\n                reduction_override='none').sum(-1)\n            matched_box_prob = torch.exp(-loss_bbox)\n\n            # positive_losses: {-log( Mean-max(P_{ij}^{cls} * P_{ij}^{loc}) )}\n            num_pos += len(gt_bboxes_)\n            positive_losses.append(\n                self.positive_bag_loss(matched_cls_prob, matched_box_prob))\n        positive_loss = torch.cat(positive_losses).sum() / max(1, num_pos)\n\n        # box_prob: P{a_{j} \\in A_{+}}\n        box_prob = torch.stack(box_prob, dim=0)\n\n        # negative_loss:\n        # \\sum_{j}{ FL((1 - P{a_{j} \\in A_{+}}) * (1 - P_{j}^{bg})) } / n||B||\n        negative_loss = self.negative_bag_loss(cls_prob, box_prob).sum() / max(\n            1, num_pos * self.pre_anchor_topk)\n\n        # avoid the absence of gradients in regression subnet\n        # when no ground-truth in a batch\n        if num_pos == 0:\n            positive_loss = bbox_preds.sum() * 0\n\n        losses = {\n            'positive_bag_loss': positive_loss,\n            'negative_bag_loss': negative_loss\n        }\n        return losses\n\n    def positive_bag_loss(self, matched_cls_prob, matched_box_prob):\n        \"\"\"Compute positive bag loss.\n\n        :math:`-log( Mean-max(P_{ij}^{cls} * P_{ij}^{loc}) )`.\n\n        :math:`P_{ij}^{cls}`: matched_cls_prob, classification probability of matched samples.\n\n        :math:`P_{ij}^{loc}`: matched_box_prob, box probability of matched samples.\n\n        Args:\n            matched_cls_prob (Tensor): Classification probabilty of matched\n                samples in shape (num_gt, pre_anchor_topk).\n            matched_box_prob (Tensor): BBox probability of matched samples,\n                in shape (num_gt, pre_anchor_topk).\n\n        Returns:\n            Tensor: Positive bag loss in shape (num_gt,).\n        \"\"\"  # noqa: E501, W605\n        # bag_prob = Mean-max(matched_prob)\n        matched_prob = matched_cls_prob * matched_box_prob\n        weight = 1 / torch.clamp(1 - matched_prob, 1e-12, None)\n        weight /= weight.sum(dim=1).unsqueeze(dim=-1)\n        bag_prob = (weight * matched_prob).sum(dim=1)\n        # positive_bag_loss = -self.alpha * log(bag_prob)\n        return self.alpha * F.binary_cross_entropy(\n            bag_prob, torch.ones_like(bag_prob), reduction='none')\n\n    def negative_bag_loss(self, cls_prob, box_prob):\n        \"\"\"Compute negative bag loss.\n\n        :math:`FL((1 - P_{a_{j} \\in A_{+}}) * (1 - P_{j}^{bg}))`.\n\n        :math:`P_{a_{j} \\in A_{+}}`: Box_probability of matched samples.\n\n        :math:`P_{j}^{bg}`: Classification probability of negative samples.\n\n        Args:\n            cls_prob (Tensor): Classification probability, in shape\n                (num_img, num_anchors, num_classes).\n            box_prob (Tensor): Box probability, in shape\n                (num_img, num_anchors, num_classes).\n\n        Returns:\n            Tensor: Negative bag loss in shape (num_img, num_anchors, num_classes).\n        \"\"\"  # noqa: E501, W605\n        prob = cls_prob * (1 - box_prob)\n        negative_bag_loss = prob**self.gamma * F.binary_cross_entropy(\n            prob, torch.zeros_like(prob), reduction='none')\n        return (1 - self.alpha) * negative_bag_loss\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/fsaf_head.py",
    "content": "import numpy as np\nimport torch\nfrom mmcv.cnn import normal_init\n\nfrom mmdet.core import (anchor_inside_flags, force_fp32, images_to_levels,\n                        multi_apply, unmap)\nfrom ..builder import HEADS\nfrom ..losses.utils import weight_reduce_loss\nfrom .retina_head import RetinaHead\n\n\n@HEADS.register_module()\nclass FSAFHead(RetinaHead):\n    \"\"\"Anchor-free head used in `FSAF <https://arxiv.org/abs/1903.00621>`_.\n\n    The head contains two subnetworks. The first classifies anchor boxes and\n    the second regresses deltas for the anchors (num_anchors is 1 for anchor-\n    free methods)\n\n    Example:\n        >>> import torch\n        >>> self = FSAFHead(11, 7)\n        >>> x = torch.rand(1, 7, 32, 32)\n        >>> cls_score, bbox_pred = self.forward_single(x)\n        >>> # Each anchor predicts a score for each class except background\n        >>> cls_per_anchor = cls_score.shape[1] / self.num_anchors\n        >>> box_per_anchor = bbox_pred.shape[1] / self.num_anchors\n        >>> assert cls_per_anchor == self.num_classes\n        >>> assert box_per_anchor == 4\n    \"\"\"\n\n    def forward_single(self, x):\n        \"\"\"Forward feature map of a single scale level.\n\n        Args:\n            x (Tensor): Feature map of a single scale level.\n\n        Returns:\n            tuple (Tensor):\n                cls_score (Tensor): Box scores for each scale level\n                    Has shape (N, num_points * num_classes, H, W).\n                bbox_pred (Tensor): Box energies / deltas for each scale\n                    level with shape (N, num_points * 4, H, W).\n        \"\"\"\n        cls_score, bbox_pred = super().forward_single(x)\n        # relu: TBLR encoder only accepts positive bbox_pred\n        return cls_score, self.relu(bbox_pred)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        super(FSAFHead, self).init_weights()\n        # The positive bias in self.retina_reg conv is to prevent predicted \\\n        #  bbox with 0 area\n        normal_init(self.retina_reg, std=0.01, bias=0.25)\n\n    def _get_targets_single(self,\n                            flat_anchors,\n                            valid_flags,\n                            gt_bboxes,\n                            gt_bboxes_ignore,\n                            gt_labels,\n                            img_meta,\n                            label_channels=1,\n                            unmap_outputs=True):\n        \"\"\"Compute regression and classification targets for anchors in\n            a single image.\n\n        Most of the codes are the same with the base class\n          :obj: `AnchorHead`, except that it also collects and returns\n          the matched gt index in the image (from 0 to num_gt-1). If the\n          anchor bbox is not matched to any gt, the corresponding value in\n          pos_gt_inds is -1.\n        \"\"\"\n        inside_flags = anchor_inside_flags(flat_anchors, valid_flags,\n                                           img_meta['img_shape'][:2],\n                                           self.train_cfg.allowed_border)\n        if not inside_flags.any():\n            return (None, ) * 7\n        # Assign gt and sample anchors\n        anchors = flat_anchors[inside_flags.type(torch.bool), :]\n        assign_result = self.assigner.assign(\n            anchors, gt_bboxes, gt_bboxes_ignore,\n            None if self.sampling else gt_labels)\n\n        sampling_result = self.sampler.sample(assign_result, anchors,\n                                              gt_bboxes)\n\n        num_valid_anchors = anchors.shape[0]\n        bbox_targets = torch.zeros_like(anchors)\n        bbox_weights = torch.zeros_like(anchors)\n        labels = anchors.new_full((num_valid_anchors, ),\n                                  self.background_label,\n                                  dtype=torch.long)\n        label_weights = anchors.new_zeros((num_valid_anchors, label_channels),\n                                          dtype=torch.float)\n        pos_gt_inds = anchors.new_full((num_valid_anchors, ),\n                                       -1,\n                                       dtype=torch.long)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n\n        if len(pos_inds) > 0:\n            if not self.reg_decoded_bbox:\n                pos_bbox_targets = self.bbox_coder.encode(\n                    sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes)\n            else:\n                pos_bbox_targets = sampling_result.pos_gt_bboxes\n            bbox_targets[pos_inds, :] = pos_bbox_targets\n            bbox_weights[pos_inds, :] = 1.0\n            # The assigned gt_index for each anchor. (0-based)\n            pos_gt_inds[pos_inds] = sampling_result.pos_assigned_gt_inds\n            if gt_labels is None:\n                # only rpn gives gt_labels as None, this time FG is 1\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[\n                    sampling_result.pos_assigned_gt_inds]\n            if self.train_cfg.pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = self.train_cfg.pos_weight\n\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # shadowed_labels is a tensor composed of tuples\n        #  (anchor_inds, class_label) that indicate those anchors lying in the\n        #  outer region of a gt or overlapped by another gt with a smaller\n        #  area.\n        #\n        # Therefore, only the shadowed labels are ignored for loss calculation.\n        # the key `shadowed_labels` is defined in :obj:`CenterRegionAssigner`\n        shadowed_labels = assign_result.get_extra_property('shadowed_labels')\n        if shadowed_labels is not None and shadowed_labels.numel():\n            if len(shadowed_labels.shape) == 2:\n                idx_, label_ = shadowed_labels[:, 0], shadowed_labels[:, 1]\n                assert (labels[idx_] != label_).all(), \\\n                    'One label cannot be both positive and ignored'\n                # If background_label is 0. Then all labels increase by 1\n                label_ += int(self.background_label == 0)\n                label_weights[idx_, label_] = 0\n            else:\n                label_weights[shadowed_labels] = 0\n\n        # map up to original set of anchors\n        if unmap_outputs:\n            num_total_anchors = flat_anchors.size(0)\n            labels = unmap(labels, num_total_anchors, inside_flags)\n            label_weights = unmap(label_weights, num_total_anchors,\n                                  inside_flags)\n            bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)\n            pos_gt_inds = unmap(\n                pos_gt_inds, num_total_anchors, inside_flags, fill=-1)\n\n        return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,\n                neg_inds, sampling_result, pos_gt_inds)\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute loss of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_points * num_classes, H, W).\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_points * 4, H, W).\n            gt_bboxes (list[Tensor]): each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        for i in range(len(bbox_preds)):  # loop over fpn level\n            # avoid 0 area of the predicted bbox\n            bbox_preds[i] = bbox_preds[i].clamp(min=1e-4)\n        # TODO: It may directly use the base-class loss function.\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n        batch_size = len(gt_bboxes)\n        device = cls_scores[0].device\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=label_channels)\n        if cls_reg_targets is None:\n            return None\n        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,\n         num_total_pos, num_total_neg,\n         pos_assigned_gt_inds_list) = cls_reg_targets\n\n        num_gts = np.array(list(map(len, gt_labels)))\n        num_total_samples = (\n            num_total_pos + num_total_neg if self.sampling else num_total_pos)\n        # anchor number of multi levels\n        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]\n        # concat all level anchors and flags to a single tensor\n        concat_anchor_list = []\n        for i in range(len(anchor_list)):\n            concat_anchor_list.append(torch.cat(anchor_list[i]))\n        all_anchor_list = images_to_levels(concat_anchor_list,\n                                           num_level_anchors)\n        losses_cls, losses_bbox = multi_apply(\n            self.loss_single,\n            cls_scores,\n            bbox_preds,\n            all_anchor_list,\n            labels_list,\n            label_weights_list,\n            bbox_targets_list,\n            bbox_weights_list,\n            num_total_samples=num_total_samples)\n\n        # `pos_assigned_gt_inds_list` (length: fpn_levels) stores the assigned\n        # gt index of each anchor bbox in each fpn level.\n        cum_num_gts = list(np.cumsum(num_gts))  # length of batch_size\n        for i, assign in enumerate(pos_assigned_gt_inds_list):\n            # loop over fpn levels\n            for j in range(1, batch_size):\n                # loop over batch size\n                # Convert gt indices in each img to those in the batch\n                assign[j][assign[j] >= 0] += int(cum_num_gts[j - 1])\n            pos_assigned_gt_inds_list[i] = assign.flatten()\n            labels_list[i] = labels_list[i].flatten()\n        num_gts = sum(map(len, gt_labels))  # total number of gt in the batch\n        # The unique label index of each gt in the batch\n        label_sequence = torch.arange(num_gts, device=device)\n        # Collect the average loss of each gt in each level\n        with torch.no_grad():\n            loss_levels, = multi_apply(\n                self.collect_loss_level_single,\n                losses_cls,\n                losses_bbox,\n                pos_assigned_gt_inds_list,\n                labels_seq=label_sequence)\n            # Shape: (fpn_levels, num_gts). Loss of each gt at each fpn level\n            loss_levels = torch.stack(loss_levels, dim=0)\n            # Locate the best fpn level for loss back-propagation\n            if loss_levels.numel() == 0:  # zero gt\n                argmin = loss_levels.new_empty((num_gts, ), dtype=torch.long)\n            else:\n                _, argmin = loss_levels.min(dim=0)\n\n        # Reweight the loss of each (anchor, label) pair, so that only those\n        #  at the best gt level are back-propagated.\n        losses_cls, losses_bbox, pos_inds = multi_apply(\n            self.reweight_loss_single,\n            losses_cls,\n            losses_bbox,\n            pos_assigned_gt_inds_list,\n            labels_list,\n            list(range(len(losses_cls))),\n            min_levels=argmin)\n        num_pos = torch.cat(pos_inds, 0).sum().float()\n        acc = self.calculate_accuracy(cls_scores, labels_list, pos_inds)\n\n        if num_pos == 0:  # No gt\n            avg_factor = num_pos + float(num_total_neg)\n        else:\n            avg_factor = num_pos\n        for i in range(len(losses_cls)):\n            losses_cls[i] /= avg_factor\n            losses_bbox[i] /= avg_factor\n        return dict(\n            loss_cls=losses_cls,\n            loss_bbox=losses_bbox,\n            num_pos=num_pos / batch_size,\n            accuracy=acc)\n\n    def calculate_accuracy(self, cls_scores, labels_list, pos_inds):\n        \"\"\"Calculate accuracy of the classification prediction.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            labels_list: (list[Tensor]): Labels for each scale level.\n            pos_inds (list[Tensor]): Positive inds for each scale level.\n\n        Returns:\n            Tensor: Accuracy.\n        \"\"\"\n        with torch.no_grad():\n            num_pos = torch.cat(pos_inds, 0).sum().float().clamp(min=1e-3)\n            num_class = cls_scores[0].size(1)\n            scores = [\n                cls.permute(0, 2, 3, 1).reshape(-1, num_class)[pos]\n                for cls, pos in zip(cls_scores, pos_inds)\n            ]\n            labels = [\n                label.reshape(-1)[pos]\n                for label, pos in zip(labels_list, pos_inds)\n            ]\n\n            def argmax(x):\n                return x.argmax(1) if x.numel() > 0 else -100\n\n            num_correct = sum([(argmax(score) == label).sum()\n                               for score, label in zip(scores, labels)])\n            return num_correct.float() / num_pos\n\n    def collect_loss_level_single(self, cls_loss, reg_loss, assigned_gt_inds,\n                                  labels_seq):\n        \"\"\"Get the average loss in each FPN level w.r.t. each gt label\n\n        Args:\n            cls_loss (Tensor): Classification loss of each feature map pixel,\n              shape (num_anchor, num_class)\n            reg_loss (Tensor): Regression loss of each feature map pixel,\n              shape (num_anchor, 4)\n            assigned_gt_inds (Tensor): It indicates which gt the prior is\n              assigned to (0-based, -1: no assignment). shape (num_anchor),\n            labels_seq: The rank of labels. shape (num_gt)\n\n        Returns:\n            shape: (num_gt), average loss of each gt in this level\n        \"\"\"\n        if len(reg_loss.shape) == 2:  # iou loss has shape (num_prior, 4)\n            reg_loss = reg_loss.sum(dim=-1)  # sum loss in tblr dims\n        if len(cls_loss.shape) == 2:\n            cls_loss = cls_loss.sum(dim=-1)  # sum loss in class dims\n        loss = cls_loss + reg_loss\n        assert loss.size(0) == assigned_gt_inds.size(0)\n        # Default loss value is 1e6 for a layer where no anchor is positive\n        #  to ensure it will not be chosen to back-propagate gradient\n        losses_ = loss.new_full(labels_seq.shape, 1e6)\n        for i, l in enumerate(labels_seq):\n            match = assigned_gt_inds == l\n            if match.any():\n                losses_[i] = loss[match].mean()\n        return losses_,\n\n    def reweight_loss_single(self, cls_loss, reg_loss, assigned_gt_inds,\n                             labels, level, min_levels):\n        \"\"\"Reweight loss values at each level.\n\n        Reassign loss values at each level by masking those where the\n        pre-calculated loss is too large. Then return the reduced losses.\n\n        Args:\n            cls_loss (Tensor): Element-wise classification loss.\n              Shape: (num_anchors, num_classes)\n            reg_loss (Tensor): Element-wise regression loss.\n              Shape: (num_anchors, 4)\n            assigned_gt_inds (Tensor): The gt indices that each anchor bbox\n              is assigned to. -1 denotes a negative anchor, otherwise it is the\n              gt index (0-based). Shape: (num_anchors, ),\n            labels (Tensor): Label assigned to anchors. Shape: (num_anchors, ).\n            level (int): The current level index in the pyramid\n              (0-4 for RetinaNet)\n            min_levels (Tensor): The best-matching level for each gt.\n              Shape: (num_gts, ),\n\n        Returns:\n            tuple:\n                - cls_loss: Reduced corrected classification loss. Scalar.\n                - reg_loss: Reduced corrected regression loss. Scalar.\n                - pos_flags (Tensor): Corrected bool tensor indicating the\n                  final postive anchors. Shape: (num_anchors, ).\n        \"\"\"\n        loc_weight = torch.ones_like(reg_loss)\n        cls_weight = torch.ones_like(cls_loss)\n        pos_flags = assigned_gt_inds >= 0  # positive pixel flag\n        pos_indices = torch.nonzero(pos_flags, as_tuple=False).flatten()\n\n        if pos_flags.any():  # pos pixels exist\n            pos_assigned_gt_inds = assigned_gt_inds[pos_flags]\n            zeroing_indices = (min_levels[pos_assigned_gt_inds] != level)\n            neg_indices = pos_indices[zeroing_indices]\n\n            if neg_indices.numel():\n                pos_flags[neg_indices] = 0\n                loc_weight[neg_indices] = 0\n                # Only the weight corresponding to the label is\n                #  zeroed out if not selected\n                zeroing_labels = labels[neg_indices]\n                assert (zeroing_labels >= 0).all()\n                cls_weight[neg_indices, zeroing_labels] = 0\n\n        # Weighted loss for both cls and reg loss\n        cls_loss = weight_reduce_loss(cls_loss, cls_weight, reduction='sum')\n        reg_loss = weight_reduce_loss(reg_loss, loc_weight, reduction='sum')\n\n        return cls_loss, reg_loss, pos_flags\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/ga_retina_head.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.ops import MaskedConv2d\nfrom ..builder import HEADS\nfrom .guided_anchor_head import FeatureAdaption, GuidedAnchorHead\n\n\n@HEADS.register_module()\nclass GARetinaHead(GuidedAnchorHead):\n    \"\"\"Guided-Anchor-based RetinaNet head.\"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 stacked_convs=4,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 **kwargs):\n        self.stacked_convs = stacked_convs\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        super(GARetinaHead, self).__init__(num_classes, in_channels, **kwargs)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n\n        self.conv_loc = nn.Conv2d(self.feat_channels, 1, 1)\n        self.conv_shape = nn.Conv2d(self.feat_channels, self.num_anchors * 2,\n                                    1)\n        self.feature_adaption_cls = FeatureAdaption(\n            self.feat_channels,\n            self.feat_channels,\n            kernel_size=3,\n            deformable_groups=self.deformable_groups)\n        self.feature_adaption_reg = FeatureAdaption(\n            self.feat_channels,\n            self.feat_channels,\n            kernel_size=3,\n            deformable_groups=self.deformable_groups)\n        self.retina_cls = MaskedConv2d(\n            self.feat_channels,\n            self.num_anchors * self.cls_out_channels,\n            3,\n            padding=1)\n        self.retina_reg = MaskedConv2d(\n            self.feat_channels, self.num_anchors * 4, 3, padding=1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the layer.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n\n        self.feature_adaption_cls.init_weights()\n        self.feature_adaption_reg.init_weights()\n\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.conv_loc, std=0.01, bias=bias_cls)\n        normal_init(self.conv_shape, std=0.01)\n        normal_init(self.retina_cls, std=0.01, bias=bias_cls)\n        normal_init(self.retina_reg, std=0.01)\n\n    def forward_single(self, x):\n        \"\"\"Forward feature map of a single scale level.\"\"\"\n        cls_feat = x\n        reg_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            reg_feat = reg_conv(reg_feat)\n\n        loc_pred = self.conv_loc(cls_feat)\n        shape_pred = self.conv_shape(reg_feat)\n\n        cls_feat = self.feature_adaption_cls(cls_feat, shape_pred)\n        reg_feat = self.feature_adaption_reg(reg_feat, shape_pred)\n\n        if not self.training:\n            mask = loc_pred.sigmoid()[0] >= self.loc_filter_thr\n        else:\n            mask = None\n        cls_score = self.retina_cls(cls_feat, mask)\n        bbox_pred = self.retina_reg(reg_feat, mask)\n        return cls_score, bbox_pred, shape_pred, loc_pred\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/ga_rpn_head.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import normal_init\n\nfrom mmdet.ops import nms\nfrom ..builder import HEADS\nfrom .guided_anchor_head import GuidedAnchorHead\nfrom .rpn_test_mixin import RPNTestMixin\n\n\n@HEADS.register_module()\nclass GARPNHead(RPNTestMixin, GuidedAnchorHead):\n    \"\"\"Guided-Anchor-based RPN head.\"\"\"\n\n    def __init__(self, in_channels, **kwargs):\n        super(GARPNHead, self).__init__(\n            1, in_channels, background_label=0, **kwargs)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.rpn_conv = nn.Conv2d(\n            self.in_channels, self.feat_channels, 3, padding=1)\n        super(GARPNHead, self)._init_layers()\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        normal_init(self.rpn_conv, std=0.01)\n        super(GARPNHead, self).init_weights()\n\n    def forward_single(self, x):\n        \"\"\"Forward feature of a single scale level.\"\"\"\n\n        x = self.rpn_conv(x)\n        x = F.relu(x, inplace=True)\n        (cls_score, bbox_pred, shape_pred,\n         loc_pred) = super(GARPNHead, self).forward_single(x)\n        return cls_score, bbox_pred, shape_pred, loc_pred\n\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             shape_preds,\n             loc_preds,\n             gt_bboxes,\n             img_metas,\n             gt_bboxes_ignore=None):\n        losses = super(GARPNHead, self).loss(\n            cls_scores,\n            bbox_preds,\n            shape_preds,\n            loc_preds,\n            gt_bboxes,\n            None,\n            img_metas,\n            gt_bboxes_ignore=gt_bboxes_ignore)\n        return dict(\n            loss_rpn_cls=losses['loss_cls'],\n            loss_rpn_bbox=losses['loss_bbox'],\n            loss_anchor_shape=losses['loss_shape'],\n            loss_anchor_loc=losses['loss_loc'])\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           mlvl_anchors,\n                           mlvl_masks,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        cfg = self.test_cfg if cfg is None else cfg\n        mlvl_proposals = []\n        for idx in range(len(cls_scores)):\n            rpn_cls_score = cls_scores[idx]\n            rpn_bbox_pred = bbox_preds[idx]\n            anchors = mlvl_anchors[idx]\n            mask = mlvl_masks[idx]\n            assert rpn_cls_score.size()[-2:] == rpn_bbox_pred.size()[-2:]\n            # if no location is kept, end.\n            if mask.sum() == 0:\n                continue\n            rpn_cls_score = rpn_cls_score.permute(1, 2, 0)\n            if self.use_sigmoid_cls:\n                rpn_cls_score = rpn_cls_score.reshape(-1)\n                scores = rpn_cls_score.sigmoid()\n            else:\n                rpn_cls_score = rpn_cls_score.reshape(-1, 2)\n                # remind that we set FG labels to [0, num_class-1]\n                # since mmdet v2.0\n                # BG cat_id: num_class\n                scores = rpn_cls_score.softmax(dim=1)[:, :-1]\n            # filter scores, bbox_pred w.r.t. mask.\n            # anchors are filtered in get_anchors() beforehand.\n            scores = scores[mask]\n            rpn_bbox_pred = rpn_bbox_pred.permute(1, 2, 0).reshape(-1,\n                                                                   4)[mask, :]\n            if scores.dim() == 0:\n                rpn_bbox_pred = rpn_bbox_pred.unsqueeze(0)\n                anchors = anchors.unsqueeze(0)\n                scores = scores.unsqueeze(0)\n            # filter anchors, bbox_pred, scores w.r.t. scores\n            if cfg.nms_pre > 0 and scores.shape[0] > cfg.nms_pre:\n                _, topk_inds = scores.topk(cfg.nms_pre)\n                rpn_bbox_pred = rpn_bbox_pred[topk_inds, :]\n                anchors = anchors[topk_inds, :]\n                scores = scores[topk_inds]\n            # get proposals w.r.t. anchors and rpn_bbox_pred\n            proposals = self.bbox_coder.decode(\n                anchors, rpn_bbox_pred, max_shape=img_shape)\n            # filter out too small bboxes\n            if cfg.min_bbox_size > 0:\n                w = proposals[:, 2] - proposals[:, 0]\n                h = proposals[:, 3] - proposals[:, 1]\n                valid_inds = torch.nonzero(\n                    (w >= cfg.min_bbox_size) & (h >= cfg.min_bbox_size),\n                    as_tuple=False).squeeze()\n                proposals = proposals[valid_inds, :]\n                scores = scores[valid_inds]\n            proposals = torch.cat([proposals, scores.unsqueeze(-1)], dim=-1)\n            # NMS in current level\n            proposals, _ = nms(proposals, cfg.nms_thr)\n            proposals = proposals[:cfg.nms_post, :]\n            mlvl_proposals.append(proposals)\n        proposals = torch.cat(mlvl_proposals, 0)\n        if cfg.nms_across_levels:\n            # NMS across multi levels\n            proposals, _ = nms(proposals, cfg.nms_thr)\n            proposals = proposals[:cfg.max_num, :]\n        else:\n            scores = proposals[:, 4]\n            num = min(cfg.max_num, proposals.shape[0])\n            _, topk_inds = scores.topk(num)\n            proposals = proposals[topk_inds, :]\n        return proposals\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/gfl_head.py",
    "content": "import torch\nimport torch.distributed as dist\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, Scale, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (anchor_inside_flags, bbox2distance, bbox_overlaps,\n                        build_assigner, build_sampler, distance2bbox,\n                        force_fp32, images_to_levels, multi_apply,\n                        multiclass_nms, unmap)\nfrom ..builder import HEADS, build_loss\nfrom .anchor_head import AnchorHead\n\n\ndef reduce_mean(tensor):\n    if not (dist.is_available() and dist.is_initialized()):\n        return tensor\n    tensor = tensor.clone()\n    dist.all_reduce(tensor.div_(dist.get_world_size()), op=dist.ReduceOp.SUM)\n    return tensor\n\n\nclass Integral(nn.Module):\n    \"\"\"A fixed layer for calculating integral result from distribution\n\n    This layer calculates the target location by :math: `sum{P(y_i) * y_i}`,\n    P(y_i) denotes the softmax vector that represents the discrete distribution\n    y_i denotes the discrete set, usually {0, 1, 2, ..., reg_max}\n\n    Args:\n        reg_max (int): The maximal value of the discrete set. Default: 16. You\n            may want to reset it according to your new dataset or related\n            settings.\n    \"\"\"\n\n    def __init__(self, reg_max=16):\n        super(Integral, self).__init__()\n        self.reg_max = reg_max\n        self.register_buffer('project',\n                             torch.linspace(0, self.reg_max, self.reg_max + 1))\n\n    def forward(self, x):\n        \"\"\"Forward feature from the regression head to get integral result of\n        bounding box location.\n\n        Args:\n            x (Tensor): Features of the regression head, shape (N, 4*(n+1)),\n                n is self.reg_max.\n\n        Returns:\n            x (Tensor): Integral result of box locations, i.e., distance\n                offsets from the box center in four directions, shape (N, 4).\n        \"\"\"\n        x = F.softmax(x.reshape(-1, self.reg_max + 1), dim=1)\n        x = F.linear(x, self.project).reshape(-1, 4)\n        return x\n\n\n@HEADS.register_module()\nclass GFLHead(AnchorHead):\n    \"\"\"\n    Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes\n    for Dense Object Detection\n\n    GFL head structure is similar with ATSS, however GFL uses\n    1) joint representation for classification and localization quality, and\n    2) flexible General distribution for bounding box locations,\n    which are supervised by\n    Quality Focal Loss (QFL) and Distribution Focal Loss (DFL), respectively\n\n    https://arxiv.org/abs/2006.04388\n\n    Args:\n        num_classes (int): Number of categories excluding the background\n            category.\n        in_channels (int): Number of channels in the input feature map.\n        stacked_convs (int): Number of conv layers in cls and reg tower.\n            Default: 4.\n        conv_cfg (dict): dictionary to construct and config conv layer.\n            Default: None.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n            Default: dict(type='GN', num_groups=32, requires_grad=True).\n        loss_qfl (dict): Config of Quality Focal Loss (QFL).\n        reg_max (int): Max value of integral set :math: `{0, ..., reg_max}`\n            in QFL setting. Default: 16.\n    Example:\n        >>> self = GFLHead(11, 7)\n        >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]]\n        >>> cls_quality_score, bbox_pred = self.forward(feats)\n        >>> assert len(cls_quality_score) == len(self.scales)\n    \"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 stacked_convs=4,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='GN', num_groups=32, requires_grad=True),\n                 loss_dfl=dict(type='DistributionFocalLoss', loss_weight=0.25),\n                 reg_max=16,\n                 **kwargs):\n        self.stacked_convs = stacked_convs\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.reg_max = reg_max\n        super(GFLHead, self).__init__(num_classes, in_channels, **kwargs)\n\n        self.sampling = False\n        if self.train_cfg:\n            self.assigner = build_assigner(self.train_cfg.assigner)\n            # SSD sampling=False so use PseudoSampler\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n\n        self.integral = Integral(self.reg_max)\n        self.loss_dfl = build_loss(loss_dfl)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        assert self.num_anchors == 1, 'anchor free version'\n        self.gfl_cls = nn.Conv2d(\n            self.feat_channels, self.cls_out_channels, 3, padding=1)\n        self.gfl_reg = nn.Conv2d(\n            self.feat_channels, 4 * (self.reg_max + 1), 3, padding=1)\n        self.scales = nn.ModuleList(\n            [Scale(1.0) for _ in self.anchor_generator.strides])\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.gfl_cls, std=0.01, bias=bias_cls)\n        normal_init(self.gfl_reg, std=0.01)\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple: Usually a tuple of classification scores and bbox prediction\n                cls_scores (list[Tensor]): Classification and quality (IoU)\n                    joint scores for all scale levels, each is a 4D-tensor,\n                    the channel number is num_classes.\n                bbox_preds (list[Tensor]): Box distribution logits for all\n                    scale levels, each is a 4D-tensor, the channel number is\n                    4*(n+1), n is max value of integral set.\n        \"\"\"\n        return multi_apply(self.forward_single, feats, self.scales)\n\n    def forward_single(self, x, scale):\n        \"\"\"Forward feature of a single scale level.\n\n        Args:\n            x (Tensor): Features of a single scale level.\n            scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize\n                the bbox prediction.\n\n        Returns:\n            tuple:\n                cls_score (Tensor): Cls and quality joint scores for a single\n                    scale level the channel number is num_classes.\n                bbox_pred (Tensor): Box distribution logits for a single scale\n                    level, the channel number is 4*(n+1), n is max value of\n                    integral set.\n        \"\"\"\n        cls_feat = x\n        reg_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            reg_feat = reg_conv(reg_feat)\n        cls_score = self.gfl_cls(cls_feat)\n        bbox_pred = scale(self.gfl_reg(reg_feat)).float()\n        return cls_score, bbox_pred\n\n    def anchor_center(self, anchors):\n        \"\"\"Get anchor centers from anchors.\n\n        Args:\n            anchors (Tensor): Anchor list with shape (N, 4), \"xyxy\" format.\n\n        Returns:\n            Tensor: Anchor centers with shape (N, 2), \"xy\" format.\n        \"\"\"\n        anchors_cx = (anchors[:, 2] + anchors[:, 0]) / 2\n        anchors_cy = (anchors[:, 3] + anchors[:, 1]) / 2\n        return torch.stack([anchors_cx, anchors_cy], dim=-1)\n\n    def loss_single(self, anchors, cls_score, bbox_pred, labels, label_weights,\n                    bbox_targets, stride, num_total_samples):\n        \"\"\"Compute loss of a single scale level.\n\n        Args:\n            anchors (Tensor): Box reference for each scale level with shape\n                (N, num_total_anchors, 4).\n            cls_score (Tensor): Cls and quality joint scores for each scale\n                level has shape (N, num_classes, H, W).\n            bbox_pred (Tensor): Box distribution logits for each scale\n                level with shape (N, 4*(n+1), H, W), n is max value of integral\n                set.\n            labels (Tensor): Labels of each anchors with shape\n                (N, num_total_anchors).\n            label_weights (Tensor): Label weights of each anchor with shape\n                (N, num_total_anchors)\n            bbox_targets (Tensor): BBox regression targets of each anchor wight\n                shape (N, num_total_anchors, 4).\n            stride (tuple): Stride in this scale level.\n            num_total_samples (int): Number of positive samples that is\n                reduced over all GPUs.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        assert stride[0] == stride[1], 'h stride is not equal to w stride!'\n        anchors = anchors.reshape(-1, 4)\n        cls_score = cls_score.permute(0, 2, 3,\n                                      1).reshape(-1, self.cls_out_channels)\n        bbox_pred = bbox_pred.permute(0, 2, 3,\n                                      1).reshape(-1, 4 * (self.reg_max + 1))\n        bbox_targets = bbox_targets.reshape(-1, 4)\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n\n        # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n        bg_class_ind = self.num_classes\n        pos_inds = ((labels >= 0)\n                    & (labels < bg_class_ind)).nonzero().squeeze(1)\n        score = label_weights.new_zeros(labels.shape)\n\n        if len(pos_inds) > 0:\n            pos_bbox_targets = bbox_targets[pos_inds]\n            pos_bbox_pred = bbox_pred[pos_inds]\n            pos_anchors = anchors[pos_inds]\n            pos_anchor_centers = self.anchor_center(pos_anchors) / stride[0]\n\n            weight_targets = cls_score.detach().sigmoid()\n            weight_targets = weight_targets.max(dim=1)[0][pos_inds]\n            pos_bbox_pred_corners = self.integral(pos_bbox_pred)\n            pos_decode_bbox_pred = distance2bbox(pos_anchor_centers,\n                                                 pos_bbox_pred_corners)\n            pos_decode_bbox_targets = pos_bbox_targets / stride[0]\n            score[pos_inds] = bbox_overlaps(\n                pos_decode_bbox_pred.detach(),\n                pos_decode_bbox_targets,\n                is_aligned=True)\n            pred_corners = pos_bbox_pred.reshape(-1, self.reg_max + 1)\n            target_corners = bbox2distance(pos_anchor_centers,\n                                           pos_decode_bbox_targets,\n                                           self.reg_max).reshape(-1)\n\n            # regression loss\n            loss_bbox = self.loss_bbox(\n                pos_decode_bbox_pred,\n                pos_decode_bbox_targets,\n                weight=weight_targets,\n                avg_factor=1.0)\n\n            # dfl loss\n            loss_dfl = self.loss_dfl(\n                pred_corners,\n                target_corners,\n                weight=weight_targets[:, None].expand(-1, 4).reshape(-1),\n                avg_factor=4.0)\n        else:\n            loss_bbox = bbox_pred.sum() * 0\n            loss_dfl = bbox_pred.sum() * 0\n            weight_targets = torch.tensor(0).cuda()\n\n        # cls (qfl) loss\n        loss_cls = self.loss_cls(\n            cls_score, (labels, score),\n            weight=label_weights,\n            avg_factor=num_total_samples)\n\n        return loss_cls, loss_bbox, loss_dfl, weight_targets.sum()\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Cls and quality scores for each scale\n                level has shape (N, num_classes, H, W).\n            bbox_preds (list[Tensor]): Box distribution logits for each scale\n                level with shape (N, 4*(n+1), H, W), n is max value of integral\n                set.\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (list[Tensor] | None): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n\n        device = cls_scores[0].device\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1\n\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=label_channels)\n        if cls_reg_targets is None:\n            return None\n\n        (anchor_list, labels_list, label_weights_list, bbox_targets_list,\n         bbox_weights_list, num_total_pos, num_total_neg) = cls_reg_targets\n\n        num_total_samples = reduce_mean(\n            torch.tensor(num_total_pos).cuda()).item()\n        num_total_samples = max(num_total_samples, 1.0)\n\n        losses_cls, losses_bbox, losses_dfl,\\\n            avg_factor = multi_apply(\n                self.loss_single,\n                anchor_list,\n                cls_scores,\n                bbox_preds,\n                labels_list,\n                label_weights_list,\n                bbox_targets_list,\n                self.anchor_generator.strides,\n                num_total_samples=num_total_samples)\n\n        avg_factor = sum(avg_factor)\n        avg_factor = reduce_mean(avg_factor).item()\n        losses_bbox = list(map(lambda x: x / avg_factor, losses_bbox))\n        losses_dfl = list(map(lambda x: x / avg_factor, losses_dfl))\n        return dict(\n            loss_cls=losses_cls, loss_bbox=losses_bbox, loss_dfl=losses_dfl)\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           mlvl_anchors,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        \"\"\"Transform outputs for a single batch item into labeled boxes.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for a single scale level\n                has shape (num_classes, H, W).\n            bbox_preds (list[Tensor]): Box distribution logits for a single\n                scale level with shape (4*(n+1), H, W), n is max value of\n                integral set.\n            mlvl_anchors (list[Tensor]): Box reference for a single scale level\n                with shape (num_total_anchors, 4).\n            img_shape (tuple[int]): Shape of the input image,\n                (height, width, 3).\n            scale_factor (ndarray): Scale factor of the image arange as\n                (w_scale, h_scale, w_scale, h_scale).\n            cfg (mmcv.Config | None): Test / postprocessing configuration,\n                if None, test_cfg would be used.\n            rescale (bool): If True, return boxes in original image space.\n                Default: False.\n\n        Returns:\n            tuple(Tensor):\n                det_bboxes (Tensor): Bbox predictions in shape (N, 5), where\n                    the first 4 columns are bounding box positions\n                    (tl_x, tl_y, br_x, br_y) and the 5-th column is a score\n                    between 0 and 1.\n                det_labels (Tensor): A (N,) tensor where each item is the\n                    predicted class label of the corresponding box.\n        \"\"\"\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_anchors)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        for cls_score, bbox_pred, stride, anchors in zip(\n                cls_scores, bbox_preds, self.anchor_generator.strides,\n                mlvl_anchors):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            assert stride[0] == stride[1]\n\n            scores = cls_score.permute(1, 2, 0).reshape(\n                -1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0)\n            bbox_pred = self.integral(bbox_pred) * stride[0]\n\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                anchors = anchors[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n\n            bboxes = distance2bbox(\n                self.anchor_center(anchors), bbox_pred, max_shape=img_shape)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n\n        mlvl_scores = torch.cat(mlvl_scores)\n        # Add a dummy background class to the backend when using sigmoid\n        # remind that we set FG labels to [0, num_class-1] since mmdet v2.0\n        # BG cat_id: num_class\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n\n        det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores,\n                                                cfg.score_thr, cfg.nms,\n                                                cfg.max_per_img)\n        return det_bboxes, det_labels\n\n    def get_targets(self,\n                    anchor_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    label_channels=1,\n                    unmap_outputs=True):\n        \"\"\"Get targets for GFL head.\n\n        This method is almost the same as `AnchorHead.get_targets()`. Besides\n        returning the targets as the parent method does, it also returns the\n        anchors as the first element of the returned tuple.\n        \"\"\"\n        num_imgs = len(img_metas)\n        assert len(anchor_list) == len(valid_flag_list) == num_imgs\n\n        # anchor number of multi levels\n        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]\n        num_level_anchors_list = [num_level_anchors] * num_imgs\n\n        # concat all level anchors and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(anchor_list[i]) == len(valid_flag_list[i])\n            anchor_list[i] = torch.cat(anchor_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_anchors, all_labels, all_label_weights, all_bbox_targets,\n         all_bbox_weights, pos_inds_list, neg_inds_list) = multi_apply(\n             self._get_target_single,\n             anchor_list,\n             valid_flag_list,\n             num_level_anchors_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             gt_labels_list,\n             img_metas,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid anchors\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled anchors of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        # split targets to a list w.r.t. multiple levels\n        anchors_list = images_to_levels(all_anchors, num_level_anchors)\n        labels_list = images_to_levels(all_labels, num_level_anchors)\n        label_weights_list = images_to_levels(all_label_weights,\n                                              num_level_anchors)\n        bbox_targets_list = images_to_levels(all_bbox_targets,\n                                             num_level_anchors)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                             num_level_anchors)\n        return (anchors_list, labels_list, label_weights_list,\n                bbox_targets_list, bbox_weights_list, num_total_pos,\n                num_total_neg)\n\n    def _get_target_single(self,\n                           flat_anchors,\n                           valid_flags,\n                           num_level_anchors,\n                           gt_bboxes,\n                           gt_bboxes_ignore,\n                           gt_labels,\n                           img_meta,\n                           label_channels=1,\n                           unmap_outputs=True):\n        \"\"\"Compute regression, classification targets for anchors in a single\n            image.\n\n        Args:\n            flat_anchors (Tensor): Multi-level anchors of the image, which are\n                concatenated into a single tensor of shape (num_anchors, 4)\n            valid_flags (Tensor): Multi level valid flags of the image,\n                which are concatenated into a single tensor of\n                    shape (num_anchors,).\n            num_level_anchors Tensor): Number of anchors of each scale level.\n            gt_bboxes (Tensor): Ground truth bboxes of the image,\n                shape (num_gts, 4).\n            gt_bboxes_ignore (Tensor): Ground truth bboxes to be\n                ignored, shape (num_ignored_gts, 4).\n            gt_labels (Tensor): Ground truth labels of each box,\n                shape (num_gts,).\n            img_meta (dict): Meta info of the image.\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple: N is the number of total anchors in the image.\n                anchors (Tensor): All anchors in the image with shape (N, 4).\n                labels (Tensor): Labels of all anchors in the image with shape\n                    (N,).\n                label_weights (Tensor): Label weights of all anchor in the\n                    image with shape (N,).\n                bbox_targets (Tensor): BBox targets of all anchors in the\n                    image with shape (N, 4).\n                bbox_weights (Tensor): BBox weights of all anchors in the\n                    image with shape (N, 4).\n                pos_inds (Tensor): Indices of postive anchor with shape\n                    (num_pos,).\n                neg_inds (Tensor): Indices of negative anchor with shape\n                    (num_neg,).\n        \"\"\"\n        inside_flags = anchor_inside_flags(flat_anchors, valid_flags,\n                                           img_meta['img_shape'][:2],\n                                           self.train_cfg.allowed_border)\n        if not inside_flags.any():\n            return (None, ) * 6\n        # assign gt and sample anchors\n        anchors = flat_anchors[inside_flags, :]\n\n        num_level_anchors_inside = self.get_num_level_anchors_inside(\n            num_level_anchors, inside_flags)\n        assign_result = self.assigner.assign(anchors, num_level_anchors_inside,\n                                             gt_bboxes, gt_bboxes_ignore,\n                                             gt_labels)\n\n        sampling_result = self.sampler.sample(assign_result, anchors,\n                                              gt_bboxes)\n\n        num_valid_anchors = anchors.shape[0]\n        bbox_targets = torch.zeros_like(anchors)\n        bbox_weights = torch.zeros_like(anchors)\n        labels = anchors.new_full((num_valid_anchors, ),\n                                  self.background_label,\n                                  dtype=torch.long)\n        label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_bbox_targets = sampling_result.pos_gt_bboxes\n            bbox_targets[pos_inds, :] = pos_bbox_targets\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[\n                    sampling_result.pos_assigned_gt_inds]\n            if self.train_cfg.pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = self.train_cfg.pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of anchors\n        if unmap_outputs:\n            num_total_anchors = flat_anchors.size(0)\n            anchors = unmap(anchors, num_total_anchors, inside_flags)\n            labels = unmap(\n                labels, num_total_anchors, inside_flags, fill=self.num_classes)\n            label_weights = unmap(label_weights, num_total_anchors,\n                                  inside_flags)\n            bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)\n\n        return (anchors, labels, label_weights, bbox_targets, bbox_weights,\n                pos_inds, neg_inds)\n\n    def get_num_level_anchors_inside(self, num_level_anchors, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_anchors)\n        num_level_anchors_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_anchors_inside\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/guided_anchor_head.py",
    "content": "import torch\nimport torch.nn as nn\nfrom mmcv.cnn import bias_init_with_prob, normal_init\n\nfrom mmdet.core import (anchor_inside_flags, build_anchor_generator,\n                        build_assigner, build_bbox_coder, build_sampler,\n                        calc_region, force_fp32, images_to_levels, multi_apply,\n                        multiclass_nms, unmap)\nfrom mmdet.ops import DeformConv, MaskedConv2d\nfrom ..builder import HEADS, build_loss\nfrom .anchor_head import AnchorHead\n\n\nclass FeatureAdaption(nn.Module):\n    \"\"\"Feature Adaption Module.\n\n    Feature Adaption Module is implemented based on DCN v1.\n    It uses anchor shape prediction rather than feature map to\n    predict offsets of deformable conv layer.\n\n    Args:\n        in_channels (int): Number of channels in the input feature map.\n        out_channels (int): Number of channels in the output feature map.\n        kernel_size (int): Deformable conv kernel size.\n        deformable_groups (int): Deformable conv group size.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size=3,\n                 deformable_groups=4):\n        super(FeatureAdaption, self).__init__()\n        offset_channels = kernel_size * kernel_size * 2\n        self.conv_offset = nn.Conv2d(\n            2, deformable_groups * offset_channels, 1, bias=False)\n        self.conv_adaption = DeformConv(\n            in_channels,\n            out_channels,\n            kernel_size=kernel_size,\n            padding=(kernel_size - 1) // 2,\n            deformable_groups=deformable_groups)\n        self.relu = nn.ReLU(inplace=True)\n\n    def init_weights(self):\n        normal_init(self.conv_offset, std=0.1)\n        normal_init(self.conv_adaption, std=0.01)\n\n    def forward(self, x, shape):\n        offset = self.conv_offset(shape.detach())\n        x = self.relu(self.conv_adaption(x, offset))\n        return x\n\n\n@HEADS.register_module()\nclass GuidedAnchorHead(AnchorHead):\n    \"\"\"Guided-Anchor-based head (GA-RPN, GA-RetinaNet, etc.).\n\n    This GuidedAnchorHead will predict high-quality feature guided\n    anchors and locations where anchors will be kept in inference.\n    There are mainly 3 categories of bounding-boxes.\n\n    - Sampled 9 pairs for target assignment. (approxes)\n    - The square boxes where the predicted anchors are based on. (squares)\n    - Guided anchors.\n\n    Please refer to https://arxiv.org/abs/1901.03278 for more details.\n\n    Args:\n        num_classes (int): Number of classes.\n        in_channels (int): Number of channels in the input feature map.\n        feat_channels (int): Number of hidden channels.\n        approx_anchor_generator (dict): Config dict for approx generator\n        square_anchor_generator (dict): Config dict for square generator\n        anchor_coder (dict): Config dict for anchor coder\n        bbox_coder (dict): Config dict for bbox coder\n        deformable_groups: (int): Group number of DCN in\n            FeatureAdaption module.\n        loc_filter_thr (float): Threshold to filter out unconcerned regions.\n        background_label (int | None): Label ID of background, set as 0 for\n            RPN and num_classes for other heads. It will automatically set as\n            num_classes if None is given.\n        loss_loc (dict): Config of location loss.\n        loss_shape (dict): Config of anchor shape loss.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox (dict): Config of bbox regression loss.\n    \"\"\"\n\n    def __init__(\n        self,\n        num_classes,\n        in_channels,\n        feat_channels=256,\n        approx_anchor_generator=dict(\n            type='AnchorGenerator',\n            octave_base_scale=8,\n            scales_per_octave=3,\n            ratios=[0.5, 1.0, 2.0],\n            strides=[4, 8, 16, 32, 64]),\n        square_anchor_generator=dict(\n            type='AnchorGenerator',\n            ratios=[1.0],\n            scales=[8],\n            strides=[4, 8, 16, 32, 64]),\n        anchor_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]\n        ),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]\n        ),\n        reg_decoded_bbox=False,\n        deformable_groups=4,\n        loc_filter_thr=0.01,\n        background_label=None,\n        train_cfg=None,\n        test_cfg=None,\n        loss_loc=dict(\n            type='FocalLoss',\n            use_sigmoid=True,\n            gamma=2.0,\n            alpha=0.25,\n            loss_weight=1.0),\n        loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0),\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),\n        loss_bbox=dict(type='SmoothL1Loss', beta=1.0,\n                       loss_weight=1.0)):  # yapf: disable\n        super(AnchorHead, self).__init__()\n        self.in_channels = in_channels\n        self.num_classes = num_classes\n        self.feat_channels = feat_channels\n        self.deformable_groups = deformable_groups\n        self.loc_filter_thr = loc_filter_thr\n\n        # build approx_anchor_generator and square_anchor_generator\n        assert (approx_anchor_generator['octave_base_scale'] ==\n                square_anchor_generator['scales'][0])\n        assert (approx_anchor_generator['strides'] ==\n                square_anchor_generator['strides'])\n        self.approx_anchor_generator = build_anchor_generator(\n            approx_anchor_generator)\n        self.square_anchor_generator = build_anchor_generator(\n            square_anchor_generator)\n        self.approxs_per_octave = self.approx_anchor_generator \\\n            .num_base_anchors[0]\n\n        self.reg_decoded_bbox = reg_decoded_bbox\n\n        self.background_label = (\n            num_classes if background_label is None else background_label)\n        # background_label should be either 0 or num_classes\n        assert (self.background_label == 0\n                or self.background_label == num_classes)\n\n        # one anchor per location\n        self.num_anchors = 1\n        self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False)\n        self.loc_focal_loss = loss_loc['type'] in ['FocalLoss']\n        self.sampling = loss_cls['type'] not in ['FocalLoss']\n        self.ga_sampling = train_cfg is not None and hasattr(\n            train_cfg, 'ga_sampler')\n        if self.use_sigmoid_cls:\n            self.cls_out_channels = self.num_classes\n        else:\n            self.cls_out_channels = self.num_classes + 1\n\n        # build bbox_coder\n        self.anchor_coder = build_bbox_coder(anchor_coder)\n        self.bbox_coder = build_bbox_coder(bbox_coder)\n\n        # build losses\n        self.loss_loc = build_loss(loss_loc)\n        self.loss_shape = build_loss(loss_shape)\n        self.loss_cls = build_loss(loss_cls)\n        self.loss_bbox = build_loss(loss_bbox)\n\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n\n        if self.train_cfg:\n            self.assigner = build_assigner(self.train_cfg.assigner)\n            # use PseudoSampler when sampling is False\n            if self.sampling and hasattr(self.train_cfg, 'sampler'):\n                sampler_cfg = self.train_cfg.sampler\n            else:\n                sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n\n            self.ga_assigner = build_assigner(self.train_cfg.ga_assigner)\n            if self.ga_sampling:\n                ga_sampler_cfg = self.train_cfg.ga_sampler\n            else:\n                ga_sampler_cfg = dict(type='PseudoSampler')\n            self.ga_sampler = build_sampler(ga_sampler_cfg, context=self)\n\n        self.fp16_enabled = False\n\n        self._init_layers()\n\n    def _init_layers(self):\n        self.relu = nn.ReLU(inplace=True)\n        self.conv_loc = nn.Conv2d(self.in_channels, 1, 1)\n        self.conv_shape = nn.Conv2d(self.in_channels, self.num_anchors * 2, 1)\n        self.feature_adaption = FeatureAdaption(\n            self.in_channels,\n            self.feat_channels,\n            kernel_size=3,\n            deformable_groups=self.deformable_groups)\n        self.conv_cls = MaskedConv2d(self.feat_channels,\n                                     self.num_anchors * self.cls_out_channels,\n                                     1)\n        self.conv_reg = MaskedConv2d(self.feat_channels, self.num_anchors * 4,\n                                     1)\n\n    def init_weights(self):\n        normal_init(self.conv_cls, std=0.01)\n        normal_init(self.conv_reg, std=0.01)\n\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.conv_loc, std=0.01, bias=bias_cls)\n        normal_init(self.conv_shape, std=0.01)\n\n        self.feature_adaption.init_weights()\n\n    def forward_single(self, x):\n        loc_pred = self.conv_loc(x)\n        shape_pred = self.conv_shape(x)\n        x = self.feature_adaption(x, shape_pred)\n        # masked conv is only used during inference for speed-up\n        if not self.training:\n            mask = loc_pred.sigmoid()[0] >= self.loc_filter_thr\n        else:\n            mask = None\n        cls_score = self.conv_cls(x, mask)\n        bbox_pred = self.conv_reg(x, mask)\n        return cls_score, bbox_pred, shape_pred, loc_pred\n\n    def forward(self, feats):\n        return multi_apply(self.forward_single, feats)\n\n    def get_sampled_approxs(self, featmap_sizes, img_metas, device='cuda'):\n        \"\"\"Get sampled approxs and inside flags according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n            device (torch.device | str): device for returned tensors\n\n        Returns:\n            tuple: approxes of each image, inside flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n\n        # since feature map sizes of all images are the same, we only compute\n        # approxes for one time\n        multi_level_approxs = self.approx_anchor_generator.grid_anchors(\n            featmap_sizes, device=device)\n        approxs_list = [multi_level_approxs for _ in range(num_imgs)]\n\n        # for each image, we compute inside flags of multi level approxes\n        inside_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            multi_level_approxs = approxs_list[img_id]\n\n            # obtain valid flags for each approx first\n            multi_level_approx_flags = self.approx_anchor_generator \\\n                .valid_flags(featmap_sizes,\n                             img_meta['pad_shape'],\n                             device=device)\n\n            for i, flags in enumerate(multi_level_approx_flags):\n                approxs = multi_level_approxs[i]\n                inside_flags_list = []\n                for i in range(self.approxs_per_octave):\n                    split_valid_flags = flags[i::self.approxs_per_octave]\n                    split_approxs = approxs[i::self.approxs_per_octave, :]\n                    inside_flags = anchor_inside_flags(\n                        split_approxs, split_valid_flags,\n                        img_meta['img_shape'][:2],\n                        self.train_cfg.allowed_border)\n                    inside_flags_list.append(inside_flags)\n                # inside_flag for a position is true if any anchor in this\n                # position is true\n                inside_flags = (\n                    torch.stack(inside_flags_list, 0).sum(dim=0) > 0)\n                multi_level_flags.append(inside_flags)\n            inside_flag_list.append(multi_level_flags)\n        return approxs_list, inside_flag_list\n\n    def get_anchors(self,\n                    featmap_sizes,\n                    shape_preds,\n                    loc_preds,\n                    img_metas,\n                    use_loc_filter=False,\n                    device='cuda'):\n        \"\"\"Get squares according to feature map sizes and guided\n        anchors.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            shape_preds (list[tensor]): Multi-level shape predictions.\n            loc_preds (list[tensor]): Multi-level location predictions.\n            img_metas (list[dict]): Image meta info.\n            use_loc_filter (bool): Use loc filter or not.\n            device (torch.device | str): device for returned tensors\n\n        Returns:\n            tuple: square approxs of each image, guided anchors of each image,\n                loc masks of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # squares for one time\n        multi_level_squares = self.square_anchor_generator.grid_anchors(\n            featmap_sizes, device=device)\n        squares_list = [multi_level_squares for _ in range(num_imgs)]\n\n        # for each image, we compute multi level guided anchors\n        guided_anchors_list = []\n        loc_mask_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_guided_anchors = []\n            multi_level_loc_mask = []\n            for i in range(num_levels):\n                squares = squares_list[img_id][i]\n                shape_pred = shape_preds[i][img_id]\n                loc_pred = loc_preds[i][img_id]\n                guided_anchors, loc_mask = self._get_guided_anchors_single(\n                    squares,\n                    shape_pred,\n                    loc_pred,\n                    use_loc_filter=use_loc_filter)\n                multi_level_guided_anchors.append(guided_anchors)\n                multi_level_loc_mask.append(loc_mask)\n            guided_anchors_list.append(multi_level_guided_anchors)\n            loc_mask_list.append(multi_level_loc_mask)\n        return squares_list, guided_anchors_list, loc_mask_list\n\n    def _get_guided_anchors_single(self,\n                                   squares,\n                                   shape_pred,\n                                   loc_pred,\n                                   use_loc_filter=False):\n        \"\"\"Get guided anchors and loc masks for a single level.\n\n        Args:\n            square (tensor): Squares of a single level.\n            shape_pred (tensor): Shape predections of a single level.\n            loc_pred (tensor): Loc predections of a single level.\n            use_loc_filter (list[tensor]): Use loc filter or not.\n\n        Returns:\n            tuple: guided anchors, location masks\n        \"\"\"\n        # calculate location filtering mask\n        loc_pred = loc_pred.sigmoid().detach()\n        if use_loc_filter:\n            loc_mask = loc_pred >= self.loc_filter_thr\n        else:\n            loc_mask = loc_pred >= 0.0\n        mask = loc_mask.permute(1, 2, 0).expand(-1, -1, self.num_anchors)\n        mask = mask.contiguous().view(-1)\n        # calculate guided anchors\n        squares = squares[mask]\n        anchor_deltas = shape_pred.permute(1, 2, 0).contiguous().view(\n            -1, 2).detach()[mask]\n        bbox_deltas = anchor_deltas.new_full(squares.size(), 0)\n        bbox_deltas[:, 2:] = anchor_deltas\n        guided_anchors = self.anchor_coder.decode(\n            squares, bbox_deltas, wh_ratio_clip=1e-6)\n        return guided_anchors, mask\n\n    def ga_loc_targets(self, gt_bboxes_list, featmap_sizes):\n        \"\"\"Compute location targets for guided anchoring.\n\n        Each feature map is divided into positive, negative and ignore regions.\n        - positive regions: target 1, weight 1\n        - ignore regions: target 0, weight 0\n        - negative regions: target 0, weight 0.1\n\n        Args:\n            gt_bboxes_list (list[Tensor]): Gt bboxes of each image.\n            featmap_sizes (list[tuple]): Multi level sizes of each feature\n                maps.\n\n        Returns:\n            tuple\n        \"\"\"\n        anchor_scale = self.approx_anchor_generator.octave_base_scale\n        anchor_strides = self.approx_anchor_generator.strides\n        # Currently only supports same stride in x and y direction.\n        for stride in anchor_strides:\n            assert (stride[0] == stride[1])\n        anchor_strides = [stride[0] for stride in anchor_strides]\n\n        center_ratio = self.train_cfg.center_ratio\n        ignore_ratio = self.train_cfg.ignore_ratio\n        img_per_gpu = len(gt_bboxes_list)\n        num_lvls = len(featmap_sizes)\n        r1 = (1 - center_ratio) / 2\n        r2 = (1 - ignore_ratio) / 2\n        all_loc_targets = []\n        all_loc_weights = []\n        all_ignore_map = []\n        for lvl_id in range(num_lvls):\n            h, w = featmap_sizes[lvl_id]\n            loc_targets = torch.zeros(\n                img_per_gpu,\n                1,\n                h,\n                w,\n                device=gt_bboxes_list[0].device,\n                dtype=torch.float32)\n            loc_weights = torch.full_like(loc_targets, -1)\n            ignore_map = torch.zeros_like(loc_targets)\n            all_loc_targets.append(loc_targets)\n            all_loc_weights.append(loc_weights)\n            all_ignore_map.append(ignore_map)\n        for img_id in range(img_per_gpu):\n            gt_bboxes = gt_bboxes_list[img_id]\n            scale = torch.sqrt((gt_bboxes[:, 2] - gt_bboxes[:, 0]) *\n                               (gt_bboxes[:, 3] - gt_bboxes[:, 1]))\n            min_anchor_size = scale.new_full(\n                (1, ), float(anchor_scale * anchor_strides[0]))\n            # assign gt bboxes to different feature levels w.r.t. their scales\n            target_lvls = torch.floor(\n                torch.log2(scale) - torch.log2(min_anchor_size) + 0.5)\n            target_lvls = target_lvls.clamp(min=0, max=num_lvls - 1).long()\n            for gt_id in range(gt_bboxes.size(0)):\n                lvl = target_lvls[gt_id].item()\n                # rescaled to corresponding feature map\n                gt_ = gt_bboxes[gt_id, :4] / anchor_strides[lvl]\n                # calculate ignore regions\n                ignore_x1, ignore_y1, ignore_x2, ignore_y2 = calc_region(\n                    gt_, r2, featmap_sizes[lvl])\n                # calculate positive (center) regions\n                ctr_x1, ctr_y1, ctr_x2, ctr_y2 = calc_region(\n                    gt_, r1, featmap_sizes[lvl])\n                all_loc_targets[lvl][img_id, 0, ctr_y1:ctr_y2 + 1,\n                                     ctr_x1:ctr_x2 + 1] = 1\n                all_loc_weights[lvl][img_id, 0, ignore_y1:ignore_y2 + 1,\n                                     ignore_x1:ignore_x2 + 1] = 0\n                all_loc_weights[lvl][img_id, 0, ctr_y1:ctr_y2 + 1,\n                                     ctr_x1:ctr_x2 + 1] = 1\n                # calculate ignore map on nearby low level feature\n                if lvl > 0:\n                    d_lvl = lvl - 1\n                    # rescaled to corresponding feature map\n                    gt_ = gt_bboxes[gt_id, :4] / anchor_strides[d_lvl]\n                    ignore_x1, ignore_y1, ignore_x2, ignore_y2 = calc_region(\n                        gt_, r2, featmap_sizes[d_lvl])\n                    all_ignore_map[d_lvl][img_id, 0, ignore_y1:ignore_y2 + 1,\n                                          ignore_x1:ignore_x2 + 1] = 1\n                # calculate ignore map on nearby high level feature\n                if lvl < num_lvls - 1:\n                    u_lvl = lvl + 1\n                    # rescaled to corresponding feature map\n                    gt_ = gt_bboxes[gt_id, :4] / anchor_strides[u_lvl]\n                    ignore_x1, ignore_y1, ignore_x2, ignore_y2 = calc_region(\n                        gt_, r2, featmap_sizes[u_lvl])\n                    all_ignore_map[u_lvl][img_id, 0, ignore_y1:ignore_y2 + 1,\n                                          ignore_x1:ignore_x2 + 1] = 1\n        for lvl_id in range(num_lvls):\n            # ignore negative regions w.r.t. ignore map\n            all_loc_weights[lvl_id][(all_loc_weights[lvl_id] < 0)\n                                    & (all_ignore_map[lvl_id] > 0)] = 0\n            # set negative regions with weight 0.1\n            all_loc_weights[lvl_id][all_loc_weights[lvl_id] < 0] = 0.1\n        # loc average factor to balance loss\n        loc_avg_factor = sum(\n            [t.size(0) * t.size(-1) * t.size(-2)\n             for t in all_loc_targets]) / 200\n        return all_loc_targets, all_loc_weights, loc_avg_factor\n\n    def _ga_shape_target_single(self,\n                                flat_approxs,\n                                inside_flags,\n                                flat_squares,\n                                gt_bboxes,\n                                gt_bboxes_ignore,\n                                img_meta,\n                                unmap_outputs=True):\n        \"\"\"Compute guided anchoring targets.\n\n        This function returns sampled anchors and gt bboxes directly\n        rather than calculates regression targets.\n\n        Args:\n            flat_approxs (Tensor): flat approxs of a single image,\n                shape (n, 4)\n            inside_flags (Tensor): inside flags of a single image,\n                shape (n, ).\n            flat_squares (Tensor): flat squares of a single image,\n                shape (approxs_per_octave * n, 4)\n            gt_bboxes (Tensor): Ground truth bboxes of a single image.\n            img_meta (dict): Meta info of a single image.\n            approxs_per_octave (int): number of approxs per octave\n            cfg (dict): RPN train configs.\n            unmap_outputs (bool): unmap outputs or not.\n\n        Returns:\n            tuple\n        \"\"\"\n        if not inside_flags.any():\n            return (None, ) * 5\n        # assign gt and sample anchors\n        expand_inside_flags = inside_flags[:, None].expand(\n            -1, self.approxs_per_octave).reshape(-1)\n        approxs = flat_approxs[expand_inside_flags, :]\n        squares = flat_squares[inside_flags, :]\n\n        assign_result = self.ga_assigner.assign(approxs, squares,\n                                                self.approxs_per_octave,\n                                                gt_bboxes, gt_bboxes_ignore)\n        sampling_result = self.ga_sampler.sample(assign_result, squares,\n                                                 gt_bboxes)\n\n        bbox_anchors = torch.zeros_like(squares)\n        bbox_gts = torch.zeros_like(squares)\n        bbox_weights = torch.zeros_like(squares)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            bbox_anchors[pos_inds, :] = sampling_result.pos_bboxes\n            bbox_gts[pos_inds, :] = sampling_result.pos_gt_bboxes\n            bbox_weights[pos_inds, :] = 1.0\n\n        # map up to original set of anchors\n        if unmap_outputs:\n            num_total_anchors = flat_squares.size(0)\n            bbox_anchors = unmap(bbox_anchors, num_total_anchors, inside_flags)\n            bbox_gts = unmap(bbox_gts, num_total_anchors, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)\n\n        return (bbox_anchors, bbox_gts, bbox_weights, pos_inds, neg_inds)\n\n    def ga_shape_targets(self,\n                         approx_list,\n                         inside_flag_list,\n                         square_list,\n                         gt_bboxes_list,\n                         img_metas,\n                         gt_bboxes_ignore_list=None,\n                         unmap_outputs=True):\n        \"\"\"Compute guided anchoring targets.\n\n        Args:\n            approx_list (list[list]): Multi level approxs of each image.\n            inside_flag_list (list[list]): Multi level inside flags of each\n                image.\n            square_list (list[list]): Multi level squares of each image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            gt_bboxes_ignore_list (list[Tensor]): ignore list of gt bboxes.\n            unmap_outputs (bool): unmap outputs or not.\n\n        Returns:\n            tuple\n        \"\"\"\n        num_imgs = len(img_metas)\n        assert len(approx_list) == len(inside_flag_list) == len(\n            square_list) == num_imgs\n        # anchor number of multi levels\n        num_level_squares = [squares.size(0) for squares in square_list[0]]\n        # concat all level anchors and flags to a single tensor\n        inside_flag_flat_list = []\n        approx_flat_list = []\n        square_flat_list = []\n        for i in range(num_imgs):\n            assert len(square_list[i]) == len(inside_flag_list[i])\n            inside_flag_flat_list.append(torch.cat(inside_flag_list[i]))\n            approx_flat_list.append(torch.cat(approx_list[i]))\n            square_flat_list.append(torch.cat(square_list[i]))\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        (all_bbox_anchors, all_bbox_gts, all_bbox_weights, pos_inds_list,\n         neg_inds_list) = multi_apply(\n             self._ga_shape_target_single,\n             approx_flat_list,\n             inside_flag_flat_list,\n             square_flat_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             img_metas,\n             unmap_outputs=unmap_outputs)\n        # no valid anchors\n        if any([bbox_anchors is None for bbox_anchors in all_bbox_anchors]):\n            return None\n        # sampled anchors of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        # split targets to a list w.r.t. multiple levels\n        bbox_anchors_list = images_to_levels(all_bbox_anchors,\n                                             num_level_squares)\n        bbox_gts_list = images_to_levels(all_bbox_gts, num_level_squares)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                             num_level_squares)\n        return (bbox_anchors_list, bbox_gts_list, bbox_weights_list,\n                num_total_pos, num_total_neg)\n\n    def loss_shape_single(self, shape_pred, bbox_anchors, bbox_gts,\n                          anchor_weights, anchor_total_num):\n        shape_pred = shape_pred.permute(0, 2, 3, 1).contiguous().view(-1, 2)\n        bbox_anchors = bbox_anchors.contiguous().view(-1, 4)\n        bbox_gts = bbox_gts.contiguous().view(-1, 4)\n        anchor_weights = anchor_weights.contiguous().view(-1, 4)\n        bbox_deltas = bbox_anchors.new_full(bbox_anchors.size(), 0)\n        bbox_deltas[:, 2:] += shape_pred\n        # filter out negative samples to speed-up weighted_bounded_iou_loss\n        inds = torch.nonzero(\n            anchor_weights[:, 0] > 0, as_tuple=False).squeeze(1)\n        bbox_deltas_ = bbox_deltas[inds]\n        bbox_anchors_ = bbox_anchors[inds]\n        bbox_gts_ = bbox_gts[inds]\n        anchor_weights_ = anchor_weights[inds]\n        pred_anchors_ = self.anchor_coder.decode(\n            bbox_anchors_, bbox_deltas_, wh_ratio_clip=1e-6)\n        loss_shape = self.loss_shape(\n            pred_anchors_,\n            bbox_gts_,\n            anchor_weights_,\n            avg_factor=anchor_total_num)\n        return loss_shape\n\n    def loss_loc_single(self, loc_pred, loc_target, loc_weight,\n                        loc_avg_factor):\n        loss_loc = self.loss_loc(\n            loc_pred.reshape(-1, 1),\n            loc_target.reshape(-1, 1).long(),\n            loc_weight.reshape(-1, 1),\n            avg_factor=loc_avg_factor)\n        return loss_loc\n\n    @force_fp32(\n        apply_to=('cls_scores', 'bbox_preds', 'shape_preds', 'loc_preds'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             shape_preds,\n             loc_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.approx_anchor_generator.num_levels\n\n        device = cls_scores[0].device\n\n        # get loc targets\n        loc_targets, loc_weights, loc_avg_factor = self.ga_loc_targets(\n            gt_bboxes, featmap_sizes)\n\n        # get sampled approxes\n        approxs_list, inside_flag_list = self.get_sampled_approxs(\n            featmap_sizes, img_metas, device=device)\n        # get squares and guided anchors\n        squares_list, guided_anchors_list, _ = self.get_anchors(\n            featmap_sizes, shape_preds, loc_preds, img_metas, device=device)\n\n        # get shape targets\n        shape_targets = self.ga_shape_targets(approxs_list, inside_flag_list,\n                                              squares_list, gt_bboxes,\n                                              img_metas)\n        if shape_targets is None:\n            return None\n        (bbox_anchors_list, bbox_gts_list, anchor_weights_list, anchor_fg_num,\n         anchor_bg_num) = shape_targets\n        anchor_total_num = (\n            anchor_fg_num if not self.ga_sampling else anchor_fg_num +\n            anchor_bg_num)\n\n        # get anchor targets\n        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1\n        cls_reg_targets = self.get_targets(\n            guided_anchors_list,\n            inside_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=label_channels)\n        if cls_reg_targets is None:\n            return None\n        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,\n         num_total_pos, num_total_neg) = cls_reg_targets\n        num_total_samples = (\n            num_total_pos + num_total_neg if self.sampling else num_total_pos)\n\n        # anchor number of multi levels\n        num_level_anchors = [\n            anchors.size(0) for anchors in guided_anchors_list[0]\n        ]\n        # concat all level anchors to a single tensor\n        concat_anchor_list = []\n        for i in range(len(guided_anchors_list)):\n            concat_anchor_list.append(torch.cat(guided_anchors_list[i]))\n        all_anchor_list = images_to_levels(concat_anchor_list,\n                                           num_level_anchors)\n\n        # get classification and bbox regression losses\n        losses_cls, losses_bbox = multi_apply(\n            self.loss_single,\n            cls_scores,\n            bbox_preds,\n            all_anchor_list,\n            labels_list,\n            label_weights_list,\n            bbox_targets_list,\n            bbox_weights_list,\n            num_total_samples=num_total_samples)\n\n        # get anchor location loss\n        losses_loc = []\n        for i in range(len(loc_preds)):\n            loss_loc = self.loss_loc_single(\n                loc_preds[i],\n                loc_targets[i],\n                loc_weights[i],\n                loc_avg_factor=loc_avg_factor)\n            losses_loc.append(loss_loc)\n\n        # get anchor shape loss\n        losses_shape = []\n        for i in range(len(shape_preds)):\n            loss_shape = self.loss_shape_single(\n                shape_preds[i],\n                bbox_anchors_list[i],\n                bbox_gts_list[i],\n                anchor_weights_list[i],\n                anchor_total_num=anchor_total_num)\n            losses_shape.append(loss_shape)\n\n        return dict(\n            loss_cls=losses_cls,\n            loss_bbox=losses_bbox,\n            loss_shape=losses_shape,\n            loss_loc=losses_loc)\n\n    @force_fp32(\n        apply_to=('cls_scores', 'bbox_preds', 'shape_preds', 'loc_preds'))\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_preds,\n                   shape_preds,\n                   loc_preds,\n                   img_metas,\n                   cfg=None,\n                   rescale=False):\n        assert len(cls_scores) == len(bbox_preds) == len(shape_preds) == len(\n            loc_preds)\n        num_levels = len(cls_scores)\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        device = cls_scores[0].device\n        # get guided anchors\n        _, guided_anchors, loc_masks = self.get_anchors(\n            featmap_sizes,\n            shape_preds,\n            loc_preds,\n            img_metas,\n            use_loc_filter=not self.training,\n            device=device)\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds[i][img_id].detach() for i in range(num_levels)\n            ]\n            guided_anchor_list = [\n                guided_anchors[img_id][i].detach() for i in range(num_levels)\n            ]\n            loc_mask_list = [\n                loc_masks[img_id][i].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list,\n                                                guided_anchor_list,\n                                                loc_mask_list, img_shape,\n                                                scale_factor, cfg, rescale)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           mlvl_anchors,\n                           mlvl_masks,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_anchors)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        for cls_score, bbox_pred, anchors, mask in zip(cls_scores, bbox_preds,\n                                                       mlvl_anchors,\n                                                       mlvl_masks):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            # if no location is kept, end.\n            if mask.sum() == 0:\n                continue\n            # reshape scores and bbox_pred\n            cls_score = cls_score.permute(1, 2,\n                                          0).reshape(-1, self.cls_out_channels)\n            if self.use_sigmoid_cls:\n                scores = cls_score.sigmoid()\n            else:\n                scores = cls_score.softmax(-1)\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            # filter scores, bbox_pred w.r.t. mask.\n            # anchors are filtered in get_anchors() beforehand.\n            scores = scores[mask, :]\n            bbox_pred = bbox_pred[mask, :]\n            if scores.dim() == 0:\n                anchors = anchors.unsqueeze(0)\n                scores = scores.unsqueeze(0)\n                bbox_pred = bbox_pred.unsqueeze(0)\n            # filter anchors, bbox_pred, scores w.r.t. scores\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                if self.use_sigmoid_cls:\n                    max_scores, _ = scores.max(dim=1)\n                else:\n                    # remind that we set FG labels to [0, num_class-1]\n                    # since mmdet v2.0\n                    # BG cat_id: num_class\n                    max_scores, _ = scores[:, :-1].max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                anchors = anchors[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n            bboxes = self.bbox_coder.decode(\n                anchors, bbox_pred, max_shape=img_shape)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n        mlvl_scores = torch.cat(mlvl_scores)\n        if self.use_sigmoid_cls:\n            # Add a dummy background class to the backend when using sigmoid\n            # remind that we set FG labels to [0, num_class-1] since mmdet v2.0\n            # BG cat_id: num_class\n            padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n            mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        # multi class NMS\n        det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores,\n                                                cfg.score_thr, cfg.nms,\n                                                cfg.max_per_img)\n        return det_bboxes, det_labels\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/lscpvnet_head.py",
    "content": "import numpy as np\nimport torch\nimport pdb\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (PointGenerator, build_assigner, build_sampler,\n                        images_to_levels, multi_apply, multiclass_nms, unmap)\nfrom mmdet.ops import DeformConv, PyramidDeformConv, ModulatedDeformConvPack, TLPool, BRPool\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\n@HEADS.register_module()\nclass LSCPVHead(AnchorFreeHead):\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 point_feat_channels=256,\n                 shared_stacked_convs=1,\n                 first_kernel_size=3,\n                 kernel_size=1,\n                 corner_dim=64,\n                 num_points=9,\n                 gradient_mul=0.1,\n                 point_strides=[8, 16, 32, 64, 128],\n                 point_base_scale=4,\n                 conv_module_type= 'norm', #norm of dcn, norm is faster\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox_init=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=0.5),\n                 loss_bbox_refine=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_heatmap=dict(\n                     type='GaussianFocalLoss',\n                     alpha=2.0,\n                     gamma=4.0,\n                     loss_weight=0.25),\n                 loss_offset=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_sem=dict(type='SEPFocalLoss', use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=0.1),\n                 use_grid_points=False,\n                 center_init=True,\n                 moment_mul=0.01,\n                 **kwargs):\n        self.num_points = num_points\n        self.point_feat_channels = point_feat_channels\n        self.shared_stacked_convs = shared_stacked_convs\n        self.use_grid_points = use_grid_points\n        self.center_init = center_init\n\n        self.first_kernel_size = first_kernel_size\n        self.kernel_size = kernel_size\n        self.corner_dim = corner_dim\n        self.conv_module_type = conv_module_type\n\n        # we use deformable conv to extract points features\n        self.dcn_kernel = int(np.sqrt(num_points))\n        self.dcn_pad = int((self.dcn_kernel - 1) / 2)\n        assert self.dcn_kernel * self.dcn_kernel == num_points, \\\n            'The points number should be a square number.'\n        assert self.dcn_kernel % 2 == 1, \\\n            'The points number should be an odd square number.'\n        dcn_base = np.arange(-self.dcn_pad,\n                             self.dcn_pad + 1).astype(np.float64)\n        dcn_base_y = np.repeat(dcn_base, self.dcn_kernel)\n        dcn_base_x = np.tile(dcn_base, self.dcn_kernel)\n        dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape((-1))\n        self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1)\n\n        super().__init__(num_classes, in_channels, loss_cls=loss_cls, **kwargs)\n\n        self.gradient_mul = gradient_mul\n        self.point_base_scale = point_base_scale\n        self.point_strides = point_strides\n        self.fpn_levels = [i for i in range(len(self.point_strides))]\n        self.point_generators = [PointGenerator() for _ in self.point_strides]\n\n        if self.train_cfg:\n            self.init_assigner = build_assigner(self.train_cfg.init.assigner)\n            self.refine_assigner = build_assigner(self.train_cfg.refine.assigner)\n            self.hm_assigner = build_assigner(self.train_cfg.heatmap.assigner)\n            # use PseudoSampler when sampling is False\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n\n        self.cls_out_channels = self.num_classes\n        self.loss_bbox_init = build_loss(loss_bbox_init)\n        self.loss_bbox_refine = build_loss(loss_bbox_refine)\n        self.loss_heatmap = build_loss(loss_heatmap)\n        self.loss_offset = build_loss(loss_offset)\n        self.loss_sem = build_loss(loss_sem)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.softplus = nn.Softplus()\n        self.cls_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n        self.bbox_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n        self.cls_convs = nn.ModuleList()\n        self.bbox_convs = nn.ModuleList()\n        self.shared_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            if self.conv_module_type == 'norm':\n                self.cls_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                  conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))\n                self.bbox_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                  conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))                                    \n            else:\n                self.cls_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad))\n                self.bbox_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad))\n\n        for i in range(self.shared_stacked_convs):\n            if self.conv_module_type == 'norm':\n                self.shared_convs.append(\n                    ConvModule(self.feat_channels, self.feat_channels, 3, stride=1, padding=1,\n                               conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))\n            else: #dcn\n                self.shared_convs.append(\n                    DCNConvModule(self.feat_channels, self.feat_channels, 3, 1,\n                                  self.norm_cfg.num_groups, self.dcn_pad))\n\n        self.hem_tl = TLPool(self.feat_channels, self.conv_cfg, self.norm_cfg, \n                             first_kernel_size=self.first_kernel_size, kernel_size=self.kernel_size,\n                             corner_dim=self.corner_dim)\n        self.hem_br = BRPool(self.feat_channels, self.conv_cfg, self.norm_cfg, \n                             first_kernel_size=self.first_kernel_size, kernel_size=self.kernel_size, \n                             corner_dim=self.corner_dim)\n\n       \n        pts_out_dim = 4*5 + (self.num_points-5)*2\n        cls_in_channels = self.feat_channels + 6\n\n        self.pts_cls_conv = PyramidDeformConv(cls_in_channels, self.point_feat_channels,\n                                              self.dcn_kernel, 1, self.dcn_pad)\n\n        self.pts_cls_out = nn.Conv2d(self.point_feat_channels, self.cls_out_channels, 1, 1, 0)\n\n        self.pts_bbox_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        self.pts_bbox_init_out = nn.Conv2d(self.point_feat_channels, pts_out_dim, 1, 1, 0)\n        pts_in_channels = self.feat_channels + 6\n        self.pts_bbox_refine_conv = PyramidDeformConv(pts_in_channels, self.point_feat_channels,\n                                                    self.dcn_kernel, 1, self.dcn_pad)\n        self.pts_bbox_refine_out = nn.Conv2d(self.point_feat_channels, 20, 1, 1, 0)\n\n        self.reppoints_hem_tl_score_out = nn.Conv2d(self.feat_channels, 1, 3, 1, 1)\n        self.reppoints_hem_br_score_out = nn.Conv2d(self.feat_channels, 1, 3, 1, 1)\n        self.reppoints_hem_tl_offset_out = nn.Conv2d(self.feat_channels, 2, 3, 1, 1)\n        self.reppoints_hem_br_offset_out = nn.Conv2d(self.feat_channels, 2, 3, 1, 1)\n\n        self.reppoints_sem_out = nn.Conv2d(self.feat_channels, self.cls_out_channels, 1, 1, 0)\n        self.reppoints_sem_embedding = ConvModule(\n            self.feat_channels,\n            self.feat_channels,\n            1,\n            conv_cfg=self.conv_cfg,\n            norm_cfg=self.norm_cfg)\n\n        self.cls_af_dcn_conv = nn.Sequential(\n                                nn.Conv2d(3 * self.point_feat_channels,\n                                            self.point_feat_channels,\n                                            1, 1, 0),\n                                            nn.ReLU())\n\n        self.bbox_af_dcn_conv = nn.Sequential(\n                                nn.Conv2d(3 * self.point_feat_channels,\n                                            self.point_feat_channels,\n                                            1, 1, 0),\n                                            nn.ReLU())\n\n        self.cls_feat_conv = nn.Conv2d(cls_in_channels, self.point_feat_channels, 3, 1, 1)\n        self.bbox_feat_conv = nn.Conv2d(pts_in_channels, self.point_feat_channels, 3, 1, 1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.bbox_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.shared_convs:\n            normal_init(m.conv, std=0.01)\n\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.pts_cls_conv, std=0.01)\n        normal_init(self.pts_cls_out, std=0.01, bias=bias_cls)\n        normal_init(self.pts_bbox_init_conv, std=0.01)\n        normal_init(self.pts_bbox_init_out, std=0.01)\n        normal_init(self.pts_bbox_refine_conv, std=0.01)\n        normal_init(self.pts_bbox_refine_out, std=0.01)\n        normal_init(self.reppoints_hem_tl_score_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_hem_tl_offset_out, std=0.01)\n        normal_init(self.reppoints_hem_br_score_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_hem_br_offset_out, std=0.01)\n        normal_init(self.reppoints_sem_out, std=0.01, bias=bias_cls)\n\n        normal_init(self.cls_feat_conv, std=0.01)\n        normal_init(self.bbox_feat_conv, std=0.01)\n        normal_init(self.cls_af_dcn_conv[0], std=0.01)\n        normal_init(self.bbox_af_dcn_conv[0], std=0.01)\n\n    def extreme_points2bbox(self, pts, y_first=True, extreme=False):\n        pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n        valid_pts, inds = torch.max(pts_reshape, dim=2)\n        neg_inds = inds == 0\n        valid_pts[neg_inds] *= -1\n        valid_pts_xy = valid_pts.view(valid_pts.shape[0], -1, 2, *valid_pts.shape[2:])\n\n        pts_y = valid_pts_xy[:, :, 0, ...] if y_first else valid_pts_xy[:, :, 1, ...]\n        pts_x = valid_pts_xy[:, :, 1, ...] if y_first else valid_pts_xy[:, :, 0, ...]\n\n        bbox_left   = pts_x[:, 1, :, :].unsqueeze(1)\n        bbox_right  = pts_x[:, 3, :, :].unsqueeze(1)\n        bbox_up     = pts_y[:, 0, :, :].unsqueeze(1)\n        bbox_bottom = pts_y[:, 2, :, :].unsqueeze(1)\n\n        bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n\n        if extreme:\n            extreme_up     = torch.cat((pts_x[:, 0:1, ...], pts_y[:, 0:1, ...]), dim = 1)\n            extreme_left   = torch.cat((pts_x[:, 1:2, ...], pts_y[:, 1:2, ...]), dim = 1)\n            extreme_bottom = torch.cat((pts_x[:, 2:3, ...], pts_y[:, 2:3, ...]), dim = 1)\n            extreme_right  = torch.cat((pts_x[:, 3:4, ...], pts_y[:, 3:4, ...]), dim = 1)\n            extremes       = torch.cat([extreme_up, extreme_left, extreme_bottom, extreme_right], \n                                       dim=1)\n            return extremes, bbox\n        else:\n            return bbox\n\n    def get_pred_reg(self, raw_reg1, raw_reg2):\n        raw_reg_reshape = raw_reg1.view(raw_reg1.shape[0], -1, 2, *raw_reg1.shape[2:])\n        pos_reg, inds = torch.max(raw_reg_reshape, dim=2)\n        neg_inds = inds == 0\n        pos_reg[neg_inds] *= -1\n\n        reg_for_dcn = torch.cat((pos_reg, raw_reg2), dim =1)\n        return reg_for_dcn\n\n    def get_bbox_gt_reg(self, gt_pts, anchor_pts, bbox_weights):\n        gt_reg = gt_pts.new_zeros([gt_pts.size(0), 20])\n        anchor_pts_repeat = anchor_pts[:, :2].repeat(1, 5)\n        offset_reg = gt_pts - anchor_pts_repeat\n        br_reg = offset_reg >= 0 \n        tl_reg = offset_reg < 0\n        tlbr_inds = torch.stack([tl_reg, br_reg], -1).reshape(-1, 20)\n        gt_reg[tlbr_inds] = torch.abs(offset_reg.reshape(-1))\n\n        pos_inds = bbox_weights[:,0]>0\n        neg_inds = bbox_weights[:,0]==0\n        gt_reg[neg_inds] = 0\n\n        xl_reg = gt_reg[..., 0::4]\n        xr_reg = gt_reg[..., 1::4]\n        yt_reg = gt_reg[..., 2::4]\n        yb_reg = gt_reg[..., 3::4]\n        yx_gt_reg = torch.stack([yt_reg, yb_reg, xl_reg, xr_reg], -1).reshape(-1, 20)\n\n        xl_inds = tlbr_inds[..., 0::4]\n        xr_inds = tlbr_inds[..., 1::4]\n        yt_inds = tlbr_inds[..., 2::4]\n        yb_inds = tlbr_inds[..., 3::4]\n        yx_inds = torch.stack([yt_inds, yb_inds, xl_inds, xr_inds], -1).reshape(-1, 20)\n\n        return yx_gt_reg, yx_inds\n\n    def forward(self, feats):\n        (cls_feats, bbox_feats, bbox_reg_init_sps, hem_score_outs, \n         hem_offset_outs, sem_scores_outs, bbox_dcn_offsets) = multi_apply(self.forward_single1, feats)\n\n        pts_cls_outs, bbox_reg_refine_sps = multi_apply(self.forward_single2,\n                                                        bbox_dcn_offsets,\n                                                        bbox_reg_init_sps,\n                                                        self.fpn_levels,\n                                                        cls_feats = cls_feats,\n                                                        bbox_feats = bbox_feats,\n                                                        num_levels = len(self.fpn_levels))\n\n        return (pts_cls_outs, bbox_reg_init_sps, bbox_reg_refine_sps, hem_score_outs,\n               hem_offset_outs, sem_scores_outs)\n\n    def forward_single1(self, x):\n        ''' Forward feature map of a single FPN level.'''\n        dcn_base_offset = self.dcn_base_offset.type_as(x)\n\n        cls_feat = x\n        bbox_feat = x\n\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for bbox_conv in self.bbox_convs:\n            bbox_feat = bbox_conv(bbox_feat)\n\n        shared_feat = bbox_feat\n        for shared_conv in self.shared_convs:\n            shared_feat = shared_conv(shared_feat)\n\n        sem_feat = shared_feat\n        hem_feat = shared_feat\n\n        sem_scores_out = self.reppoints_sem_out(sem_feat)\n        sem_feat = self.reppoints_sem_embedding(sem_feat)\n\n        cls_feat = cls_feat + sem_feat\n        bbox_feat = bbox_feat + sem_feat\n        hem_feat = hem_feat + sem_feat\n\n        # generate heatmap and offset\n        hem_tl_feat = self.hem_tl(hem_feat)\n        hem_br_feat = self.hem_br(hem_feat)\n\n        hem_tl_score_out = self.reppoints_hem_tl_score_out(hem_tl_feat)\n        hem_tl_offset_out = self.reppoints_hem_tl_offset_out(hem_tl_feat)\n        hem_br_score_out = self.reppoints_hem_br_score_out(hem_br_feat)\n        hem_br_offset_out = self.reppoints_hem_br_offset_out(hem_br_feat)\n\n        hem_score_out = torch.cat([hem_tl_score_out, hem_br_score_out], dim=1)\n        hem_offset_out = torch.cat([hem_tl_offset_out, hem_br_offset_out], dim=1)\n\n        bbox_reg_init_out = self.pts_bbox_init_out(\n                        self.relu(self.pts_bbox_init_conv(bbox_feat)))\n\n        bbox_reg_init_sp = self.softplus(bbox_reg_init_out[:, :20, ...])\n        bbox_pred_reg = self.get_pred_reg(bbox_reg_init_sp, bbox_reg_init_out[:, 20:, ...])\n\n        bbox_pred_reg_grad_mul = (1 - self.gradient_mul)*bbox_pred_reg.detach(\n                                  ) + self.gradient_mul * bbox_pred_reg\n\n        bbox_dcn_offset = bbox_pred_reg_grad_mul - dcn_base_offset\n\n        hem_feat = torch.cat([hem_score_out, hem_offset_out], dim=1)\n        cls_feat = torch.cat([cls_feat, hem_feat], dim=1)\n        bbox_feat = torch.cat([bbox_feat, hem_feat], dim=1)\n\n        return (cls_feat, bbox_feat, bbox_reg_init_sp, hem_score_out, hem_offset_out, sem_scores_out,\n               bbox_dcn_offset)\n\n    def forward_single2(self, bbox_dcn_offset, bbox_reg_init_sp, fpn_level, \n                              cls_feats, bbox_feats, num_levels):\n        level_list = []\n        level_list.append(fpn_level)\n        if fpn_level == 0:\n            level_list.append(fpn_level+1)\n            level_list.append(fpn_level+2)\n        elif fpn_level == num_levels-1:\n            level_list.append(fpn_level-1)\n            level_list.append(fpn_level-2)\n        else:\n            level_list.append(fpn_level-1)\n            level_list.append(fpn_level+1)\n\n        base_h = bbox_feats[fpn_level].size(2)\n        base_w = bbox_feats[fpn_level].size(3)\n        bbox_refine_raws = []\n        pts_cls_raws = []\n        for level in level_list:\n            current_h = bbox_feats[level].size(2)\n            current_w = bbox_feats[level].size(3)\n            scale_h = current_h/base_h\n            scale_w = current_w/base_w\n            offset_y = bbox_dcn_offset[:, 0::2, ...]\n            offset_x = bbox_dcn_offset[:, 1::2, ...]\n            offset_y *= scale_h\n            offset_x *= scale_w\n            bbox_dcn_offset_ = torch.stack([offset_y, offset_x], 2).view(bbox_dcn_offset.size(0),\n                                              -1, bbox_dcn_offset.size(2), bbox_dcn_offset.size(3))\n            \n            bbox_refine_raws.append(self.pts_bbox_refine_conv(bbox_feats[level], bbox_dcn_offset_, scale_h,\n                                                              scale_w))\n            pts_cls_raws.append(self.pts_cls_conv(cls_feats[level], bbox_dcn_offset_, scale_h, scale_w))\n\n        bbox_reg_refine_out = self.pts_bbox_refine_out(\n                self.relu(self.bbox_GN(self.bbox_af_dcn_conv(torch.cat(bbox_refine_raws, dim=1))+\n                                    self.bbox_feat_conv(bbox_feats[fpn_level]))))\n\n        bbox_reg_refine_sp = self.softplus(bbox_reg_refine_out + bbox_reg_init_sp.detach())\n\n        pts_cls_out = self.pts_cls_out(\n                      self.relu(self.cls_GN(self.cls_af_dcn_conv(torch.cat(pts_cls_raws, dim=1))+\n                                self.cls_feat_conv(cls_feats[fpn_level]))))\n\n        return (pts_cls_out, bbox_reg_refine_sp)\n\n    def get_points(self, featmap_sizes, img_metas):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n\n        Returns:\n            tuple: points of each image, valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # points center for one time\n        multi_level_points = []\n        for i in range(num_levels):\n            points = self.point_generators[i].grid_points(\n                featmap_sizes[i], self.point_strides[i])\n            multi_level_points.append(points)\n        points_list = [[point.clone() for point in multi_level_points] for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level grids\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            for i in range(num_levels):\n                point_stride = self.point_strides[i]\n                feat_h, feat_w = featmap_sizes[i]\n                h, w = img_meta['pad_shape'][:2]\n                valid_feat_h = min(int(np.ceil(h / point_stride)), feat_h)\n                valid_feat_w = min(int(np.ceil(w / point_stride)), feat_w)\n                flags = self.point_generators[i].valid_flags(\n                    (feat_h, feat_w), (valid_feat_h, valid_feat_w))\n                multi_level_flags.append(flags)\n            valid_flag_list.append(multi_level_flags)\n\n        return points_list, valid_flag_list\n\n    def centers_to_bboxes(self, point_list):\n        \"\"\"Get bboxes according to center points. Only used in MaxIOUAssigner.\n        \"\"\"\n        bbox_list = []\n        for i_img, point in enumerate(point_list):\n            bbox = []\n            for i_lvl in range(len(self.point_strides)):\n                scale = self.point_base_scale * self.point_strides[i_lvl] * 0.5\n                bbox_shift = torch.Tensor([-scale, -scale, scale, scale]).view(1, 4).type_as(point[0])\n                bbox_center = torch.cat([point[i_lvl][:, :2], point[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift)\n            bbox_list.append(bbox)\n        return bbox_list\n\n    def offset_to_pts(self, center_list, pred_list):\n        \"\"\"Change from point offset to point coordinate.\"\"\"\n        pts_list = []\n        for i_lvl in range(len(self.point_strides)):\n            pts_lvl = []\n            for i_img in range(len(center_list)):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(\n                    1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                yx_pts_shift = pts_shift.permute(1, 2, 0).view(\n                    -1, 2 * self.num_points)\n                y_pts_shift = yx_pts_shift[..., 0::2]\n                x_pts_shift = yx_pts_shift[..., 1::2]\n                xy_pts_shift = torch.stack([x_pts_shift, y_pts_shift], -1)\n                xy_pts_shift = xy_pts_shift.view(*yx_pts_shift.shape[:-1], -1)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_lvl.append(pts)\n            pts_lvl = torch.stack(pts_lvl, 0)\n            pts_list.append(pts_lvl)\n        return pts_list\n\n    def _point_target_single(self,\n                             flat_proposals,\n                             valid_flags,\n                             num_level_proposals,\n                             gt_bboxes,\n                             gt_extremes,\n                             gt_bboxes_ignore,\n                             gt_labels,\n                             label_channels=1,\n                             stage='init',\n                             unmap_outputs=True):\n        inside_flags = valid_flags\n        if not inside_flags.any():\n            return (None, ) * 6\n        # assign gt and sample proposals\n        proposals = flat_proposals[inside_flags, :]\n        num_level_proposals_inside = self.get_num_level_proposals_inside(num_level_proposals, \n                                                                         inside_flags)\n        if stage == 'init':\n            assigner = self.init_assigner\n            assigner_type = self.train_cfg.init.assigner.type\n            pos_weight = self.train_cfg.init.pos_weight\n        else:\n            assigner = self.refine_assigner\n            assigner_type = self.train_cfg.refine.assigner.type\n            pos_weight = self.train_cfg.refine.pos_weight\n\n        if assigner_type != \"ATSSAssigner\":\n            assign_result = assigner.assign(proposals, gt_bboxes, gt_extremes, gt_bboxes_ignore, \n                                            gt_labels)\n        else:\n            assign_result = assigner.assign(proposals, num_level_proposals_inside, gt_bboxes, \n                                            gt_bboxes_ignore, gt_labels)\n        sampling_result = self.sampler.sample(assign_result, proposals, gt_bboxes)\n\n        num_valid_proposals = proposals.shape[0]\n        bboxes_gt    = proposals.new_zeros([num_valid_proposals, 4])\n        extremes_gt  = proposals.new_zeros([num_valid_proposals, 10])\n        bbox_weights = proposals.new_zeros([num_valid_proposals, 4])\n        labels       = proposals.new_full((num_valid_proposals, ), self.background_label, \n                                           dtype=torch.long)\n        label_weights = proposals.new_zeros(num_valid_proposals, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_gt_bboxes = sampling_result.pos_gt_bboxes\n            bboxes_gt[pos_inds, :] = pos_gt_bboxes\n\n            pos_gt_extremes = gt_extremes[sampling_result.pos_assigned_gt_inds]\n            extremes_gt[pos_inds, :] = pos_gt_extremes\n\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]\n            if pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of proposals\n        if unmap_outputs:\n            num_total_proposals = flat_proposals.size(0)\n            labels              = unmap(labels, num_total_proposals, inside_flags)\n            label_weights       = unmap(label_weights, num_total_proposals, inside_flags)\n            bboxes_gt           = unmap(bboxes_gt, num_total_proposals, inside_flags)\n            extremes_gt         = unmap(extremes_gt, num_total_proposals, inside_flags)\n            bbox_weights        = unmap(bbox_weights, num_total_proposals, inside_flags)\n\n        return labels, label_weights, bboxes_gt, extremes_gt, bbox_weights, pos_inds, neg_inds\n\n    def get_targets(self,\n                    proposals_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    gt_extremes_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    stage='init',\n                    label_channels=1,\n                    unmap_outputs=True):\n        assert stage in ['init', 'refine']\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n        num_level_proposals_list = [num_level_proposals] * num_imgs\n\n        # concat all level points and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_extremes_list is None:\n            gt_extremes_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n    \n        (all_labels, all_label_weights, all_bboxes_gt, all_extremes_gt,\n         all_bbox_weights, pos_inds_list, neg_inds_list) = multi_apply(\n             self._point_target_single,\n             proposals_list,\n             valid_flag_list,\n             num_level_proposals_list,\n             gt_bboxes_list,\n             gt_extremes_list,\n             gt_bboxes_ignore_list,\n             gt_labels_list,\n             stage=stage,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n\n        labels_list        = images_to_levels(all_labels, num_level_proposals)\n        label_weights_list = images_to_levels(all_label_weights, num_level_proposals)\n        bboxes_gt_list     = images_to_levels(all_bboxes_gt, num_level_proposals)\n        extremes_gt_list   = images_to_levels(all_extremes_gt, num_level_proposals)\n        bbox_weights_list  = images_to_levels(all_bbox_weights, num_level_proposals)\n\n        if stage == 'init':\n            anchor_pts_list = images_to_levels(proposals_list, num_level_proposals)\n            return (labels_list, label_weights_list, bboxes_gt_list, extremes_gt_list,\n                    bbox_weights_list, num_total_pos, num_total_neg, anchor_pts_list)\n        else:\n            return (labels_list, label_weights_list, bboxes_gt_list, extremes_gt_list,\n                    bbox_weights_list, num_total_pos, num_total_neg)\n\n    def _hm_target_single(self,\n                          flat_points,\n                          inside_flags,\n                          gt_bboxes,\n                          gt_labels,\n                          unmap_outputs=True):\n        # assign gt and sample points\n        if not inside_flags.any():\n            return (None, ) * 12\n        points = flat_points[inside_flags, :]\n\n        assigner = self.hm_assigner\n        gt_hm_tl, gt_offset_tl, pos_inds_tl, neg_inds_tl, \\\n        gt_hm_br, gt_offset_br, pos_inds_br, neg_inds_br = \\\n            assigner.assign(points, gt_bboxes, gt_labels)\n\n        num_valid_points = points.shape[0]\n        hm_tl_weights = points.new_zeros(num_valid_points, dtype=torch.float)\n        hm_br_weights = points.new_zeros(num_valid_points, dtype=torch.float)\n        offset_tl_weights = points.new_zeros([num_valid_points, 2], dtype=torch.float)\n        offset_br_weights = points.new_zeros([num_valid_points, 2], dtype=torch.float)\n\n        hm_tl_weights[pos_inds_tl] = 1.0\n        hm_tl_weights[neg_inds_tl] = 1.0\n        offset_tl_weights[pos_inds_tl, :] = 1.0\n\n        hm_br_weights[pos_inds_br] = 1.0\n        hm_br_weights[neg_inds_br] = 1.0\n        offset_br_weights[pos_inds_br, :] = 1.0\n\n        # map up to original set of grids\n        if unmap_outputs:\n            num_total_points = flat_points.shape[0]\n            gt_hm_tl = unmap(gt_hm_tl, num_total_points, inside_flags)\n            gt_offset_tl = unmap(gt_offset_tl, num_total_points, inside_flags)\n            hm_tl_weights = unmap(hm_tl_weights, num_total_points, inside_flags)\n            offset_tl_weights = unmap(offset_tl_weights, num_total_points, inside_flags)\n\n            gt_hm_br = unmap(gt_hm_br, num_total_points, inside_flags)\n            gt_offset_br = unmap(gt_offset_br, num_total_points, inside_flags)\n            hm_br_weights = unmap(hm_br_weights, num_total_points, inside_flags)\n            offset_br_weights = unmap(offset_br_weights, num_total_points, inside_flags)\n\n        return (gt_hm_tl, gt_offset_tl, hm_tl_weights, offset_tl_weights, pos_inds_tl, neg_inds_tl,\n                gt_hm_br, gt_offset_br, hm_br_weights, offset_br_weights, pos_inds_br, neg_inds_br)\n\n    def get_hm_targets(self,\n                       proposals_list,\n                       valid_flag_list,\n                       gt_bboxes_list,\n                       img_metas,\n                       gt_labels_list=None,\n                       unmap_outputs=True):\n        \"\"\"Compute refinement and classification targets for points.\n\n        Args:\n            points_list (list[list]): Multi level points of each image.\n            valid_flag_list (list[list]): Multi level valid flags of each image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            cfg (dict): train sample configs.\n\n        Returns:\n            tuple\n        \"\"\"\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n\n        # concat all level points and flags to a single tensor\n        for i in range(len(proposals_list)):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_gt_hm_tl, all_gt_offset_tl, all_hm_tl_weights, all_offset_tl_weights, pos_inds_tl_list,\n         neg_inds_tl_list, all_gt_hm_br, all_gt_offset_br, all_hm_br_weights, all_offset_br_weights,\n         pos_inds_br_list, neg_inds_br_list) = \\\n            multi_apply(\n                self._hm_target_single,\n                proposals_list,\n                valid_flag_list,\n                gt_bboxes_list,\n                gt_labels_list,\n                unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([gt_hm_tl is None for gt_hm_tl in all_gt_hm_tl]):\n            return None\n        # sampled points of all images\n        num_total_pos_tl = sum([max(inds.numel(), 1) for inds in pos_inds_tl_list])\n        num_total_neg_tl = sum([max(inds.numel(), 1) for inds in neg_inds_tl_list])\n        num_total_pos_br = sum([max(inds.numel(), 1) for inds in pos_inds_br_list])\n        num_total_neg_br = sum([max(inds.numel(), 1) for inds in neg_inds_br_list])\n\n        gt_hm_tl_list = images_to_levels(all_gt_hm_tl, num_level_proposals)\n        gt_offset_tl_list = images_to_levels(all_gt_offset_tl, num_level_proposals)\n        hm_tl_weight_list = images_to_levels(all_hm_tl_weights, num_level_proposals)\n        offset_tl_weight_list = images_to_levels(all_offset_tl_weights, num_level_proposals)\n\n        gt_hm_br_list = images_to_levels(all_gt_hm_br, num_level_proposals)\n        gt_offset_br_list = images_to_levels(all_gt_offset_br, num_level_proposals)\n        hm_br_weight_list = images_to_levels(all_hm_br_weights, num_level_proposals)\n        offset_br_weight_list = images_to_levels(all_offset_br_weights, num_level_proposals)\n\n        return (gt_hm_tl_list, gt_offset_tl_list, hm_tl_weight_list, offset_tl_weight_list,\n                gt_hm_br_list, gt_offset_br_list, hm_br_weight_list, offset_br_weight_list,\n                num_total_pos_tl, num_total_neg_tl, num_total_pos_br, num_total_neg_br)\n\n    def loss_single(self, cls_score, bbox_pts_pred_init, bbox_pts_pred_refine, hm_score, hm_offset,\n                    labels, label_weights, instance_bboxes_gt_init, instance_bboxes_gt_refine,\n                    instance_extremes_gt_init, instance_extremes_gt_refine,\n                    bbox_weights_init, bbox_weights_refine, \n                    gt_hm_tl, gt_offset_tl, gt_hm_tl_weight, gt_offset_tl_weight,\n                    gt_hm_br, gt_offset_br, gt_hm_br_weight, gt_offset_br_weight,\n                    anchor_pts, stride,\n                    num_total_samples_init, num_total_samples_refine,\n                    num_total_samples_tl, num_total_samples_br):\n\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n        cls_score = cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n        loss_cls = self.loss_cls(\n            cls_score, labels, label_weights, avg_factor=num_total_samples_refine)\n\n        # points loss\n        instance_bboxes_gt_init   = instance_bboxes_gt_init.reshape(-1, 4)\n        instance_extremes_gt_init = instance_extremes_gt_init.reshape(-1, 10)\n        bbox_weights_init         = bbox_weights_init.reshape(-1, 4).repeat(1, 5)\n        bbox_pts_pred_init        = bbox_pts_pred_init.permute(0, 2, 3, 1).reshape(-1, 20)*stride\n        anchor_pts = anchor_pts.reshape(-1, 3)\n\n        bbox_gt_reg_init, bbox_yx_inds_init = self.get_bbox_gt_reg(instance_extremes_gt_init,\n                                                                   anchor_pts,\n                                                                   bbox_weights_init)\n\n        normalize_term = self.point_base_scale * stride\n        loss_bbox_init = 0\n        \n        loss_bbox_init += self.loss_bbox_init(bbox_pts_pred_init / normalize_term,\n                                              bbox_gt_reg_init / normalize_term,\n                                              bbox_weights_init,\n                                              avg_factor = num_total_samples_init,\n                                              anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                              bbox_gt = instance_bboxes_gt_init / normalize_term,\n                                              pos_inds = bbox_yx_inds_init)\n\n        instance_bboxes_gt_refine   = instance_bboxes_gt_refine.reshape(-1, 4)\n        instance_extremes_gt_refine = instance_extremes_gt_refine.reshape(-1, 10)\n        bbox_weights_refine         = bbox_weights_refine.reshape(-1, 4).repeat(1, 5)\n        bbox_pts_pred_refine        = bbox_pts_pred_refine.permute(0, 2, 3, 1).reshape(-1, 20)*stride \n\n        bbox_gt_reg_refine, bbox_yx_inds_refine = self.get_bbox_gt_reg(instance_extremes_gt_refine,\n                                                                       anchor_pts,\n                                                                       bbox_weights_refine)\n        loss_bbox_refine = 0\n        \n        loss_bbox_refine += self.loss_bbox_refine(bbox_pts_pred_refine / normalize_term,\n                                                  bbox_gt_reg_refine / normalize_term,\n                                                  bbox_weights_refine,\n                                                  avg_factor = num_total_samples_refine,\n                                                  anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                  bbox_gt = instance_bboxes_gt_refine / normalize_term,\n                                                  pos_inds = bbox_yx_inds_refine)\n\n        # heatmap cls loss\n        hm_score = hm_score.permute(0, 2, 3, 1).reshape(-1, 2)\n        hm_score_tl, hm_score_br = torch.chunk(hm_score, 2, dim=-1)\n        hm_score_tl = hm_score_tl.squeeze(1).sigmoid()\n        hm_score_br = hm_score_br.squeeze(1).sigmoid()\n\n        gt_hm_tl = gt_hm_tl.reshape(-1)\n        gt_hm_tl_weight = gt_hm_tl_weight.reshape(-1)\n        gt_hm_br = gt_hm_br.reshape(-1)\n        gt_hm_br_weight = gt_hm_br_weight.reshape(-1)\n\n        loss_heatmap = 0\n        loss_heatmap += self.loss_heatmap(\n            hm_score_tl, gt_hm_tl, gt_hm_tl_weight, avg_factor=num_total_samples_tl\n        )\n        loss_heatmap += self.loss_heatmap(\n            hm_score_br, gt_hm_br, gt_hm_br_weight, avg_factor=num_total_samples_br\n        )\n        loss_heatmap /= 2.0\n\n        # heatmap offset loss\n        hm_offset = hm_offset.permute(0, 2, 3, 1).reshape(-1, 4)\n        hm_offset_tl, hm_offset_br = torch.chunk(hm_offset, 2, dim=-1)\n\n        gt_offset_tl = gt_offset_tl.reshape(-1, 2)\n        gt_offset_tl_weight = gt_offset_tl_weight.reshape(-1, 2)\n        gt_offset_br = gt_offset_br.reshape(-1, 2)\n        gt_offset_br_weight = gt_offset_br_weight.reshape(-1, 2)\n\n        loss_offset = 0\n        loss_offset += self.loss_offset(\n            hm_offset_tl, gt_offset_tl, gt_offset_tl_weight,\n            avg_factor=num_total_samples_tl\n        )\n        loss_offset += self.loss_offset(\n            hm_offset_br, gt_offset_br, gt_offset_br_weight,\n            avg_factor=num_total_samples_br\n        )\n        loss_offset /= 2.0\n\n        return loss_cls, loss_bbox_init, loss_bbox_refine, loss_heatmap, loss_offset\n\n    def loss(self,\n             cls_scores,\n             bbox_pts_preds_init,\n             bbox_pts_preds_refine,\n             hm_scores,\n             hm_offsets,\n             sem_scores,\n             gt_bboxes,\n             gt_extremes,\n             gt_sem_map,\n             gt_sem_weights,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.point_generators)\n        label_channels = self.cls_out_channels\n\n        # target for initial stage\n        base_pts_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n\n        if self.train_cfg.init.assigner['type'] != 'MaxIoUAssigner':\n            # Assign target for center list\n            candidate_list = base_pts_list\n        else:\n            # transform center list to bbox list and\n            #   assign target for bbox list\n            bbox_list = self.centers_to_bboxes(center_list)\n            candidate_list = bbox_list\n\n        cls_reg_targets_init = self.get_targets(candidate_list.copy(),\n                                                valid_flag_list.copy(),\n                                                gt_bboxes,\n                                                gt_extremes, img_metas,\n                                                gt_bboxes_ignore_list=gt_bboxes_ignore,\n                                                gt_labels_list=gt_labels,\n                                                stage='init',\n                                                label_channels=label_channels)\n\n        (*_, instance_bboxes_gt_list_init, instance_extremes_gt_list_init, bbox_weights_list_init,\n         num_total_pos_init, num_total_neg_init, anchor_pts_list) = cls_reg_targets_init\n\n        # target for heatmap in initial stage\n        proposal_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        heatmap_targets = self.get_hm_targets(\n            proposal_list,\n            valid_flag_list.copy(),\n            gt_bboxes,\n            img_metas,\n            gt_labels)\n        (gt_hm_tl_list, gt_offset_tl_list, gt_hm_tl_weight_list, gt_offset_tl_weight_list,\n         gt_hm_br_list, gt_offset_br_list, gt_hm_br_weight_list, gt_offset_br_weight_list,\n         num_total_pos_tl, num_total_neg_tl, num_total_pos_br, num_total_neg_br) = heatmap_targets\n\n        # target for refinement stage\n        bbox_list = []\n        for i_img, base_pts in enumerate(base_pts_list):\n            bbox = []\n            for i_lvl in range(len(bbox_pts_preds_init)):\n                bbox_preds_init = self.extreme_points2bbox(\n                             bbox_pts_preds_init[i_lvl].detach())\n                bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                bbox_center = torch.cat([base_pts[i_lvl][:, :2], base_pts[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n            bbox_list.append(bbox)\n\n        cls_reg_targets_refine = self.get_targets(bbox_list,\n                                                  valid_flag_list,\n                                                  gt_bboxes,\n                                                  gt_extremes, img_metas,\n                                                  gt_bboxes_ignore_list=gt_bboxes_ignore,\n                                                  gt_labels_list=gt_labels,\n                                                  stage='refine',\n                                                  label_channels=label_channels)\n\n        (labels_list, label_weights_list, instance_bboxes_gt_list_refine, instance_extremes_gt_list_refine,\n        bbox_weights_list_refine, num_total_pos_refine, num_total_neg_refine) = cls_reg_targets_refine\n\n        # compute loss\n        loss_cls, loss_bbox_init, loss_bbox_refine, losses_heatmap, losses_offset = multi_apply(\n            self.loss_single,\n            cls_scores,\n            bbox_pts_preds_init,\n            bbox_pts_preds_refine,\n            hm_scores,\n            hm_offsets,\n            labels_list,\n            label_weights_list,\n            instance_bboxes_gt_list_init,\n            instance_bboxes_gt_list_refine,\n            instance_extremes_gt_list_init,\n            instance_extremes_gt_list_refine,\n            bbox_weights_list_init,\n            bbox_weights_list_refine,\n            gt_hm_tl_list,\n            gt_offset_tl_list,\n            gt_hm_tl_weight_list,\n            gt_offset_tl_weight_list,\n            gt_hm_br_list,\n            gt_offset_br_list,\n            gt_hm_br_weight_list,\n            gt_offset_br_weight_list,\n            anchor_pts_list,\n            self.point_strides,\n            num_total_samples_init=num_total_pos_init,\n            num_total_samples_refine=num_total_pos_refine,\n            num_total_samples_tl=num_total_pos_tl,\n            num_total_samples_br=num_total_pos_br)\n\n        # sem loss\n        concat_sem_scores = []\n        concat_gt_sem_map = []\n        concat_gt_sem_weights = []\n\n        for i in range(5):\n            sem_score = sem_scores[i]\n            gt_lvl_sem_map = F.interpolate(gt_sem_map, sem_score.shape[-2:]).reshape(-1)\n            gt_lvl_sem_weight = F.interpolate(gt_sem_weights, sem_score.shape[-2:]).reshape(-1)\n            sem_score = sem_score.reshape(-1)\n\n            try:\n                concat_sem_scores = torch.cat([concat_sem_scores, sem_score])\n                concat_gt_sem_map = torch.cat([concat_gt_sem_map, gt_lvl_sem_map])\n                concat_gt_sem_weights = torch.cat([concat_gt_sem_weights, gt_lvl_sem_weight])\n            except:\n                concat_sem_scores = sem_score\n                concat_gt_sem_map = gt_lvl_sem_map\n                concat_gt_sem_weights = gt_lvl_sem_weight\n\n        loss_sem = self.loss_sem(concat_sem_scores, concat_gt_sem_map, concat_gt_sem_weights, \n                                 avg_factor=(concat_gt_sem_map > 0).sum())\n\n        loss_dict_all = {'loss_cls': loss_cls,\n                         'loss_bbox_init': loss_bbox_init,\n                         'loss_bbox_refine': loss_bbox_refine,\n                         'loss_heatmap': losses_heatmap,\n                         'loss_offset': losses_offset,\n                         'loss_sem': loss_sem,\n                         }\n        return loss_dict_all\n\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_pts_preds_init,\n                   bbox_pts_preds_refine,\n                   hm_scores,\n                   hm_offsets,\n                   sem_scores,\n                   img_metas,\n                   cfg=None,\n                   rescale=False,\n                   nms=True):\n        assert len(cls_scores) == len(bbox_pts_preds_refine)\n        extreme_bbox_preds = [self.extreme_points2bbox(pts_pred, extreme=True) \n                              for pts_pred in bbox_pts_preds_refine]\n        num_levels = len(cls_scores)\n        mlvl_points = [\n            self.point_generators[i].grid_points(cls_scores[i].size()[-2:],\n                                                 self.point_strides[i])\n            for i in range(num_levels)\n        ]\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                extreme_bbox_preds[i][1][img_id].detach() for i in range(num_levels)\n            ]\n            hm_scores_list = [\n                hm_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            hm_offsets_list = [\n                hm_offsets[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list, hm_scores_list, \n                                                hm_offsets_list,\n                                                mlvl_points, img_shape,\n                                                scale_factor, cfg, rescale,\n                                                nms)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           hm_scores,\n                           hm_offsets,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False,\n                           nms=True):\n        def select(score_map, x, y, ks=2, i=0):\n            H, W = score_map.shape[-2], score_map.shape[-1]\n            score_map = score_map.sigmoid()\n            score_map_original = score_map.clone()\n\n            score_map, indices = F.max_pool2d_with_indices(score_map.unsqueeze(0), kernel_size=ks, stride=1, \n                                                           padding=(ks - 1) // 2)\n\n            indices = indices.squeeze(0).squeeze(0)\n\n            if ks % 2 == 0:\n                round_func = torch.floor\n            else:\n                round_func = torch.round\n\n            x_round = round_func((x / self.point_strides[i]).clamp(min=0, max=score_map.shape[-1] - 1))\n            y_round = round_func((y / self.point_strides[i]).clamp(min=0, max=score_map.shape[-2] - 1))\n\n            select_indices = indices[y_round.to(torch.long), x_round.to(torch.long)]\n            new_x = select_indices % W\n            new_y = select_indices // W\n\n            score_map_squeeze = score_map_original.squeeze(0)\n            score = score_map_squeeze[new_y, new_x]\n\n            new_x, new_y = new_x.to(torch.float), new_y.to(torch.float)\n\n            return new_x, new_y, score\n\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_points)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        for i_lvl, (cls_score, bbox_pred, points) in enumerate(zip(cls_scores, bbox_preds, mlvl_points)):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(-1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n            bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1)\n            bboxes = bbox_pred * self.point_strides[i_lvl] + bbox_pos_center\n            x1 = bboxes[:, 0].clamp(min=0, max=img_shape[1])\n            y1 = bboxes[:, 1].clamp(min=0, max=img_shape[0])\n            x2 = bboxes[:, 2].clamp(min=0, max=img_shape[1])\n            y2 = bboxes[:, 3].clamp(min=0, max=img_shape[0])\n\n            if i_lvl > 0:\n                i = 0 if i_lvl in (1, 2) else 1\n\n                x1_new, y1_new, score1_new = select(hm_scores[i][0, ...], x1, y1, 2, i)\n                x2_new, y2_new, score2_new = select(hm_scores[i][1, ...], x2, y2, 2, i)\n\n                hm_offset = hm_offsets[i].permute(1, 2, 0)\n                point_stride = self.point_strides[i]\n\n                x1 = ((x1_new + hm_offset[y1_new.to(torch.long), x1_new.to(torch.long), 0]) * \n                       point_stride).clamp(min=0, max=img_shape[1])\n                y1 = ((y1_new + hm_offset[y1_new.to(torch.long), x1_new.to(torch.long), 1]) * \n                       point_stride).clamp(min=0, max=img_shape[0])\n                x2 = ((x2_new + hm_offset[y2_new.to(torch.long), x2_new.to(torch.long), 2]) * \n                       point_stride).clamp(min=0, max=img_shape[1])\n                y2 = ((y2_new + hm_offset[y2_new.to(torch.long), x2_new.to(torch.long), 3]) * \n                       point_stride).clamp(min=0, max=img_shape[0])\n            bboxes = torch.stack([x1, y1, x2, y2], dim=-1)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n        mlvl_scores = torch.cat(mlvl_scores)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        if nms:\n            det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores,\n                                                    cfg.score_thr, cfg.nms,\n                                                    cfg.max_per_img)\n            return det_bboxes, det_labels\n        else:\n            return mlvl_bboxes, mlvl_scores\n\n    def get_num_level_proposals_inside(self, num_level_proposals, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_proposals)\n        num_level_proposals_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_proposals_inside\n\n\nclass DCNConvModule(nn.Module):\n    def __init__(\n        self, \n        in_channels = 256,\n        out_channels = 256,\n        kernel_size = 3,\n        dilation = 1,\n        num_groups = 1,\n        dcn_pad = 1\n    ):\n        super(DCNConvModule, self).__init__()\n\n        self.conv = ModulatedDeformConvPack(in_channels, out_channels, kernel_size, 1, dcn_pad)\n        self.bn = nn.GroupNorm(num_groups, out_channels)\n\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, x):\n        x = self.relu(self.bn(self.conv(x)))\n        return x"
  },
  {
    "path": "code/mmdet/models/dense_heads/lsnet_head.py",
    "content": "import pdb\nimport math\nimport torch\nimport numpy as np\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init, kaiming_init\n\nfrom mmdet.core import (PointGenerator, build_assigner, build_sampler,\n                        images_to_levels, multi_apply, multiclass_nms, \n                        multiclass_nms_lsvr, unmap)\nfrom mmdet.ops import DeformConv, PyramidDeformConv, DeformConvPack, ModulatedDeformConvPack\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\n@HEADS.register_module()\nclass LSHead(AnchorFreeHead):\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 point_feat_channels=256,\n                 num_kernel_points=9,\n                 gradient_mul=0.1,\n                 point_strides=[8, 16, 32, 64, 128],\n                 point_base_scale=4,\n                 task = 'bbox',\n                 num_vectors = 4,\n                 conv_module_type= 'norm', #norm of dcn, norm is faster\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox_init=dict(type='CrossIOULoss', loss_weight=1.0),\n                 loss_bbox_refine=dict(type='CrossIOULoss', loss_weight=2.0),\n                 loss_segm_init = None,\n                 loss_segm_refine = None,\n                 loss_pose_init = None,\n                 loss_pose_refine = None,\n                 **kwargs):\n        self.task = task\n        self.num_vectors = num_vectors\n        self.num_kernel_points = num_kernel_points\n        self.point_feat_channels = point_feat_channels\n        self.conv_module_type = conv_module_type\n\n        # we use deformable conv to extract points features\n        self.dcn_kernel = int(np.sqrt(num_kernel_points))\n        self.dcn_pad    = int((self.dcn_kernel - 1) / 2)\n        assert self.dcn_kernel * self.dcn_kernel == num_kernel_points, \\\n            'The points number should be a square number.'\n        assert self.dcn_kernel % 2 == 1, \\\n            'The points number should be an odd square number.'\n        dcn_base = np.arange(-self.dcn_pad, self.dcn_pad + 1).astype(np.float64)\n        dcn_base_y = np.repeat(dcn_base, self.dcn_kernel)\n        dcn_base_x = np.tile(dcn_base, self.dcn_kernel)\n        dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape((-1))\n        self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1)\n\n        super().__init__(num_classes, in_channels, loss_cls=loss_cls, **kwargs)\n\n        self.gradient_mul     = gradient_mul\n        self.point_base_scale = point_base_scale\n        self.point_strides    = point_strides\n        self.fpn_levels       = [i for i in range(len(self.point_strides))]\n        self.point_generators = [PointGenerator() for _ in self.point_strides]\n\n        if self.train_cfg:\n            self.init_assigner = build_assigner(self.train_cfg.init.assigner)\n            self.refine_assigner = build_assigner(self.train_cfg.refine.assigner)\n            # use PseudoSampler when sampling is False\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n\n        self.cls_out_channels = self.num_classes\n\n        if self.task == 'bbox':\n            self.loss_bbox_init = build_loss(loss_bbox_init)\n            self.loss_bbox_refine = build_loss(loss_bbox_refine)\n        elif self.task == 'segm':\n            self.loss_segm_init = build_loss(loss_segm_init)\n            self.loss_segm_refine = build_loss(loss_segm_refine)\n        elif self.task == 'pose_bbox':\n            self.loss_bbox_init = build_loss(loss_bbox_init)\n            self.loss_bbox_refine = build_loss(loss_bbox_refine)\n            self.loss_pose_init = build_loss(loss_pose_init)\n            self.loss_pose_refine = build_loss(loss_pose_refine)\n        elif self.task == 'pose_kbox':\n            self.loss_pose_init = build_loss(loss_pose_init)\n            self.loss_pose_refine = build_loss(loss_pose_refine)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.softplus = nn.Softplus()\n        self.cls_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n        self.cls_convs = nn.ModuleList()\n\n        if self.task == 'bbox':\n            self.bbox_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n            self.bbox_convs = nn.ModuleList()\n        elif self.task == 'segm':\n            self.segm_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n            self.segm_convs = nn.ModuleList()\n        elif self.task == 'pose_bbox':\n            self.bbox_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n            self.bbox_convs = nn.ModuleList()\n            self.pose_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n            self.pose_convs = nn.ModuleList()\n        elif self.task == 'pose_kbox':\n            self.pose_GN = nn.GroupNorm(self.norm_cfg.num_groups, self.feat_channels)\n            self.pose_convs = nn.ModuleList()\n        \n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            if self.conv_module_type == 'norm':\n                self.cls_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                  conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))                                \n            else: #dcn\n                self.cls_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad))\n                \n            if self.task == 'bbox':\n                if self.conv_module_type == 'norm':\n                    self.bbox_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                    conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))                                \n                else: #dcn\n                    self.bbox_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad))\n            elif self.task == 'segm':\n                if self.conv_module_type == 'norm':\n                    self.segm_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                    conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))                                \n                else: #dcn\n                    self.segm_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad))\n            elif self.task == 'pose_bbox':\n                if self.conv_module_type == 'norm':\n                    self.bbox_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                    conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))     \n                    self.pose_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                    conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))                                \n                else: #dcn\n                    self.bbox_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad)) \n                    self.pose_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad)) \n            elif self.task == 'pose_kbox':\n                if self.conv_module_type == 'norm':    \n                    self.pose_convs.append(ConvModule(chn, self.feat_channels, 3, stride=1, padding=1,\n                                                    conv_cfg=self.conv_cfg, norm_cfg=self.norm_cfg))                                \n                else: #dcn\n                    self.pose_convs.append(DCNConvModule(chn, self.feat_channels, self.dcn_kernel, 1,\n                                                   self.norm_cfg.num_groups, self.dcn_pad)) \n\n        \n        self.pts_cls_conv = PyramidDeformConv(self.feat_channels, self.point_feat_channels,\n                                              self.dcn_kernel, 1, self.dcn_pad)\n        self.pts_cls_out = nn.Conv2d(self.point_feat_channels, self.cls_out_channels, 1, 1, 0)\n\n        self.cls_af_dcn_conv = nn.Sequential(\n                                nn.Conv2d(3 * self.point_feat_channels,\n                                            self.point_feat_channels,\n                                            1, 1, 0),\n                                nn.ReLU())\n        self.cls_feat_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n\n        if self.task == 'bbox':\n            bbox_out_dim = 4*(self.num_vectors+1) + (self.num_kernel_points-self.num_vectors-1)*2\n            self.pts_bbox_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n            self.pts_bbox_init_out = nn.Conv2d(self.point_feat_channels, bbox_out_dim, 1, 1, 0)\n\n            self.pts_bbox_refine_conv = PyramidDeformConv(self.feat_channels, self.point_feat_channels,\n                                                          self.dcn_kernel, 1, self.dcn_pad)\n            self.pts_bbox_refine_out = nn.Conv2d(self.point_feat_channels, 4*(self.num_vectors+1), 1, 1, 0)\n\n            self.bbox_af_dcn_conv = nn.Sequential(\n                                nn.Conv2d(3 * self.point_feat_channels,\n                                            self.point_feat_channels,\n                                            1, 1, 0),\n                                nn.ReLU())\n\n            self.bbox_feat_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n\n        elif self.task == 'segm':\n            segm_out_dim = (self.num_vectors+1)*4\n\n            self.pts_segm_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n            self.pts_segm_init_out = nn.Conv2d(self.point_feat_channels, segm_out_dim, 1, 1, 0)\n\n            self.pts_segm_refine_conv = PyramidDeformConv(self.feat_channels, \n                                                          self.point_feat_channels,\n                                                          self.dcn_kernel, 1, self.dcn_pad)\n            self.pts_segm_refine_out = nn.Conv2d(self.point_feat_channels, segm_out_dim, 1, 1, 0)\n\n\n            self.segm_af_dcn_conv = nn.Sequential(\n                                nn.Conv2d(3 * self.point_feat_channels,\n                                            self.point_feat_channels,\n                                            1, 1, 0),\n                                nn.ReLU())\n\n            self.segm_feat_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)    \n        elif self.task == 'pose_bbox':\n            self.pts_bbox_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n            self.pts_bbox_init_out = nn.Conv2d(self.point_feat_channels, 28, 1, 1, 0)\n\n            self.pts_bbox_refine_conv = PyramidDeformConv(self.feat_channels, self.point_feat_channels,\n                                                          self.dcn_kernel, 1, self.dcn_pad)\n            self.pts_bbox_refine_out = nn.Conv2d(self.point_feat_channels, 20, 1, 1, 0)\n\n            self.bbox_af_dcn_conv = nn.Sequential(\n                                nn.Conv2d(3 * self.point_feat_channels,\n                                            self.point_feat_channels,\n                                            1, 1, 0),\n                                nn.ReLU())\n\n            self.bbox_feat_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n\n            pose_out_dim = (self.num_vectors+1)*4\n\n            self.pts_pose_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n            self.pts_pose_init_out = nn.Conv2d(self.point_feat_channels, pose_out_dim, 1, 1, 0)\n\n            self.pts_pose_refine_conv = PyramidDeformConv(self.feat_channels, \n                                                          self.point_feat_channels,\n                                                          self.dcn_kernel, 1, self.dcn_pad)\n            self.pts_pose_refine_out = nn.Conv2d(self.point_feat_channels, pose_out_dim, 1, 1, 0)\n\n\n            self.pose_af_dcn_conv = nn.Sequential(\n                                    nn.Conv2d(3 * self.point_feat_channels,\n                                                self.point_feat_channels,\n                                                1, 1, 0),\n                                    nn.ReLU())\n\n            self.pose_feat_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n        elif self.task == 'pose_kbox':\n            pose_out_dim = (self.num_vectors+1)*4\n\n            self.pts_pose_init_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n            self.pts_pose_init_out = nn.Conv2d(self.point_feat_channels, pose_out_dim, 1, 1, 0)\n\n            self.pts_pose_refine_conv = PyramidDeformConv(self.feat_channels, \n                                                          self.point_feat_channels,\n                                                          self.dcn_kernel, 1, self.dcn_pad)\n            self.pts_pose_refine_out = nn.Conv2d(self.point_feat_channels, pose_out_dim, 1, 1, 0)\n\n\n            self.pose_af_dcn_conv = nn.Sequential(\n                                    nn.Conv2d(3 * self.point_feat_channels,\n                                                self.point_feat_channels,\n                                                1, 1, 0),\n                                    nn.ReLU())\n\n            self.pose_feat_conv = nn.Conv2d(self.feat_channels, self.point_feat_channels, 3, 1, 1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        if self.task == 'bbox':\n            for m in self.bbox_convs:\n                normal_init(m.conv, std=0.01)\n        elif self.task == 'segm':\n            for m in self.segm_convs:\n                normal_init(m.conv, std=0.01)\n        elif self.task == 'pose_bbox':\n            for m in self.bbox_convs:\n                normal_init(m.conv, std=0.01)\n\n            for m in self.pose_convs:\n                normal_init(m.conv, std=0.01)\n        elif self.task == 'pose_kbox':\n            for m in self.pose_convs:\n                normal_init(m.conv, std=0.01)\n\n        bias_cls = bias_init_with_prob(0.01)\n        kaiming_init(self.pts_cls_conv)\n        normal_init(self.pts_cls_out, std=0.01, bias=bias_cls)\n        normal_init(self.cls_feat_conv, std=0.01)\n        normal_init(self.cls_af_dcn_conv[0], std=0.01)\n\n        if self.task == 'bbox':\n            normal_init(self.pts_bbox_init_conv, std=0.01)\n            normal_init(self.pts_bbox_init_out, std=0.01)\n            kaiming_init(self.pts_bbox_refine_conv)\n            normal_init(self.pts_bbox_refine_out, std=0.01)\n            normal_init(self.bbox_feat_conv, std=0.01)\n            normal_init(self.bbox_af_dcn_conv[0], std=0.01)\n        elif self.task == 'segm':\n            normal_init(self.pts_segm_init_conv, std=0.01)\n            normal_init(self.pts_segm_init_out, std=0.01)\n            kaiming_init(self.pts_segm_refine_conv)\n            normal_init(self.pts_segm_refine_out, std=0.01)\n            normal_init(self.segm_feat_conv, std=0.01)\n            normal_init(self.segm_af_dcn_conv[0], std=0.01)\n        elif self.task == 'pose_bbox':\n            normal_init(self.pts_bbox_init_conv, std=0.01)\n            normal_init(self.pts_bbox_init_out, std=0.01)\n            kaiming_init(self.pts_bbox_refine_conv)\n            normal_init(self.pts_bbox_refine_out, std=0.01)\n            normal_init(self.bbox_feat_conv, std=0.01)\n            normal_init(self.bbox_af_dcn_conv[0], std=0.01)\n\n            normal_init(self.pts_pose_init_conv, std=0.01)\n            normal_init(self.pts_pose_init_out, std=0.01)\n            kaiming_init(self.pts_pose_refine_conv)\n            normal_init(self.pts_pose_refine_out, std=0.01)\n            normal_init(self.pose_feat_conv, std=0.01)\n            normal_init(self.pose_af_dcn_conv[0], std=0.01)\n        elif self.task == 'pose_kbox':\n            normal_init(self.pts_pose_init_conv, std=0.01)\n            normal_init(self.pts_pose_init_out, std=0.01)\n            kaiming_init(self.pts_pose_refine_conv)\n            normal_init(self.pts_pose_refine_out, std=0.01)\n            normal_init(self.pose_feat_conv, std=0.01)\n            normal_init(self.pose_af_dcn_conv[0], std=0.01)\n\n    def extreme_points2bbox(self, pts, y_first=True, extreme=False):\n        pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n        valid_pts, inds = torch.max(pts_reshape, dim=2)\n        neg_inds = inds == 0\n        valid_pts[neg_inds] *= -1\n        valid_pts_xy = valid_pts.view(valid_pts.shape[0], -1, 2, *valid_pts.shape[2:])\n\n        pts_y = valid_pts_xy[:, :, 0, ...] if y_first else valid_pts_xy[:, :, 1, ...]\n        pts_x = valid_pts_xy[:, :, 1, ...] if y_first else valid_pts_xy[:, :, 0, ...]\n\n        bbox_left   = pts_x[:, 1, :, :].unsqueeze(1)\n        bbox_right  = pts_x[:, 3, :, :].unsqueeze(1)\n        bbox_up     = pts_y[:, 0, :, :].unsqueeze(1)\n        bbox_bottom = pts_y[:, 2, :, :].unsqueeze(1)\n\n        bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n\n        if extreme:\n            extreme_up     = torch.cat((pts_x[:, 0:1, ...], pts_y[:, 0:1, ...]), dim = 1)\n            extreme_left   = torch.cat((pts_x[:, 1:2, ...], pts_y[:, 1:2, ...]), dim = 1)\n            extreme_bottom = torch.cat((pts_x[:, 2:3, ...], pts_y[:, 2:3, ...]), dim = 1)\n            extreme_right  = torch.cat((pts_x[:, 3:4, ...], pts_y[:, 3:4, ...]), dim = 1)\n            extremes       = torch.cat([extreme_up, extreme_left, extreme_bottom, extreme_right], \n                                       dim=1)\n            return extremes, bbox\n        else:\n            return bbox\n    \n    def vectors2bbox(self, pts, y_first=True, vector=False):\n        pts_reshape = pts[:,:-4,...].view(pts.shape[0], -1, 2, *pts.shape[2:])\n        valid_pts, inds = torch.max(pts_reshape, dim=2)\n        neg_inds = inds == 0\n        valid_pts[neg_inds] *= -1\n        valid_pts_xy = valid_pts.view(valid_pts.shape[0], -1, 2, *valid_pts.shape[2:])\n\n        pts_y = valid_pts_xy[:, :, 0, ...] if y_first else valid_pts_xy[:, :, 1, ...]\n        pts_x = valid_pts_xy[:, :, 1, ...] if y_first else valid_pts_xy[:, :, 0, ...]\n\n        vectors_xmin = pts_x.min(1)[0]\n        vectors_ymin = pts_y.min(1)[0]\n        vectors_xmax = pts_x.max(1)[0]\n        vectors_ymax = pts_y.max(1)[0]\n\n        bbox = torch.stack([vectors_xmin, vectors_ymin, vectors_xmax, vectors_ymax], 1)\n\n        if vector:\n            vectors = torch.stack([pts_x, pts_y], 2).reshape(pts_y.shape[0], -1, *pts_y.shape[2:])\n            return vectors, bbox\n        else:\n            return bbox\n\n    def get_pred_reg(self, raw_reg1, raw_reg2):\n        if raw_reg2 is not None:\n            raw_reg_reshape = raw_reg1.view(raw_reg1.shape[0], -1, 2, *raw_reg1.shape[2:])\n            pos_reg, inds = torch.max(raw_reg_reshape, dim=2)\n            neg_inds = inds == 0\n            pos_reg[neg_inds] *= -1\n\n            reg_for_dcn = torch.cat((pos_reg, raw_reg2), dim =1)\n            return reg_for_dcn\n        else:\n            raw_reg_reshape = raw_reg1.view(raw_reg1.shape[0], -1, 4, *raw_reg1.shape[2:])\n            raw_reg_cts = raw_reg_reshape[:, -1:, ...]\n            raw_reg_polys = raw_reg_reshape[:, :-1, ...]\n\n            if self.task == 'segm':\n                kernel_stride = math.ceil(self.num_vectors/(self.num_kernel_points-1))\n                raw_reg_poly_offs = raw_reg_polys[:, ::kernel_stride, ...]\n            elif 'pose' in self.task:\n                kernel_stride = 2\n                raw_reg_poly_offs = raw_reg_polys[:, 1::kernel_stride, ...]\n\n            raw_reg_offsets = torch.cat([raw_reg_poly_offs, raw_reg_cts], dim=1)\n            raw_reg_offsets_reshape = raw_reg_offsets.reshape(raw_reg_offsets.shape[0], -1, 2,\n                                                              *raw_reg_offsets.shape[3:])\n\n            reg_for_dcn, inds = torch.max(raw_reg_offsets_reshape, dim = 2)\n            neg_inds = inds==0\n            reg_for_dcn[neg_inds] *= -1\n            return reg_for_dcn\n\n    def get_bbox_gt_reg(self, gt_pts, anchor_pts, bbox_weights):\n        gt_reg = gt_pts.new_zeros([gt_pts.size(0), 20])\n        anchor_pts_repeat = anchor_pts[:, :2].repeat(1, 5)\n        offset_reg = gt_pts - anchor_pts_repeat\n        br_reg = offset_reg >= 0 \n        tl_reg = offset_reg < 0\n        tlbr_inds = torch.stack([tl_reg, br_reg], -1).reshape(-1, 20)\n        gt_reg[tlbr_inds] = torch.abs(offset_reg.reshape(-1))\n\n        pos_inds = bbox_weights[:,0]>0\n        neg_inds = bbox_weights[:,0]==0\n        gt_reg[neg_inds] = 0\n\n        xl_reg = gt_reg[..., 0::4]\n        xr_reg = gt_reg[..., 1::4]\n        yt_reg = gt_reg[..., 2::4]\n        yb_reg = gt_reg[..., 3::4]\n        yx_gt_reg = torch.stack([yt_reg, yb_reg, xl_reg, xr_reg], -1).reshape(-1, 20)\n\n        xl_inds = tlbr_inds[..., 0::4]\n        xr_inds = tlbr_inds[..., 1::4]\n        yt_inds = tlbr_inds[..., 2::4]\n        yb_inds = tlbr_inds[..., 3::4]\n        yx_inds = torch.stack([yt_inds, yb_inds, xl_inds, xr_inds], -1).reshape(-1, 20)\n\n        return yx_gt_reg, yx_inds\n\n    def get_poly_gt_reg(self, gt_pts, anchor_pts, bbox_weights):\n        gt_reg = gt_pts.new_zeros([gt_pts.size(0), gt_pts.size(1)*2])\n        anchor_pts_repeat = anchor_pts[:, :2].repeat(1, self.num_vectors+1)\n        offset_reg = gt_pts - anchor_pts_repeat\n        br_reg = offset_reg >= 0 \n        tl_reg = offset_reg < 0\n        tlbr_inds = torch.stack([tl_reg, br_reg], -1).reshape(-1, gt_pts.size(1)*2)\n        gt_reg[tlbr_inds] = torch.abs(offset_reg.reshape(-1))\n\n        pos_inds = bbox_weights[:,0]>0\n        neg_inds = bbox_weights[:,0]==0\n        gt_reg[neg_inds] = 0\n\n        xl_reg = gt_reg[..., 0::4]\n        xr_reg = gt_reg[..., 1::4]\n        yt_reg = gt_reg[..., 2::4]\n        yb_reg = gt_reg[..., 3::4]\n        yx_gt_reg = torch.stack([yt_reg, yb_reg, xl_reg, xr_reg], -1).reshape(-1, gt_pts.size(1)*2)\n\n        xl_inds = tlbr_inds[..., 0::4]\n        xr_inds = tlbr_inds[..., 1::4]\n        yt_inds = tlbr_inds[..., 2::4]\n        yb_inds = tlbr_inds[..., 3::4]\n        yx_inds = torch.stack([yt_inds, yb_inds, xl_inds, xr_inds], -1).reshape(-1, gt_pts.size(1)*2)\n\n        return yx_gt_reg, yx_inds\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      gt_bboxes,\n                      gt_extremes = None,\n                      gt_keypoints = None,\n                      gt_masks = None,\n                      gt_labels = None,\n                      gt_bboxes_ignore=None,\n                      proposal_cfg = None,\n                      **kwargs):\n        outs = self(x)\n        if gt_labels is None:\n            loss_inputs = outs + (gt_bboxes, gt_extremes, gt_keypoints, gt_masks, img_metas)\n        else:\n            loss_inputs = outs + (gt_bboxes, gt_extremes, gt_keypoints, gt_masks, gt_labels, img_metas)\n        losses = self.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)\n        if proposal_cfg is None:\n            return losses\n        else:\n            proposal_list = self.get_bboxes(*outs, img_metas, cfg=proposal_cfg)\n            return losses, proposal_list\n\n    def forward(self, feats):\n        (cls_feats, bbox_feats, bbox_reg_init_sps, bbox_dcn_offsets,\n         segm_feats, segm_reg_init_sps, segm_dcn_offsets,\n         pose_feats, pose_reg_init_sps, pose_dcn_offsets) = multi_apply(self.forward_single1, feats)\n\n        (pts_cls_outs, bbox_reg_refine_sps, segm_reg_refine_sps, pose_reg_refine_sps\n        ) = multi_apply(self.forward_single2,\n                        bbox_dcn_offsets,\n                        bbox_reg_init_sps,\n                        segm_dcn_offsets,\n                        segm_reg_init_sps,\n                        pose_dcn_offsets,\n                        pose_reg_init_sps,\n                        self.fpn_levels,\n                        cls_feats = cls_feats,\n                        bbox_feats = bbox_feats,\n                        segm_feats = segm_feats,\n                        pose_feats = pose_feats,\n                        num_levels = len(self.fpn_levels))\n\n        return (pts_cls_outs, bbox_reg_init_sps, bbox_reg_refine_sps, \n                segm_reg_init_sps, segm_reg_refine_sps, pose_reg_init_sps, pose_reg_refine_sps)\n\n    def forward_single1(self, x):\n        \"\"\" Forward feature map of a single FPN level.\"\"\"\n        dcn_base_offset = self.dcn_base_offset.type_as(x)\n\n        cls_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n\n        if self.task == 'bbox':\n            bbox_feat = x\n            for bbox_conv in self.bbox_convs:\n                 bbox_feat = bbox_conv(bbox_feat)\n\n            bbox_reg_init_out = self.pts_bbox_init_out(\n                        self.relu(self.pts_bbox_init_conv(bbox_feat)))\n\n            bbox_reg_init_sp = self.softplus(bbox_reg_init_out[:, :4*(self.num_vectors+1), ...])\n            bbox_pred_reg = self.get_pred_reg(bbox_reg_init_sp, \n                                              bbox_reg_init_out[:, 4*(self.num_vectors+1):, ...])\n\n            bbox_pred_reg_grad_mul = (1 - self.gradient_mul)*bbox_pred_reg.detach(\n                                    ) + self.gradient_mul * bbox_pred_reg\n\n            bbox_dcn_offset = bbox_pred_reg_grad_mul - dcn_base_offset\n\n            return (cls_feat, bbox_feat, bbox_reg_init_sp, bbox_dcn_offset, None, None, None,\n                    None, None, None)\n\n        elif self.task == 'segm':\n            segm_feat = x\n            for segm_conv in self.segm_convs:\n                 segm_feat = segm_conv(segm_feat)\n\n            segm_reg_init_out = self.pts_segm_init_out(\n                        self.relu(self.pts_segm_init_conv(segm_feat)))\n\n            segm_reg_init_sp = self.softplus(segm_reg_init_out)\n            segm_pred_reg = self.get_pred_reg(segm_reg_init_sp, None)\n\n            segm_pred_reg_grad_mul = (1 - self.gradient_mul)*segm_pred_reg.detach(\n                                    ) + self.gradient_mul * segm_pred_reg\n\n            segm_dcn_offset = segm_pred_reg_grad_mul - dcn_base_offset\n\n            return (cls_feat, None, None, None, segm_feat, segm_reg_init_sp, segm_dcn_offset,\n                    None, None, None)\n        \n        elif self.task == 'pose_bbox':\n            bbox_feat = x\n            pose_feat = x\n            for bbox_conv in self.bbox_convs:\n                 bbox_feat = bbox_conv(bbox_feat)\n            for pose_conv in self.pose_convs:\n                 pose_feat = pose_conv(pose_feat)\n\n            bbox_reg_init_out = self.pts_bbox_init_out(\n                        self.relu(self.pts_bbox_init_conv(bbox_feat)))\n\n            bbox_reg_init_sp = self.softplus(bbox_reg_init_out[:, :20, ...])\n            bbox_pred_reg = self.get_pred_reg(bbox_reg_init_sp, bbox_reg_init_out[:, 20:, ...])\n\n            bbox_pred_reg_grad_mul = (1 - self.gradient_mul)*bbox_pred_reg.detach(\n                                    ) + self.gradient_mul * bbox_pred_reg\n\n            bbox_dcn_offset = bbox_pred_reg_grad_mul - dcn_base_offset\n\n            pose_reg_init_out = self.pts_pose_init_out(\n                        self.relu(self.pts_pose_init_conv(pose_feat)))\n\n            pose_reg_init_sp = self.softplus(pose_reg_init_out)\n            pose_pred_reg = self.get_pred_reg(pose_reg_init_sp, None)\n\n            pose_pred_reg_grad_mul = (1 - self.gradient_mul)*pose_pred_reg.detach(\n                                    ) + self.gradient_mul * pose_pred_reg\n\n            pose_dcn_offset = pose_pred_reg_grad_mul - dcn_base_offset\n\n            return (cls_feat, bbox_feat, bbox_reg_init_sp, bbox_dcn_offset, None, None, None,\n                    pose_feat, pose_reg_init_sp, pose_dcn_offset)\n        elif self.task == 'pose_kbox':\n            pose_feat = x\n            for pose_conv in self.pose_convs:\n                 pose_feat = pose_conv(pose_feat)\n\n            pose_reg_init_out = self.pts_pose_init_out(\n                        self.relu(self.pts_pose_init_conv(pose_feat)))\n\n            pose_reg_init_sp = self.softplus(pose_reg_init_out)\n            pose_pred_reg = self.get_pred_reg(pose_reg_init_sp, None)\n\n            pose_pred_reg_grad_mul = (1 - self.gradient_mul)*pose_pred_reg.detach(\n                                    ) + self.gradient_mul * pose_pred_reg\n\n            pose_dcn_offset = pose_pred_reg_grad_mul - dcn_base_offset\n\n            return (cls_feat, None, None, None, None, None, None,\n                    pose_feat, pose_reg_init_sp, pose_dcn_offset)\n\n    def forward_single2(self, bbox_dcn_offset, bbox_reg_init_sp, segm_dcn_offset, segm_reg_init_sp, \n                        pose_dcn_offset, pose_reg_init_sp, fpn_level, cls_feats, bbox_feats, segm_feats, \n                        pose_feats, num_levels):\n        level_list = []\n        level_list.append(fpn_level)\n        if fpn_level == 0:\n            level_list.append(fpn_level+1)\n            level_list.append(fpn_level+2)\n        elif fpn_level == num_levels-1:\n            level_list.append(fpn_level-1)\n            level_list.append(fpn_level-2)\n        else:\n            level_list.append(fpn_level-1)\n            level_list.append(fpn_level+1)\n\n        base_h = cls_feats[fpn_level].size(2)\n        base_w = cls_feats[fpn_level].size(3)\n        bbox_refine_raws = []\n        pts_cls_raws     = []\n        segm_refine_raws = []\n        pose_refine_raws = []\n\n        for level in level_list:\n            current_h = cls_feats[level].size(2)\n            current_w = cls_feats[level].size(3)\n            scale_h = current_h/base_h\n            scale_w = current_w/base_w\n            if self.task == 'bbox':\n                offset_y = bbox_dcn_offset[:, 0::2, ...]\n                offset_x = bbox_dcn_offset[:, 1::2, ...]\n                offset_y *= scale_h\n                offset_x *= scale_w\n                bbox_dcn_offset_ = torch.stack([offset_y, offset_x], 2).view(bbox_dcn_offset.size(0),\n                                                -1, bbox_dcn_offset.size(2), bbox_dcn_offset.size(3))\n                \n                bbox_refine_raws.append(self.pts_bbox_refine_conv(bbox_feats[level], bbox_dcn_offset_, \n                                                                  scale_h,\n                                                                  scale_w))\n                pts_cls_raws.append(self.pts_cls_conv(cls_feats[level], bbox_dcn_offset_, scale_h, scale_w))\n\n            elif self.task == 'segm':\n                segm_offset_y = segm_dcn_offset[:, 0::2, ...]\n                segm_offset_x = segm_dcn_offset[:, 1::2, ...]\n                segm_offset_y *= scale_h\n                segm_offset_x *= scale_w\n                segm_dcn_offset_ = torch.stack([segm_offset_y, segm_offset_x], \n                                                2).view(segm_dcn_offset.size(0), -1, \n                                                segm_dcn_offset.size(2), segm_dcn_offset.size(3))\n                \n                segm_refine_raws.append(self.pts_segm_refine_conv(segm_feats[level], \n                                                                  segm_dcn_offset_, \n                                                                  scale_h, scale_w))\n\n                pts_cls_raws.append(self.pts_cls_conv(cls_feats[level], segm_dcn_offset_, \n                                                      scale_h, scale_w))\n\n            elif self.task == 'pose_bbox':\n                offset_y = bbox_dcn_offset[:, 0::2, ...]\n                offset_x = bbox_dcn_offset[:, 1::2, ...]\n                offset_y *= scale_h\n                offset_x *= scale_w\n                bbox_dcn_offset_ = torch.stack([offset_y, offset_x], 2).view(bbox_dcn_offset.size(0),\n                                                -1, bbox_dcn_offset.size(2), bbox_dcn_offset.size(3))\n                \n                bbox_refine_raws.append(self.pts_bbox_refine_conv(bbox_feats[level], bbox_dcn_offset_, \n                                                                  scale_h,\n                                                                  scale_w))\n\n                pose_offset_y = pose_dcn_offset[:, 0::2, ...]\n                pose_offset_x = pose_dcn_offset[:, 1::2, ...]\n                pose_offset_y *= scale_h\n                pose_offset_x *= scale_w\n                pose_dcn_offset_ = torch.stack([pose_offset_y, pose_offset_x], \n                                                2).view(pose_dcn_offset.size(0), -1, \n                                                pose_dcn_offset.size(2), pose_dcn_offset.size(3))\n                \n                pose_refine_raws.append(self.pts_pose_refine_conv(pose_feats[level], \n                                                                  pose_dcn_offset_, \n                                                                  scale_h, scale_w))\n\n                pts_cls_raws.append(self.pts_cls_conv(cls_feats[level], pose_dcn_offset_, \n                                                      scale_h, scale_w))\n            elif self.task == 'pose_kbox':\n                pose_offset_y = pose_dcn_offset[:, 0::2, ...]\n                pose_offset_x = pose_dcn_offset[:, 1::2, ...]\n                pose_offset_y *= scale_h\n                pose_offset_x *= scale_w\n                pose_dcn_offset_ = torch.stack([pose_offset_y, pose_offset_x], \n                                                2).view(pose_dcn_offset.size(0), -1, \n                                                pose_dcn_offset.size(2), pose_dcn_offset.size(3))\n                \n                pose_refine_raws.append(self.pts_pose_refine_conv(pose_feats[level], \n                                                                  pose_dcn_offset_, \n                                                                  scale_h, scale_w))\n\n                pts_cls_raws.append(self.pts_cls_conv(cls_feats[level], pose_dcn_offset_, \n                                                      scale_h, scale_w))\n\n\n        if self.task == 'bbox':\n            bbox_reg_refine_out = self.pts_bbox_refine_out(\n                self.relu(self.bbox_GN(self.bbox_af_dcn_conv(torch.cat(bbox_refine_raws, dim=1))+\n                                    self.bbox_feat_conv(bbox_feats[fpn_level]))))\n\n            bbox_reg_refine_sp = self.softplus(bbox_reg_refine_out + bbox_reg_init_sp.detach())\n\n            pts_cls_out = self.pts_cls_out(\n                        self.relu(self.cls_GN(self.cls_af_dcn_conv(torch.cat(pts_cls_raws, dim=1))+\n                                    self.cls_feat_conv(cls_feats[fpn_level]))))\n\n            return (pts_cls_out, bbox_reg_refine_sp, None, None)\n\n        elif self.task == 'segm':\n            segm_reg_refine_out = self.pts_segm_refine_out(\n                self.relu(self.segm_GN(self.segm_af_dcn_conv(torch.cat(segm_refine_raws, dim=1))+\n                                    self.segm_feat_conv(segm_feats[fpn_level]))))\n\n            segm_reg_refine_sp = self.softplus(segm_reg_refine_out + segm_reg_init_sp.detach())\n\n            pts_cls_out = self.pts_cls_out(\n                        self.relu(self.cls_GN(self.cls_af_dcn_conv(torch.cat(pts_cls_raws, dim=1))+\n                                    self.cls_feat_conv(cls_feats[fpn_level]))))\n\n            return (pts_cls_out, None, segm_reg_refine_sp, None)\n\n        elif self.task == 'pose_bbox':\n            bbox_reg_refine_out = self.pts_bbox_refine_out(\n                self.relu(self.bbox_GN(self.bbox_af_dcn_conv(torch.cat(bbox_refine_raws, dim=1))+\n                                    self.bbox_feat_conv(bbox_feats[fpn_level]))))\n\n            bbox_reg_refine_sp = self.softplus(bbox_reg_refine_out + bbox_reg_init_sp.detach())\n\n            pts_cls_out = self.pts_cls_out(\n                        self.relu(self.cls_GN(self.cls_af_dcn_conv(torch.cat(pts_cls_raws, dim=1))+\n                                    self.cls_feat_conv(cls_feats[fpn_level]))))\n            \n            pose_reg_refine_out = self.pts_pose_refine_out(\n                self.relu(self.pose_GN(self.pose_af_dcn_conv(torch.cat(pose_refine_raws, dim=1))+\n                                    self.pose_feat_conv(pose_feats[fpn_level]))))\n\n            pose_reg_refine_sp = self.softplus(pose_reg_refine_out + pose_reg_init_sp.detach())\n\n            return (pts_cls_out, bbox_reg_refine_sp, None, pose_reg_refine_sp)\n\n        elif self.task == 'pose_kbox':\n            pts_cls_out = self.pts_cls_out(\n                        self.relu(self.cls_GN(self.cls_af_dcn_conv(torch.cat(pts_cls_raws, dim=1))+\n                                    self.cls_feat_conv(cls_feats[fpn_level]))))\n\n            pose_reg_refine_out = self.pts_pose_refine_out(\n                self.relu(self.pose_GN(self.pose_af_dcn_conv(torch.cat(pose_refine_raws, dim=1))+\n                                    self.pose_feat_conv(pose_feats[fpn_level]))))\n\n            pose_reg_refine_sp = self.softplus(pose_reg_refine_out + pose_reg_init_sp.detach())\n\n            return (pts_cls_out, None, None, pose_reg_refine_sp)\n\n    def get_points(self, featmap_sizes, img_metas):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n\n        Returns:\n            tuple: points of each image, valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # points center for one time\n        multi_level_points = []\n        for i in range(num_levels):\n            points = self.point_generators[i].grid_points(\n                featmap_sizes[i], self.point_strides[i])\n            multi_level_points.append(points)\n        points_list = [[point.clone() for point in multi_level_points] for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level grids\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            for i in range(num_levels):\n                point_stride = self.point_strides[i]\n                feat_h, feat_w = featmap_sizes[i]\n                h, w = img_meta['pad_shape'][:2]\n                valid_feat_h = min(int(np.ceil(h / point_stride)), feat_h)\n                valid_feat_w = min(int(np.ceil(w / point_stride)), feat_w)\n                flags = self.point_generators[i].valid_flags(\n                    (feat_h, feat_w), (valid_feat_h, valid_feat_w))\n                multi_level_flags.append(flags)\n            valid_flag_list.append(multi_level_flags)\n\n        return points_list, valid_flag_list\n\n    def _target_single(self,\n                       flat_proposals,\n                       valid_flags,\n                       num_level_proposals,\n                       gt_bboxes,\n                       gt_extremes,\n                       gt_polygons,\n                       gt_keypoints,\n                       vs_keypoints,\n                       gt_bboxes_ignore,\n                       gt_labels,\n                       label_channels=1,\n                       stage='init',\n                       unmap_outputs=True):\n        inside_flags = valid_flags\n        if not inside_flags.any():\n            return (None, ) * 8\n        # assign gt and sample proposals\n        proposals = flat_proposals[inside_flags, :]\n        num_level_proposals_inside = self.get_num_level_proposals_inside(num_level_proposals, \n                                                                         inside_flags)\n        if stage == 'init':\n            assigner = self.init_assigner\n            assigner_type = self.train_cfg.init.assigner.type\n            pos_weight = self.train_cfg.init.pos_weight\n        else:\n            assigner = self.refine_assigner\n            assigner_type = self.train_cfg.refine.assigner.type\n            pos_weight = self.train_cfg.refine.pos_weight\n\n        if assigner_type != \"ATSSAssigner\":\n            assign_result = assigner.assign(proposals, gt_bboxes, gt_extremes, gt_bboxes_ignore,\n                                            gt_labels)\n        else:\n            assign_result = assigner.assign(proposals, num_level_proposals_inside, gt_bboxes, \n                                            gt_bboxes_ignore, gt_labels)\n        sampling_result = self.sampler.sample(assign_result, proposals, gt_bboxes)\n\n        num_valid_proposals = proposals.shape[0]\n        bboxes_gt      = proposals.new_zeros([num_valid_proposals, 4])\n        bbox_weights = proposals.new_zeros([num_valid_proposals, 4])\n        labels       = proposals.new_full((num_valid_proposals, ), self.background_label, \n                                           dtype=torch.long)\n        label_weights = proposals.new_zeros(num_valid_proposals, dtype=torch.float)\n        if self.task == 'bbox':\n            extremes_gt = proposals.new_zeros([num_valid_proposals, (self.num_vectors+1)*2])\n        elif self.task == 'segm':\n            polygons_gt = proposals.new_zeros([num_valid_proposals, (self.num_vectors+1)*2])\n        elif self.task == 'pose_bbox':\n            extremes_gt = proposals.new_zeros([num_valid_proposals, 10])\n            keypoints_vs = proposals.new_zeros([num_valid_proposals, self.num_vectors])\n            keypoints_gt = proposals.new_zeros([num_valid_proposals, (self.num_vectors+1)*2])\n        elif self.task  == 'pose_kbox':\n            keypoints_vs = proposals.new_zeros([num_valid_proposals, self.num_vectors])\n            keypoints_gt = proposals.new_zeros([num_valid_proposals, (self.num_vectors+1)*2])\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_gt_bboxes = sampling_result.pos_gt_bboxes\n            bboxes_gt[pos_inds, :] = pos_gt_bboxes\n\n            if self.task == 'bbox':\n                pos_gt_extremes = gt_extremes[sampling_result.pos_assigned_gt_inds]\n                extremes_gt[pos_inds, :] = pos_gt_extremes\n            elif self.task == 'segm':\n                pos_gt_polygons = gt_polygons[sampling_result.pos_assigned_gt_inds]\n                polygons_gt[pos_inds, :] = pos_gt_polygons\n            elif self.task == 'pose_bbox':\n                pos_gt_extremes = gt_extremes[sampling_result.pos_assigned_gt_inds]\n                extremes_gt[pos_inds, :] = pos_gt_extremes\n\n                pos_vs_keypoints = vs_keypoints[sampling_result.pos_assigned_gt_inds]\n                keypoints_vs[pos_inds, :] = pos_vs_keypoints\n\n                pos_gt_keypoints = gt_keypoints[sampling_result.pos_assigned_gt_inds]\n                keypoints_gt[pos_inds, :] = pos_gt_keypoints\n            elif self.task == 'pose_kbox':\n                pos_vs_keypoints = vs_keypoints[sampling_result.pos_assigned_gt_inds]\n                keypoints_vs[pos_inds, :] = pos_vs_keypoints\n\n                pos_gt_keypoints = gt_keypoints[sampling_result.pos_assigned_gt_inds]\n                keypoints_gt[pos_inds, :] = pos_gt_keypoints\n\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]\n            if pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of proposals\n        if unmap_outputs:\n            num_total_proposals = flat_proposals.size(0)\n            labels              = unmap(labels, num_total_proposals, inside_flags)\n            label_weights       = unmap(label_weights, num_total_proposals, inside_flags)\n            bboxes_gt           = unmap(bboxes_gt, num_total_proposals, inside_flags)\n            bbox_weights        = unmap(bbox_weights, num_total_proposals, inside_flags)\n            if self.task == 'bbox':\n                extremes_gt = unmap(extremes_gt, num_total_proposals, inside_flags)\n                return (labels, label_weights, bboxes_gt, extremes_gt, None, None, None, \n                        bbox_weights, pos_inds, neg_inds)\n            elif self.task == 'segm':\n                polygons_gt = unmap(polygons_gt, num_total_proposals, inside_flags)\n                return (labels, label_weights, bboxes_gt, None, polygons_gt, None, None, bbox_weights, \n                        pos_inds, neg_inds)\n            elif self.task == 'pose_bbox':\n                extremes_gt = unmap(extremes_gt, num_total_proposals, inside_flags)\n                keypoints_gt = unmap(keypoints_gt, num_total_proposals, inside_flags)\n                keypoints_vs = unmap(keypoints_vs, num_total_proposals, inside_flags)\n                return (labels, label_weights, bboxes_gt, extremes_gt, None, keypoints_gt, keypoints_vs, \n                        bbox_weights, pos_inds, neg_inds)\n            elif self.task == 'pose_kbox':\n                keypoints_gt = unmap(keypoints_gt, num_total_proposals, inside_flags)\n                keypoints_vs = unmap(keypoints_vs, num_total_proposals, inside_flags)\n                return (labels, label_weights, bboxes_gt, None, None, keypoints_gt, keypoints_vs, \n                        bbox_weights, pos_inds, neg_inds)\n\n    def get_targets(self,\n                    proposals_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    gt_extremes_list,\n                    gt_polygons_list,\n                    gt_keypoints_list,\n                    vs_keypoints_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    stage='init',\n                    label_channels=1,\n                    unmap_outputs=True):\n        \n        assert stage in ['init', 'refine']\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n        num_level_proposals_list = [num_level_proposals] * num_imgs\n\n        # concat all level points and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_extremes_list is None:\n            gt_extremes_list = [None for _ in range(num_imgs)]\n        if gt_polygons_list is None:\n            gt_polygons_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        if gt_keypoints_list is None:\n            gt_keypoints_list = [None for _ in range(num_imgs)]\n        if vs_keypoints_list is None:\n            vs_keypoints_list = [None for _ in range(num_imgs)]\n\n\n        (all_labels, all_label_weights, all_bboxes_gt, all_extremes_gt, all_polygons_gt,\n         all_keypoints_gt, all_keypoints_vs, all_bbox_weights, pos_inds_list, \n         neg_inds_list) = multi_apply(self._target_single,\n                                      proposals_list,\n                                      valid_flag_list,\n                                      num_level_proposals_list,\n                                      gt_bboxes_list,\n                                      gt_extremes_list,\n                                      gt_polygons_list,\n                                      gt_keypoints_list,\n                                      vs_keypoints_list,\n                                      gt_bboxes_ignore_list,\n                                      gt_labels_list,\n                                      stage=stage,\n                                      label_channels=label_channels,\n                                      unmap_outputs=unmap_outputs)\n       \n        # no valid points\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n\n        labels_list        = images_to_levels(all_labels, num_level_proposals)\n        label_weights_list = images_to_levels(all_label_weights, num_level_proposals)\n        bboxes_gt_list     = images_to_levels(all_bboxes_gt, num_level_proposals)\n        bbox_weights_list  = images_to_levels(all_bbox_weights, num_level_proposals)\n        if self.task == 'bbox':\n            extremes_gt_list  = images_to_levels(all_extremes_gt, num_level_proposals)\n            polygons_gt_list  = [None for _ in self.point_strides]\n            keypoints_gt_list = [None for _ in self.point_strides]\n            keypoints_vs_list = [None for _ in self.point_strides]\n        elif self.task == 'segm':\n            polygons_gt_list  = images_to_levels(all_polygons_gt, num_level_proposals)\n            extremes_gt_list  = [None for _ in self.point_strides]\n            keypoints_gt_list = [None for _ in self.point_strides]\n            keypoints_vs_list = [None for _ in self.point_strides]\n        elif self.task == 'pose_bbox':\n            extremes_gt_list  = images_to_levels(all_extremes_gt, num_level_proposals)\n            keypoints_gt_list = images_to_levels(all_keypoints_gt, num_level_proposals)\n            keypoints_vs_list = images_to_levels(all_keypoints_vs, num_level_proposals)\n            polygons_gt_list  = [None for _ in self.point_strides]\n        elif self.task == 'pose_kbox':\n            keypoints_gt_list = images_to_levels(all_keypoints_gt, num_level_proposals)\n            keypoints_vs_list = images_to_levels(all_keypoints_vs, num_level_proposals)\n            polygons_gt_list  = [None for _ in self.point_strides]\n            extremes_gt_list  = [None for _ in self.point_strides]\n\n        if stage == 'init':\n            anchor_pts_list = images_to_levels(proposals_list, num_level_proposals)\n            return (labels_list, label_weights_list, bboxes_gt_list, extremes_gt_list, polygons_gt_list,\n                    keypoints_gt_list, keypoints_vs_list, bbox_weights_list, num_total_pos, num_total_neg,\n                    anchor_pts_list)\n        else:\n            return (labels_list, label_weights_list, bboxes_gt_list, extremes_gt_list, polygons_gt_list,\n                    keypoints_gt_list, keypoints_vs_list, bbox_weights_list, num_total_pos, num_total_neg)\n    \n    def loss_single(self, \n                    cls_score, \n                    bbox_pts_pred_init, \n                    bbox_pts_pred_refine,\n                    segm_pts_pred_init, \n                    segm_pts_pred_refine,\n                    pose_pts_pred_init, \n                    pose_pts_pred_refine,\n                    labels, \n                    label_weights,\n                    bboxes_gt_init, \n                    bboxes_gt_refine,\n                    polygons_gt_init, \n                    polygons_gt_refine, \n                    extremes_gt_init, \n                    extremes_gt_refine,\n                    keypoints_gt_init, \n                    keypoints_gt_refine,\n                    keypoints_vs_init, \n                    keypoints_vs_refine,\n                    bbox_weights_init,\n                    bbox_weights_refine,\n                    anchor_pts,\n                    stride, \n                    num_total_samples_init, \n                    num_total_samples_refine):\n\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n\n        cls_score = cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n\n        loss_cls = self.loss_cls(cls_score, labels, label_weights,\n                                 avg_factor=num_total_samples_refine)\n        # points loss\n        loss_bbox_init   = 0\n        loss_bbox_refine = 0\n        loss_segm_init   = 0\n        loss_segm_refine = 0\n        loss_pose_init   = 0\n        loss_pose_refine = 0\n\n        if self.task == 'bbox':\n            bboxes_gt_init     = bboxes_gt_init.reshape(-1, 4)\n            extremes_gt_init   = extremes_gt_init.reshape(-1, (self.num_vectors+1)*2)\n            bbox_weights_init  = bbox_weights_init.reshape(-1, 4).repeat(1, self.num_vectors+1)\n            bbox_pts_pred_init = bbox_pts_pred_init.permute(0, 2, 3, 1).reshape(-1,\n                                                             (self.num_vectors+1)*4)*stride\n            anchor_pts = anchor_pts.reshape(-1, 3)\n\n            bbox_gt_reg_init, bbox_yx_inds_init = self.get_bbox_gt_reg(extremes_gt_init,\n                                                                       anchor_pts,\n                                                                       bbox_weights_init)\n\n            normalize_term = self.point_base_scale * stride\n\n            loss_bbox_init += self.loss_bbox_init(bbox_pts_pred_init / normalize_term,\n                                                  bbox_gt_reg_init / normalize_term,\n                                                  bbox_weights_init,\n                                                  avg_factor = num_total_samples_init,\n                                                  anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                  bbox_gt = bboxes_gt_init / normalize_term,\n                                                  pos_inds = bbox_yx_inds_init)\n\n            bboxes_gt_refine     = bboxes_gt_refine.reshape(-1, 4)\n            extremes_gt_refine   = extremes_gt_refine.reshape(-1, (self.num_vectors+1)*2)\n            bbox_weights_refine  = bbox_weights_refine.reshape(-1, 4).repeat(1, self.num_vectors+1)\n            bbox_pts_pred_refine = bbox_pts_pred_refine.permute(0, 2, 3, 1).reshape(-1, \n                                                                 (self.num_vectors+1)*4)*stride\n\n            bbox_gt_reg_refine, bbox_yx_inds_refine = self.get_bbox_gt_reg(extremes_gt_refine,\n                                                                           anchor_pts,\n                                                                           bbox_weights_refine) \n\n            loss_bbox_refine += self.loss_bbox_refine(bbox_pts_pred_refine / normalize_term,\n                                                      bbox_gt_reg_refine / normalize_term,\n                                                      bbox_weights_refine,\n                                                      avg_factor = num_total_samples_refine,\n                                                      anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                      bbox_gt = bboxes_gt_refine / normalize_term,\n                                                      pos_inds = bbox_yx_inds_refine)\n        elif self.task == 'segm':\n            #points loss\n            num_poly_pts = (self.num_vectors+1)*2\n            bboxes_gt_init     = bboxes_gt_init.reshape(-1, 4)\n            polygons_gt_init   = polygons_gt_init.reshape(-1, num_poly_pts)\n            bbox_weights_init  = bbox_weights_init.reshape(-1, 4)[:,:1].repeat(1, num_poly_pts*2)\n            segm_pts_pred_init = segm_pts_pred_init.permute(0, 2, 3, 1).reshape(-1,\n                                                               num_poly_pts*2)*stride\n            anchor_pts = anchor_pts.reshape(-1, 3)\n\n            poly_gt_reg_init, poly_yx_inds_init = self.get_poly_gt_reg(polygons_gt_init,\n                                                                       anchor_pts,\n                                                                       bbox_weights_init)\n\n            normalize_term = self.point_base_scale * stride\n\n            loss_segm_init += self.loss_segm_init(segm_pts_pred_init / normalize_term,\n                                                  poly_gt_reg_init / normalize_term,\n                                                  bbox_weights_init,\n                                                  avg_factor = num_total_samples_init,\n                                                  anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                  bbox_gt = bboxes_gt_init / normalize_term,\n                                                  pos_inds = poly_yx_inds_init)\n                                                \n            bboxes_gt_refine     = bboxes_gt_refine.reshape(-1, 4)\n            polygons_gt_refine   = polygons_gt_refine.reshape(-1, num_poly_pts)\n            bbox_weights_refine  = bbox_weights_refine.reshape(-1, 4)[:,:1].repeat(1, num_poly_pts*2)\n            segm_pts_pred_refine = segm_pts_pred_refine.permute(0, 2, 3, 1).reshape(-1,\n                                                               num_poly_pts*2)*stride\n\n            poly_gt_reg_refine, poly_yx_inds_refine = self.get_poly_gt_reg(polygons_gt_refine,\n                                                                           anchor_pts,\n                                                                           bbox_weights_refine)\n\n            loss_segm_refine += self.loss_segm_refine(segm_pts_pred_refine / normalize_term,\n                                                      poly_gt_reg_refine / normalize_term,\n                                                      bbox_weights_refine,\n                                                      avg_factor = num_total_samples_refine,\n                                                      anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                      bbox_gt = bboxes_gt_refine / normalize_term,\n                                                      pos_inds = poly_yx_inds_refine)\n        elif self.task == 'pose_bbox':\n            bboxes_gt_init     = bboxes_gt_init.reshape(-1, 4)\n            extremes_gt_init   = extremes_gt_init.reshape(-1, 10)\n            bbox_weights_init  = bbox_weights_init.reshape(-1, 4).repeat(1, 5)\n            bbox_pts_pred_init = bbox_pts_pred_init.permute(0, 2, 3, 1).reshape(-1, 20)*stride\n            anchor_pts = anchor_pts.reshape(-1, 3)\n\n            bbox_gt_reg_init, bbox_yx_inds_init = self.get_bbox_gt_reg(extremes_gt_init,\n                                                                       anchor_pts,\n                                                                       bbox_weights_init)\n\n            normalize_term = self.point_base_scale * stride\n\n            loss_bbox_init += self.loss_bbox_init(bbox_pts_pred_init / normalize_term,\n                                                  bbox_gt_reg_init / normalize_term,\n                                                  bbox_weights_init,\n                                                  avg_factor = num_total_samples_init,\n                                                  anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                  bbox_gt = bboxes_gt_init / normalize_term,\n                                                  pos_inds = bbox_yx_inds_init)\n\n            bboxes_gt_refine     = bboxes_gt_refine.reshape(-1, 4)\n            extremes_gt_refine   = extremes_gt_refine.reshape(-1, 10)\n            bbox_weights_refine  = bbox_weights_refine.reshape(-1, 4).repeat(1, 5)\n            bbox_pts_pred_refine = bbox_pts_pred_refine.permute(0, 2, 3, 1).reshape(-1, 20)*stride\n\n            bbox_gt_reg_refine, bbox_yx_inds_refine = self.get_bbox_gt_reg(extremes_gt_refine,\n                                                                           anchor_pts,\n                                                                           bbox_weights_refine) \n\n            loss_bbox_refine += self.loss_bbox_refine(bbox_pts_pred_refine / normalize_term,\n                                                      bbox_gt_reg_refine / normalize_term,\n                                                      bbox_weights_refine,\n                                                      avg_factor = num_total_samples_refine,\n                                                      anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                      bbox_gt = bboxes_gt_refine / normalize_term,\n                                                      pos_inds = bbox_yx_inds_refine)\n\n            \n            num_pose_pts = (self.num_vectors+1)*2\n            keypoints_gt_init  = keypoints_gt_init.reshape(-1, num_pose_pts)\n            keypoints_vs_init  = keypoints_vs_init.reshape(-1, self.num_vectors)\n            bbox_weights_init  = bbox_weights_init[:,:1].repeat(1, num_pose_pts*2)\n            pose_pts_pred_init = pose_pts_pred_init.permute(0, 2, 3, 1).reshape(-1,\n                                                               num_pose_pts*2)*stride\n\n            pose_gt_reg_init, pose_yx_inds_init = self.get_poly_gt_reg(keypoints_gt_init,\n                                                                       anchor_pts,\n                                                                       bbox_weights_init)\n\n\n            loss_pose_init += self.loss_pose_init(pose_pts_pred_init / normalize_term,\n                                                  pose_gt_reg_init / normalize_term,\n                                                  bbox_weights_init,\n                                                  avg_factor = num_total_samples_init,\n                                                  anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                  bbox_gt = None,\n                                                  pos_inds = pose_yx_inds_init,\n                                                  vs = keypoints_vs_init)\n                                                \n            keypoints_gt_refine = keypoints_gt_refine.reshape(-1, num_pose_pts)\n            keypoints_vs_refine = keypoints_vs_refine.reshape(-1, self.num_vectors)\n            bbox_weights_refine = bbox_weights_refine[:,:1].repeat(1, num_pose_pts*2)\n            pose_pts_pred_refine = pose_pts_pred_refine.permute(0, 2, 3, 1).reshape(-1,\n                                                                 num_pose_pts*2)*stride\n\n            pose_gt_reg_refine, pose_yx_inds_refine = self.get_poly_gt_reg(keypoints_gt_refine,\n                                                                           anchor_pts,\n                                                                           bbox_weights_refine)\n\n            loss_pose_refine += self.loss_pose_refine(pose_pts_pred_refine / normalize_term,\n                                                      pose_gt_reg_refine / normalize_term,\n                                                      bbox_weights_refine,\n                                                      avg_factor = num_total_samples_refine,\n                                                      anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                      bbox_gt = None,\n                                                      pos_inds = pose_yx_inds_refine,\n                                                      vs = keypoints_vs_refine)\n\n        elif self.task == 'pose_kbox':\n            bbox_weights_init   = bbox_weights_init.reshape(-1, 4)\n            bbox_weights_refine = bbox_weights_refine.reshape(-1, 4)\n            anchor_pts = anchor_pts.reshape(-1, 3)\n            normalize_term = self.point_base_scale * stride\n\n            num_pose_pts = (self.num_vectors+1)*2\n            keypoints_gt_init  = keypoints_gt_init.reshape(-1, num_pose_pts)\n            keypoints_vs_init  = keypoints_vs_init.reshape(-1, self.num_vectors)\n            bbox_weights_init  = bbox_weights_init[:,:1].repeat(1, num_pose_pts*2)\n            pose_pts_pred_init = pose_pts_pred_init.permute(0, 2, 3, 1).reshape(-1,\n                                                               num_pose_pts*2)*stride\n\n            pose_gt_reg_init, pose_yx_inds_init = self.get_poly_gt_reg(keypoints_gt_init,\n                                                                       anchor_pts,\n                                                                       bbox_weights_init)\n\n\n            loss_pose_init += self.loss_pose_init(pose_pts_pred_init / normalize_term,\n                                                  pose_gt_reg_init / normalize_term,\n                                                  bbox_weights_init,\n                                                  avg_factor = num_total_samples_init,\n                                                  anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                  bbox_gt = None,\n                                                  pos_inds = pose_yx_inds_init,\n                                                  vs = keypoints_vs_init)\n                                                \n            keypoints_gt_refine = keypoints_gt_refine.reshape(-1, num_pose_pts)\n            keypoints_vs_refine = keypoints_vs_refine.reshape(-1, self.num_vectors)\n            bbox_weights_refine = bbox_weights_refine[:,:1].repeat(1, num_pose_pts*2)\n            pose_pts_pred_refine = pose_pts_pred_refine.permute(0, 2, 3, 1).reshape(-1,\n                                                                 num_pose_pts*2)*stride\n\n            pose_gt_reg_refine, pose_yx_inds_refine = self.get_poly_gt_reg(keypoints_gt_refine,\n                                                                           anchor_pts,\n                                                                           bbox_weights_refine)\n\n            loss_pose_refine += self.loss_pose_refine(pose_pts_pred_refine / normalize_term,\n                                                      pose_gt_reg_refine / normalize_term,\n                                                      bbox_weights_refine,\n                                                      avg_factor = num_total_samples_refine,\n                                                      anchor_pts = anchor_pts[:,:-1] / normalize_term,\n                                                      bbox_gt = None,\n                                                      pos_inds = pose_yx_inds_refine,\n                                                      vs = keypoints_vs_refine)\n\n        return (loss_cls, loss_bbox_init, loss_bbox_refine, loss_segm_init, loss_segm_refine,\n                loss_pose_init, loss_pose_refine)\n\n    def loss(self,\n             cls_scores,\n             bbox_pts_preds_init,\n             bbox_pts_preds_refine,\n             segm_pts_preds_init,\n             segm_pts_preds_refine,\n             pose_pts_preds_init,\n             pose_pts_preds_refine,\n             gt_bboxes,\n             gt_extremes,\n             gt_keypoints_vs,\n             gt_masks,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        if self.task == 'bbox':\n            gt_polygons = None\n            gt_keypoints, vs_keypoints = None, None\n            if gt_extremes is None:\n                gt_extremes = self.get_border_center(gt_bboxes) # border centers and bbox center\n        elif self.task == 'segm':\n            gt_extremes = None\n            gt_keypoints, vs_keypoints = None, None\n            gt_polygons, gt_bboxes = self.process_polygons(gt_masks, cls_scores)\n        elif self.task == 'pose_bbox':\n            gt_polygons = None\n            if gt_extremes is None:\n                gt_extremes = self.get_border_center(gt_bboxes) # border centers and bbox center\n            gt_keypoints, vs_keypoints = self.process_keypoints_with_bbox(gt_bboxes, gt_keypoints_vs)\n        elif self.task == 'pose_kbox':\n            gt_polygons = None\n            gt_extremes = None\n            gt_keypoints, gt_bboxes, vs_keypoints = self.process_keypoints_with_kbox(gt_keypoints_vs)\n\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.point_generators)\n        label_channels = self.cls_out_channels\n\n        # target for initial stage\n        base_pts_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n\n        candidate_list = base_pts_list\n\n        cls_reg_targets_init = self.get_targets(candidate_list.copy(),\n                                                valid_flag_list.copy(),\n                                                gt_bboxes,\n                                                gt_extremes, \n                                                gt_polygons,\n                                                gt_keypoints,\n                                                vs_keypoints,\n                                                img_metas,\n                                                gt_bboxes_ignore_list=gt_bboxes_ignore,\n                                                gt_labels_list=gt_labels,\n                                                stage='init',\n                                                label_channels=label_channels)\n\n        (*_, bboxes_gt_list_init, extremes_gt_list_init, polygons_gt_list_init, keypoints_gt_list_init,\n         keypoints_vs_list_init, bbox_weights_list_init, num_total_pos_init, num_total_neg_init, \n         anchor_pts_list) = cls_reg_targets_init\n\n        # target for refinement stage\n        bbox_list = []\n        if self.task == 'bbox' or self.task == 'pose_bbox':\n            for i_img, base_pts in enumerate(base_pts_list):\n                bbox = []\n                for i_lvl in range(len(bbox_pts_preds_init)):\n                    bbox_preds_init = self.extreme_points2bbox(bbox_pts_preds_init[i_lvl].detach())\n                    bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                    bbox_center = torch.cat([base_pts[i_lvl][:, :2], base_pts[i_lvl][:, :2]], dim=1)\n                    bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n                bbox_list.append(bbox)\n\n        elif self.task == 'segm':\n            for i_img, base_pts in enumerate(base_pts_list):\n                bbox = []\n                for i_lvl in range(len(segm_pts_preds_init)):\n                    bbox_preds_init = self.vectors2bbox(segm_pts_preds_init[i_lvl].detach())\n                    bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                    bbox_center = torch.cat([base_pts[i_lvl][:, :2], base_pts[i_lvl][:, :2]], dim=1)\n                    bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n                bbox_list.append(bbox)\n        elif self.task == 'pose_kbox':\n            for i_img, base_pts in enumerate(base_pts_list):\n                bbox = []\n                for i_lvl in range(len(pose_pts_preds_init)):\n                    bbox_preds_init = self.vectors2bbox(pose_pts_preds_init[i_lvl].detach())\n                    bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                    bbox_center = torch.cat([base_pts[i_lvl][:, :2], base_pts[i_lvl][:, :2]], dim=1)\n                    bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n                bbox_list.append(bbox)\n\n        cls_reg_targets_refine = self.get_targets(bbox_list,\n                                                  valid_flag_list,\n                                                  gt_bboxes,\n                                                  gt_extremes,\n                                                  gt_polygons,\n                                                  gt_keypoints,\n                                                  vs_keypoints,\n                                                  img_metas,\n                                                  gt_bboxes_ignore_list=gt_bboxes_ignore,\n                                                  gt_labels_list=gt_labels,\n                                                  stage='refine',\n                                                  label_channels=label_channels)\n\n        (labels_list, label_weights_list, bboxes_gt_list_refine, extremes_gt_list_refine, \n         polygons_gt_list_refine, keypoints_gt_list_refine, keypoints_vs_list_refine, \n         bbox_weights_list_refine, num_total_pos_refine, num_total_neg_refine) = cls_reg_targets_refine\n\n        # compute loss\n        (loss_cls, loss_bbox_init, loss_bbox_refine, loss_segm_init, loss_segm_refine,\n         loss_pose_init, loss_pose_refine) = multi_apply(\n            self.loss_single,\n            cls_scores,\n            bbox_pts_preds_init,\n            bbox_pts_preds_refine,\n            segm_pts_preds_init,\n            segm_pts_preds_refine,\n            pose_pts_preds_init,\n            pose_pts_preds_refine,\n            labels_list,\n            label_weights_list,\n            bboxes_gt_list_init,\n            bboxes_gt_list_refine,\n            polygons_gt_list_init,\n            polygons_gt_list_refine,\n            extremes_gt_list_init,\n            extremes_gt_list_refine,\n            keypoints_gt_list_init,\n            keypoints_gt_list_refine,\n            keypoints_vs_list_init,\n            keypoints_vs_list_refine,\n            bbox_weights_list_init,\n            bbox_weights_list_refine,\n            anchor_pts_list,\n            self.point_strides,\n            num_total_samples_init=num_total_pos_init,\n            num_total_samples_refine=num_total_pos_refine)\n\n        if self.task == 'bbox':\n            loss_dict_all = {\n                'loss_cls': loss_cls,\n                'loss_bbox_init': loss_bbox_init,\n                'loss_bbox_refine': loss_bbox_refine\n            }\n        elif self.task == 'segm':\n            loss_dict_all = {\n                'loss_cls': loss_cls,\n                'loss_segm_init': loss_segm_init,\n                'loss_segm_refine': loss_segm_refine\n            }\n        elif self.task == 'pose_bbox':\n            loss_dict_all = {\n                'loss_cls': loss_cls,\n                'loss_bbox_init': loss_bbox_init,\n                'loss_bbox_refine': loss_bbox_refine,\n                'loss_pose_init': loss_pose_init,\n                'loss_pose_refine': loss_pose_refine\n            }\n        elif self.task == 'pose_kbox':\n            loss_dict_all = {\n                'loss_cls': loss_cls,\n                'loss_pose_init': loss_pose_init,\n                'loss_pose_refine': loss_pose_refine\n            }\n\n        return loss_dict_all\n\n    def get_bboxes(self,\n                   cls_scores,\n                   bbox_pts_preds_init,\n                   bbox_pts_preds_refine,\n                   segm_pts_preds_init,\n                   segm_pts_preds_refine,\n                   pose_pts_preds_init,\n                   pose_pts_preds_refine,\n                   img_metas,\n                   cfg=None,\n                   rescale=False,\n                   nms=True):\n        if self.task == 'bbox':\n            extreme_bbox_preds = [self.extreme_points2bbox(pts_pred, extreme=True)\n                                  for pts_pred in bbox_pts_preds_refine]\n        elif self.task == 'segm':\n            poly_bbox_preds = [self.vectors2bbox(pts_pred, vector=True)\n                                  for pts_pred in segm_pts_preds_refine]\n        elif self.task == 'pose_bbox':\n            extreme_bbox_preds = [self.extreme_points2bbox(pts_pred, extreme=True)\n                                  for pts_pred in bbox_pts_preds_refine]\n            kps_bbox_preds = [self.vectors2bbox(pts_pred, vector=True) for pts_pred in pose_pts_preds_refine]\n        elif self.task == 'pose_kbox':\n            kps_bbox_preds = [self.vectors2bbox(pts_pred, vector=True) for pts_pred in pose_pts_preds_refine]\n        \n        num_levels = len(cls_scores)\n        mlvl_points = [self.point_generators[i].grid_points(cls_scores[i].size()[-2:],\n                                                 self.point_strides[i])\n            for i in range(num_levels)\n        ]\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [cls_scores[i][img_id].detach() for i in range(num_levels)]\n            if self.task == 'bbox':\n                bbox_pred_list = [extreme_bbox_preds[i][1][img_id].detach() \n                                         for i in range(num_levels)]\n                extreme_pred_list = [extreme_bbox_preds[i][0][img_id].detach() \n                                         for i in range(num_levels)]\n                polygon_pred_list = [None for _ in range(num_levels)]\n                kps_pred_list = [None for _ in range(num_levels)]\n\n            elif self.task == 'segm':\n                bbox_pred_list = [poly_bbox_preds[i][1][img_id].detach() \n                                         for i in range(num_levels)]\n                polygon_pred_list = [poly_bbox_preds[i][0][img_id].detach() \n                                         for i in range(num_levels)]\n                extreme_pred_list = [None for _ in range(num_levels)]\n                kps_pred_list = [None for _ in range(num_levels)]\n\n            elif self.task == 'pose_bbox':\n                bbox_pred_list = [extreme_bbox_preds[i][1][img_id].detach() \n                                         for i in range(num_levels)]\n                kps_pred_list = [kps_bbox_preds[i][0][img_id].detach() \n                                         for i in range(num_levels)]\n                extreme_pred_list = [None for _ in range(num_levels)]\n                polygon_pred_list = [None for _ in range(num_levels)]\n            elif self.task == 'pose_kbox':\n                bbox_pred_list = [kps_bbox_preds[i][1][img_id].detach() \n                                         for i in range(num_levels)]\n                kps_pred_list = [kps_bbox_preds[i][0][img_id].detach() \n                                         for i in range(num_levels)]\n                extreme_pred_list = [None for _ in range(num_levels)]\n                polygon_pred_list = [None for _ in range(num_levels)]\n\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list,\n                                                extreme_pred_list, polygon_pred_list, kps_pred_list,\n                                                mlvl_points, img_shape, scale_factor, cfg, rescale, nms)\n\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           extreme_preds,\n                           polygon_preds,\n                           kps_preds,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False,\n                           nms=True):\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(mlvl_points)\n        mlvl_bboxes   = []\n        mlvl_extremes = []\n        mlvl_polygons = []\n        mlvl_kps      = []\n        mlvl_scores   = []\n        for i_lvl, (cls_score, bbox_pred, extreme_pred, polygon_pred,\n                    kps_pred, points) in enumerate(zip(cls_scores, bbox_preds, extreme_preds, polygon_preds,\n                                                       kps_preds, mlvl_points)):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(-1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            if self.task == 'bbox':\n                extreme_pred = extreme_pred.permute(1, 2, 0).reshape(-1, self.num_vectors*2)\n            elif self.task == 'segm':\n                polygon_pred = polygon_pred.permute(1, 2, 0).reshape(-1, self.num_vectors*2)\n            elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                kps_pred = kps_pred.permute(1, 2, 0).reshape(-1, self.num_vectors*2)\n\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                if self.task == 'bbox':\n                    extreme_pred = extreme_pred[topk_inds, :]\n                elif self.task == 'segm':\n                    polygon_pred = polygon_pred[topk_inds, :]\n                elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                    kps_pred = kps_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n            bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1)\n            bboxes = bbox_pred * self.point_strides[i_lvl] + bbox_pos_center\n            if self.task == 'bbox':\n                extreme_pos_center = torch.cat([points[:, :2], points[:, :2],\n                                                points[:, :2], points[:, :2]],dim=1)\n\n                extremes = extreme_pred*self.point_strides[i_lvl] + extreme_pos_center\n            elif self.task == 'segm':\n                poly_pos_center = bbox_pos_center[:,:2].repeat(1, self.num_vectors)\n                polygons = polygon_pred*self.point_strides[i_lvl] + poly_pos_center\n            elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                kps_pos_center = bbox_pos_center[:,:2].repeat(1, self.num_vectors)\n                kps = kps_pred*self.point_strides[i_lvl] + kps_pos_center\n\n            x1 = bboxes[:, 0].clamp(min=0, max=img_shape[1])\n            y1 = bboxes[:, 1].clamp(min=0, max=img_shape[0])\n            x2 = bboxes[:, 2].clamp(min=0, max=img_shape[1])\n            y2 = bboxes[:, 3].clamp(min=0, max=img_shape[0])\n            mlvl_bboxes.append(torch.stack([x1, y1, x2, y2], dim=-1))\n\n            if self.task == 'bbox':\n                xt = extremes[:, 0].clamp(min=0, max=img_shape[1])\n                yl = extremes[:, 3].clamp(min=0, max=img_shape[0])\n                xb = extremes[:, 4].clamp(min=0, max=img_shape[1])\n                yr = extremes[:, 7].clamp(min=0, max=img_shape[0])\n                mlvl_extremes.append(torch.stack([xt, y1, x1, yl, xb, y2, x2, yr], dim=-1))\n\n            elif self.task == 'segm':\n                polygons_x = polygons[:, 0::2].reshape(-1)\n                polygons_y = polygons[:, 1::2].reshape(-1)\n\n                polygons_x_ = polygons_x.clamp(min=0, max=img_shape[1])\n                polygons_y_ = polygons_y.clamp(min=0, max=img_shape[0])\n\n                polygons_x_ = polygons_x_.reshape(polygons.size(0), -1)\n                polygons_y_ = polygons_y_.reshape(polygons.size(0), -1)\n\n                polygons_ = torch.stack([polygons_x_, polygons_y_], 2).reshape(polygons.size(0), -1)\n                mlvl_polygons.append(polygons_)\n            elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                kps_x = kps[:, 0::2].reshape(-1)\n                kps_y = kps[:, 1::2].reshape(-1)\n\n                kps_x_ = kps_x.clamp(min=0, max=img_shape[1])\n                kps_y_ = kps_y.clamp(min=0, max=img_shape[0])\n\n                kps_x_ = kps_x_.reshape(kps.size(0), -1)\n                kps_y_ = kps_y_.reshape(kps.size(0), -1)\n\n                kps_ = torch.stack([kps_x_, kps_y_], 2).reshape(kps.size(0), -1)\n\n                mlvl_kps.append(kps_)\n\n            mlvl_scores.append(scores)\n\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if self.task == 'bbox':\n            mlvl_extremes = torch.cat(mlvl_extremes)\n        elif self.task == 'segm':\n            mlvl_polygons = torch.cat(mlvl_polygons)\n        elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n            mlvl_kps = torch.cat(mlvl_kps)\n\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n            if self.task == 'bbox':\n                extreme_scale_factor = np.tile(scale_factor, 2)\n                mlvl_extremes /= mlvl_extremes.new_tensor(extreme_scale_factor)\n            elif self.task == 'segm':\n                poly_scale_factor = np.tile(scale_factor[:2], self.num_vectors)\n                mlvl_polygons /= mlvl_polygons.new_tensor(poly_scale_factor)\n            elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                kps_scale_factor = np.tile(scale_factor[:2], self.num_vectors)\n                mlvl_kps /= mlvl_kps.new_tensor(kps_scale_factor)         \n\n        mlvl_scores = torch.cat(mlvl_scores)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        if nms:\n            if self.task == 'bbox':\n                det_bboxes, det_extremes, det_labels = multiclass_nms_lsvr(mlvl_bboxes, \n                                                                           mlvl_extremes,\n                                                                           mlvl_scores,\n                                                                           self.num_vectors,\n                                                                           cfg.score_thr, \n                                                                           cfg.nms, cfg.max_per_img)\n                return det_bboxes, det_extremes, det_labels\n            elif self.task == 'segm':\n                det_bboxes, det_polygons, det_labels = multiclass_nms_lsvr(mlvl_bboxes, \n                                                                           mlvl_polygons,\n                                                                           mlvl_scores,\n                                                                           self.num_vectors,\n                                                                           cfg.score_thr, \n                                                                           cfg.nms, cfg.max_per_img)\n                return det_bboxes, det_polygons, det_labels\n            elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                det_bboxes, det_kps, det_labels = multiclass_nms_lsvr(mlvl_bboxes, \n                                                                      mlvl_kps,\n                                                                      mlvl_scores,\n                                                                      self.num_vectors,\n                                                                      cfg.score_thr, \n                                                                      cfg.nms, cfg.max_per_img)\n                return det_bboxes, det_kps, det_labels\n\n        else:\n            if self.task == 'bbox':\n                return mlvl_bboxes, mlvl_extremes, mlvl_scores\n            elif self.task == 'segm':\n                return mlvl_bboxes, mlvl_polygons, mlvl_scores\n            elif self.task == 'pose_bbox' or self.task == 'pose_kbox':\n                return mlvl_bboxes, mlvl_kps, mlvl_scores\n\n    def get_num_level_proposals_inside(self, num_level_proposals, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_proposals)\n        num_level_proposals_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_proposals_inside\n\n    def get_border_center(self, gt_bboxes_list):\n        gt_extremes_list = []\n        for gt_bboxes in gt_bboxes_list:\n            border_t_center_x = (gt_bboxes[:, 2] + gt_bboxes[:, 0])/2.0\n            border_t_center_y = gt_bboxes[:, 1]\n            border_l_center_x = gt_bboxes[:, 0]\n            border_l_center_y = (gt_bboxes[:, 3] + gt_bboxes[:, 1])/2.0\n            border_b_center_x = (gt_bboxes[:, 2] + gt_bboxes[:, 0])/2.0\n            border_b_center_y = gt_bboxes[:, 3]\n            border_r_center_x = gt_bboxes[:, 2] \n            border_r_center_y = (gt_bboxes[:, 3] + gt_bboxes[:, 1])/2.0\n\n            bbox_ct_x = (gt_bboxes[:, 2] + gt_bboxes[:, 0])/2.0\n            bbox_ct_y = (gt_bboxes[:, 3] + gt_bboxes[:, 1])/2.0\n\n            gt_extremes_list.append(torch.stack([border_t_center_x, border_t_center_y,\n                                                 border_l_center_x, border_l_center_y,\n                                                 border_b_center_x, border_b_center_y,\n                                                 border_r_center_x, border_r_center_y,\n                                                 bbox_ct_x, bbox_ct_y], dim=1))\n        return gt_extremes_list\n\n    def component_polygon_area(self, poly):\n        \"\"\"Compute the area of a component of a polygon.\n\n        Using the shoelace formula:\n        https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates\n\n        Args:\n            x (ndarray): x coordinates of the component\n            y (ndarray): y coordinates of the component\n\n        Return:\n            float: the are of the component\n        \"\"\" \n        x = poly[:,0]\n        y = poly[:,1]\n        return 0.5 * np.abs(\n            np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))\n\n    def process_polygons(self, gt_masks_list, cls_scores):\n        device = cls_scores[0].device\n        dtype  = cls_scores[0].dtype\n        gt_polygons_list = []\n        gt_bboxes_list = []\n        for img_id, gt_masks in enumerate(gt_masks_list):\n            polygons = gt_masks.masks\n            polygon_areas = gt_masks.areas \n            gt_polygons = []\n            gt_bboxes = []\n            for instance_id, instance_polys in enumerate(polygons):\n                max_area = self.component_polygon_area(instance_polys[0].reshape(-1, 2))\n                max_idx = 0\n                for component_id, component_poly in enumerate(instance_polys):\n                    component_area = self.component_polygon_area(component_poly.reshape(-1, 2))\n                    if max_area < component_area:\n                        max_area = component_area\n                        max_idx = component_id\n\n                poly = torch.tensor(instance_polys[max_idx].reshape(-1, 2), dtype=dtype, device=device)\n                gt_polygons.append(poly)\n\n            gt_polygons_stack = torch.stack(gt_polygons)\n\n            polys_xmin = gt_polygons_stack[:,:,0].min(1)[0]\n            polys_ymin = gt_polygons_stack[:,:,1].min(1)[0]\n            polys_xmax = gt_polygons_stack[:,:,0].max(1)[0]\n            polys_ymax = gt_polygons_stack[:,:,1].max(1)[0]\n\n            polys_ct_x = (polys_xmin + polys_xmax)/2\n            polys_ct_y = (polys_ymin + polys_ymax)/2\n\n            polys_cts = torch.stack([polys_ct_x, polys_ct_y], 1).unsqueeze(1)\n\n            gt_polygons_list.append(torch.cat([gt_polygons_stack, polys_cts],\n                                              dim=1).reshape(polys_cts.size(0), -1))\n            gt_bboxes_list.append(torch.stack([polys_xmin, polys_ymin, polys_xmax, polys_ymax], \n                                                1))\n\n        return gt_polygons_list, gt_bboxes_list\n\n    def process_keypoints_with_bbox(self, gt_bboxes_list, gt_keypoints_vs_list):\n        gt_keypoints_list = []\n        vs_keypoints_list = []\n        for img_id, gt_bboxes in enumerate(gt_bboxes_list):\n            gt_keypoints_vs = gt_keypoints_vs_list[img_id]\n\n            gt_keypoints_x = gt_keypoints_vs[:, 0::3]\n            gt_keypoints_y = gt_keypoints_vs[:, 1::3]\n            vs_keypoints   = gt_keypoints_vs[:, 2::3]\n\n            xmin = gt_bboxes[:, 0]\n            ymin = gt_bboxes[:, 1]\n            xmax = gt_bboxes[:, 2]\n            ymax = gt_bboxes[:, 3]\n\n            ct_x = (xmin + xmax)/2\n            ct_y = (ymin + ymax)/2\n\n            bbox_cts = torch.stack([ct_x, ct_y], 1)\n            gt_keypoints = torch.stack((gt_keypoints_x, gt_keypoints_y),\n                                       dim=2).reshape(gt_keypoints_vs.size(0), -1)\n            gt_keypoints = torch.cat((gt_keypoints, bbox_cts), 1)\n\n            gt_keypoints_list.append(gt_keypoints)\n            vs_keypoints_list.append(vs_keypoints)\n\n        return gt_keypoints_list, vs_keypoints_list\n\n    def process_keypoints_with_kbox(self, gt_keypoints_vs_list):\n        gt_keypoints_list = []\n        vs_keypoints_list = []\n        gt_keybboxes_list = []\n        for img_id, gt_keypoints_vs in enumerate(gt_keypoints_vs_list):\n            gt_keypoints_x = gt_keypoints_vs[:, 0::3]\n            gt_keypoints_y = gt_keypoints_vs[:, 1::3]\n            vs_keypoints   = gt_keypoints_vs[:, 2::3]\n\n            vs_zero_x = gt_keypoints_x[vs_keypoints==0]\n            vs_zero_y = gt_keypoints_y[vs_keypoints==0]\n\n            gt_keypoints_x[vs_keypoints==0] = 10000000\n            gt_keypoints_y[vs_keypoints==0] = 10000000\n\n            xmin = gt_keypoints_x.min(1)[0]\n            ymin = gt_keypoints_y.min(1)[0]\n\n            gt_keypoints_x[vs_keypoints==0] = -1\n            gt_keypoints_y[vs_keypoints==0] = -1\n\n            xmax = gt_keypoints_x.max(1)[0]\n            ymax = gt_keypoints_y.max(1)[0]\n\n\n            gt_keypoints_x[vs_keypoints==0] = vs_zero_x\n            gt_keypoints_y[vs_keypoints==0] = vs_zero_y\n\n            ct_x = (xmin + xmax)/2\n            ct_y = (ymin + ymax)/2\n\n            bbox_cts = torch.stack([ct_x, ct_y], 1)\n            gt_keypoints = torch.stack((gt_keypoints_x, gt_keypoints_y),\n                                       dim=2).reshape(gt_keypoints_vs.size(0), -1)\n\n            gt_keypoints = torch.cat((gt_keypoints, bbox_cts), 1)\n            gt_keybboxes = torch.stack([xmin, ymin, xmax, ymax], 1)\n\n            gt_keypoints_list.append(gt_keypoints)\n            vs_keypoints_list.append(vs_keypoints)\n            gt_keybboxes_list.append(gt_keybboxes)\n\n        return gt_keypoints_list, gt_keybboxes_list, vs_keypoints_list\n\nclass DCNConvModule(nn.Module):\n    def __init__(\n        self, \n        in_channels = 256,\n        out_channels = 256,\n        kernel_size = 3,\n        dilation = 1,\n        num_groups = 1,\n        dcn_pad = 1\n    ):\n        super(DCNConvModule, self).__init__()\n\n        self.conv = ModulatedDeformConvPack(in_channels, out_channels, kernel_size, 1, dcn_pad)\n        self.bn = nn.GroupNorm(num_groups, out_channels)\n\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, x):\n        x = self.relu(self.bn(self.conv(x)))\n        return x\n\n\n                \n\n\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/nasfcos_head.py",
    "content": "import copy\n\nimport torch.nn as nn\nfrom mmcv.cnn import (ConvModule, Scale, bias_init_with_prob,\n                      caffe2_xavier_init, normal_init)\n\nfrom mmdet.models.dense_heads.fcos_head import FCOSHead\nfrom ..builder import HEADS\n\n\n@HEADS.register_module()\nclass NASFCOSHead(FCOSHead):\n    \"\"\"Anchor-free head used in `NASFCOS <https://arxiv.org/abs/1906.04423>`_.\n\n    It is quite similar with FCOS head, except for the searched structure\n    of classification branch and bbox regression branch, where a structure\n    of \"dconv3x3, conv3x3, dconv3x3, conv1x1\" is utilized instead.\n\n    \"\"\"\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        dconv3x3_config = dict(\n            type='DCNv2',\n            kernel_size=3,\n            use_bias=True,\n            deformable_groups=2,\n            padding=1)\n        conv3x3_config = dict(type='Conv', kernel_size=3, padding=1)\n        conv1x1_config = dict(type='Conv', kernel_size=1)\n\n        self.arch_config = [\n            dconv3x3_config, conv3x3_config, dconv3x3_config, conv1x1_config\n        ]\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i, op_ in enumerate(self.arch_config):\n            op = copy.deepcopy(op_)\n            chn = self.in_channels if i == 0 else self.feat_channels\n            assert isinstance(op, dict)\n            use_bias = op.pop('use_bias', False)\n            padding = op.pop('padding', 0)\n            kernel_size = op.pop('kernel_size')\n            module = ConvModule(\n                chn,\n                self.feat_channels,\n                kernel_size,\n                stride=1,\n                padding=padding,\n                norm_cfg=self.norm_cfg,\n                bias=use_bias,\n                conv_cfg=op)\n\n            self.cls_convs.append(copy.deepcopy(module))\n            self.reg_convs.append(copy.deepcopy(module))\n\n        self.fcos_cls = nn.Conv2d(\n            self.feat_channels, self.cls_out_channels, 3, padding=1)\n        self.fcos_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1)\n        self.fcos_centerness = nn.Conv2d(self.feat_channels, 1, 3, padding=1)\n\n        self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides])\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        # retinanet_bias_init\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.fcos_reg, std=0.01)\n        normal_init(self.fcos_centerness, std=0.01)\n        normal_init(self.fcos_cls, std=0.01, bias=bias_cls)\n\n        for branch in [self.cls_convs, self.reg_convs]:\n            for module in branch.modules():\n                if isinstance(module, ConvModule) \\\n                        and isinstance(module.conv, nn.Conv2d):\n                    caffe2_xavier_init(module.conv)\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/pisa_retinanet_head.py",
    "content": "import torch\n\nfrom mmdet.core import force_fp32, images_to_levels\nfrom ..builder import HEADS\nfrom ..losses import carl_loss, isr_p\nfrom .retina_head import RetinaHead\n\n\n@HEADS.register_module()\nclass PISARetinaHead(RetinaHead):\n    \"\"\"PISA Retinanet Head.\n\n    The head owns the same structure with Retinanet Head, but differs in two\n        aspects:\n        1. Importance-based Sample Reweighting Positive (ISR-P) is applied to\n            change the positive loss weights.\n        2. Classification-aware regression loss is adopted as a third loss.\n    \"\"\"\n\n    @force_fp32(apply_to=('cls_scores', 'bbox_preds'))\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            gt_bboxes (list[Tensor]): Ground truth bboxes of each image\n                with shape (num_obj, 4).\n            gt_labels (list[Tensor]): Ground truth labels of each image\n                with shape (num_obj, 4).\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (list[Tensor]): Ignored gt bboxes of each image.\n                Default: None.\n\n        Returns:\n            dict: Loss dict, comprise classification loss, regression loss and\n                carl loss.\n        \"\"\"\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n\n        device = cls_scores[0].device\n\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=label_channels,\n            return_sampling_results=True)\n        if cls_reg_targets is None:\n            return None\n        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,\n         num_total_pos, num_total_neg, sampling_results_list) = cls_reg_targets\n        num_total_samples = (\n            num_total_pos + num_total_neg if self.sampling else num_total_pos)\n\n        # anchor number of multi levels\n        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]\n        # concat all level anchors and flags to a single tensor\n        concat_anchor_list = []\n        for i in range(len(anchor_list)):\n            concat_anchor_list.append(torch.cat(anchor_list[i]))\n        all_anchor_list = images_to_levels(concat_anchor_list,\n                                           num_level_anchors)\n\n        num_imgs = len(img_metas)\n        flatten_cls_scores = [\n            cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, label_channels)\n            for cls_score in cls_scores\n        ]\n        flatten_cls_scores = torch.cat(\n            flatten_cls_scores, dim=1).reshape(-1,\n                                               flatten_cls_scores[0].size(-1))\n        flatten_bbox_preds = [\n            bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4)\n            for bbox_pred in bbox_preds\n        ]\n        flatten_bbox_preds = torch.cat(\n            flatten_bbox_preds, dim=1).view(-1, flatten_bbox_preds[0].size(-1))\n        flatten_labels = torch.cat(labels_list, dim=1).reshape(-1)\n        flatten_label_weights = torch.cat(\n            label_weights_list, dim=1).reshape(-1)\n        flatten_anchors = torch.cat(all_anchor_list, dim=1).reshape(-1, 4)\n        flatten_bbox_targets = torch.cat(\n            bbox_targets_list, dim=1).reshape(-1, 4)\n        flatten_bbox_weights = torch.cat(\n            bbox_weights_list, dim=1).reshape(-1, 4)\n\n        # Apply ISR-P\n        isr_cfg = self.train_cfg.get('isr', None)\n        if isr_cfg is not None:\n            all_targets = (flatten_labels, flatten_label_weights,\n                           flatten_bbox_targets, flatten_bbox_weights)\n            with torch.no_grad():\n                all_targets = isr_p(\n                    flatten_cls_scores,\n                    flatten_bbox_preds,\n                    all_targets,\n                    flatten_anchors,\n                    sampling_results_list,\n                    bbox_coder=self.bbox_coder,\n                    loss_cls=self.loss_cls,\n                    num_class=self.num_classes,\n                    **self.train_cfg.isr)\n            (flatten_labels, flatten_label_weights, flatten_bbox_targets,\n             flatten_bbox_weights) = all_targets\n\n        # For convenience we compute loss once instead separating by fpn level,\n        # so that we don't need to separate the weights by level again.\n        # The result should be the same\n        losses_cls = self.loss_cls(\n            flatten_cls_scores,\n            flatten_labels,\n            flatten_label_weights,\n            avg_factor=num_total_samples)\n        losses_bbox = self.loss_bbox(\n            flatten_bbox_preds,\n            flatten_bbox_targets,\n            flatten_bbox_weights,\n            avg_factor=num_total_samples)\n        loss_dict = dict(loss_cls=losses_cls, loss_bbox=losses_bbox)\n\n        # CARL Loss\n        carl_cfg = self.train_cfg.get('carl', None)\n        if carl_cfg is not None:\n            loss_carl = carl_loss(\n                flatten_cls_scores,\n                flatten_labels,\n                flatten_bbox_preds,\n                flatten_bbox_targets,\n                self.loss_bbox,\n                **self.train_cfg.carl,\n                avg_factor=num_total_pos,\n                sigmoid=True,\n                num_class=self.num_classes)\n            loss_dict.update(loss_carl)\n\n        return loss_dict\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/pisa_ssd_head.py",
    "content": "import torch\n\nfrom mmdet.core import multi_apply\nfrom ..builder import HEADS\nfrom ..losses import CrossEntropyLoss, SmoothL1Loss, carl_loss, isr_p\nfrom .ssd_head import SSDHead\n\n\n# TODO: add loss evaluator for SSD\n@HEADS.register_module()\nclass PISASSDHead(SSDHead):\n\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            gt_bboxes (list[Tensor]): Ground truth bboxes of each image\n                with shape (num_obj, 4).\n            gt_labels (list[Tensor]): Ground truth labels of each image\n                with shape (num_obj, 4).\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (list[Tensor]): Ignored gt bboxes of each image.\n                Default: None.\n\n        Returns:\n            dict: Loss dict, comprise classification loss regression loss and\n                carl loss.\n        \"\"\"\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n\n        device = cls_scores[0].device\n\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=1,\n            unmap_outputs=False,\n            return_sampling_results=True)\n        if cls_reg_targets is None:\n            return None\n        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,\n         num_total_pos, num_total_neg, sampling_results_list) = cls_reg_targets\n\n        num_images = len(img_metas)\n        all_cls_scores = torch.cat([\n            s.permute(0, 2, 3, 1).reshape(\n                num_images, -1, self.cls_out_channels) for s in cls_scores\n        ], 1)\n        all_labels = torch.cat(labels_list, -1).view(num_images, -1)\n        all_label_weights = torch.cat(label_weights_list,\n                                      -1).view(num_images, -1)\n        all_bbox_preds = torch.cat([\n            b.permute(0, 2, 3, 1).reshape(num_images, -1, 4)\n            for b in bbox_preds\n        ], -2)\n        all_bbox_targets = torch.cat(bbox_targets_list,\n                                     -2).view(num_images, -1, 4)\n        all_bbox_weights = torch.cat(bbox_weights_list,\n                                     -2).view(num_images, -1, 4)\n\n        # concat all level anchors to a single tensor\n        all_anchors = []\n        for i in range(num_images):\n            all_anchors.append(torch.cat(anchor_list[i]))\n\n        isr_cfg = self.train_cfg.get('isr', None)\n        all_targets = (all_labels.view(-1), all_label_weights.view(-1),\n                       all_bbox_targets.view(-1,\n                                             4), all_bbox_weights.view(-1, 4))\n        # apply ISR-P\n        if isr_cfg is not None:\n            all_targets = isr_p(\n                all_cls_scores.view(-1, all_cls_scores.size(-1)),\n                all_bbox_preds.view(-1, 4),\n                all_targets,\n                torch.cat(all_anchors),\n                sampling_results_list,\n                loss_cls=CrossEntropyLoss(),\n                bbox_coder=self.bbox_coder,\n                **self.train_cfg.isr,\n                num_class=self.num_classes)\n            (new_labels, new_label_weights, new_bbox_targets,\n             new_bbox_weights) = all_targets\n            all_labels = new_labels.view(all_labels.shape)\n            all_label_weights = new_label_weights.view(all_label_weights.shape)\n            all_bbox_targets = new_bbox_targets.view(all_bbox_targets.shape)\n            all_bbox_weights = new_bbox_weights.view(all_bbox_weights.shape)\n\n        # add CARL loss\n        carl_loss_cfg = self.train_cfg.get('carl', None)\n        if carl_loss_cfg is not None:\n            loss_carl = carl_loss(\n                all_cls_scores.view(-1, all_cls_scores.size(-1)),\n                all_targets[0],\n                all_bbox_preds.view(-1, 4),\n                all_targets[2],\n                SmoothL1Loss(beta=1.),\n                **self.train_cfg.carl,\n                avg_factor=num_total_pos,\n                num_class=self.num_classes)\n\n        # check NaN and Inf\n        assert torch.isfinite(all_cls_scores).all().item(), \\\n            'classification scores become infinite or NaN!'\n        assert torch.isfinite(all_bbox_preds).all().item(), \\\n            'bbox predications become infinite or NaN!'\n\n        losses_cls, losses_bbox = multi_apply(\n            self.loss_single,\n            all_cls_scores,\n            all_bbox_preds,\n            all_anchors,\n            all_labels,\n            all_label_weights,\n            all_bbox_targets,\n            all_bbox_weights,\n            num_total_samples=num_total_pos)\n        loss_dict = dict(loss_cls=losses_cls, loss_bbox=losses_bbox)\n        if carl_loss_cfg is not None:\n            loss_dict.update(loss_carl)\n        return loss_dict\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/reppoints_head.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (PointGenerator, build_assigner, build_sampler,\n                        images_to_levels, multi_apply, multiclass_nms, unmap)\nfrom mmdet.ops import DeformConv\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\n\n@HEADS.register_module()\nclass RepPointsHead(AnchorFreeHead):\n    \"\"\"RepPoint head.\n\n    Args:\n        point_feat_channels (int): Number of channels of points features.\n        gradient_mul (float): The multiplier to gradients from\n            points refinement and recognition.\n        point_strides (Iterable): points strides.\n        point_base_scale (int): bbox scale for assigning labels.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox_init (dict): Config of initial points loss.\n        loss_bbox_refine (dict): Config of points loss in refinement.\n        use_grid_points (bool): If we use bounding box representation, the\n        reppoints is represented as grid points on the bounding box.\n        center_init (bool): Whether to use center point assignment.\n        transform_method (str): The methods to transform RepPoints to bbox.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 point_feat_channels=256,\n                 num_points=9,\n                 gradient_mul=0.1,\n                 point_strides=[8, 16, 32, 64, 128],\n                 point_base_scale=4,\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox_init=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=0.5),\n                 loss_bbox_refine=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 use_grid_points=False,\n                 center_init=True,\n                 transform_method='moment',\n                 moment_mul=0.01,\n                 **kwargs):\n        self.num_points = num_points\n        self.point_feat_channels = point_feat_channels\n        self.use_grid_points = use_grid_points\n        self.center_init = center_init\n\n        # we use deformable conv to extract points features\n        self.dcn_kernel = int(np.sqrt(num_points))\n        self.dcn_pad = int((self.dcn_kernel - 1) / 2)\n        assert self.dcn_kernel * self.dcn_kernel == num_points, \\\n            'The points number should be a square number.'\n        assert self.dcn_kernel % 2 == 1, \\\n            'The points number should be an odd square number.'\n        dcn_base = np.arange(-self.dcn_pad,\n                             self.dcn_pad + 1).astype(np.float64)\n        dcn_base_y = np.repeat(dcn_base, self.dcn_kernel)\n        dcn_base_x = np.tile(dcn_base, self.dcn_kernel)\n        dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape(\n            (-1))\n        self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1)\n\n        super().__init__(num_classes, in_channels, loss_cls=loss_cls, **kwargs)\n\n        self.gradient_mul = gradient_mul\n        self.point_base_scale = point_base_scale\n        self.point_strides = point_strides\n        self.point_generators = [PointGenerator() for _ in self.point_strides]\n\n        if self.train_cfg:\n            self.init_assigner = build_assigner(self.train_cfg.init.assigner)\n            self.refine_assigner = build_assigner(self.train_cfg.refine.assigner)\n            # use PseudoSampler when sampling is False\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.transform_method = transform_method\n        if self.transform_method == 'moment':\n            self.moment_transfer = nn.Parameter(data=torch.zeros(2), requires_grad=True)\n            self.moment_mul = moment_mul\n\n        self.cls_out_channels = self.num_classes\n        self.loss_bbox_init = build_loss(loss_bbox_init)\n        self.loss_bbox_refine = build_loss(loss_bbox_refine)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        pts_out_dim = 4 if self.use_grid_points else 2 * self.num_points\n        self.reppoints_cls_conv = DeformConv(self.feat_channels,\n                                             self.point_feat_channels,\n                                             self.dcn_kernel, 1, self.dcn_pad)\n        self.reppoints_cls_out = nn.Conv2d(self.point_feat_channels,\n                                           self.cls_out_channels, 1, 1, 0)\n        self.reppoints_pts_init_conv = nn.Conv2d(self.feat_channels,\n                                                 self.point_feat_channels, 3,\n                                                 1, 1)\n        self.reppoints_pts_init_out = nn.Conv2d(self.point_feat_channels,\n                                                pts_out_dim, 1, 1, 0)\n        self.reppoints_pts_refine_conv = DeformConv(self.feat_channels,\n                                                    self.point_feat_channels,\n                                                    self.dcn_kernel, 1,\n                                                    self.dcn_pad)\n        self.reppoints_pts_refine_out = nn.Conv2d(self.point_feat_channels,\n                                                  pts_out_dim, 1, 1, 0)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.reppoints_cls_conv, std=0.01)\n        normal_init(self.reppoints_cls_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_pts_init_conv, std=0.01)\n        normal_init(self.reppoints_pts_init_out, std=0.01)\n        normal_init(self.reppoints_pts_refine_conv, std=0.01)\n        normal_init(self.reppoints_pts_refine_out, std=0.01)\n\n    def points2bbox(self, pts, y_first=True):\n        \"\"\"Converting the points set into bounding box.\n\n        :param pts: the input points sets (fields), each points\n            set (fields) is represented as 2n scalar.\n        :param y_first: if y_fisrt=True, the point set is represented as\n            [y1, x1, y2, x2 ... yn, xn], otherwise the point set is\n            represented as [x1, y1, x2, y2 ... xn, yn].\n        :return: each points set is converting to a bbox [x1, y1, x2, y2].\n        \"\"\"\n        pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n        pts_y = pts_reshape[:, :, 0, ...] if y_first else pts_reshape[:, :, 1, ...]\n        pts_x = pts_reshape[:, :, 1, ...] if y_first else pts_reshape[:, :, 0, ...]\n        if self.transform_method == 'minmax':\n            bbox_left = pts_x.min(dim=1, keepdim=True)[0]\n            bbox_right = pts_x.max(dim=1, keepdim=True)[0]\n            bbox_up = pts_y.min(dim=1, keepdim=True)[0]\n            bbox_bottom = pts_y.max(dim=1, keepdim=True)[0]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom],\n                             dim=1)\n        elif self.transform_method == 'partial_minmax':\n            pts_y = pts_y[:, :4, ...]\n            pts_x = pts_x[:, :4, ...]\n            bbox_left = pts_x.min(dim=1, keepdim=True)[0]\n            bbox_right = pts_x.max(dim=1, keepdim=True)[0]\n            bbox_up = pts_y.min(dim=1, keepdim=True)[0]\n            bbox_bottom = pts_y.max(dim=1, keepdim=True)[0]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom],\n                             dim=1)\n        elif self.transform_method == 'moment':\n            pts_y_mean = pts_y.mean(dim=1, keepdim=True)\n            pts_x_mean = pts_x.mean(dim=1, keepdim=True)\n            pts_y_std = torch.std(pts_y - pts_y_mean, dim=1, keepdim=True)\n            pts_x_std = torch.std(pts_x - pts_x_mean, dim=1, keepdim=True)\n            moment_transfer = (self.moment_transfer * self.moment_mul) + (\n                self.moment_transfer.detach() * (1 - self.moment_mul))\n            moment_width_transfer = moment_transfer[0]\n            moment_height_transfer = moment_transfer[1]\n            half_width = pts_x_std * torch.exp(moment_width_transfer)\n            half_height = pts_y_std * torch.exp(moment_height_transfer)\n            bbox = torch.cat([\n                pts_x_mean - half_width, pts_y_mean - half_height,\n                pts_x_mean + half_width, pts_y_mean + half_height\n            ],\n                             dim=1)\n        elif self.transform_method == \"exact_minmax\":\n            pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n            pts_reshape = pts_reshape[:, :2, ...]\n            pts_y = pts_reshape[:, :, 0, ...] if y_first else pts_reshape[:, :, 1, ...]\n            pts_x = pts_reshape[:, :, 1, ...] if y_first else pts_reshape[:, :, 0, ...]\n            bbox_left = pts_x[:, 0:1, ...]\n            bbox_right = pts_x[:, 1:2, ...]\n            bbox_up = pts_y[:, 0:1, ...]\n            bbox_bottom = pts_y[:, 1:2, ...]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n        else:\n            raise NotImplementedError\n        return bbox\n\n    def gen_grid_from_reg(self, reg, previous_boxes):\n        \"\"\"Base on the previous bboxes and regression values, we compute the\n        regressed bboxes and generate the grids on the bboxes.\n\n        :param reg: the regression value to previous bboxes.\n        :param previous_boxes: previous bboxes.\n        :return: generate grids on the regressed bboxes.\n        \"\"\"\n        b, _, h, w = reg.shape\n        bxy = (previous_boxes[:, :2, ...] + previous_boxes[:, 2:, ...]) / 2.\n        bwh = (previous_boxes[:, 2:, ...] -\n               previous_boxes[:, :2, ...]).clamp(min=1e-6)\n        grid_topleft = bxy + bwh * reg[:, :2, ...] - 0.5 * bwh * torch.exp(\n            reg[:, 2:, ...])\n        grid_wh = bwh * torch.exp(reg[:, 2:, ...])\n        grid_left = grid_topleft[:, [0], ...]\n        grid_top = grid_topleft[:, [1], ...]\n        grid_width = grid_wh[:, [0], ...]\n        grid_height = grid_wh[:, [1], ...]\n        intervel = torch.linspace(0., 1., self.dcn_kernel).view(\n            1, self.dcn_kernel, 1, 1).type_as(reg)\n        grid_x = grid_left + grid_width * intervel\n        grid_x = grid_x.unsqueeze(1).repeat(1, self.dcn_kernel, 1, 1, 1)\n        grid_x = grid_x.view(b, -1, h, w)\n        grid_y = grid_top + grid_height * intervel\n        grid_y = grid_y.unsqueeze(2).repeat(1, 1, self.dcn_kernel, 1, 1)\n        grid_y = grid_y.view(b, -1, h, w)\n        grid_yx = torch.stack([grid_y, grid_x], dim=2)\n        grid_yx = grid_yx.view(b, -1, h, w)\n        regressed_bbox = torch.cat([\n            grid_left, grid_top, grid_left + grid_width, grid_top + grid_height\n        ], 1)\n        return grid_yx, regressed_bbox\n\n    def forward(self, feats):\n        return multi_apply(self.forward_single, feats)\n\n    def forward_single(self, x):\n        \"\"\" Forward feature map of a single FPN level.\"\"\"\n        dcn_base_offset = self.dcn_base_offset.type_as(x)\n        # If we use center_init, the initial reppoints is from center points.\n        # If we use bounding bbox representation, the initial reppoints is\n        #   from regular grid placed on a pre-defined bbox.\n        if self.use_grid_points or not self.center_init:\n            scale = self.point_base_scale / 2\n            points_init = dcn_base_offset / dcn_base_offset.max() * scale\n            bbox_init = x.new_tensor([-scale, -scale, scale,\n                                      scale]).view(1, 4, 1, 1)\n        else:\n            points_init = 0\n        cls_feat = x\n        pts_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            pts_feat = reg_conv(pts_feat)\n        # initialize reppoints\n        pts_out_init = self.reppoints_pts_init_out(\n            self.relu(self.reppoints_pts_init_conv(pts_feat)))\n        if self.use_grid_points:\n            pts_out_init, bbox_out_init = self.gen_grid_from_reg(\n                pts_out_init, bbox_init.detach())\n        else:\n            pts_out_init = pts_out_init + points_init\n        # refine and classify reppoints\n        pts_out_init_grad_mul = (1 - self.gradient_mul) * pts_out_init.detach(\n        ) + self.gradient_mul * pts_out_init\n        dcn_offset = pts_out_init_grad_mul - dcn_base_offset\n        cls_out = self.reppoints_cls_out(\n            self.relu(self.reppoints_cls_conv(cls_feat, dcn_offset)))\n        pts_out_refine = self.reppoints_pts_refine_out(\n            self.relu(self.reppoints_pts_refine_conv(pts_feat, dcn_offset)))\n        if self.use_grid_points:\n            pts_out_refine, bbox_out_refine = self.gen_grid_from_reg(\n                pts_out_refine, bbox_out_init.detach())\n        else:\n            pts_out_refine = pts_out_refine + pts_out_init.detach()\n        return cls_out, pts_out_init, pts_out_refine\n\n    def get_points(self, featmap_sizes, img_metas):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n\n        Returns:\n            tuple: points of each image, valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # points center for one time\n        multi_level_points = []\n        for i in range(num_levels):\n            points = self.point_generators[i].grid_points(\n                featmap_sizes[i], self.point_strides[i])\n            multi_level_points.append(points)\n        points_list = [[point.clone() for point in multi_level_points] for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level grids\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            for i in range(num_levels):\n                point_stride = self.point_strides[i]\n                feat_h, feat_w = featmap_sizes[i]\n                h, w = img_meta['pad_shape'][:2]\n                valid_feat_h = min(int(np.ceil(h / point_stride)), feat_h)\n                valid_feat_w = min(int(np.ceil(w / point_stride)), feat_w)\n                flags = self.point_generators[i].valid_flags(\n                    (feat_h, feat_w), (valid_feat_h, valid_feat_w))\n                multi_level_flags.append(flags)\n            valid_flag_list.append(multi_level_flags)\n\n        return points_list, valid_flag_list\n\n    def centers_to_bboxes(self, point_list):\n        \"\"\"Get bboxes according to center points. Only used in MaxIOUAssigner.\n        \"\"\"\n        bbox_list = []\n        for i_img, point in enumerate(point_list):\n            bbox = []\n            for i_lvl in range(len(self.point_strides)):\n                scale = self.point_base_scale * self.point_strides[i_lvl] * 0.5\n                bbox_shift = torch.Tensor([-scale, -scale, scale, scale]).view(1, 4).type_as(point[0])\n                bbox_center = torch.cat([point[i_lvl][:, :2], point[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift)\n            bbox_list.append(bbox)\n        return bbox_list\n\n    def offset_to_pts(self, center_list, pred_list):\n        \"\"\"Change from point offset to point coordinate.\"\"\"\n        pts_list = []\n        for i_lvl in range(len(self.point_strides)):\n            pts_lvl = []\n            for i_img in range(len(center_list)):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(\n                    1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                yx_pts_shift = pts_shift.permute(1, 2, 0).view(\n                    -1, 2 * self.num_points)\n                y_pts_shift = yx_pts_shift[..., 0::2]\n                x_pts_shift = yx_pts_shift[..., 1::2]\n                xy_pts_shift = torch.stack([x_pts_shift, y_pts_shift], -1)\n                xy_pts_shift = xy_pts_shift.view(*yx_pts_shift.shape[:-1], -1)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_lvl.append(pts)\n            pts_lvl = torch.stack(pts_lvl, 0)\n            pts_list.append(pts_lvl)\n        return pts_list\n\n    def _point_target_single(self,\n                             flat_proposals,\n                             valid_flags,\n                             num_level_proposals,\n                             gt_bboxes,\n                             gt_bboxes_ignore,\n                             gt_labels,\n                             label_channels=1,\n                             stage='init',\n                             unmap_outputs=True):\n        inside_flags = valid_flags\n        if not inside_flags.any():\n            return (None, ) * 6\n        # assign gt and sample proposals\n        proposals = flat_proposals[inside_flags, :]\n\n        num_level_proposals_inside = self.get_num_level_proposals_inside(num_level_proposals, inside_flags)\n        if stage == 'init':\n            assigner = self.init_assigner\n            assigner_type = self.train_cfg.init.assigner.type\n            pos_weight = self.train_cfg.init.pos_weight\n        else:\n            assigner = self.refine_assigner\n            assigner_type = self.train_cfg.refine.assigner.type\n            pos_weight = self.train_cfg.refine.pos_weight\n        if assigner_type != \"ATSSAssigner\":\n            assign_result = assigner.assign(proposals, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        else:\n            assign_result = assigner.assign(proposals, num_level_proposals_inside, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        sampling_result = self.sampler.sample(assign_result, proposals, gt_bboxes)\n\n        num_valid_proposals = proposals.shape[0]\n        bbox_gt = proposals.new_zeros([num_valid_proposals, 4])\n        bbox_weights = proposals.new_zeros([num_valid_proposals, 4])\n        labels = proposals.new_full((num_valid_proposals, ), self.background_label, dtype=torch.long)\n        label_weights = proposals.new_zeros(num_valid_proposals, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_gt_bboxes = sampling_result.pos_gt_bboxes\n            bbox_gt[pos_inds, :] = pos_gt_bboxes\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]\n            if pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of proposals\n        if unmap_outputs:\n            num_total_proposals = flat_proposals.size(0)\n            labels = unmap(labels, num_total_proposals, inside_flags)\n            label_weights = unmap(label_weights, num_total_proposals, inside_flags)\n            bbox_gt = unmap(bbox_gt, num_total_proposals, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_proposals, inside_flags)\n\n        return labels, label_weights, bbox_gt, bbox_weights, pos_inds, neg_inds\n\n    def get_targets(self,\n                    proposals_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    stage='init',\n                    label_channels=1,\n                    unmap_outputs=True):\n        \"\"\"Compute corresponding GT box and classification targets for\n        proposals.\n\n        Args:\n            proposals_list (list[list]): Multi level points/bboxes of each\n                image.\n            valid_flag_list (list[list]): Multi level valid flags of each\n                image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            gt_bboxes_ignore_list (list[Tensor]): Ground truth bboxes to be\n                ignored.\n            gt_bboxes_list (list[Tensor]): Ground truth labels of each box.\n            stage (str): `init` or `refine`. Generate target for init stage or\n                refine stage\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple:\n                - labels_list (list[Tensor]): Labels of each level.\n                - label_weights_list (list[Tensor]): Label weights of each level.  # noqa: E501\n                - bbox_gt_list (list[Tensor]): Ground truth bbox of each level.\n                - proposal_list (list[Tensor]): Proposals(points/bboxes) of each level.  # noqa: E501\n                - proposal_weights_list (list[Tensor]): Proposal weights of each level.  # noqa: E501\n                - num_total_pos (int): Number of positive samples in all images.  # noqa: E501\n                - num_total_neg (int): Number of negative samples in all images.  # noqa: E501\n        \"\"\"\n        assert stage in ['init', 'refine']\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n        num_level_proposals_list = [num_level_proposals] * num_imgs\n\n        # concat all level points and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_labels, all_label_weights, all_bbox_gt, all_bbox_weights,\n         pos_inds_list, neg_inds_list) = multi_apply(\n             self._point_target_single,\n             proposals_list,\n             valid_flag_list,\n             num_level_proposals_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             gt_labels_list,\n             stage=stage,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        labels_list = images_to_levels(all_labels, num_level_proposals)\n        label_weights_list = images_to_levels(all_label_weights, num_level_proposals)\n        bbox_gt_list = images_to_levels(all_bbox_gt, num_level_proposals)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                                 num_level_proposals)\n        return (labels_list, label_weights_list, bbox_gt_list, bbox_weights_list,\n                num_total_pos, num_total_neg)\n\n    def loss_single(self, cls_score, pts_pred_init, pts_pred_refine,\n                    labels, label_weights,\n                    bbox_gt_init, bbox_weights_init,\n                    bbox_gt_refine, bbox_weights_refine,\n                    stride, num_total_samples_init, num_total_samples_refine):\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n        cls_score = cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n        loss_cls = self.loss_cls(\n            cls_score,\n            labels,\n            label_weights,\n            avg_factor=num_total_samples_refine)\n\n        # points loss\n        bbox_gt_init = bbox_gt_init.reshape(-1, 4)\n        bbox_weights_init = bbox_weights_init.reshape(-1, 4)\n        bbox_pred_init = self.points2bbox(\n            pts_pred_init.reshape(-1, 2 * self.num_points), y_first=False)\n        bbox_gt_refine = bbox_gt_refine.reshape(-1, 4)\n        bbox_weights_refine = bbox_weights_refine.reshape(-1, 4)\n        bbox_pred_refine = self.points2bbox(\n            pts_pred_refine.reshape(-1, 2 * self.num_points), y_first=False)\n        normalize_term = self.point_base_scale * stride\n        loss_pts_init = self.loss_bbox_init(\n            bbox_pred_init / normalize_term,\n            bbox_gt_init / normalize_term,\n            bbox_weights_init,\n            avg_factor=num_total_samples_init)\n        loss_pts_refine = self.loss_bbox_refine(\n            bbox_pred_refine / normalize_term,\n            bbox_gt_refine / normalize_term,\n            bbox_weights_refine,\n            avg_factor=num_total_samples_refine)\n        return loss_cls, loss_pts_init, loss_pts_refine\n\n    def loss(self,\n             cls_scores,\n             pts_preds_init,\n             pts_preds_refine,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.point_generators)\n        label_channels = self.cls_out_channels\n\n        # target for initial stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        pts_coordinate_preds_init = self.offset_to_pts(center_list, pts_preds_init)\n        if self.train_cfg.init.assigner['type'] != 'MaxIoUAssigner':\n            # Assign target for center list\n            candidate_list = center_list\n        else:\n            # transform center list to bbox list and\n            #   assign target for bbox list\n            bbox_list = self.centers_to_bboxes(center_list)\n            candidate_list = bbox_list\n        cls_reg_targets_init = self.get_targets(\n            candidate_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            stage='init',\n            label_channels=label_channels)\n        (*_, bbox_gt_list_init, bbox_weights_list_init,\n         num_total_pos_init, num_total_neg_init) = cls_reg_targets_init\n\n        # target for refinement stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        pts_coordinate_preds_refine = self.offset_to_pts(center_list, pts_preds_refine)\n        bbox_list = []\n        for i_img, center in enumerate(center_list):\n            bbox = []\n            for i_lvl in range(len(pts_preds_refine)):\n                bbox_preds_init = self.points2bbox(\n                    pts_preds_init[i_lvl].detach())\n                bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                bbox_center = torch.cat([center[i_lvl][:, :2], center[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n            bbox_list.append(bbox)\n        cls_reg_targets_refine = self.get_targets(\n            bbox_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            stage='refine',\n            label_channels=label_channels)\n        (labels_list, label_weights_list,\n         bbox_gt_list_refine, bbox_weights_list_refine,\n         num_total_pos_refine, num_total_neg_refine) = cls_reg_targets_refine\n\n        # compute loss\n        losses_cls, losses_pts_init, losses_pts_refine = multi_apply(\n            self.loss_single,\n            cls_scores,\n            pts_coordinate_preds_init,\n            pts_coordinate_preds_refine,\n            labels_list,\n            label_weights_list,\n            bbox_gt_list_init,\n            bbox_weights_list_init,\n            bbox_gt_list_refine,\n            bbox_weights_list_refine,\n            self.point_strides,\n            num_total_samples_init=num_total_pos_init,\n            num_total_samples_refine=num_total_pos_refine)\n        loss_dict_all = {\n            'loss_cls': losses_cls,\n            'loss_pts_init': losses_pts_init,\n            'loss_pts_refine': losses_pts_refine\n        }\n        return loss_dict_all\n\n    def get_bboxes(self,\n                   cls_scores,\n                   pts_preds_init,\n                   pts_preds_refine,\n                   img_metas,\n                   cfg=None,\n                   rescale=False,\n                   nms=True):\n        assert len(cls_scores) == len(pts_preds_refine)\n        bbox_preds_refine = [self.points2bbox(pts_pred_refine) for pts_pred_refine in pts_preds_refine]\n        num_levels = len(cls_scores)\n        mlvl_points = [\n            self.point_generators[i].grid_points(cls_scores[i].size()[-2:],\n                                                 self.point_strides[i])\n            for i in range(num_levels)\n        ]\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds_refine[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list,\n                                                mlvl_points, img_shape,\n                                                scale_factor, cfg, rescale,\n                                                nms)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False,\n                           nms=True):\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_points)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        for i_lvl, (cls_score, bbox_pred, points) in enumerate(zip(cls_scores, bbox_preds, mlvl_points)):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(-1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n            bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1)\n            bboxes = bbox_pred * self.point_strides[i_lvl] + bbox_pos_center\n            x1 = bboxes[:, 0].clamp(min=0, max=img_shape[1])\n            y1 = bboxes[:, 1].clamp(min=0, max=img_shape[0])\n            x2 = bboxes[:, 2].clamp(min=0, max=img_shape[1])\n            y2 = bboxes[:, 3].clamp(min=0, max=img_shape[0])\n            bboxes = torch.stack([x1, y1, x2, y2], dim=-1)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n        mlvl_scores = torch.cat(mlvl_scores)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        if nms:\n            det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores,\n                                                    cfg.score_thr, cfg.nms,\n                                                    cfg.max_per_img)\n            return det_bboxes, det_labels\n        else:\n            return mlvl_bboxes, mlvl_scores\n\n    def get_num_level_proposals_inside(self, num_level_proposals, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_proposals)\n        num_level_proposals_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_proposals_inside\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/reppoints_v2_head.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom mmdet.core import (PointGenerator, build_assigner, build_sampler,\n                        images_to_levels, multi_apply, multiclass_nms, unmap)\nfrom mmdet.ops import DeformConv, TLPool, BRPool\nfrom ..builder import HEADS, build_loss\nfrom .anchor_free_head import AnchorFreeHead\n\n\n@HEADS.register_module()\nclass RepPointsV2Head(AnchorFreeHead):\n    \"\"\"RepPoint head.\n\n    Args:\n        point_feat_channels (int): Number of channels of points features.\n        gradient_mul (float): The multiplier to gradients from\n            points refinement and recognition.\n        point_strides (Iterable): points strides.\n        point_base_scale (int): bbox scale for assigning labels.\n        loss_cls (dict): Config of classification loss.\n        loss_bbox_init (dict): Config of initial points loss.\n        loss_bbox_refine (dict): Config of points loss in refinement.\n        use_grid_points (bool): If we use bounding box representation, the\n        reppoints is represented as grid points on the bounding box.\n        center_init (bool): Whether to use center point assignment.\n        transform_method (str): The methods to transform RepPoints to bbox.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 point_feat_channels=256,\n                 shared_stacked_convs=1,\n                 first_kernel_size=3,\n                 kernel_size=1,\n                 corner_dim=64,\n                 num_points=9,\n                 gradient_mul=0.1,\n                 point_strides=[8, 16, 32, 64, 128],\n                 point_base_scale=4,\n                 loss_cls=dict(\n                     type='FocalLoss',\n                     use_sigmoid=True,\n                     gamma=2.0,\n                     alpha=0.25,\n                     loss_weight=1.0),\n                 loss_bbox_init=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=0.5),\n                 loss_bbox_refine=dict(\n                     type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_heatmap=dict(\n                     type='GaussianFocalLoss',\n                     alpha=2.0,\n                     gamma=4.0,\n                     loss_weight=0.25),\n                 loss_offset=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0),\n                 loss_sem=dict(type='SEPFocalLoss', use_sigmoid=True, gamma=2.0, alpha=0.25, loss_weight=0.1),\n                 use_grid_points=False,\n                 center_init=True,\n                 transform_method='moment',\n                 moment_mul=0.01,\n                 **kwargs):\n        self.num_points = num_points\n        self.point_feat_channels = point_feat_channels\n        self.shared_stacked_convs = shared_stacked_convs\n        self.use_grid_points = use_grid_points\n        self.center_init = center_init\n\n        self.first_kernel_size = first_kernel_size\n        self.kernel_size = kernel_size\n        self.corner_dim = corner_dim\n\n        # we use deformable conv to extract points features\n        self.dcn_kernel = int(np.sqrt(num_points))\n        self.dcn_pad = int((self.dcn_kernel - 1) / 2)\n        assert self.dcn_kernel * self.dcn_kernel == num_points, \\\n            'The points number should be a square number.'\n        assert self.dcn_kernel % 2 == 1, \\\n            'The points number should be an odd square number.'\n        dcn_base = np.arange(-self.dcn_pad,\n                             self.dcn_pad + 1).astype(np.float64)\n        dcn_base_y = np.repeat(dcn_base, self.dcn_kernel)\n        dcn_base_x = np.tile(dcn_base, self.dcn_kernel)\n        dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape(\n            (-1))\n        self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1)\n\n        super().__init__(num_classes, in_channels, loss_cls=loss_cls, **kwargs)\n\n        self.gradient_mul = gradient_mul\n        self.point_base_scale = point_base_scale\n        self.point_strides = point_strides\n        self.point_generators = [PointGenerator() for _ in self.point_strides]\n\n        if self.train_cfg:\n            self.init_assigner = build_assigner(self.train_cfg.init.assigner)\n            self.refine_assigner = build_assigner(self.train_cfg.refine.assigner)\n            self.hm_assigner = build_assigner(self.train_cfg.heatmap.assigner)\n            # use PseudoSampler when sampling is False\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.transform_method = transform_method\n        if self.transform_method == 'moment':\n            self.moment_transfer = nn.Parameter(data=torch.zeros(2), requires_grad=True)\n            self.moment_mul = moment_mul\n\n        self.cls_out_channels = self.num_classes\n        self.loss_bbox_init = build_loss(loss_bbox_init)\n        self.loss_bbox_refine = build_loss(loss_bbox_refine)\n        self.loss_heatmap = build_loss(loss_heatmap)\n        self.loss_offset = build_loss(loss_offset)\n        self.loss_sem = build_loss(loss_sem)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        self.shared_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n\n        for i in range(self.shared_stacked_convs):\n            self.shared_convs.append(\n                ConvModule(\n                    self.feat_channels,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n\n        self.hem_tl = TLPool(self.feat_channels, self.conv_cfg, self.norm_cfg, first_kernel_size=self.first_kernel_size, kernel_size=self.kernel_size, corner_dim=self.corner_dim)\n        self.hem_br = BRPool(self.feat_channels, self.conv_cfg, self.norm_cfg, first_kernel_size=self.first_kernel_size, kernel_size=self.kernel_size, corner_dim=self.corner_dim)\n\n        pts_out_dim = 4 if self.use_grid_points else 2 * self.num_points\n\n        cls_in_channels = self.feat_channels + 6\n        self.reppoints_cls_conv = DeformConv(cls_in_channels,\n                                             self.point_feat_channels,\n                                             self.dcn_kernel, 1, self.dcn_pad)\n        self.reppoints_cls_out = nn.Conv2d(self.point_feat_channels,\n                                           self.cls_out_channels, 1, 1, 0)\n\n        self.reppoints_pts_init_conv = nn.Conv2d(self.feat_channels,\n                                                 self.point_feat_channels, 3, 1, 1)\n        self.reppoints_pts_init_out = nn.Conv2d(self.point_feat_channels,\n                                                pts_out_dim, 1, 1, 0)\n        pts_in_channels = self.feat_channels + 6\n        self.reppoints_pts_refine_conv = DeformConv(pts_in_channels,\n                                                    self.point_feat_channels,\n                                                    self.dcn_kernel, 1,\n                                                    self.dcn_pad)\n        self.reppoints_pts_refine_out = nn.Conv2d(self.point_feat_channels,\n                                                  pts_out_dim, 1, 1, 0)\n\n        self.reppoints_hem_tl_score_out = nn.Conv2d(self.feat_channels, 1, 3, 1, 1)\n        self.reppoints_hem_br_score_out = nn.Conv2d(self.feat_channels, 1, 3, 1, 1)\n        self.reppoints_hem_tl_offset_out = nn.Conv2d(self.feat_channels, 2, 3, 1, 1)\n        self.reppoints_hem_br_offset_out = nn.Conv2d(self.feat_channels, 2, 3, 1, 1)\n\n        self.reppoints_sem_out = nn.Conv2d(self.feat_channels, self.cls_out_channels, 1, 1, 0)\n        self.reppoints_sem_embedding = ConvModule(\n            self.feat_channels,\n            self.feat_channels,\n            1,\n            conv_cfg=self.conv_cfg,\n            norm_cfg=self.norm_cfg)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.shared_convs:\n            normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.reppoints_cls_conv, std=0.01)\n        normal_init(self.reppoints_cls_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_pts_init_conv, std=0.01)\n        normal_init(self.reppoints_pts_init_out, std=0.01)\n        normal_init(self.reppoints_pts_refine_conv, std=0.01)\n        normal_init(self.reppoints_pts_refine_out, std=0.01)\n        normal_init(self.reppoints_hem_tl_score_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_hem_tl_offset_out, std=0.01)\n        normal_init(self.reppoints_hem_br_score_out, std=0.01, bias=bias_cls)\n        normal_init(self.reppoints_hem_br_offset_out, std=0.01)\n        normal_init(self.reppoints_sem_out, std=0.01, bias=bias_cls)\n\n    def points2bbox(self, pts, y_first=True):\n        \"\"\"Converting the points set into bounding box.\n\n        :param pts: the input points sets (fields), each points\n            set (fields) is represented as 2n scalar.\n        :param y_first: if y_fisrt=True, the point set is represented as\n            [y1, x1, y2, x2 ... yn, xn], otherwise the point set is\n            represented as [x1, y1, x2, y2 ... xn, yn].\n        :return: each points set is converting to a bbox [x1, y1, x2, y2].\n        \"\"\"\n        pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n        pts_y = pts_reshape[:, :, 0, ...] if y_first else pts_reshape[:, :, 1, ...]\n        pts_x = pts_reshape[:, :, 1, ...] if y_first else pts_reshape[:, :, 0, ...]\n        if self.transform_method == 'minmax':\n            bbox_left = pts_x.min(dim=1, keepdim=True)[0]\n            bbox_right = pts_x.max(dim=1, keepdim=True)[0]\n            bbox_up = pts_y.min(dim=1, keepdim=True)[0]\n            bbox_bottom = pts_y.max(dim=1, keepdim=True)[0]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom],\n                             dim=1)\n        elif self.transform_method == 'partial_minmax':\n            pts_y = pts_y[:, :4, ...]\n            pts_x = pts_x[:, :4, ...]\n            bbox_left = pts_x.min(dim=1, keepdim=True)[0]\n            bbox_right = pts_x.max(dim=1, keepdim=True)[0]\n            bbox_up = pts_y.min(dim=1, keepdim=True)[0]\n            bbox_bottom = pts_y.max(dim=1, keepdim=True)[0]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom],\n                             dim=1)\n        elif self.transform_method == 'moment':\n            pts_y_mean = pts_y.mean(dim=1, keepdim=True)\n            pts_x_mean = pts_x.mean(dim=1, keepdim=True)\n            pts_y_std = torch.std(pts_y - pts_y_mean, dim=1, keepdim=True)\n            pts_x_std = torch.std(pts_x - pts_x_mean, dim=1, keepdim=True)\n            moment_transfer = (self.moment_transfer * self.moment_mul) + (\n                self.moment_transfer.detach() * (1 - self.moment_mul))\n            moment_width_transfer = moment_transfer[0]\n            moment_height_transfer = moment_transfer[1]\n            half_width = pts_x_std * torch.exp(moment_width_transfer)\n            half_height = pts_y_std * torch.exp(moment_height_transfer)\n            bbox = torch.cat([\n                pts_x_mean - half_width, pts_y_mean - half_height,\n                pts_x_mean + half_width, pts_y_mean + half_height\n            ],\n                             dim=1)\n        elif self.transform_method == \"exact_minmax\":\n            pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:])\n            pts_reshape = pts_reshape[:, :2, ...]\n            pts_y = pts_reshape[:, :, 0, ...] if y_first else pts_reshape[:, :, 1, ...]\n            pts_x = pts_reshape[:, :, 1, ...] if y_first else pts_reshape[:, :, 0, ...]\n            bbox_left = pts_x[:, 0:1, ...]\n            bbox_right = pts_x[:, 1:2, ...]\n            bbox_up = pts_y[:, 0:1, ...]\n            bbox_bottom = pts_y[:, 1:2, ...]\n            bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n        else:\n            raise NotImplementedError\n        return bbox\n\n    def gen_grid_from_reg(self, reg, previous_boxes):\n        \"\"\"Base on the previous bboxes and regression values, we compute the\n        regressed bboxes and generate the grids on the bboxes.\n\n        :param reg: the regression value to previous bboxes.\n        :param previous_boxes: previous bboxes.\n        :return: generate grids on the regressed bboxes.\n        \"\"\"\n        b, _, h, w = reg.shape\n        bxy = (previous_boxes[:, :2, ...] + previous_boxes[:, 2:, ...]) / 2.\n        bwh = (previous_boxes[:, 2:, ...] -\n               previous_boxes[:, :2, ...]).clamp(min=1e-6)\n        grid_topleft = bxy + bwh * reg[:, :2, ...] - 0.5 * bwh * torch.exp(\n            reg[:, 2:, ...])\n        grid_wh = bwh * torch.exp(reg[:, 2:, ...])\n        grid_left = grid_topleft[:, [0], ...]\n        grid_top = grid_topleft[:, [1], ...]\n        grid_width = grid_wh[:, [0], ...]\n        grid_height = grid_wh[:, [1], ...]\n        intervel = torch.linspace(0., 1., self.dcn_kernel).view(\n            1, self.dcn_kernel, 1, 1).type_as(reg)\n        grid_x = grid_left + grid_width * intervel\n        grid_x = grid_x.unsqueeze(1).repeat(1, self.dcn_kernel, 1, 1, 1)\n        grid_x = grid_x.view(b, -1, h, w)\n        grid_y = grid_top + grid_height * intervel\n        grid_y = grid_y.unsqueeze(2).repeat(1, 1, self.dcn_kernel, 1, 1)\n        grid_y = grid_y.view(b, -1, h, w)\n        grid_yx = torch.stack([grid_y, grid_x], dim=2)\n        grid_yx = grid_yx.view(b, -1, h, w)\n        regressed_bbox = torch.cat([\n            grid_left, grid_top, grid_left + grid_width, grid_top + grid_height\n        ], 1)\n        return grid_yx, regressed_bbox\n\n    def forward(self, feats):\n        return multi_apply(self.forward_single, feats)\n\n    def forward_single(self, x):\n        \"\"\" Forward feature map of a single FPN level.\"\"\"\n        dcn_base_offset = self.dcn_base_offset.type_as(x)\n        # If we use center_init, the initial reppoints is from center points.\n        # If we use bounding bbox representation, the initial reppoints is\n        #   from regular grid placed on a pre-defined bbox.\n        if self.use_grid_points or not self.center_init:\n            scale = self.point_base_scale / 2\n            points_init = dcn_base_offset / dcn_base_offset.max() * scale\n            bbox_init = x.new_tensor([-scale, -scale, scale,\n                                      scale]).view(1, 4, 1, 1)\n        else:\n            points_init = 0\n        cls_feat = x\n        pts_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            pts_feat = reg_conv(pts_feat)\n\n        shared_feat = pts_feat\n        for shared_conv in self.shared_convs:\n            shared_feat = shared_conv(shared_feat)\n\n        sem_feat = shared_feat\n        hem_feat = shared_feat\n\n        sem_scores_out = self.reppoints_sem_out(sem_feat)\n        sem_feat = self.reppoints_sem_embedding(sem_feat)\n\n        cls_feat = cls_feat + sem_feat\n        pts_feat = pts_feat + sem_feat\n        hem_feat = hem_feat + sem_feat\n\n        # generate heatmap and offset\n        hem_tl_feat = self.hem_tl(hem_feat)\n        hem_br_feat = self.hem_br(hem_feat)\n\n        hem_tl_score_out = self.reppoints_hem_tl_score_out(hem_tl_feat)\n        hem_tl_offset_out = self.reppoints_hem_tl_offset_out(hem_tl_feat)\n        hem_br_score_out = self.reppoints_hem_br_score_out(hem_br_feat)\n        hem_br_offset_out = self.reppoints_hem_br_offset_out(hem_br_feat)\n\n        hem_score_out = torch.cat([hem_tl_score_out, hem_br_score_out], dim=1)\n        hem_offset_out = torch.cat([hem_tl_offset_out, hem_br_offset_out], dim=1)\n\n        # initialize reppoints\n        pts_out_init = self.reppoints_pts_init_out(self.relu(self.reppoints_pts_init_conv(pts_feat)))\n        if self.use_grid_points:\n            pts_out_init, bbox_out_init = self.gen_grid_from_reg(pts_out_init, bbox_init.detach())\n        else:\n            pts_out_init = pts_out_init + points_init\n        # refine and classify reppoints\n        pts_out_init_grad_mul = (1 - self.gradient_mul) * pts_out_init.detach() + self.gradient_mul * pts_out_init\n        dcn_offset = pts_out_init_grad_mul - dcn_base_offset\n\n        hem_feat = torch.cat([hem_score_out, hem_offset_out], dim=1)\n        cls_feat = torch.cat([cls_feat, hem_feat], dim=1)\n        pts_feat = torch.cat([pts_feat, hem_feat], dim=1)\n\n        cls_out = self.reppoints_cls_out(self.relu(self.reppoints_cls_conv(cls_feat, dcn_offset)))\n        pts_out_refine = self.reppoints_pts_refine_out(self.relu(self.reppoints_pts_refine_conv(pts_feat, dcn_offset)))\n        if self.use_grid_points:\n            pts_out_refine, bbox_out_refine = self.gen_grid_from_reg(pts_out_refine, bbox_out_init.detach())\n        else:\n            pts_out_refine = pts_out_refine + pts_out_init.detach()\n        return cls_out, pts_out_init, pts_out_refine, hem_score_out, hem_offset_out, sem_scores_out\n\n    def get_points(self, featmap_sizes, img_metas):\n        \"\"\"Get points according to feature map sizes.\n\n        Args:\n            featmap_sizes (list[tuple]): Multi-level feature map sizes.\n            img_metas (list[dict]): Image meta info.\n\n        Returns:\n            tuple: points of each image, valid flags of each image\n        \"\"\"\n        num_imgs = len(img_metas)\n        num_levels = len(featmap_sizes)\n\n        # since feature map sizes of all images are the same, we only compute\n        # points center for one time\n        multi_level_points = []\n        for i in range(num_levels):\n            points = self.point_generators[i].grid_points(\n                featmap_sizes[i], self.point_strides[i])\n            multi_level_points.append(points)\n        points_list = [[point.clone() for point in multi_level_points] for _ in range(num_imgs)]\n\n        # for each image, we compute valid flags of multi level grids\n        valid_flag_list = []\n        for img_id, img_meta in enumerate(img_metas):\n            multi_level_flags = []\n            for i in range(num_levels):\n                point_stride = self.point_strides[i]\n                feat_h, feat_w = featmap_sizes[i]\n                h, w = img_meta['pad_shape'][:2]\n                valid_feat_h = min(int(np.ceil(h / point_stride)), feat_h)\n                valid_feat_w = min(int(np.ceil(w / point_stride)), feat_w)\n                flags = self.point_generators[i].valid_flags(\n                    (feat_h, feat_w), (valid_feat_h, valid_feat_w))\n                multi_level_flags.append(flags)\n            valid_flag_list.append(multi_level_flags)\n\n        return points_list, valid_flag_list\n\n    def centers_to_bboxes(self, point_list):\n        \"\"\"Get bboxes according to center points. Only used in MaxIOUAssigner.\n        \"\"\"\n        bbox_list = []\n        for i_img, point in enumerate(point_list):\n            bbox = []\n            for i_lvl in range(len(self.point_strides)):\n                scale = self.point_base_scale * self.point_strides[i_lvl] * 0.5\n                bbox_shift = torch.Tensor([-scale, -scale, scale, scale]).view(1, 4).type_as(point[0])\n                bbox_center = torch.cat([point[i_lvl][:, :2], point[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift)\n            bbox_list.append(bbox)\n        return bbox_list\n\n    def offset_to_pts(self, center_list, pred_list):\n        \"\"\"Change from point offset to point coordinate.\"\"\"\n        pts_list = []\n        for i_lvl in range(len(self.point_strides)):\n            pts_lvl = []\n            for i_img in range(len(center_list)):\n                pts_center = center_list[i_img][i_lvl][:, :2].repeat(\n                    1, self.num_points)\n                pts_shift = pred_list[i_lvl][i_img]\n                yx_pts_shift = pts_shift.permute(1, 2, 0).view(\n                    -1, 2 * self.num_points)\n                y_pts_shift = yx_pts_shift[..., 0::2]\n                x_pts_shift = yx_pts_shift[..., 1::2]\n                xy_pts_shift = torch.stack([x_pts_shift, y_pts_shift], -1)\n                xy_pts_shift = xy_pts_shift.view(*yx_pts_shift.shape[:-1], -1)\n                pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center\n                pts_lvl.append(pts)\n            pts_lvl = torch.stack(pts_lvl, 0)\n            pts_list.append(pts_lvl)\n        return pts_list\n\n    def _point_target_single(self,\n                             flat_proposals,\n                             valid_flags,\n                             num_level_proposals,\n                             gt_bboxes,\n                             gt_bboxes_ignore,\n                             gt_labels,\n                             label_channels=1,\n                             stage='init',\n                             unmap_outputs=True):\n        inside_flags = valid_flags\n        if not inside_flags.any():\n            return (None, ) * 6\n        # assign gt and sample proposals\n        proposals = flat_proposals[inside_flags, :]\n\n        num_level_proposals_inside = self.get_num_level_proposals_inside(num_level_proposals, inside_flags)\n        if stage == 'init':\n            assigner = self.init_assigner\n            assigner_type = self.train_cfg.init.assigner.type\n            pos_weight = self.train_cfg.init.pos_weight\n        else:\n            assigner = self.refine_assigner\n            assigner_type = self.train_cfg.refine.assigner.type\n            pos_weight = self.train_cfg.refine.pos_weight\n        if assigner_type != \"ATSSAssigner\":\n            assign_result = assigner.assign(proposals, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        else:\n            assign_result = assigner.assign(proposals, num_level_proposals_inside, gt_bboxes, gt_bboxes_ignore, gt_labels)\n        sampling_result = self.sampler.sample(assign_result, proposals, gt_bboxes)\n\n        num_valid_proposals = proposals.shape[0]\n        bbox_gt = proposals.new_zeros([num_valid_proposals, 4])\n        bbox_weights = proposals.new_zeros([num_valid_proposals, 4])\n        labels = proposals.new_full((num_valid_proposals, ), self.background_label, dtype=torch.long)\n        label_weights = proposals.new_zeros(num_valid_proposals, dtype=torch.float)\n\n        pos_inds = sampling_result.pos_inds\n        neg_inds = sampling_result.neg_inds\n        if len(pos_inds) > 0:\n            pos_gt_bboxes = sampling_result.pos_gt_bboxes\n            bbox_gt[pos_inds, :] = pos_gt_bboxes\n            bbox_weights[pos_inds, :] = 1.0\n            if gt_labels is None:\n                labels[pos_inds] = 1\n            else:\n                labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]\n            if pos_weight <= 0:\n                label_weights[pos_inds] = 1.0\n            else:\n                label_weights[pos_inds] = pos_weight\n        if len(neg_inds) > 0:\n            label_weights[neg_inds] = 1.0\n\n        # map up to original set of proposals\n        if unmap_outputs:\n            num_total_proposals = flat_proposals.size(0)\n            labels = unmap(labels, num_total_proposals, inside_flags)\n            label_weights = unmap(label_weights, num_total_proposals, inside_flags)\n            bbox_gt = unmap(bbox_gt, num_total_proposals, inside_flags)\n            bbox_weights = unmap(bbox_weights, num_total_proposals, inside_flags)\n\n        return labels, label_weights, bbox_gt, bbox_weights, pos_inds, neg_inds\n\n    def get_targets(self,\n                    proposals_list,\n                    valid_flag_list,\n                    gt_bboxes_list,\n                    img_metas,\n                    gt_bboxes_ignore_list=None,\n                    gt_labels_list=None,\n                    stage='init',\n                    label_channels=1,\n                    unmap_outputs=True):\n        \"\"\"Compute corresponding GT box and classification targets for\n        proposals.\n\n        Args:\n            proposals_list (list[list]): Multi level points/bboxes of each\n                image.\n            valid_flag_list (list[list]): Multi level valid flags of each\n                image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            gt_bboxes_ignore_list (list[Tensor]): Ground truth bboxes to be\n                ignored.\n            gt_bboxes_list (list[Tensor]): Ground truth labels of each box.\n            stage (str): `init` or `refine`. Generate target for init stage or\n                refine stage\n            label_channels (int): Channel of label.\n            unmap_outputs (bool): Whether to map outputs back to the original\n                set of anchors.\n\n        Returns:\n            tuple:\n                - labels_list (list[Tensor]): Labels of each level.\n                - label_weights_list (list[Tensor]): Label weights of each level.  # noqa: E501\n                - bbox_gt_list (list[Tensor]): Ground truth bbox of each level.\n                - proposal_list (list[Tensor]): Proposals(points/bboxes) of each level.  # noqa: E501\n                - proposal_weights_list (list[Tensor]): Proposal weights of each level.  # noqa: E501\n                - num_total_pos (int): Number of positive samples in all images.  # noqa: E501\n                - num_total_neg (int): Number of negative samples in all images.  # noqa: E501\n        \"\"\"\n        assert stage in ['init', 'refine']\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n        num_level_proposals_list = [num_level_proposals] * num_imgs\n\n        # concat all level points and flags to a single tensor\n        for i in range(num_imgs):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        # compute targets for each image\n        if gt_bboxes_ignore_list is None:\n            gt_bboxes_ignore_list = [None for _ in range(num_imgs)]\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_labels, all_label_weights, all_bbox_gt, all_bbox_weights,\n         pos_inds_list, neg_inds_list) = multi_apply(\n             self._point_target_single,\n             proposals_list,\n             valid_flag_list,\n             num_level_proposals_list,\n             gt_bboxes_list,\n             gt_bboxes_ignore_list,\n             gt_labels_list,\n             stage=stage,\n             label_channels=label_channels,\n             unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([labels is None for labels in all_labels]):\n            return None\n        # sampled points of all images\n        num_total_pos = sum([max(inds.numel(), 1) for inds in pos_inds_list])\n        num_total_neg = sum([max(inds.numel(), 1) for inds in neg_inds_list])\n        labels_list = images_to_levels(all_labels, num_level_proposals)\n        label_weights_list = images_to_levels(all_label_weights, num_level_proposals)\n        bbox_gt_list = images_to_levels(all_bbox_gt, num_level_proposals)\n        bbox_weights_list = images_to_levels(all_bbox_weights,\n                                                 num_level_proposals)\n        return (labels_list, label_weights_list, bbox_gt_list, bbox_weights_list,\n                num_total_pos, num_total_neg)\n\n    def _hm_target_single(self,\n                          flat_points,\n                          inside_flags,\n                          gt_bboxes,\n                          gt_labels,\n                          unmap_outputs=True):\n        # assign gt and sample points\n        if not inside_flags.any():\n            return (None, ) * 12\n        points = flat_points[inside_flags, :]\n\n        assigner = self.hm_assigner\n        gt_hm_tl, gt_offset_tl, pos_inds_tl, neg_inds_tl, \\\n        gt_hm_br, gt_offset_br, pos_inds_br, neg_inds_br = \\\n            assigner.assign(points, gt_bboxes, gt_labels)\n\n        num_valid_points = points.shape[0]\n        hm_tl_weights = points.new_zeros(num_valid_points, dtype=torch.float)\n        hm_br_weights = points.new_zeros(num_valid_points, dtype=torch.float)\n        offset_tl_weights = points.new_zeros([num_valid_points, 2], dtype=torch.float)\n        offset_br_weights = points.new_zeros([num_valid_points, 2], dtype=torch.float)\n\n        hm_tl_weights[pos_inds_tl] = 1.0\n        hm_tl_weights[neg_inds_tl] = 1.0\n        offset_tl_weights[pos_inds_tl, :] = 1.0\n\n        hm_br_weights[pos_inds_br] = 1.0\n        hm_br_weights[neg_inds_br] = 1.0\n        offset_br_weights[pos_inds_br, :] = 1.0\n\n        # map up to original set of grids\n        if unmap_outputs:\n            num_total_points = flat_points.shape[0]\n            gt_hm_tl = unmap(gt_hm_tl, num_total_points, inside_flags)\n            gt_offset_tl = unmap(gt_offset_tl, num_total_points, inside_flags)\n            hm_tl_weights = unmap(hm_tl_weights, num_total_points, inside_flags)\n            offset_tl_weights = unmap(offset_tl_weights, num_total_points, inside_flags)\n\n            gt_hm_br = unmap(gt_hm_br, num_total_points, inside_flags)\n            gt_offset_br = unmap(gt_offset_br, num_total_points, inside_flags)\n            hm_br_weights = unmap(hm_br_weights, num_total_points, inside_flags)\n            offset_br_weights = unmap(offset_br_weights, num_total_points, inside_flags)\n\n        return (gt_hm_tl, gt_offset_tl, hm_tl_weights, offset_tl_weights, pos_inds_tl, neg_inds_tl,\n                gt_hm_br, gt_offset_br, hm_br_weights, offset_br_weights, pos_inds_br, neg_inds_br)\n\n    def get_hm_targets(self,\n                       proposals_list,\n                       valid_flag_list,\n                       gt_bboxes_list,\n                       img_metas,\n                       gt_labels_list=None,\n                       unmap_outputs=True):\n        \"\"\"Compute refinement and classification targets for points.\n\n        Args:\n            points_list (list[list]): Multi level points of each image.\n            valid_flag_list (list[list]): Multi level valid flags of each image.\n            gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.\n            img_metas (list[dict]): Meta info of each image.\n            cfg (dict): train sample configs.\n\n        Returns:\n            tuple\n        \"\"\"\n        num_imgs = len(img_metas)\n        assert len(proposals_list) == len(valid_flag_list) == num_imgs\n\n        # points number of multi levels\n        num_level_proposals = [points.size(0) for points in proposals_list[0]]\n\n        # concat all level points and flags to a single tensor\n        for i in range(len(proposals_list)):\n            assert len(proposals_list[i]) == len(valid_flag_list[i])\n            proposals_list[i] = torch.cat(proposals_list[i])\n            valid_flag_list[i] = torch.cat(valid_flag_list[i])\n\n        if gt_labels_list is None:\n            gt_labels_list = [None for _ in range(num_imgs)]\n        (all_gt_hm_tl, all_gt_offset_tl, all_hm_tl_weights, all_offset_tl_weights, pos_inds_tl_list, neg_inds_tl_list,\n         all_gt_hm_br, all_gt_offset_br, all_hm_br_weights, all_offset_br_weights, pos_inds_br_list, neg_inds_br_list) = \\\n            multi_apply(\n                self._hm_target_single,\n                proposals_list,\n                valid_flag_list,\n                gt_bboxes_list,\n                gt_labels_list,\n                unmap_outputs=unmap_outputs)\n        # no valid points\n        if any([gt_hm_tl is None for gt_hm_tl in all_gt_hm_tl]):\n            return None\n        # sampled points of all images\n        num_total_pos_tl = sum([max(inds.numel(), 1) for inds in pos_inds_tl_list])\n        num_total_neg_tl = sum([max(inds.numel(), 1) for inds in neg_inds_tl_list])\n        num_total_pos_br = sum([max(inds.numel(), 1) for inds in pos_inds_br_list])\n        num_total_neg_br = sum([max(inds.numel(), 1) for inds in neg_inds_br_list])\n\n        gt_hm_tl_list = images_to_levels(all_gt_hm_tl, num_level_proposals)\n        gt_offset_tl_list = images_to_levels(all_gt_offset_tl, num_level_proposals)\n        hm_tl_weight_list = images_to_levels(all_hm_tl_weights, num_level_proposals)\n        offset_tl_weight_list = images_to_levels(all_offset_tl_weights, num_level_proposals)\n\n        gt_hm_br_list = images_to_levels(all_gt_hm_br, num_level_proposals)\n        gt_offset_br_list = images_to_levels(all_gt_offset_br, num_level_proposals)\n        hm_br_weight_list = images_to_levels(all_hm_br_weights, num_level_proposals)\n        offset_br_weight_list = images_to_levels(all_offset_br_weights, num_level_proposals)\n\n        return (gt_hm_tl_list, gt_offset_tl_list, hm_tl_weight_list, offset_tl_weight_list,\n                gt_hm_br_list, gt_offset_br_list, hm_br_weight_list, offset_br_weight_list,\n                num_total_pos_tl, num_total_neg_tl, num_total_pos_br, num_total_neg_br)\n\n    def loss_single(self, cls_score, pts_pred_init, pts_pred_refine, hm_score, hm_offset,\n                    labels, label_weights,\n                    bbox_gt_init, bbox_weights_init,\n                    bbox_gt_refine, bbox_weights_refine,\n                    gt_hm_tl, gt_offset_tl, gt_hm_tl_weight, gt_offset_tl_weight,\n                    gt_hm_br, gt_offset_br, gt_hm_br_weight, gt_offset_br_weight,\n                    stride,\n                    num_total_samples_init, num_total_samples_refine,\n                    num_total_samples_tl, num_total_samples_br):\n        # classification loss\n        labels = labels.reshape(-1)\n        label_weights = label_weights.reshape(-1)\n        cls_score = cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels)\n        loss_cls = self.loss_cls(\n            cls_score, labels, label_weights, avg_factor=num_total_samples_refine)\n\n        # points loss\n        bbox_gt_init = bbox_gt_init.reshape(-1, 4)\n        bbox_weights_init = bbox_weights_init.reshape(-1, 4)\n        bbox_pred_init = self.points2bbox(pts_pred_init.reshape(-1, 2 * self.num_points), y_first=False)\n        bbox_gt_refine = bbox_gt_refine.reshape(-1, 4)\n        bbox_weights_refine = bbox_weights_refine.reshape(-1, 4)\n        bbox_pred_refine = self.points2bbox(pts_pred_refine.reshape(-1, 2 * self.num_points), y_first=False)\n        normalize_term = self.point_base_scale * stride\n        loss_pts_init = self.loss_bbox_init(\n            bbox_pred_init / normalize_term,\n            bbox_gt_init / normalize_term,\n            bbox_weights_init,\n            avg_factor=num_total_samples_init)\n        loss_pts_refine = self.loss_bbox_refine(\n            bbox_pred_refine / normalize_term,\n            bbox_gt_refine / normalize_term,\n            bbox_weights_refine,\n            avg_factor=num_total_samples_refine)\n\n        # heatmap cls loss\n        hm_score = hm_score.permute(0, 2, 3, 1).reshape(-1, 2)\n        hm_score_tl, hm_score_br = torch.chunk(hm_score, 2, dim=-1)\n        hm_score_tl = hm_score_tl.squeeze(1).sigmoid()\n        hm_score_br = hm_score_br.squeeze(1).sigmoid()\n\n        gt_hm_tl = gt_hm_tl.reshape(-1)\n        gt_hm_tl_weight = gt_hm_tl_weight.reshape(-1)\n        gt_hm_br = gt_hm_br.reshape(-1)\n        gt_hm_br_weight = gt_hm_br_weight.reshape(-1)\n\n        loss_heatmap = 0\n        loss_heatmap += self.loss_heatmap(\n            hm_score_tl, gt_hm_tl, gt_hm_tl_weight, avg_factor=num_total_samples_tl\n        )\n        loss_heatmap += self.loss_heatmap(\n            hm_score_br, gt_hm_br, gt_hm_br_weight, avg_factor=num_total_samples_br\n        )\n        loss_heatmap /= 2.0\n\n        # heatmap offset loss\n        hm_offset = hm_offset.permute(0, 2, 3, 1).reshape(-1, 4)\n        hm_offset_tl, hm_offset_br = torch.chunk(hm_offset, 2, dim=-1)\n\n        gt_offset_tl = gt_offset_tl.reshape(-1, 2)\n        gt_offset_tl_weight = gt_offset_tl_weight.reshape(-1, 2)\n        gt_offset_br = gt_offset_br.reshape(-1, 2)\n        gt_offset_br_weight = gt_offset_br_weight.reshape(-1, 2)\n\n        loss_offset = 0\n        loss_offset += self.loss_offset(\n            hm_offset_tl, gt_offset_tl, gt_offset_tl_weight,\n            avg_factor=num_total_samples_tl\n        )\n        loss_offset += self.loss_offset(\n            hm_offset_br, gt_offset_br, gt_offset_br_weight,\n            avg_factor=num_total_samples_br\n        )\n        loss_offset /= 2.0\n\n        return loss_cls, loss_pts_init, loss_pts_refine, loss_heatmap, loss_offset\n\n    def loss(self,\n             cls_scores,\n             pts_preds_init,\n             pts_preds_refine,\n             hm_scores,\n             hm_offsets,\n             sem_scores,\n             gt_bboxes,\n             gt_sem_map,\n             gt_sem_weights,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == len(self.point_generators)\n        label_channels = self.cls_out_channels\n\n        # target for initial stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        pts_coordinate_preds_init = self.offset_to_pts(center_list, pts_preds_init)\n        if self.train_cfg.init.assigner['type'] != 'MaxIoUAssigner':\n            # Assign target for center list\n            candidate_list = center_list\n        else:\n            # transform center list to bbox list and\n            #   assign target for bbox list\n            bbox_list = self.centers_to_bboxes(center_list)\n            candidate_list = bbox_list\n        cls_reg_targets_init = self.get_targets(\n            candidate_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            stage='init',\n            label_channels=label_channels)\n        (*_, bbox_gt_list_init, bbox_weights_list_init,\n         num_total_pos_init, num_total_neg_init) = cls_reg_targets_init\n\n        # target for heatmap in initial stage\n        proposal_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        heatmap_targets = self.get_hm_targets(\n            proposal_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_labels)\n        (gt_hm_tl_list, gt_offset_tl_list, gt_hm_tl_weight_list, gt_offset_tl_weight_list,\n         gt_hm_br_list, gt_offset_br_list, gt_hm_br_weight_list, gt_offset_br_weight_list,\n         num_total_pos_tl, num_total_neg_tl, num_total_pos_br, num_total_neg_br) = heatmap_targets\n\n        # target for refinement stage\n        center_list, valid_flag_list = self.get_points(featmap_sizes, img_metas)\n        pts_coordinate_preds_refine = self.offset_to_pts(center_list, pts_preds_refine)\n        bbox_list = []\n        for i_img, center in enumerate(center_list):\n            bbox = []\n            for i_lvl in range(len(pts_preds_refine)):\n                bbox_preds_init = self.points2bbox(\n                    pts_preds_init[i_lvl].detach())\n                bbox_shift = bbox_preds_init * self.point_strides[i_lvl]\n                bbox_center = torch.cat([center[i_lvl][:, :2], center[i_lvl][:, :2]], dim=1)\n                bbox.append(bbox_center + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4))\n            bbox_list.append(bbox)\n        cls_reg_targets_refine = self.get_targets(\n            bbox_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            stage='refine',\n            label_channels=label_channels)\n        (labels_list, label_weights_list,\n         bbox_gt_list_refine, bbox_weights_list_refine,\n         num_total_pos_refine, num_total_neg_refine) = cls_reg_targets_refine\n\n        # compute loss\n        losses_cls, losses_pts_init, losses_pts_refine, losses_heatmap, losses_offset = multi_apply(\n            self.loss_single,\n            cls_scores,\n            pts_coordinate_preds_init,\n            pts_coordinate_preds_refine,\n            hm_scores,\n            hm_offsets,\n            labels_list,\n            label_weights_list,\n            bbox_gt_list_init,\n            bbox_weights_list_init,\n            bbox_gt_list_refine,\n            bbox_weights_list_refine,\n            gt_hm_tl_list,\n            gt_offset_tl_list,\n            gt_hm_tl_weight_list,\n            gt_offset_tl_weight_list,\n            gt_hm_br_list,\n            gt_offset_br_list,\n            gt_hm_br_weight_list,\n            gt_offset_br_weight_list,\n            self.point_strides,\n            num_total_samples_init=num_total_pos_init,\n            num_total_samples_refine=num_total_pos_refine,\n            num_total_samples_tl=num_total_pos_tl,\n            num_total_samples_br=num_total_pos_br)\n\n        # sem loss\n        concat_sem_scores = []\n        concat_gt_sem_map = []\n        concat_gt_sem_weights = []\n\n        for i in range(5):\n            sem_score = sem_scores[i]\n            gt_lvl_sem_map = F.interpolate(gt_sem_map, sem_score.shape[-2:]).reshape(-1)\n            gt_lvl_sem_weight = F.interpolate(gt_sem_weights, sem_score.shape[-2:]).reshape(-1)\n            sem_score = sem_score.reshape(-1)\n\n            try:\n                concat_sem_scores = torch.cat([concat_sem_scores, sem_score])\n                concat_gt_sem_map = torch.cat([concat_gt_sem_map, gt_lvl_sem_map])\n                concat_gt_sem_weights = torch.cat([concat_gt_sem_weights, gt_lvl_sem_weight])\n            except:\n                concat_sem_scores = sem_score\n                concat_gt_sem_map = gt_lvl_sem_map\n                concat_gt_sem_weights = gt_lvl_sem_weight\n\n        loss_sem = self.loss_sem(concat_sem_scores, concat_gt_sem_map, concat_gt_sem_weights, avg_factor=(concat_gt_sem_map > 0).sum())\n\n        loss_dict_all = {'loss_cls': losses_cls,\n                         'loss_pts_init': losses_pts_init,\n                         'loss_pts_refine': losses_pts_refine,\n                         'loss_heatmap': losses_heatmap,\n                         'loss_offset': losses_offset,\n                         'loss_sem': loss_sem,\n                         }\n        return loss_dict_all\n\n    def get_bboxes(self,\n                   cls_scores,\n                   pts_preds_init,\n                   pts_preds_refine,\n                   hm_scores,\n                   hm_offsets,\n                   sem_scores,\n                   img_metas,\n                   cfg=None,\n                   rescale=False,\n                   nms=True):\n        assert len(cls_scores) == len(pts_preds_refine)\n        bbox_preds_refine = [self.points2bbox(pts_pred_refine) for pts_pred_refine in pts_preds_refine]\n        num_levels = len(cls_scores)\n        mlvl_points = [\n            self.point_generators[i].grid_points(cls_scores[i].size()[-2:],\n                                                 self.point_strides[i])\n            for i in range(num_levels)\n        ]\n        result_list = []\n        for img_id in range(len(img_metas)):\n            cls_score_list = [\n                cls_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            bbox_pred_list = [\n                bbox_preds_refine[i][img_id].detach() for i in range(num_levels)\n            ]\n            hm_scores_list = [\n                hm_scores[i][img_id].detach() for i in range(num_levels)\n            ]\n            hm_offsets_list = [\n                hm_offsets[i][img_id].detach() for i in range(num_levels)\n            ]\n            img_shape = img_metas[img_id]['img_shape']\n            scale_factor = img_metas[img_id]['scale_factor']\n            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list, hm_scores_list, hm_offsets_list,\n                                                mlvl_points, img_shape,\n                                                scale_factor, cfg, rescale,\n                                                nms)\n            result_list.append(proposals)\n        return result_list\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           hm_scores,\n                           hm_offsets,\n                           mlvl_points,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False,\n                           nms=True):\n        def select(score_map, x, y, ks=2, i=0):\n            H, W = score_map.shape[-2], score_map.shape[-1]\n            score_map = score_map.sigmoid()\n            score_map_original = score_map.clone()\n\n            score_map, indices = F.max_pool2d_with_indices(score_map.unsqueeze(0), kernel_size=ks, stride=1, padding=(ks - 1) // 2)\n\n            indices = indices.squeeze(0).squeeze(0)\n\n            if ks % 2 == 0:\n                round_func = torch.floor\n            else:\n                round_func = torch.round\n\n            x_round = round_func((x / self.point_strides[i]).clamp(min=0, max=score_map.shape[-1] - 1))\n            y_round = round_func((y / self.point_strides[i]).clamp(min=0, max=score_map.shape[-2] - 1))\n\n            select_indices = indices[y_round.to(torch.long), x_round.to(torch.long)]\n            new_x = select_indices % W\n            new_y = select_indices // W\n\n            score_map_squeeze = score_map_original.squeeze(0)\n            score = score_map_squeeze[new_y, new_x]\n\n            new_x, new_y = new_x.to(torch.float), new_y.to(torch.float)\n\n            return new_x, new_y, score\n\n        cfg = self.test_cfg if cfg is None else cfg\n        assert len(cls_scores) == len(bbox_preds) == len(mlvl_points)\n        mlvl_bboxes = []\n        mlvl_scores = []\n        for i_lvl, (cls_score, bbox_pred, points) in enumerate(zip(cls_scores, bbox_preds, mlvl_points)):\n            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]\n            scores = cls_score.permute(1, 2, 0).reshape(-1, self.cls_out_channels).sigmoid()\n            bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            nms_pre = cfg.get('nms_pre', -1)\n            if nms_pre > 0 and scores.shape[0] > nms_pre:\n                max_scores, _ = scores.max(dim=1)\n                _, topk_inds = max_scores.topk(nms_pre)\n                points = points[topk_inds, :]\n                bbox_pred = bbox_pred[topk_inds, :]\n                scores = scores[topk_inds, :]\n            bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1)\n            bboxes = bbox_pred * self.point_strides[i_lvl] + bbox_pos_center\n            x1 = bboxes[:, 0].clamp(min=0, max=img_shape[1])\n            y1 = bboxes[:, 1].clamp(min=0, max=img_shape[0])\n            x2 = bboxes[:, 2].clamp(min=0, max=img_shape[1])\n            y2 = bboxes[:, 3].clamp(min=0, max=img_shape[0])\n\n            if i_lvl > 0:\n                i = 0 if i_lvl in (1, 2) else 1\n\n                x1_new, y1_new, score1_new = select(hm_scores[i][0, ...], x1, y1, 2, i)\n                x2_new, y2_new, score2_new = select(hm_scores[i][1, ...], x2, y2, 2, i)\n\n                hm_offset = hm_offsets[i].permute(1, 2, 0)\n                point_stride = self.point_strides[i]\n\n                x1 = ((x1_new + hm_offset[y1_new.to(torch.long), x1_new.to(torch.long), 0]) * point_stride).clamp(min=0, max=img_shape[1])\n                y1 = ((y1_new + hm_offset[y1_new.to(torch.long), x1_new.to(torch.long), 1]) * point_stride).clamp(min=0, max=img_shape[0])\n                x2 = ((x2_new + hm_offset[y2_new.to(torch.long), x2_new.to(torch.long), 2]) * point_stride).clamp(min=0, max=img_shape[1])\n                y2 = ((y2_new + hm_offset[y2_new.to(torch.long), x2_new.to(torch.long), 3]) * point_stride).clamp(min=0, max=img_shape[0])\n            bboxes = torch.stack([x1, y1, x2, y2], dim=-1)\n            mlvl_bboxes.append(bboxes)\n            mlvl_scores.append(scores)\n        mlvl_bboxes = torch.cat(mlvl_bboxes)\n        if rescale:\n            mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor)\n        mlvl_scores = torch.cat(mlvl_scores)\n        padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1)\n        mlvl_scores = torch.cat([mlvl_scores, padding], dim=1)\n        if nms:\n            det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores,\n                                                    cfg.score_thr, cfg.nms,\n                                                    cfg.max_per_img)\n            return det_bboxes, det_labels\n        else:\n            return mlvl_bboxes, mlvl_scores\n\n    def get_num_level_proposals_inside(self, num_level_proposals, inside_flags):\n        split_inside_flags = torch.split(inside_flags, num_level_proposals)\n        num_level_proposals_inside = [\n            int(flags.sum()) for flags in split_inside_flags\n        ]\n        return num_level_proposals_inside\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/retina_head.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom ..builder import HEADS\nfrom .anchor_head import AnchorHead\n\n\n@HEADS.register_module()\nclass RetinaHead(AnchorHead):\n    \"\"\"An anchor-based head used in\n    `RetinaNet <https://arxiv.org/pdf/1708.02002.pdf>`_.\n\n    The head contains two subnetworks. The first classifies anchor boxes and\n    the second regresses deltas for the anchors.\n\n    Example:\n        >>> import torch\n        >>> self = RetinaHead(11, 7)\n        >>> x = torch.rand(1, 7, 32, 32)\n        >>> cls_score, bbox_pred = self.forward_single(x)\n        >>> # Each anchor predicts a score for each class except background\n        >>> cls_per_anchor = cls_score.shape[1] / self.num_anchors\n        >>> box_per_anchor = bbox_pred.shape[1] / self.num_anchors\n        >>> assert cls_per_anchor == (self.num_classes)\n        >>> assert box_per_anchor == 4\n    \"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 in_channels,\n                 stacked_convs=4,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 anchor_generator=dict(\n                     type='AnchorGenerator',\n                     octave_base_scale=4,\n                     scales_per_octave=3,\n                     ratios=[0.5, 1.0, 2.0],\n                     strides=[8, 16, 32, 64, 128]),\n                 **kwargs):\n        self.stacked_convs = stacked_convs\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        super(RetinaHead, self).__init__(\n            num_classes,\n            in_channels,\n            anchor_generator=anchor_generator,\n            **kwargs)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.stacked_convs):\n            chn = self.in_channels if i == 0 else self.feat_channels\n            self.cls_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n            self.reg_convs.append(\n                ConvModule(\n                    chn,\n                    self.feat_channels,\n                    3,\n                    stride=1,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        self.retina_cls = nn.Conv2d(\n            self.feat_channels,\n            self.num_anchors * self.cls_out_channels,\n            3,\n            padding=1)\n        self.retina_reg = nn.Conv2d(\n            self.feat_channels, self.num_anchors * 4, 3, padding=1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs:\n            normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.retina_cls, std=0.01, bias=bias_cls)\n        normal_init(self.retina_reg, std=0.01)\n\n    def forward_single(self, x):\n        \"\"\"Forward feature of a single scale level.\n\n        Args:\n            x (Tensor): Features of a single scale level.\n\n        Returns:\n            tuple:\n                cls_score (Tensor): Cls scores for a single scale level\n                    the channels number is num_anchors * num_classes.\n                bbox_pred (Tensor): Box energies / deltas for a single scale\n                    level, the channels number is num_anchors * 4.\n        \"\"\"\n        cls_feat = x\n        reg_feat = x\n        for cls_conv in self.cls_convs:\n            cls_feat = cls_conv(cls_feat)\n        for reg_conv in self.reg_convs:\n            reg_feat = reg_conv(reg_feat)\n        cls_score = self.retina_cls(cls_feat)\n        bbox_pred = self.retina_reg(reg_feat)\n        return cls_score, bbox_pred\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/retina_sepbn_head.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, bias_init_with_prob, normal_init\n\nfrom ..builder import HEADS\nfrom .anchor_head import AnchorHead\n\n\n@HEADS.register_module()\nclass RetinaSepBNHead(AnchorHead):\n    \"\"\"\"RetinaHead with separate BN.\n\n    In RetinaHead, conv/norm layers are shared across different FPN levels,\n    while in RetinaSepBNHead, conv layers are shared across different FPN\n    levels, but BN layers are separated.\n    \"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 num_ins,\n                 in_channels,\n                 stacked_convs=4,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 **kwargs):\n        self.stacked_convs = stacked_convs\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.num_ins = num_ins\n        super(RetinaSepBNHead, self).__init__(num_classes, in_channels,\n                                              **kwargs)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.relu = nn.ReLU(inplace=True)\n        self.cls_convs = nn.ModuleList()\n        self.reg_convs = nn.ModuleList()\n        for i in range(self.num_ins):\n            cls_convs = nn.ModuleList()\n            reg_convs = nn.ModuleList()\n            for i in range(self.stacked_convs):\n                chn = self.in_channels if i == 0 else self.feat_channels\n                cls_convs.append(\n                    ConvModule(\n                        chn,\n                        self.feat_channels,\n                        3,\n                        stride=1,\n                        padding=1,\n                        conv_cfg=self.conv_cfg,\n                        norm_cfg=self.norm_cfg))\n                reg_convs.append(\n                    ConvModule(\n                        chn,\n                        self.feat_channels,\n                        3,\n                        stride=1,\n                        padding=1,\n                        conv_cfg=self.conv_cfg,\n                        norm_cfg=self.norm_cfg))\n            self.cls_convs.append(cls_convs)\n            self.reg_convs.append(reg_convs)\n        for i in range(self.stacked_convs):\n            for j in range(1, self.num_ins):\n                self.cls_convs[j][i].conv = self.cls_convs[0][i].conv\n                self.reg_convs[j][i].conv = self.reg_convs[0][i].conv\n        self.retina_cls = nn.Conv2d(\n            self.feat_channels,\n            self.num_anchors * self.cls_out_channels,\n            3,\n            padding=1)\n        self.retina_reg = nn.Conv2d(\n            self.feat_channels, self.num_anchors * 4, 3, padding=1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.cls_convs[0]:\n            normal_init(m.conv, std=0.01)\n        for m in self.reg_convs[0]:\n            normal_init(m.conv, std=0.01)\n        bias_cls = bias_init_with_prob(0.01)\n        normal_init(self.retina_cls, std=0.01, bias=bias_cls)\n        normal_init(self.retina_reg, std=0.01)\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple: Usually a tuple of classification scores and bbox prediction\n                cls_scores (list[Tensor]): Classification scores for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * num_classes.\n                bbox_preds (list[Tensor]): Box energies / deltas for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * 4.\n        \"\"\"\n        cls_scores = []\n        bbox_preds = []\n        for i, x in enumerate(feats):\n            cls_feat = feats[i]\n            reg_feat = feats[i]\n            for cls_conv in self.cls_convs[i]:\n                cls_feat = cls_conv(cls_feat)\n            for reg_conv in self.reg_convs[i]:\n                reg_feat = reg_conv(reg_feat)\n            cls_score = self.retina_cls(cls_feat)\n            bbox_pred = self.retina_reg(reg_feat)\n            cls_scores.append(cls_score)\n            bbox_preds.append(bbox_pred)\n        return cls_scores, bbox_preds\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/rpn_head.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import normal_init\n\nfrom mmdet.ops import batched_nms\nfrom ..builder import HEADS\nfrom .anchor_head import AnchorHead\nfrom .rpn_test_mixin import RPNTestMixin\n\n\n@HEADS.register_module()\nclass RPNHead(RPNTestMixin, AnchorHead):\n    \"\"\"RPN head.\n\n    Args:\n        in_channels (int): Number of channels in the input feature map.\n    \"\"\"  # noqa: W605\n\n    def __init__(self, in_channels, **kwargs):\n        super(RPNHead, self).__init__(\n            1, in_channels, background_label=0, **kwargs)\n\n    def _init_layers(self):\n        \"\"\"Initialize layers of the head.\"\"\"\n        self.rpn_conv = nn.Conv2d(\n            self.in_channels, self.feat_channels, 3, padding=1)\n        self.rpn_cls = nn.Conv2d(self.feat_channels,\n                                 self.num_anchors * self.cls_out_channels, 1)\n        self.rpn_reg = nn.Conv2d(self.feat_channels, self.num_anchors * 4, 1)\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        normal_init(self.rpn_conv, std=0.01)\n        normal_init(self.rpn_cls, std=0.01)\n        normal_init(self.rpn_reg, std=0.01)\n\n    def forward_single(self, x):\n        \"\"\"Forward feature map of a single scale level.\"\"\"\n        x = self.rpn_conv(x)\n        x = F.relu(x, inplace=True)\n        rpn_cls_score = self.rpn_cls(x)\n        rpn_bbox_pred = self.rpn_reg(x)\n        return rpn_cls_score, rpn_bbox_pred\n\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        losses = super(RPNHead, self).loss(\n            cls_scores,\n            bbox_preds,\n            gt_bboxes,\n            None,\n            img_metas,\n            gt_bboxes_ignore=gt_bboxes_ignore)\n        return dict(\n            loss_rpn_cls=losses['loss_cls'], loss_rpn_bbox=losses['loss_bbox'])\n\n    def _get_bboxes_single(self,\n                           cls_scores,\n                           bbox_preds,\n                           mlvl_anchors,\n                           img_shape,\n                           scale_factor,\n                           cfg,\n                           rescale=False):\n        \"\"\"Transform outputs for a single batch item into bbox predictions.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (num_anchors * num_classes, H, W).\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (num_anchors * 4, H, W).\n            mlvl_anchors (list[Tensor]): Box reference for each scale level\n                with shape (num_total_anchors, 4).\n            img_shape (tuple[int]): Shape of the input image,\n                (height, width, 3).\n            scale_factor (ndarray): Scale factor of the image arange as\n                (w_scale, h_scale, w_scale, h_scale).\n            cfg (mmcv.Config): Test / postprocessing configuration,\n                if None, test_cfg would be used.\n            rescale (bool): If True, return boxes in original image space.\n\n        Returns:\n            Tensor: Labeled boxes in shape (n, 5), where the first 4 columns\n                are bounding box positions (tl_x, tl_y, br_x, br_y) and the\n                5-th column is a score between 0 and 1.\n        \"\"\"\n        cfg = self.test_cfg if cfg is None else cfg\n        # bboxes from different level should be independent during NMS,\n        # level_ids are used as labels for batched NMS to separate them\n        level_ids = []\n        mlvl_scores = []\n        mlvl_bbox_preds = []\n        mlvl_valid_anchors = []\n        for idx in range(len(cls_scores)):\n            rpn_cls_score = cls_scores[idx]\n            rpn_bbox_pred = bbox_preds[idx]\n            assert rpn_cls_score.size()[-2:] == rpn_bbox_pred.size()[-2:]\n            rpn_cls_score = rpn_cls_score.permute(1, 2, 0)\n            if self.use_sigmoid_cls:\n                rpn_cls_score = rpn_cls_score.reshape(-1)\n                scores = rpn_cls_score.sigmoid()\n            else:\n                rpn_cls_score = rpn_cls_score.reshape(-1, 2)\n                # we set FG labels to [0, num_class-1] and BG label to\n                # num_class in other heads since mmdet v2.0, However we\n                # keep BG label as 0 and FG label as 1 in rpn head\n                scores = rpn_cls_score.softmax(dim=1)[:, 1]\n            rpn_bbox_pred = rpn_bbox_pred.permute(1, 2, 0).reshape(-1, 4)\n            anchors = mlvl_anchors[idx]\n            if cfg.nms_pre > 0 and scores.shape[0] > cfg.nms_pre:\n                # sort is faster than topk\n                # _, topk_inds = scores.topk(cfg.nms_pre)\n                ranked_scores, rank_inds = scores.sort(descending=True)\n                topk_inds = rank_inds[:cfg.nms_pre]\n                scores = ranked_scores[:cfg.nms_pre]\n                rpn_bbox_pred = rpn_bbox_pred[topk_inds, :]\n                anchors = anchors[topk_inds, :]\n            mlvl_scores.append(scores)\n            mlvl_bbox_preds.append(rpn_bbox_pred)\n            mlvl_valid_anchors.append(anchors)\n            level_ids.append(\n                scores.new_full((scores.size(0), ), idx, dtype=torch.long))\n\n        scores = torch.cat(mlvl_scores)\n        anchors = torch.cat(mlvl_valid_anchors)\n        rpn_bbox_pred = torch.cat(mlvl_bbox_preds)\n        proposals = self.bbox_coder.decode(\n            anchors, rpn_bbox_pred, max_shape=img_shape)\n        ids = torch.cat(level_ids)\n\n        if cfg.min_bbox_size > 0:\n            w = proposals[:, 2] - proposals[:, 0]\n            h = proposals[:, 3] - proposals[:, 1]\n            valid_inds = torch.nonzero(\n                (w >= cfg.min_bbox_size)\n                & (h >= cfg.min_bbox_size),\n                as_tuple=False).squeeze()\n            if valid_inds.sum().item() != len(proposals):\n                proposals = proposals[valid_inds, :]\n                scores = scores[valid_inds]\n                ids = ids[valid_inds]\n\n        # TODO: remove the hard coded nms type\n        nms_cfg = dict(type='nms', iou_thr=cfg.nms_thr)\n        dets, keep = batched_nms(proposals, scores, ids, nms_cfg)\n        return dets[:cfg.nms_post]\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/rpn_test_mixin.py",
    "content": "import sys\n\nfrom mmdet.core import merge_aug_proposals\n\nif sys.version_info >= (3, 7):\n    from mmdet.utils.contextmanagers import completed\n\n\nclass RPNTestMixin(object):\n    \"\"\"Test methods of RPN.\"\"\"\n\n    if sys.version_info >= (3, 7):\n\n        async def async_simple_test_rpn(self, x, img_metas):\n            sleep_interval = self.rpn_head.test_cfg.pop(\n                'async_sleep_interval', 0.025)\n            async with completed(\n                    __name__, 'rpn_head_forward',\n                    sleep_interval=sleep_interval):\n                rpn_outs = self(x)\n\n            proposal_list = self.get_bboxes(*rpn_outs, img_metas)\n            return proposal_list\n\n    def simple_test_rpn(self, x, img_metas):\n        \"\"\"Test without augmentation.\n\n        Args:\n            x (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n            img_metas (list[dict]): Meta info of each image.\n\n        Returns:\n            list[Tensor]: Proposals of each image.\n        \"\"\"\n        rpn_outs = self(x)\n        proposal_list = self.get_bboxes(*rpn_outs, img_metas)\n        return proposal_list\n\n    def aug_test_rpn(self, feats, img_metas):\n        samples_per_gpu = len(img_metas[0])\n        aug_proposals = [[] for _ in range(samples_per_gpu)]\n        for x, img_meta in zip(feats, img_metas):\n            proposal_list = self.simple_test_rpn(x, img_meta)\n            for i, proposals in enumerate(proposal_list):\n                aug_proposals[i].append(proposals)\n        # reorganize the order of 'img_metas' to match the dimensions\n        # of 'aug_proposals'\n        aug_img_metas = []\n        for i in range(samples_per_gpu):\n            aug_img_meta = []\n            for j in range(len(img_metas)):\n                aug_img_meta.append(img_metas[j][i])\n            aug_img_metas.append(aug_img_meta)\n        # after merging, proposals will be rescaled to the original image size\n        merged_proposals = [\n            merge_aug_proposals(proposals, aug_img_meta, self.test_cfg)\n            for proposals, aug_img_meta in zip(aug_proposals, aug_img_metas)\n        ]\n        return merged_proposals\n"
  },
  {
    "path": "code/mmdet/models/dense_heads/ssd_head.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import xavier_init\n\nfrom mmdet.core import (build_anchor_generator, build_assigner,\n                        build_bbox_coder, build_sampler, multi_apply)\nfrom ..builder import HEADS\nfrom ..losses import smooth_l1_loss\nfrom .anchor_head import AnchorHead\n\n\n# TODO: add loss evaluator for SSD\n@HEADS.register_module()\nclass SSDHead(AnchorHead):\n    \"\"\"SSD head used in https://arxiv.org/abs/1512.02325.\n\n    Args:\n        num_classes (int): Number of categories excluding the background\n            category.\n        in_channels (int): Number of channels in the input feature map.\n        anchor_generator (dict): Config dict for anchor generator\n        background_label (int | None): Label ID of background, set as 0 for\n            RPN and num_classes for other heads. It will automatically set as\n            num_classes if None is given.\n        bbox_coder (dict): Config of bounding box coder.\n        reg_decoded_bbox (bool): If true, the regression loss would be\n            applied on decoded bounding boxes. Default: False\n        train_cfg (dict): Training config of anchor head.\n        test_cfg (dict): Testing config of anchor head.\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_classes=80,\n                 in_channels=(512, 1024, 512, 256, 256, 256),\n                 anchor_generator=dict(\n                     type='SSDAnchorGenerator',\n                     scale_major=False,\n                     input_size=300,\n                     strides=[8, 16, 32, 64, 100, 300],\n                     ratios=([2], [2, 3], [2, 3], [2, 3], [2], [2]),\n                     basesize_ratio_range=(0.1, 0.9)),\n                 background_label=None,\n                 bbox_coder=dict(\n                     type='DeltaXYWHBBoxCoder',\n                     target_means=[.0, .0, .0, .0],\n                     target_stds=[1.0, 1.0, 1.0, 1.0],\n                 ),\n                 reg_decoded_bbox=False,\n                 train_cfg=None,\n                 test_cfg=None):\n        super(AnchorHead, self).__init__()\n        self.num_classes = num_classes\n        self.in_channels = in_channels\n        self.cls_out_channels = num_classes + 1  # add background class\n        self.anchor_generator = build_anchor_generator(anchor_generator)\n        num_anchors = self.anchor_generator.num_base_anchors\n\n        reg_convs = []\n        cls_convs = []\n        for i in range(len(in_channels)):\n            reg_convs.append(\n                nn.Conv2d(\n                    in_channels[i],\n                    num_anchors[i] * 4,\n                    kernel_size=3,\n                    padding=1))\n            cls_convs.append(\n                nn.Conv2d(\n                    in_channels[i],\n                    num_anchors[i] * (num_classes + 1),\n                    kernel_size=3,\n                    padding=1))\n        self.reg_convs = nn.ModuleList(reg_convs)\n        self.cls_convs = nn.ModuleList(cls_convs)\n\n        self.background_label = (\n            num_classes if background_label is None else background_label)\n        # background_label should be either 0 or num_classes\n        assert (self.background_label == 0\n                or self.background_label == num_classes)\n\n        self.bbox_coder = build_bbox_coder(bbox_coder)\n        self.reg_decoded_bbox = reg_decoded_bbox\n        self.use_sigmoid_cls = False\n        self.cls_focal_loss = False\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n        # set sampling=False for archor_target\n        self.sampling = False\n        if self.train_cfg:\n            self.assigner = build_assigner(self.train_cfg.assigner)\n            # SSD sampling=False so use PseudoSampler\n            sampler_cfg = dict(type='PseudoSampler')\n            self.sampler = build_sampler(sampler_cfg, context=self)\n        self.fp16_enabled = False\n\n    def init_weights(self):\n        \"\"\"Initialize weights of the head.\"\"\"\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                xavier_init(m, distribution='uniform', bias=0)\n\n    def forward(self, feats):\n        \"\"\"Forward features from the upstream network.\n\n        Args:\n            feats (tuple[Tensor]): Features from the upstream network, each is\n                a 4D-tensor.\n\n        Returns:\n            tuple:\n                cls_scores (list[Tensor]): Classification scores for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * num_classes.\n                bbox_preds (list[Tensor]): Box energies / deltas for all scale\n                    levels, each is a 4D-tensor, the channels number is\n                    num_anchors * 4.\n        \"\"\"\n        cls_scores = []\n        bbox_preds = []\n        for feat, reg_conv, cls_conv in zip(feats, self.reg_convs,\n                                            self.cls_convs):\n            cls_scores.append(cls_conv(feat))\n            bbox_preds.append(reg_conv(feat))\n        return cls_scores, bbox_preds\n\n    def loss_single(self, cls_score, bbox_pred, anchor, labels, label_weights,\n                    bbox_targets, bbox_weights, num_total_samples):\n        \"\"\"Compute loss of a single image.\n\n        Args:\n            cls_score (Tensor): Box scores for eachimage\n                Has shape (num_total_anchors, num_classes).\n            bbox_pred (Tensor): Box energies / deltas for each image\n                level with shape (num_total_anchors, 4).\n            anchors (Tensor): Box reference for each scale level with shape\n                (num_total_anchors, 4).\n            labels (Tensor): Labels of each anchors with shape\n                (num_total_anchors,).\n            label_weights (Tensor): Label weights of each anchor with shape\n                (num_total_anchors,)\n            bbox_targets (Tensor): BBox regression targets of each anchor wight\n                shape (num_total_anchors, 4).\n            bbox_weights (Tensor): BBox regression loss weights of each anchor\n                with shape (num_total_anchors, 4).\n            num_total_samples (int): If sampling, num total samples equal to\n                the number of total anchors; Otherwise, it is the number of\n                positive anchors.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n\n        loss_cls_all = F.cross_entropy(\n            cls_score, labels, reduction='none') * label_weights\n        # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n        pos_inds = ((labels >= 0) &\n                    (labels < self.background_label)).nonzero().reshape(-1)\n        neg_inds = (labels == self.background_label).nonzero().view(-1)\n\n        num_pos_samples = pos_inds.size(0)\n        num_neg_samples = self.train_cfg.neg_pos_ratio * num_pos_samples\n        if num_neg_samples > neg_inds.size(0):\n            num_neg_samples = neg_inds.size(0)\n        topk_loss_cls_neg, _ = loss_cls_all[neg_inds].topk(num_neg_samples)\n        loss_cls_pos = loss_cls_all[pos_inds].sum()\n        loss_cls_neg = topk_loss_cls_neg.sum()\n        loss_cls = (loss_cls_pos + loss_cls_neg) / num_total_samples\n\n        if self.reg_decoded_bbox:\n            bbox_pred = self.bbox_coder.decode(anchor, bbox_pred)\n\n        loss_bbox = smooth_l1_loss(\n            bbox_pred,\n            bbox_targets,\n            bbox_weights,\n            beta=self.train_cfg.smoothl1_beta,\n            avg_factor=num_total_samples)\n        return loss_cls[None], loss_bbox\n\n    def loss(self,\n             cls_scores,\n             bbox_preds,\n             gt_bboxes,\n             gt_labels,\n             img_metas,\n             gt_bboxes_ignore=None):\n        \"\"\"Compute losses of the head.\n\n        Args:\n            cls_scores (list[Tensor]): Box scores for each scale level\n                Has shape (N, num_anchors * num_classes, H, W)\n            bbox_preds (list[Tensor]): Box energies / deltas for each scale\n                level with shape (N, num_anchors * 4, H, W)\n            gt_bboxes (list[Tensor]): each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            img_metas (list[dict]): Meta information of each image, e.g.,\n                image size, scaling factor, etc.\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]\n        assert len(featmap_sizes) == self.anchor_generator.num_levels\n\n        device = cls_scores[0].device\n\n        anchor_list, valid_flag_list = self.get_anchors(\n            featmap_sizes, img_metas, device=device)\n        cls_reg_targets = self.get_targets(\n            anchor_list,\n            valid_flag_list,\n            gt_bboxes,\n            img_metas,\n            gt_bboxes_ignore_list=gt_bboxes_ignore,\n            gt_labels_list=gt_labels,\n            label_channels=1,\n            unmap_outputs=False)\n        if cls_reg_targets is None:\n            return None\n        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,\n         num_total_pos, num_total_neg) = cls_reg_targets\n\n        num_images = len(img_metas)\n        all_cls_scores = torch.cat([\n            s.permute(0, 2, 3, 1).reshape(\n                num_images, -1, self.cls_out_channels) for s in cls_scores\n        ], 1)\n        all_labels = torch.cat(labels_list, -1).view(num_images, -1)\n        all_label_weights = torch.cat(label_weights_list,\n                                      -1).view(num_images, -1)\n        all_bbox_preds = torch.cat([\n            b.permute(0, 2, 3, 1).reshape(num_images, -1, 4)\n            for b in bbox_preds\n        ], -2)\n        all_bbox_targets = torch.cat(bbox_targets_list,\n                                     -2).view(num_images, -1, 4)\n        all_bbox_weights = torch.cat(bbox_weights_list,\n                                     -2).view(num_images, -1, 4)\n\n        # concat all level anchors to a single tensor\n        all_anchors = []\n        for i in range(num_images):\n            all_anchors.append(torch.cat(anchor_list[i]))\n\n        # check NaN and Inf\n        assert torch.isfinite(all_cls_scores).all().item(), \\\n            'classification scores become infinite or NaN!'\n        assert torch.isfinite(all_bbox_preds).all().item(), \\\n            'bbox predications become infinite or NaN!'\n\n        losses_cls, losses_bbox = multi_apply(\n            self.loss_single,\n            all_cls_scores,\n            all_bbox_preds,\n            all_anchors,\n            all_labels,\n            all_label_weights,\n            all_bbox_targets,\n            all_bbox_weights,\n            num_total_samples=num_total_pos)\n        return dict(loss_cls=losses_cls, loss_bbox=losses_bbox)\n"
  },
  {
    "path": "code/mmdet/models/detectors/__init__.py",
    "content": "from .atss import ATSS\nfrom .base import BaseDetector\nfrom .cascade_rcnn import CascadeRCNN\nfrom .dense_reppoints_detector import DenseRepPointsDetector\nfrom .dense_reppoints_v2_detector import DenseRepPointsV2Detector\nfrom .fast_rcnn import FastRCNN\nfrom .faster_rcnn import FasterRCNN\nfrom .fcos import FCOS\nfrom .fovea import FOVEA\nfrom .fsaf import FSAF\nfrom .gfl import GFL\nfrom .grid_rcnn import GridRCNN\nfrom .htc import HybridTaskCascade\nfrom .mask_rcnn import MaskRCNN\nfrom .mask_scoring_rcnn import MaskScoringRCNN\nfrom .nasfcos import NASFCOS\nfrom .point_rend import PointRend\nfrom .reppoints_detector import RepPointsDetector\nfrom .reppoints_v2_detector import RepPointsV2Detector\nfrom .retinanet import RetinaNet\nfrom .lsnet import LSDetector\nfrom .lscpvnet import LSCPVDetector\nfrom .rpn import RPN\nfrom .single_stage import SingleStageDetector\nfrom .two_stage import TwoStageDetector\n\n__all__ = [\n    'ATSS', 'BaseDetector', 'SingleStageDetector', 'TwoStageDetector', 'RPN',\n    'FastRCNN', 'FasterRCNN', 'MaskRCNN', 'CascadeRCNN', 'HybridTaskCascade',\n    'RetinaNet', 'FCOS', 'GridRCNN', 'MaskScoringRCNN', 'RepPointsDetector',\n    'FOVEA', 'FSAF', 'NASFCOS', 'PointRend', 'GFL', 'RepPointsV2Detector',\n    'DenseRepPointsDetector', 'DenseRepPointsV2Detector', 'LSDetector',\n    'LSCPVDetector'\n]\n"
  },
  {
    "path": "code/mmdet/models/detectors/atss.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass ATSS(SingleStageDetector):\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(ATSS, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                   test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/base.py",
    "content": "import warnings\nfrom abc import ABCMeta, abstractmethod\nfrom collections import OrderedDict\n\nimport mmcv\nimport numpy as np\nimport torch\nimport torch.distributed as dist\nimport torch.nn as nn\nfrom mmcv.utils import print_log\n\nfrom mmdet.core import auto_fp16\nfrom mmdet.utils import get_root_logger\n\n\nclass BaseDetector(nn.Module, metaclass=ABCMeta):\n    \"\"\"Base class for detectors\"\"\"\n\n    def __init__(self):\n        super(BaseDetector, self).__init__()\n        self.fp16_enabled = False\n\n    @property\n    def with_neck(self):\n        \"\"\"bool: whether the detector has a neck\"\"\"\n        return hasattr(self, 'neck') and self.neck is not None\n\n    # TODO: these properties need to be carefully handled\n    # for both single stage & two stage detectors\n    @property\n    def with_shared_head(self):\n        \"\"\"bool: whether the detector has a shared head in the RoI Head\"\"\"\n        return hasattr(self.roi_head,\n                       'shared_head') and self.roi_head.shared_head is not None\n\n    @property\n    def with_bbox(self):\n        \"\"\"bool: whether the detector has a bbox head\"\"\"\n        return ((hasattr(self.roi_head, 'bbox_head')\n                 and self.roi_head.bbox_head is not None)\n                or (hasattr(self, 'bbox_head') and self.bbox_head is not None))\n\n    @property\n    def with_mask(self):\n        \"\"\"bool: whether the detector has a mask head\"\"\"\n        return ((hasattr(self.roi_head, 'mask_head')\n                 and self.roi_head.mask_head is not None)\n                or (hasattr(self, 'mask_head') and self.mask_head is not None))\n\n    @abstractmethod\n    def extract_feat(self, imgs):\n        \"\"\"Extract features from images\"\"\"\n        pass\n\n    def extract_feats(self, imgs):\n        \"\"\"Extract features from multiple images\n\n        Args:\n            imgs (list[torch.Tensor]): A list of images. The images are\n                augmented from the same image but in different ways.\n\n        Returns:\n            list[torch.Tensor]: Features of different images\n        \"\"\"\n        assert isinstance(imgs, list)\n        return [self.extract_feat(img) for img in imgs]\n\n    @abstractmethod\n    def forward_train(self, imgs, img_metas, **kwargs):\n        \"\"\"\n        Args:\n            img (list[Tensor]): List of tensors of shape (1, C, H, W).\n                Typically these should be mean centered and std scaled.\n            img_metas (list[dict]): List of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and my also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys, see\n                :class:`mmdet.datasets.pipelines.Collect`.\n            kwargs (keyword arguments): Specific to concrete implementation.\n        \"\"\"\n        pass\n\n    async def async_simple_test(self, img, img_metas, **kwargs):\n        raise NotImplementedError\n\n    @abstractmethod\n    def simple_test(self, img, img_metas, **kwargs):\n        pass\n\n    @abstractmethod\n    def aug_test(self, imgs, img_metas, **kwargs):\n        \"\"\"Test function with test time augmentation\"\"\"\n        pass\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in detector\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if pretrained is not None:\n            logger = get_root_logger()\n            print_log(f'load model from: {pretrained}', logger=logger)\n\n    async def aforward_test(self, *, img, img_metas, **kwargs):\n        for var, name in [(img, 'img'), (img_metas, 'img_metas')]:\n            if not isinstance(var, list):\n                raise TypeError(f'{name} must be a list, but got {type(var)}')\n\n        num_augs = len(img)\n        if num_augs != len(img_metas):\n            raise ValueError(f'num of augmentations ({len(img)}) '\n                             f'!= num of image metas ({len(img_metas)})')\n        # TODO: remove the restriction of samples_per_gpu == 1 when prepared\n        samples_per_gpu = img[0].size(0)\n        assert samples_per_gpu == 1\n\n        if num_augs == 1:\n            return await self.async_simple_test(img[0], img_metas[0], **kwargs)\n        else:\n            raise NotImplementedError\n\n    def forward_test(self, imgs, img_metas, **kwargs):\n        \"\"\"\n        Args:\n            imgs (List[Tensor]): the outer list indicates test-time\n                augmentations and inner Tensor should have a shape NxCxHxW,\n                which contains all images in the batch.\n            img_metas (List[List[dict]]): the outer list indicates test-time\n                augs (multiscale, flip, etc.) and the inner list indicates\n                images in a batch.\n        \"\"\"\n        for var, name in [(imgs, 'imgs'), (img_metas, 'img_metas')]:\n            if not isinstance(var, list):\n                raise TypeError(f'{name} must be a list, but got {type(var)}')\n\n        num_augs = len(imgs)\n        if num_augs != len(img_metas):\n            raise ValueError(f'num of augmentations ({len(imgs)}) '\n                             f'!= num of image meta ({len(img_metas)})')\n        # TODO: remove the restriction of samples_per_gpu == 1 when prepared\n        samples_per_gpu = imgs[0].size(0)\n        assert samples_per_gpu == 1\n\n        if num_augs == 1:\n            \"\"\"\n            proposals (List[List[Tensor]]): the outer list indicates test-time\n                augs (multiscale, flip, etc.) and the inner list indicates\n                images in a batch. The Tensor should have a shape Px4, where\n                P is the number of proposals.\n            \"\"\"\n            if 'proposals' in kwargs:\n                kwargs['proposals'] = kwargs['proposals'][0]\n            return self.simple_test(imgs[0], img_metas[0], **kwargs)\n        else:\n            # TODO: support test augmentation for predefined proposals\n            assert 'proposals' not in kwargs\n            return self.aug_test(imgs, img_metas, **kwargs)\n\n    @auto_fp16(apply_to=('img', ))\n    def forward(self, img, img_metas, return_loss=True, **kwargs):\n        \"\"\"\n        Calls either forward_train or forward_test depending on whether\n        return_loss=True. Note this setting will change the expected inputs.\n        When `return_loss=True`, img and img_meta are single-nested (i.e.\n        Tensor and List[dict]), and when `resturn_loss=False`, img and img_meta\n        should be double nested (i.e.  List[Tensor], List[List[dict]]), with\n        the outer list indicating test time augmentations.\n        \"\"\"\n        if return_loss:\n            return self.forward_train(img, img_metas, **kwargs)\n        else:\n            return self.forward_test(img, img_metas, **kwargs)\n\n    def _parse_losses(self, losses):\n        \"\"\"Parse the raw outputs (losses) of the network.\n\n        Args:\n            losses (dict): Raw output of the network, which usually contain\n                losses and other necessary infomation.\n\n        Returns:\n            tuple[Tensor, dict]: (loss, log_vars), loss is the loss tensor\n                which may be a weighted sum of all losses, log_vars contains\n                all the variables to be sent to the logger.\n        \"\"\"\n        log_vars = OrderedDict()\n        for loss_name, loss_value in losses.items():\n            if isinstance(loss_value, torch.Tensor):\n                log_vars[loss_name] = loss_value.mean()\n            elif isinstance(loss_value, list):\n                log_vars[loss_name] = sum(_loss.mean() for _loss in loss_value)\n            else:\n                raise TypeError(\n                    f'{loss_name} is not a tensor or list of tensors')\n\n        loss = sum(_value for _key, _value in log_vars.items()\n                   if 'loss' in _key)\n\n        log_vars['loss'] = loss\n        for loss_name, loss_value in log_vars.items():\n            # reduce loss when distributed training\n            if dist.is_available() and dist.is_initialized():\n                loss_value = loss_value.data.clone()\n                dist.all_reduce(loss_value.div_(dist.get_world_size()))\n            log_vars[loss_name] = loss_value.item()\n\n        return loss, log_vars\n\n    def train_step(self, data, optimizer):\n        \"\"\"The iteration step during training.\n\n        This method defines an iteration step during training, except for the\n        back propagation and optimizer updating, which are done in an optimizer\n        hook. Note that in some complicated cases or models, the whole process\n        including back propagation and optimizer updating is also defined in\n        this method, such as GAN.\n\n        Args:\n            data (dict): The output of dataloader.\n            optimizer (:obj:`torch.optim.Optimizer` | dict): The optimizer of\n                runner is passed to ``train_step()``. This argument is unused\n                and reserved.\n\n        Returns:\n            dict: It should contain at least 3 keys: ``loss``, ``log_vars``,\n                ``num_samples``.\n                ``loss`` is a tensor for back propagation, which can be a\n                weighted sum of multiple losses.\n                ``log_vars`` contains all the variables to be sent to the\n                logger.\n                ``num_samples`` indicates the batch size (when the model is\n                DDP, it means the batch size on each GPU), which is used for\n                averaging the logs.\n        \"\"\"\n        losses = self(**data)\n        loss, log_vars = self._parse_losses(losses)\n\n        outputs = dict(\n            loss=loss, log_vars=log_vars, num_samples=len(data['img_metas']))\n\n        return outputs\n\n    def val_step(self, data, optimizer):\n        \"\"\"The iteration step during validation.\n\n        This method shares the same signature as :func:`train_step`, but used\n        during val epochs. Note that the evaluation after training epochs is\n        not implemented with this method, but an evaluation hook.\n        \"\"\"\n        losses = self(**data)\n        loss, log_vars = self._parse_losses(losses)\n\n        outputs = dict(\n            loss=loss, log_vars=log_vars, num_samples=len(data['img_metas']))\n\n        return outputs\n\n    def show_result(self,\n                    img,\n                    result,\n                    score_thr=0.3,\n                    bbox_color='green',\n                    text_color='green',\n                    thickness=1,\n                    font_scale=0.5,\n                    win_name='',\n                    show=False,\n                    wait_time=0,\n                    out_file=None):\n        \"\"\"Draw `result` over `img`.\n\n        Args:\n            img (str or Tensor): The image to be displayed.\n            result (Tensor or tuple): The results to draw over `img`\n                bbox_result or (bbox_result, segm_result).\n            score_thr (float, optional): Minimum score of bboxes to be shown.\n                Default: 0.3.\n            bbox_color (str or tuple or :obj:`Color`): Color of bbox lines.\n            text_color (str or tuple or :obj:`Color`): Color of texts.\n            thickness (int): Thickness of lines.\n            font_scale (float): Font scales of texts.\n            win_name (str): The window name.\n            wait_time (int): Value of waitKey param.\n                Default: 0.\n            show (bool): Whether to show the image.\n                Default: False.\n            out_file (str or None): The filename to write the image.\n                Default: None.\n\n        Returns:\n            img (Tensor): Only if not `show` or `out_file`\n        \"\"\"\n        img = mmcv.imread(img)\n        img = img.copy()\n        if isinstance(result, tuple):\n            bbox_result, segm_result = result\n            if isinstance(segm_result, tuple):\n                segm_result = segm_result[0]  # ms rcnn\n        else:\n            bbox_result, segm_result = result, None\n        bboxes = np.vstack(bbox_result)\n        labels = [\n            np.full(bbox.shape[0], i, dtype=np.int32)\n            for i, bbox in enumerate(bbox_result)\n        ]\n        labels = np.concatenate(labels)\n        # draw segmentation masks\n        if segm_result is not None and len(labels) > 0:  # non empty\n            segms = mmcv.concat_list(segm_result)\n            inds = np.where(bboxes[:, -1] > score_thr)[0]\n            np.random.seed(42)\n            color_masks = [\n                np.random.randint(0, 256, (1, 3), dtype=np.uint8)\n                for _ in range(max(labels) + 1)\n            ]\n            for i in inds:\n                i = int(i)\n                color_mask = color_masks[labels[i]]\n                mask = segms[i]\n                img[mask] = img[mask] * 0.5 + color_mask * 0.5\n        # if out_file specified, do not show image in window\n        if out_file is not None:\n            show = False\n        # draw bounding boxes\n        mmcv.imshow_det_bboxes(\n            img,\n            bboxes,\n            labels,\n            class_names=self.CLASSES,\n            score_thr=score_thr,\n            bbox_color=bbox_color,\n            text_color=text_color,\n            thickness=thickness,\n            font_scale=font_scale,\n            win_name=win_name,\n            show=show,\n            wait_time=wait_time,\n            out_file=out_file)\n\n        if not (show or out_file):\n            warnings.warn('show==False and out_file is not specified, only '\n                          'result image will be returned')\n            return img\n"
  },
  {
    "path": "code/mmdet/models/detectors/cascade_rcnn.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass CascadeRCNN(TwoStageDetector):\n    \"\"\"Implementation of `Cascade R-CNN and Cascade Mask R-CNN\n    <https://arxiv.org/abs/1906.09756>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck=None,\n                 rpn_head=None,\n                 roi_head=None,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(CascadeRCNN, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            rpn_head=rpn_head,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n\n    def show_result(self, data, result, **kwargs):\n        \"\"\"Show prediction results of the detector\"\"\"\n        if self.with_mask:\n            ms_bbox_result, ms_segm_result = result\n            if isinstance(ms_bbox_result, dict):\n                result = (ms_bbox_result['ensemble'],\n                          ms_segm_result['ensemble'])\n        else:\n            if isinstance(result, dict):\n                result = result['ensemble']\n        return super(CascadeRCNN, self).show_result(data, result, **kwargs)\n"
  },
  {
    "path": "code/mmdet/models/detectors/dense_reppoints_detector.py",
    "content": "import mmcv\nimport numpy as np\nimport scipy.interpolate\nimport torch\n\nfrom mmdet.core import bbox2result\nfrom .single_stage import SingleStageDetector\nfrom ..builder import DETECTORS\n\n\n@DETECTORS.register_module()\nclass DenseRepPointsDetector(SingleStageDetector):\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(DenseRepPointsDetector, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                                     test_cfg, pretrained)\n    @property\n    def with_mask(self):\n        return True\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x, test=False)\n        loss_inputs = outs + (gt_bboxes, gt_masks, gt_labels, img_metas)\n        losses = self.bbox_head.loss(\n            *loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)\n        return losses\n\n    def simple_test(self, img, img_meta, rescale=False):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x, test=True)\n        bbox_inputs = outs + (img_meta, self.test_cfg, rescale)\n        bbox_list = self.bbox_head.get_bboxes(*bbox_inputs)\n\n        det_bboxes, det_points, det_pts_scores, det_cls = bbox_list[0]\n\n        ori_shape = img_meta[0]['ori_shape']\n        scale_factor = img_meta[0]['scale_factor']\n\n        bbox_results = bbox2result(det_bboxes, det_cls, self.bbox_head.num_classes)\n        rle_results = self.get_seg_masks(det_pts_scores, det_points, det_bboxes, det_cls,\n                                          self.test_cfg, ori_shape, scale_factor, rescale)\n        # For visualization(rescale=False), we also return pts_results to show the points\n        if not rescale:\n            det_points_reshape = det_points.reshape(det_points.shape[0], -1, 2)\n            det_pts_scores_reshape = det_pts_scores.reshape(det_pts_scores.shape[0], -1, 1)\n            det_pts_score_cat = torch.cat([det_points_reshape, det_pts_scores_reshape], dim=-1) \\\n                .reshape(det_points.shape[0], -1)\n            det_pts_score_cls_cat = torch.cat([det_pts_score_cat, det_points[:, [-1]]], dim=-1)\n            pts_results = pts2result(det_pts_score_cls_cat, det_cls, self.bbox_head.num_classes)\n            return (bbox_results, rle_results), pts_results\n        else:\n            return bbox_results, rle_results\n\n    def get_seg_masks(self, pts_score, det_pts, det_bboxes, det_labels,\n                      test_cfg, ori_shape, scale_factor, rescale=False):\n        \"\"\"\n        Get segmentation masks from points and scores\n\n        Args:\n            pts_score (Tensor or ndarray): shape (n, num_pts)\n            det_pts (Tensor): shape (n, num_pts*2)\n            det_bboxes (Tensor): shape (n, 4)\n            det_labels (Tensor): shape (n, 1)\n            test_cfg (dict): rcnn testing config\n            ori_shape: original image size\n            scale_factor: scale factor for image\n            rescale: whether rescale to original size\n        Returns:\n            list[list]: encoded masks\n        \"\"\"\n\n        cls_segms = [[] for _ in range(self.bbox_head.num_classes)]\n        bboxes = det_bboxes.cpu().numpy()[:, :4]\n        labels = det_labels.cpu().numpy()\n\n        if rescale:\n            img_h, img_w = ori_shape[:2]\n        else:\n            img_h = np.round(ori_shape[0] * scale_factor).astype(np.int32)\n            img_w = np.round(ori_shape[1] * scale_factor).astype(np.int32)\n        scale_factor = 1.0\n\n        for i in range(bboxes.shape[0]):\n            bbox = (bboxes[i, :] / scale_factor).astype(np.int32)\n            label = labels[i]\n            w = max(bbox[2] - bbox[0], 1)\n            h = max(bbox[3] - bbox[1], 1)\n\n            im_mask = np.zeros((img_h, img_w), dtype=np.uint8)\n            im_pts = det_pts[i].clone()\n            im_pts = im_pts.reshape(-1, 2)\n            im_pts_score = pts_score[i]\n\n            im_pts[:, 0] = (im_pts[:, 0] - bbox[0])\n            im_pts[:, 1] = (im_pts[:, 1] - bbox[1])\n            _h, _w = h, w\n            corner_pts = im_pts.new_tensor([[0, 0], [_h - 1, 0], [0, _w - 1], [_w - 1, _h - 1]])\n            corner_score = im_pts_score.new_tensor([0, 0, 0, 0])\n            im_pts = torch.cat([im_pts, corner_pts], dim=0).cpu().numpy()\n            im_pts_score = torch.cat([im_pts_score, corner_score], dim=0).cpu().numpy()\n            # im_pts = im_pts.cpu().numpy()\n            # im_pts_score = im_pts_score.cpu().numpy()\n            # im_pts_score = (im_pts_score > 0.5).astype(np.float32)\n            grids = tuple(np.mgrid[0:_w:1, 0:_h:1])\n            bbox_mask = scipy.interpolate.griddata(im_pts, im_pts_score, grids)\n            bbox_mask = bbox_mask.transpose(1, 0)\n            bbox_mask = mmcv.imresize(bbox_mask, (w, h))\n\n            bbox_mask = bbox_mask.astype(np.float32)\n            bbox_mask[np.isnan(bbox_mask)] = 0\n            bbox_mask = (bbox_mask > test_cfg.get('pts_score_thr', 0.5)).astype(np.uint8)\n            im_mask[bbox[1]:bbox[1] + h, bbox[0]:bbox[0] + w] = bbox_mask\n            cls_segms[label].append(im_mask)\n        return cls_segms\n\n\ndef pts2result(pts, labels, num_classes):\n    \"\"\"Convert detection results to a list of numpy arrays.\n\n    Args:\n        bboxes (Tensor): shape (n, pts_num)\n        labels (Tensor): shape (n, )\n        num_classes (int): class number, including background class\n\n    Returns:\n        list(ndarray): bbox results of each class\n    \"\"\"\n    if pts.shape[0] == 0:\n        return [np.zeros((0, pts.shape[1]), dtype=np.float32) for i in range(num_classes)]\n    else:\n        pts = pts.cpu().numpy()\n        labels = labels.cpu().numpy()\n        return [pts[labels == i, :] for i in range(num_classes)]\n"
  },
  {
    "path": "code/mmdet/models/detectors/dense_reppoints_v2_detector.py",
    "content": "import mmcv\nimport numpy as np\nimport scipy.interpolate\nimport torch\n\nfrom mmdet.core import bbox2result\nfrom .single_stage import SingleStageDetector\nfrom ..builder import DETECTORS\n\n\n@DETECTORS.register_module()\nclass DenseRepPointsV2Detector(SingleStageDetector):\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(DenseRepPointsV2Detector, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                                     test_cfg, pretrained)\n    @property\n    def with_mask(self):\n        return True\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None,\n                      gt_sem_map=None,\n                      gt_contours=None):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x, test=False)\n        loss_inputs = outs + (gt_bboxes, gt_masks, gt_sem_map, gt_contours, gt_labels, img_metas)\n        losses = self.bbox_head.loss(\n            *loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)\n        return losses\n\n    def simple_test(self, img, img_meta, rescale=False):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x, test=True)\n        bbox_inputs = outs + (img_meta, self.test_cfg, rescale)\n        bbox_list = self.bbox_head.get_bboxes(*bbox_inputs)\n\n        det_bboxes, det_points, det_points_refine, det_pts_scores, det_pts_scores_refine, det_cls = bbox_list[0]\n\n        ori_shape = img_meta[0]['ori_shape']\n        scale_factor = img_meta[0]['scale_factor']\n\n        bbox_results = bbox2result(det_bboxes, det_cls, self.bbox_head.num_classes)\n        rle_results = self.get_seg_masks(det_pts_scores, det_points, det_bboxes, det_cls, det_pts_scores_refine, det_points_refine,\n                                          self.test_cfg, ori_shape, scale_factor, rescale)\n        # For visualization(rescale=False), we also return pts_results to show the points\n        if not rescale:\n            det_points_reshape = det_points.reshape(det_points.shape[0], -1, 2)\n            det_pts_scores_reshape = det_pts_scores.reshape(det_pts_scores.shape[0], -1, 1)\n            det_pts_score_cat = torch.cat([det_points_reshape, det_pts_scores_reshape], dim=-1) \\\n                .reshape(det_points.shape[0], -1)\n            det_pts_score_cls_cat = torch.cat([det_pts_score_cat, det_points[:, [-1]]], dim=-1)\n            pts_results = pts2result(det_pts_score_cls_cat, det_cls, self.bbox_head.num_classes)\n            return (bbox_results, rle_results), pts_results\n        else:\n            return bbox_results, rle_results\n\n    def get_seg_masks(self, pts_score, det_pts, det_bboxes, det_labels, det_scores_refine, det_pts_refine,\n                      test_cfg, ori_shape, scale_factor, rescale=False):\n        \"\"\"\n        Get segmentation masks from points and scores\n\n        Args:\n            pts_score (Tensor or ndarray): shape (n, num_pts)\n            det_pts (Tensor): shape (n, num_pts*2)\n            det_bboxes (Tensor): shape (n, 4)\n            det_labels (Tensor): shape (n, 1)\n            test_cfg (dict): rcnn testing config\n            ori_shape: original image size\n            scale_factor: scale factor for image\n            rescale: whether rescale to original size\n        Returns:\n            list[list]: encoded masks\n        \"\"\"\n\n        cls_segms = [[] for _ in range(self.bbox_head.num_classes)]\n        bboxes = det_bboxes.cpu().numpy()[:, :4]\n        labels = det_labels.cpu().numpy()\n\n        if rescale:\n            img_h, img_w = ori_shape[:2]\n        else:\n            img_h = np.round(ori_shape[0] * scale_factor).astype(np.int32)\n            img_w = np.round(ori_shape[1] * scale_factor).astype(np.int32)\n        scale_factor = 1.0\n\n        for i in range(bboxes.shape[0]):\n            bbox = (bboxes[i, :] / scale_factor).astype(np.int32)\n            label = labels[i]\n            w = max(bbox[2] - bbox[0], 1)\n            h = max(bbox[3] - bbox[1], 1)\n\n            im_mask = np.zeros((img_h, img_w), dtype=np.uint8)\n            im_pts = det_pts[i].clone()\n            im_pts = im_pts.reshape(-1, 2)\n            im_pts_score = pts_score[i]\n\n            im_pts = im_pts[im_pts_score > 0, :]\n            im_pts_score = im_pts_score[im_pts_score > 0]\n\n            if det_pts_refine is not None:\n                det_pts_refine_valid = det_pts_refine[i].reshape(-1, 2)\n                det_pts_refine_valid = det_pts_refine_valid[det_scores_refine[i] > 0, :]\n                det_scores_refine_valid = det_scores_refine[i][det_scores_refine[i] > 0]\n                im_pts = torch.cat([im_pts, det_pts_refine_valid])\n                im_pts_score = torch.cat([im_pts_score, det_scores_refine_valid])\n\n            im_pts[:, 0] = (im_pts[:, 0] - bbox[0])\n            im_pts[:, 1] = (im_pts[:, 1] - bbox[1])\n            _h, _w = h, w\n            corner_pts = im_pts.new_tensor([[0, 0], [_h - 1, 0], [0, _w - 1], [_w - 1, _h - 1]])\n            corner_score = im_pts_score.new_tensor([0, 0, 0, 0])\n            im_pts = torch.cat([im_pts, corner_pts], dim=0).cpu().numpy()\n            im_pts_score = torch.cat([im_pts_score, corner_score], dim=0).cpu().numpy()\n            # im_pts = im_pts.cpu().numpy()\n            # im_pts_score = im_pts_score.cpu().numpy()\n            # im_pts_score = (im_pts_score > 0.5).astype(np.float32)\n            grids = tuple(np.mgrid[0:_w:1, 0:_h:1])\n            bbox_mask = scipy.interpolate.griddata(im_pts, im_pts_score, grids)\n            bbox_mask = bbox_mask.transpose(1, 0)\n            bbox_mask = mmcv.imresize(bbox_mask, (w, h))\n\n            bbox_mask = bbox_mask.astype(np.float32)\n            bbox_mask[np.isnan(bbox_mask)] = 0\n            bbox_mask = (bbox_mask > test_cfg.get('pts_score_thr', 0.5)).astype(np.uint8)\n            im_mask[bbox[1]:bbox[1] + h, bbox[0]:bbox[0] + w] = bbox_mask\n            cls_segms[label].append(im_mask)\n        return cls_segms\n\n\ndef pts2result(pts, labels, num_classes):\n    \"\"\"Convert detection results to a list of numpy arrays.\n\n    Args:\n        bboxes (Tensor): shape (n, pts_num)\n        labels (Tensor): shape (n, )\n        num_classes (int): class number, including background class\n\n    Returns:\n        list(ndarray): bbox results of each class\n    \"\"\"\n    if pts.shape[0] == 0:\n        return [np.zeros((0, pts.shape[1]), dtype=np.float32) for i in range(num_classes)]\n    else:\n        pts = pts.cpu().numpy()\n        labels = labels.cpu().numpy()\n        return [pts[labels == i, :] for i in range(num_classes)]\n"
  },
  {
    "path": "code/mmdet/models/detectors/fast_rcnn.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass FastRCNN(TwoStageDetector):\n    \"\"\"Implementation of `Fast R-CNN <https://arxiv.org/abs/1504.08083>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 roi_head,\n                 train_cfg,\n                 test_cfg,\n                 neck=None,\n                 pretrained=None):\n        super(FastRCNN, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n\n    def forward_test(self, imgs, img_metas, proposals, **kwargs):\n        \"\"\"\n        Args:\n            imgs (List[Tensor]): the outer list indicates test-time\n                augmentations and inner Tensor should have a shape NxCxHxW,\n                which contains all images in the batch.\n            img_metas (List[List[dict]]): the outer list indicates test-time\n                augs (multiscale, flip, etc.) and the inner list indicates\n                images in a batch.\n            proposals (List[List[Tensor]]): the outer list indicates test-time\n                augs (multiscale, flip, etc.) and the inner list indicates\n                images in a batch. The Tensor should have a shape Px4, where\n                P is the number of proposals.\n        \"\"\"\n        for var, name in [(imgs, 'imgs'), (img_metas, 'img_metas')]:\n            if not isinstance(var, list):\n                raise TypeError(f'{name} must be a list, but got {type(var)}')\n\n        num_augs = len(imgs)\n        if num_augs != len(img_metas):\n            raise ValueError(f'num of augmentations ({len(imgs)}) '\n                             f'!= num of image meta ({len(img_metas)})')\n        # TODO: remove the restriction of samples_per_gpu == 1 when prepared\n        samples_per_gpu = imgs[0].size(0)\n        assert samples_per_gpu == 1\n\n        if num_augs == 1:\n            return self.simple_test(imgs[0], img_metas[0], proposals[0],\n                                    **kwargs)\n        else:\n            # TODO: support test-time augmentation\n            assert NotImplementedError\n"
  },
  {
    "path": "code/mmdet/models/detectors/faster_rcnn.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass FasterRCNN(TwoStageDetector):\n    \"\"\"Implementation of `Faster R-CNN <https://arxiv.org/abs/1506.01497>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 rpn_head,\n                 roi_head,\n                 train_cfg,\n                 test_cfg,\n                 neck=None,\n                 pretrained=None):\n        super(FasterRCNN, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            rpn_head=rpn_head,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/fcos.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass FCOS(SingleStageDetector):\n    \"\"\"Implementation of `FCOS <https://arxiv.org/abs/1904.01355>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(FCOS, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                   test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/fovea.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass FOVEA(SingleStageDetector):\n    \"\"\"Implementation of `FoveaBox <https://arxiv.org/abs/1904.03797>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(FOVEA, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                    test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/fsaf.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass FSAF(SingleStageDetector):\n    \"\"\"Implementation of `FSAF <https://arxiv.org/abs/1903.00621>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(FSAF, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                   test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/gfl.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass GFL(SingleStageDetector):\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(GFL, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                  test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/grid_rcnn.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass GridRCNN(TwoStageDetector):\n    \"\"\"Grid R-CNN.\n\n    This detector is the implementation of:\n    - Grid R-CNN (https://arxiv.org/abs/1811.12030)\n    - Grid R-CNN Plus: Faster and Better (https://arxiv.org/abs/1906.05688)\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 rpn_head,\n                 roi_head,\n                 train_cfg,\n                 test_cfg,\n                 neck=None,\n                 pretrained=None):\n        super(GridRCNN, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            rpn_head=rpn_head,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/htc.py",
    "content": "from ..builder import DETECTORS\nfrom .cascade_rcnn import CascadeRCNN\n\n\n@DETECTORS.register_module()\nclass HybridTaskCascade(CascadeRCNN):\n    \"\"\"Implementation of `HTC <https://arxiv.org/abs/1901.07518>`_\"\"\"\n\n    def __init__(self, **kwargs):\n        super(HybridTaskCascade, self).__init__(**kwargs)\n\n    @property\n    def with_semantic(self):\n        \"\"\"bool: whether the detector has a semantic head\"\"\"\n        return self.roi_head.with_semantic\n"
  },
  {
    "path": "code/mmdet/models/detectors/lscpvnet.py",
    "content": "import numpy as np\nimport pdb\nimport torch\n\nfrom mmdet.core import bbox2result, bbox_mapping_back, multiclass_nms\nfrom ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass LSCPVDetector(SingleStageDetector):\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(LSCPVDetector, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                                test_cfg, pretrained)\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_sem_map=None,\n                      gt_sem_weights=None,\n                      gt_extremes=None):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x)\n        loss_inputs = outs + (gt_bboxes, gt_extremes, gt_sem_map, gt_sem_weights, gt_labels, img_metas)\n        losses = self.bbox_head.loss(\n            *loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)\n        return losses\n\n    def simple_test(self, img, img_metas, rescale=False, show=False, out_dir=False):\n        \"\"\"Test function without test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            np.ndarray: proposals\n        \"\"\"\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x)\n        bbox_list = self.bbox_head.get_bboxes(\n            *outs, img_metas, rescale=rescale)\n        bbox_results = [\n                bbox2result(det_bboxes, det_labels, self.bbox_head.num_classes)\n                for det_bboxes, det_labels in bbox_list\n        ]\n        return bbox_results[0]\n\n    def merge_aug_results(self, aug_bboxes, aug_scores, img_metas):\n        \"\"\"Merge augmented detection bboxes and scores.\n\n        Args:\n            aug_bboxes (list[Tensor]): shape (n, 4*#class)\n            aug_scores (list[Tensor] or None): shape (n, #class)\n            img_shapes (list[Tensor]): shape (3, ).\n\n        Returns:\n            tuple: (bboxes, scores)\n        \"\"\"\n        recovered_bboxes = []\n        for bboxes, img_info in zip(aug_bboxes, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            flip_direction = img_info[0]['flip_direction']\n            bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,\n                                       flip_direction)\n            recovered_bboxes.append(bboxes)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        if aug_scores is None:\n            return bboxes\n        else:\n            scores = torch.cat(aug_scores, dim=0)\n            return bboxes, scores\n\n    def aug_test_simple(self, imgs, img_metas, rescale=False):\n        \"\"\"Test function with test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            list[ndarray]: bbox results of each class\n        \"\"\"\n        # recompute feats to save memory\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_scores = []\n        for x, img_meta in zip(feats, img_metas):\n            # only one image in the batch\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_metas, self.test_cfg, False, False)\n            det_bboxes, det_scores = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n            aug_bboxes.append(det_bboxes)\n            aug_scores.append(det_scores)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = self.merge_aug_results(\n            aug_bboxes, aug_scores, img_metas)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                self.test_cfg.score_thr,\n                                                self.test_cfg.nms,\n                                                self.test_cfg.max_per_img)\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= det_bboxes.new_tensor(\n                img_metas[0][0]['scale_factor'])\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        return bbox_results\n\n    def merge_aug_vote_results(self, aug_bboxes, aug_labels, img_metas):\n        \"\"\"Merge augmented detection bboxes and scores.\n\n        Args:\n            aug_bboxes (list[Tensor]): shape (n, 4*#class)\n            aug_scores (list[Tensor] or None): shape (n, #class)\n            img_shapes (list[Tensor]): shape (3, ).\n            rcnn_test_cfg (dict): rcnn test config.\n\n        Returns:\n            tuple: (bboxes, scores)\n        \"\"\"\n        recovered_bboxes = []\n        for bboxes, img_info in zip(aug_bboxes, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            bboxes[:, :4] = bbox_mapping_back(bboxes[:, :4], img_shape, scale_factor, flip)\n            recovered_bboxes.append(bboxes)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        if aug_labels is None:\n            return bboxes\n        else:\n            labels = torch.cat(aug_labels, dim=0)\n            return bboxes, labels\n\n    def remove_boxes(self, boxes, min_scale, max_scale):\n        areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])\n        keep = torch.nonzero((areas >= min_scale * min_scale) & (areas <= max_scale * max_scale), \n                              as_tuple=False).squeeze(1)\n\n        return keep\n\n    def bboxes_vote(self, boxes, scores, vote_thresh=0.66):\n        eps = 1e-6\n\n        boxes = boxes.cpu().numpy()\n        scores = scores.cpu().numpy().reshape(-1, 1)\n        det = np.concatenate((boxes, scores), axis=1)\n        if det.shape[0] <= 1:\n            return np.zeros((0, 5)), np.zeros((0, 1))\n        order = det[:, 4].ravel().argsort()[::-1]\n        det = det[order, :]\n        dets = []\n        while det.shape[0] > 0:\n            # IOU\n            area = (det[:, 2] - det[:, 0]) * (det[:, 3] - det[:, 1])\n            xx1 = np.maximum(det[0, 0], det[:, 0])\n            yy1 = np.maximum(det[0, 1], det[:, 1])\n            xx2 = np.minimum(det[0, 2], det[:, 2])\n            yy2 = np.minimum(det[0, 3], det[:, 3])\n            w = np.maximum(0.0, xx2 - xx1)\n            h = np.maximum(0.0, yy2 - yy1)\n            inter = w * h\n            union = area[0] + area[:] - inter\n            union = np.maximum(union, eps)\n            o = inter / union\n            o[0] = 1\n\n            # get needed merge det and delete these  det\n            merge_index = np.where(o >= vote_thresh)[0]\n            det_accu = det[merge_index, :]\n            det_accu_iou = o[merge_index]\n            det = np.delete(det, merge_index, 0)\n\n            if merge_index.shape[0] <= 1:\n                try:\n                    dets = np.row_stack((dets, det_accu))\n                except:\n                    dets = det_accu\n                continue\n            else:\n                soft_det_accu = det_accu.copy()\n                soft_det_accu[:, 4] = soft_det_accu[:, 4] * (1 - det_accu_iou)\n                soft_index = np.where(soft_det_accu[:, 4] >= 0.05)[0]\n                soft_det_accu = soft_det_accu[soft_index, :]\n\n                det_accu[:, 0:4] = det_accu[:, 0:4] * np.tile(det_accu[:, -1:], (1, 4))\n                max_score = np.max(det_accu[:, 4])\n                det_accu_sum = np.zeros((1, 5))\n                det_accu_sum[:, 0:4] = np.sum(det_accu[:, 0:4], axis=0) / np.sum(det_accu[:, -1:])\n                det_accu_sum[:, 4] = max_score\n\n                if soft_det_accu.shape[0] > 0:\n                    det_accu_sum = np.row_stack((det_accu_sum, soft_det_accu))\n\n                try:\n                    dets = np.row_stack((dets, det_accu_sum))\n                except:\n                    dets = det_accu_sum\n\n        order = dets[:, 4].ravel().argsort()[::-1]\n        dets = dets[order, :]\n\n        boxes = torch.from_numpy(dets[:, :4]).float().cuda()\n        scores = torch.from_numpy(dets[:, 4]).float().cuda()\n\n        return boxes, scores\n\n    def aug_test_vote(self, imgs, img_metas, rescale=False):\n        # recompute feats to save memory\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_labels = []\n        for i, (x, img_meta) in enumerate(zip(feats, img_metas)):\n            # only one image in the batch\n            # TODO more flexible\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_meta, self.test_cfg, False, True)\n            det_bboxes, det_labels = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n            keeped = self.remove_boxes(det_bboxes, self.test_cfg.scale_ranges[i // 2][0],\n                                       self.test_cfg.scale_ranges[i // 2][1])\n            det_bboxes, det_labels = det_bboxes[keeped, :], det_labels[keeped]\n            aug_bboxes.append(det_bboxes)\n            aug_labels.append(det_labels)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_labels = self.merge_aug_vote_results(\n            aug_bboxes, aug_labels, img_metas)\n\n        det_bboxes = []\n        det_labels = []\n        for j in range(80):\n            inds = (merged_labels == j).nonzero().squeeze(1)\n\n            scores_j = merged_bboxes[inds, 4]\n            bboxes_j = merged_bboxes[inds, :4].view(-1, 4)\n            bboxes_j, scores_j = self.bboxes_vote(bboxes_j, scores_j)\n\n            if len(bboxes_j) > 0:\n                det_bboxes.append(torch.cat([bboxes_j, scores_j[:, None]], dim=1))\n                det_labels.append(torch.full((bboxes_j.shape[0],), j, dtype=torch.int64, \n                                              device=scores_j.device))\n\n        if len(det_bboxes) > 0:\n            det_bboxes = torch.cat(det_bboxes, dim=0)\n            det_labels = torch.cat(det_labels)\n        else:\n            det_bboxes = merged_bboxes.new_zeros((0, 5))\n            det_labels = merged_bboxes.new_zeros((0,), dtype=torch.long)\n\n        if det_bboxes.shape[0] > 1000 > 0:\n            cls_scores = det_bboxes[:, 4]\n            image_thresh, _ = torch.kthvalue(\n                cls_scores.cpu(),\n                det_bboxes.shape[0] - 1000 + 1\n            )\n            keep = cls_scores >= image_thresh.item()\n            keep = torch.nonzero(keep, as_tuple=False).squeeze(1)\n            det_bboxes = det_bboxes[keep]\n            det_labels = det_labels[keep]\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= img_metas[0][0]['scale_factor']\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        return bbox_results\n\n    def aug_test(self, imgs, img_metas, rescale=False):\n        if self.test_cfg.get(\"method\", \"simple\") == \"simple\":\n            return self.aug_test_simple(imgs, img_metas, rescale)\n        else:\n            return self.aug_test_vote(imgs, img_metas, rescale)"
  },
  {
    "path": "code/mmdet/models/detectors/lsnet.py",
    "content": "import torch\nimport pdb\nimport mmcv\nimport numpy as np\n\nfrom mmdet.core import (bbox2result, bbox_extreme2result, bbox_mapping_back, multiclass_nms,\n                        bbox_poly2result, instance_mapping_back)\nfrom ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass LSDetector(SingleStageDetector):\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(LSDetector, self).__init__(backbone, neck, bbox_head, train_cfg, test_cfg, \n                                         pretrained)\n\n        self.flip_idx = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10],\n                         [11, 12], [13, 14], [15, 16]]\n\n    def merge_aug_results(self, aug_bboxes, aug_scores, img_metas):\n        recovered_bboxes = []\n        for bboxes, img_info in zip(aug_bboxes, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            flip_direction = img_info[0]['flip_direction']\n            bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,\n                                       flip_direction)\n            recovered_bboxes.append(bboxes)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        if aug_scores is None:\n            return bboxes\n        else:\n            scores = torch.cat(aug_scores, dim=0)\n            return bboxes, scores\n    \n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_masks = None,\n                      gt_extremes = None,\n                      gt_keypoints = None,\n                      gt_bboxes_ignore=None):\n        x = self.extract_feat(img)\n        losses = self.bbox_head.forward_train(x, img_metas, gt_bboxes, gt_extremes, gt_keypoints,\n                                              gt_masks, gt_labels, gt_bboxes_ignore)\n        return losses\n\n    def simple_test(self, img, img_metas, rescale=False, show=False, out_dir=False):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x)\n        bbox_list = self.bbox_head.get_bboxes(\n            *outs, img_metas, rescale=rescale)\n\n        if self.bbox_head.task == 'bbox':\n            bbox_results = [\n                bbox_extreme2result(det_bboxes, det_extremes, det_labels, self.bbox_head.num_classes)\n                for det_bboxes, det_extremes, det_labels in bbox_list\n            ]\n        elif self.bbox_head.task == 'segm':\n            bbox_results = [\n                bbox_poly2result(det_bboxes, det_polygons, det_labels, \n                                 self.bbox_head.num_classes,\n                                 self.bbox_head.num_vectors)\n                for det_bboxes, det_polygons, det_labels in bbox_list\n            ]\n        elif self.bbox_head.task == 'pose_bbox' or self.bbox_head.task == 'pose_kbox':\n            if show or out_dir:\n                bbox_results = [\n                   bbox_poly2result(det_bboxes, det_kps, det_labels, \n                                    self.bbox_head.num_classes,\n                                    self.bbox_head.num_vectors)\n                   for det_bboxes, det_kps, det_labels in bbox_list            \n                ]\n            else:\n                for det_bboxes, det_kps, det_labels in bbox_list:\n                    bbox_w, bbox_h = det_bboxes[:, 2] - det_bboxes[:, 0], det_bboxes[:, 3] - det_bboxes[:, 1]\n                    areas = bbox_w*bbox_h\n                    pos_inds = areas > 1024\n\n                    det_bboxes = det_bboxes[pos_inds]\n                    det_kps    = det_kps[pos_inds]\n                    det_labels = det_labels[pos_inds]\n\n                bbox_results = [\n                    bbox_poly2result(det_bboxes, det_kps, det_labels,\n                                     self.bbox_head.num_classes,\n                                     self.bbox_head.num_vectors)\n                ]\n\n        return bbox_results[0]\n\n    def aug_test_simple(self, imgs, img_metas, rescale=False):\n        # recompute feats to save memory\n\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_scores = []\n        aug_ex_or_poly = []\n        for x, img_meta in zip(feats, img_metas):\n            # only one image in the batch\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_metas, self.test_cfg, False, False)\n            det_bboxes, det_ex_or_poly, det_scores = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n            aug_bboxes.append(det_bboxes)\n            aug_ex_or_poly.append(det_ex_or_poly)\n            aug_scores.append(det_scores)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = self.merge_aug_results(\n            aug_bboxes, aug_scores, img_metas)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                self.test_cfg.score_thr,\n                                                self.test_cfg.nms,\n                                                self.test_cfg.max_per_img)\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= det_bboxes.new_tensor(\n                img_metas[0][0]['scale_factor'])\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        return bbox_results\n\n    def merge_aug_vote_results(self, aug_bboxes, aug_vectors, aug_labels, img_metas):\n        recovered_bboxes  = []\n        recovered_vectors = []\n        for bboxes, vectors, img_info in zip(aug_bboxes, aug_vectors, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            bboxes[:, :4], vectors = instance_mapping_back(bboxes[:, :4], vectors, img_shape, \n                                                           scale_factor, flip, self.bbox_head.task)\n            recovered_bboxes.append(bboxes)\n            recovered_vectors.append(vectors)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        vectors = torch.cat(recovered_vectors, dim=0)\n        if aug_labels is None:\n            return bboxes, vectors\n        else:\n            labels = torch.cat(aug_labels, dim=0)\n            return bboxes, vectors, labels\n\n    def remove_boxes(self, boxes, min_scale, max_scale):\n        areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])\n        keep = torch.nonzero((areas >= min_scale * min_scale) & (areas <= max_scale * max_scale), \n                              as_tuple=False).squeeze(1)\n\n        return keep\n\n    def bboxes_vote(self, boxes, scores, vote_thresh=0.66):\n        eps = 1e-6\n\n        boxes = boxes.cpu().numpy()\n        scores = scores.cpu().numpy().reshape(-1, 1)\n        det = np.concatenate((boxes, scores), axis=1)\n        if det.shape[0] <= 1:\n            return np.zeros((0, 5)), np.zeros((0, 1))\n        order = det[:, 4].ravel().argsort()[::-1]\n        det = det[order, :]\n        dets = []\n        while det.shape[0] > 0:\n            # IOU\n            area = (det[:, 2] - det[:, 0]) * (det[:, 3] - det[:, 1])\n            xx1 = np.maximum(det[0, 0], det[:, 0])\n            yy1 = np.maximum(det[0, 1], det[:, 1])\n            xx2 = np.minimum(det[0, 2], det[:, 2])\n            yy2 = np.minimum(det[0, 3], det[:, 3])\n            w = np.maximum(0.0, xx2 - xx1)\n            h = np.maximum(0.0, yy2 - yy1)\n            inter = w * h\n            union = area[0] + area[:] - inter\n            union = np.maximum(union, eps)\n            o = inter / union\n            o[0] = 1\n\n            # get needed merge det and delete these  det\n            merge_index = np.where(o >= vote_thresh)[0]\n            det_accu = det[merge_index, :]\n            det_accu_iou = o[merge_index]\n            det = np.delete(det, merge_index, 0)\n\n            if merge_index.shape[0] <= 1:\n                try:\n                    dets = np.row_stack((dets, det_accu))\n                except:\n                    dets = det_accu\n                continue\n            else:\n                soft_det_accu = det_accu.copy()\n                soft_det_accu[:, 4] = soft_det_accu[:, 4] * (1 - det_accu_iou)\n                soft_index = np.where(soft_det_accu[:, 4] >= 0.05)[0]\n                soft_det_accu = soft_det_accu[soft_index, :]\n\n                det_accu[:, 0:4] = det_accu[:, 0:4] * np.tile(det_accu[:, -1:], (1, 4))\n                max_score = np.max(det_accu[:, 4])\n                det_accu_sum = np.zeros((1, 5))\n                det_accu_sum[:, 0:4] = np.sum(det_accu[:, 0:4], axis=0) / np.sum(det_accu[:, -1:])\n                det_accu_sum[:, 4] = max_score\n\n                if soft_det_accu.shape[0] > 0:\n                    det_accu_sum = np.row_stack((det_accu_sum, soft_det_accu))\n\n                try:\n                    dets = np.row_stack((dets, det_accu_sum))\n                except:\n                    dets = det_accu_sum\n\n        order = dets[:, 4].ravel().argsort()[::-1]\n        dets = dets[order, :]\n\n        boxes = torch.from_numpy(dets[:, :4]).float().cuda()\n        scores = torch.from_numpy(dets[:, 4]).float().cuda()\n\n        return boxes, scores\n    \n    def instances_vote(self, boxes, vectors, scores, vote_thresh=0.66):\n        eps = 1e-6\n\n        num_vect_pts = vectors.size(1)\n        boxes = boxes.cpu().numpy()\n        vectors = vectors.cpu().numpy()\n        scores = scores.cpu().numpy().reshape(-1, 1)\n        det = np.concatenate((boxes, scores, vectors), axis=1)\n        if det.shape[0] <= 1:\n            return np.zeros((0, 5)), np.zeros((0, num_vect_pts)), np.zeros((0, 1))\n        order = det[:, 4].ravel().argsort()[::-1]\n        det = det[order, :]\n        dets = []\n        while det.shape[0] > 0:\n            # IOU\n            area = (det[:, 2] - det[:, 0]) * (det[:, 3] - det[:, 1])\n            xx1 = np.maximum(det[0, 0], det[:, 0])\n            yy1 = np.maximum(det[0, 1], det[:, 1])\n            xx2 = np.minimum(det[0, 2], det[:, 2])\n            yy2 = np.minimum(det[0, 3], det[:, 3])\n            w = np.maximum(0.0, xx2 - xx1)\n            h = np.maximum(0.0, yy2 - yy1)\n            inter = w * h\n            union = area[0] + area[:] - inter\n            union = np.maximum(union, eps)\n            o = inter / union\n            o[0] = 1\n\n            # get needed merge det and delete these  det\n            merge_index = np.where(o >= vote_thresh)[0]\n            det_accu = det[merge_index, :]\n            det_accu_iou = o[merge_index]\n            det = np.delete(det, merge_index, 0)\n\n            if merge_index.shape[0] <= 1:\n                try:\n                    dets = np.row_stack((dets, det_accu))\n                except:\n                    dets = det_accu\n                continue\n            else:\n                soft_det_accu = det_accu.copy()\n                soft_det_accu[:, 4] = soft_det_accu[:, 4] * (1 - det_accu_iou)\n                soft_index = np.where(soft_det_accu[:, 4] >= 0.05)[0]\n                soft_det_accu = soft_det_accu[soft_index, :]\n\n                det_accu[:, 0:4] = det_accu[:, 0:4] * np.tile(det_accu[:, 4:5], (1, 4))\n                det_accu[:, 5:] = det_accu[:, 5:] * np.tile(det_accu[:, 4:5], (1, num_vect_pts))\n\n                max_score = np.max(det_accu[:, 4])\n                det_accu_sum = np.zeros((1, 5+num_vect_pts))\n                det_accu_sum[:, 0:4] = np.sum(det_accu[:, 0:4], axis=0) / np.sum(det_accu[:, 4:5])\n                det_accu_sum[:, 5:] = np.sum(det_accu[:, 5:], axis=0) / np.sum(det_accu[:, 4:5])\n                det_accu_sum[:, 4] = max_score\n\n                if soft_det_accu.shape[0] > 0:\n                    det_accu_sum = np.row_stack((det_accu_sum, soft_det_accu))\n\n                try:\n                    dets = np.row_stack((dets, det_accu_sum))\n                except:\n                    dets = det_accu_sum\n\n        order = dets[:, 4].ravel().argsort()[::-1]\n        dets = dets[order, :]\n\n        boxes = torch.from_numpy(dets[:, :4]).float().cuda()\n        vectors =  torch.from_numpy(dets[:, 5:]).float().cuda()\n        scores = torch.from_numpy(dets[:, 4]).float().cuda()\n\n        return boxes, vectors, scores\n\n    def aug_test_vote(self, imgs, img_metas, rescale=False, show=False, out_dir=False):\n        # recompute feats to save memory\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_labels = []\n        aug_vectors = []\n        for i, (x, img_meta) in enumerate(zip(feats, img_metas)):\n            # only one image in the batch\n            # TODO more flexible\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_meta, self.test_cfg, False, True)\n            det_bboxes, det_vectors, det_labels = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n\n            keeped = self.remove_boxes(det_bboxes, self.test_cfg.scale_ranges[i // 2][0], \n                                       self.test_cfg.scale_ranges[i // 2][1])\n            det_bboxes, det_vectors, det_labels = det_bboxes[keeped, :], det_vectors[keeped, :], \\\n                                                  det_labels[keeped]\n            aug_bboxes.append(det_bboxes)\n            aug_vectors.append(det_vectors)\n            aug_labels.append(det_labels)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_vectors, merged_labels = self.merge_aug_vote_results(\n                                               aug_bboxes, aug_vectors, aug_labels, img_metas)\n\n        det_bboxes  = []\n        det_vectors = []\n        det_labels  = []\n        for j in range(self.bbox_head.num_classes):\n            inds = (merged_labels == j).nonzero().squeeze(1)\n\n            scores_j = merged_bboxes[inds, 4]\n            bboxes_j = merged_bboxes[inds, :4].view(-1, 4)\n            vectors_j = merged_vectors[inds]\n            bboxes_j, vectors_j, scores_j = self.instances_vote(bboxes_j, vectors_j, scores_j)\n\n            if len(bboxes_j) > 0:\n                det_bboxes.append(torch.cat([bboxes_j, scores_j[:, None]], dim=1))\n                det_vectors.append(vectors_j)\n\n                det_labels.append(torch.full((bboxes_j.shape[0],), j, dtype=torch.int64,\n                                              device=scores_j.device))\n\n        if len(det_bboxes) > 0:\n            det_bboxes = torch.cat(det_bboxes, dim=0)\n            det_vectors = torch.cat(det_vectors, dim=0)\n            det_labels = torch.cat(det_labels)\n\n        else:\n            det_bboxes = merged_bboxes.new_zeros((0, 5))\n            det_vectors = merged_bboxes.new_zeros((0, self.bbox_head.num_vectors*2))\n            det_labels = merged_bboxes.new_zeros((0,), dtype=torch.long)\n\n        if det_bboxes.shape[0] > 1000 > 0:\n            cls_scores = det_bboxes[:, 4]\n            image_thresh, _ = torch.kthvalue(\n                cls_scores.cpu(),\n                det_bboxes.shape[0] - 1000 + 1\n            )\n            keep = cls_scores >= image_thresh.item()\n            keep = torch.nonzero(keep, as_tuple=False).squeeze(1)\n            det_bboxes = det_bboxes[keep]\n            det_vectors = det_vectors[keep]\n            det_labels = det_labels[keep]\n\n        if rescale:\n            _det_bboxes = det_bboxes\n            _det_vectors = det_vectors\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_vectors = det_vectors.clone()\n            _det_bboxes[:, :4] *= img_metas[0][0]['scale_factor']\n            _det_vectors *= np.tile(img_metas[0][0]['scale_factor'][:2], self.bbox_head.num_vectors)\n\n        if self.bbox_head.task == 'bbox':\n            bbox_results = bbox_extreme2result(_det_bboxes, _det_vectors, det_labels,\n                                               self.bbox_head.num_classes)\n\n        elif self.bbox_head.task == 'segm':\n            bbox_results = bbox_poly2result(_det_bboxes, _det_vectors, det_labels,\n                                            self.bbox_head.num_classes,\n                                            self.bbox_head.num_vectors)\n        elif self.bbox_head.task == 'pose_bbox' or self.bbox_head.task == 'pose_kbox':\n            if show or out_dir:\n                bbox_results = bbox_poly2result(_det_bboxes, _det_vectors, det_labels,\n                                                self.bbox_head.num_classes,\n                                                self.bbox_head.num_vectors)\n            else:\n                bbox_w, bbox_h = _det_bboxes[:, 2] - _det_bboxes[:, 0], _det_bboxes[:, 3] - _det_bboxes[:, 1]\n                areas = bbox_w*bbox_h\n                pos_inds = areas > 1024\n\n                _det_bboxes  = _det_bboxes[pos_inds]\n                _det_vectors = _det_vectors[pos_inds]\n                det_labels   = det_labels[pos_inds]\n                bbox_results = bbox_poly2result(_det_bboxes, _det_vectors, det_labels,\n                                                self.bbox_head.num_classes,\n                                                self.bbox_head.num_vectors)\n\n        return bbox_results\n\n    def aug_test(self, imgs, img_metas, rescale=False, show=False, out_dir=False):\n        if self.test_cfg.get(\"method\", \"simple\") == \"simple\":\n            assert self.bbox_head.task == 'bbox', \\\n            'aug_test_simple supports only the object detection now, please use aug_test_vote for segm and pose'\n            return self.aug_test_simple(imgs, img_metas, rescale)\n        else:\n            return self.aug_test_vote(imgs, img_metas, rescale, show, out_dir)\n\n    def show_result(self,\n                    img,\n                    result,\n                    score_thr=0.3,\n                    show=False,\n                    out_file=None):\n\n        img = mmcv.imread(img)\n        img = img.copy()\n\n        bbox_result, vector_result = result[0], result[1]\n\n        bboxes = np.vstack(bbox_result)\n        vectors = np.vstack(vector_result)\n        labels = [\n            np.full(bbox.shape[0], i, dtype=np.int32)\n            for i, bbox in enumerate(bbox_result)\n        ]\n        labels = np.concatenate(labels)\n\n        # if out_file specified, do not show image in window\n        if show:\n            warnings.warn('show is not supported, please use show-dir')\n            return img\n        if self.bbox_head.task == 'bbox':\n            mmcv.imshow_extremes(img, bboxes, vectors, labels, class_names=self.CLASSES,\n                                 score_thr=score_thr, out_file=out_file)\n        elif self.bbox_head.task == 'segm':\n            mmcv.imshow_polygons(img, bboxes, vectors, labels, class_names=self.CLASSES,\n                                 score_thr=score_thr, out_file=out_file)\n        elif 'pose' in self.bbox_head.task:\n            mmcv.imshow_pose(img, bboxes, vectors, labels, class_names=self.CLASSES,\n                                 score_thr=score_thr, out_file=out_file)"
  },
  {
    "path": "code/mmdet/models/detectors/mask_rcnn.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass MaskRCNN(TwoStageDetector):\n    \"\"\"Implementation of `Mask R-CNN <https://arxiv.org/abs/1703.06870>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 rpn_head,\n                 roi_head,\n                 train_cfg,\n                 test_cfg,\n                 neck=None,\n                 pretrained=None):\n        super(MaskRCNN, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            rpn_head=rpn_head,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/mask_scoring_rcnn.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass MaskScoringRCNN(TwoStageDetector):\n    \"\"\"Mask Scoring RCNN.\n\n    https://arxiv.org/abs/1903.00241\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 rpn_head,\n                 roi_head,\n                 train_cfg,\n                 test_cfg,\n                 neck=None,\n                 pretrained=None):\n        super(MaskScoringRCNN, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            rpn_head=rpn_head,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/nasfcos.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass NASFCOS(SingleStageDetector):\n    \"\"\"NAS-FCOS: Fast Neural Architecture Search for Object Detection.\n\n    https://arxiv.org/abs/1906.0442\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(NASFCOS, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                      test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/point_rend.py",
    "content": "from ..builder import DETECTORS\nfrom .two_stage import TwoStageDetector\n\n\n@DETECTORS.register_module()\nclass PointRend(TwoStageDetector):\n    \"\"\"PointRend: Image Segmentation as Rendering\n\n    This detector is the implementation of\n    `PointRend <https://arxiv.org/abs/1912.08193>`_.\n\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 rpn_head,\n                 roi_head,\n                 train_cfg,\n                 test_cfg,\n                 neck=None,\n                 pretrained=None):\n        super(PointRend, self).__init__(\n            backbone=backbone,\n            neck=neck,\n            rpn_head=rpn_head,\n            roi_head=roi_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg,\n            pretrained=pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/reppoints_detector.py",
    "content": "import torch\n\nfrom mmdet.core import bbox2result, bbox_mapping_back, multiclass_nms\nfrom ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass RepPointsDetector(SingleStageDetector):\n    \"\"\"RepPoints: Point Set Representation for Object Detection.\n\n        This detector is the implementation of:\n        - RepPoints detector (https://arxiv.org/pdf/1904.11490)\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(RepPointsDetector,\n              self).__init__(backbone, neck, bbox_head, train_cfg, test_cfg,\n                             pretrained)\n\n    def merge_aug_results(self, aug_bboxes, aug_scores, img_metas):\n        \"\"\"Merge augmented detection bboxes and scores.\n\n        Args:\n            aug_bboxes (list[Tensor]): shape (n, 4*#class)\n            aug_scores (list[Tensor] or None): shape (n, #class)\n            img_shapes (list[Tensor]): shape (3, ).\n\n        Returns:\n            tuple: (bboxes, scores)\n        \"\"\"\n        recovered_bboxes = []\n        for bboxes, img_info in zip(aug_bboxes, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            flip_direction = img_info[0]['flip_direction']\n            bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,\n                                       flip_direction)\n            recovered_bboxes.append(bboxes)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        if aug_scores is None:\n            return bboxes\n        else:\n            scores = torch.cat(aug_scores, dim=0)\n            return bboxes, scores\n\n    def aug_test(self, imgs, img_metas, rescale=False):\n        \"\"\"Test function with test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            list[ndarray]: bbox results of each class\n        \"\"\"\n        # recompute feats to save memory\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_scores = []\n        for x, img_meta in zip(feats, img_metas):\n            # only one image in the batch\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_metas, self.test_cfg, False, False)\n            det_bboxes, det_scores = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n            aug_bboxes.append(det_bboxes)\n            aug_scores.append(det_scores)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = self.merge_aug_results(\n            aug_bboxes, aug_scores, img_metas)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                self.test_cfg.score_thr,\n                                                self.test_cfg.nms,\n                                                self.test_cfg.max_per_img)\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= det_bboxes.new_tensor(\n                img_metas[0][0]['scale_factor'])\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        return bbox_results\n"
  },
  {
    "path": "code/mmdet/models/detectors/reppoints_v2_detector.py",
    "content": "import numpy as np\n\nimport torch\n\nfrom mmdet.core import bbox2result, bbox_mapping_back, multiclass_nms\nfrom ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass RepPointsV2Detector(SingleStageDetector):\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(RepPointsV2Detector, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                                test_cfg, pretrained)\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_sem_map=None,\n                      gt_sem_weights=None):\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x)\n        loss_inputs = outs + (gt_bboxes, gt_sem_map, gt_sem_weights, gt_labels, img_metas)\n        losses = self.bbox_head.loss(\n            *loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)\n        return losses\n\n    def merge_aug_results(self, aug_bboxes, aug_scores, img_metas):\n        \"\"\"Merge augmented detection bboxes and scores.\n\n        Args:\n            aug_bboxes (list[Tensor]): shape (n, 4*#class)\n            aug_scores (list[Tensor] or None): shape (n, #class)\n            img_shapes (list[Tensor]): shape (3, ).\n\n        Returns:\n            tuple: (bboxes, scores)\n        \"\"\"\n        recovered_bboxes = []\n        for bboxes, img_info in zip(aug_bboxes, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            flip_direction = img_info[0]['flip_direction']\n            bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,\n                                       flip_direction)\n            recovered_bboxes.append(bboxes)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        if aug_scores is None:\n            return bboxes\n        else:\n            scores = torch.cat(aug_scores, dim=0)\n            return bboxes, scores\n\n    def aug_test_simple(self, imgs, img_metas, rescale=False):\n        \"\"\"Test function with test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            list[ndarray]: bbox results of each class\n        \"\"\"\n        # recompute feats to save memory\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_scores = []\n        for x, img_meta in zip(feats, img_metas):\n            # only one image in the batch\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_metas, self.test_cfg, False, False)\n            det_bboxes, det_scores = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n            aug_bboxes.append(det_bboxes)\n            aug_scores.append(det_scores)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = self.merge_aug_results(\n            aug_bboxes, aug_scores, img_metas)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                self.test_cfg.score_thr,\n                                                self.test_cfg.nms,\n                                                self.test_cfg.max_per_img)\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= det_bboxes.new_tensor(\n                img_metas[0][0]['scale_factor'])\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        return bbox_results\n\n    def merge_aug_vote_results(self, aug_bboxes, aug_labels, img_metas):\n        \"\"\"Merge augmented detection bboxes and scores.\n\n        Args:\n            aug_bboxes (list[Tensor]): shape (n, 4*#class)\n            aug_scores (list[Tensor] or None): shape (n, #class)\n            img_shapes (list[Tensor]): shape (3, ).\n            rcnn_test_cfg (dict): rcnn test config.\n\n        Returns:\n            tuple: (bboxes, scores)\n        \"\"\"\n        recovered_bboxes = []\n        for bboxes, img_info in zip(aug_bboxes, img_metas):\n            img_shape = img_info[0]['img_shape']\n            scale_factor = img_info[0]['scale_factor']\n            flip = img_info[0]['flip']\n            bboxes[:, :4] = bbox_mapping_back(bboxes[:, :4], img_shape, scale_factor, flip)\n            recovered_bboxes.append(bboxes)\n        bboxes = torch.cat(recovered_bboxes, dim=0)\n        if aug_labels is None:\n            return bboxes\n        else:\n            labels = torch.cat(aug_labels, dim=0)\n            return bboxes, labels\n\n    def remove_boxes(self, boxes, min_scale, max_scale):\n        areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])\n        keep = torch.nonzero((areas >= min_scale * min_scale) & (areas <= max_scale * max_scale), as_tuple=False).squeeze(1)\n\n        return keep\n\n    def bboxes_vote(self, boxes, scores, vote_thresh=0.66):\n        eps = 1e-6\n\n        boxes = boxes.cpu().numpy()\n        scores = scores.cpu().numpy().reshape(-1, 1)\n        det = np.concatenate((boxes, scores), axis=1)\n        if det.shape[0] <= 1:\n            return np.zeros((0, 5)), np.zeros((0, 1))\n        order = det[:, 4].ravel().argsort()[::-1]\n        det = det[order, :]\n        dets = []\n        while det.shape[0] > 0:\n            # IOU\n            area = (det[:, 2] - det[:, 0]) * (det[:, 3] - det[:, 1])\n            xx1 = np.maximum(det[0, 0], det[:, 0])\n            yy1 = np.maximum(det[0, 1], det[:, 1])\n            xx2 = np.minimum(det[0, 2], det[:, 2])\n            yy2 = np.minimum(det[0, 3], det[:, 3])\n            w = np.maximum(0.0, xx2 - xx1)\n            h = np.maximum(0.0, yy2 - yy1)\n            inter = w * h\n            union = area[0] + area[:] - inter\n            union = np.maximum(union, eps)\n            o = inter / union\n            o[0] = 1\n\n            # get needed merge det and delete these  det\n            merge_index = np.where(o >= vote_thresh)[0]\n            det_accu = det[merge_index, :]\n            det_accu_iou = o[merge_index]\n            det = np.delete(det, merge_index, 0)\n\n            if merge_index.shape[0] <= 1:\n                try:\n                    dets = np.row_stack((dets, det_accu))\n                except:\n                    dets = det_accu\n                continue\n            else:\n                soft_det_accu = det_accu.copy()\n                soft_det_accu[:, 4] = soft_det_accu[:, 4] * (1 - det_accu_iou)\n                soft_index = np.where(soft_det_accu[:, 4] >= 0.05)[0]\n                soft_det_accu = soft_det_accu[soft_index, :]\n\n                det_accu[:, 0:4] = det_accu[:, 0:4] * np.tile(det_accu[:, -1:], (1, 4))\n                max_score = np.max(det_accu[:, 4])\n                det_accu_sum = np.zeros((1, 5))\n                det_accu_sum[:, 0:4] = np.sum(det_accu[:, 0:4], axis=0) / np.sum(det_accu[:, -1:])\n                det_accu_sum[:, 4] = max_score\n\n                if soft_det_accu.shape[0] > 0:\n                    det_accu_sum = np.row_stack((det_accu_sum, soft_det_accu))\n\n                try:\n                    dets = np.row_stack((dets, det_accu_sum))\n                except:\n                    dets = det_accu_sum\n\n        order = dets[:, 4].ravel().argsort()[::-1]\n        dets = dets[order, :]\n\n        boxes = torch.from_numpy(dets[:, :4]).float().cuda()\n        scores = torch.from_numpy(dets[:, 4]).float().cuda()\n\n        return boxes, scores\n\n    def aug_test_vote(self, imgs, img_metas, rescale=False):\n        # recompute feats to save memory\n        feats = self.extract_feats(imgs)\n\n        aug_bboxes = []\n        aug_labels = []\n        for i, (x, img_meta) in enumerate(zip(feats, img_metas)):\n            # only one image in the batch\n            # TODO more flexible\n            outs = self.bbox_head(x)\n            bbox_inputs = outs + (img_meta, self.test_cfg, False, True)\n            det_bboxes, det_labels = self.bbox_head.get_bboxes(*bbox_inputs)[0]\n            keeped = self.remove_boxes(det_bboxes, self.test_cfg.scale_ranges[i // 2][0], self.test_cfg.scale_ranges[i // 2][1])\n            det_bboxes, det_labels = det_bboxes[keeped, :], det_labels[keeped]\n            aug_bboxes.append(det_bboxes)\n            aug_labels.append(det_labels)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_labels = self.merge_aug_vote_results(\n            aug_bboxes, aug_labels, img_metas)\n\n        det_bboxes = []\n        det_labels = []\n        for j in range(80):\n            inds = (merged_labels == j).nonzero().squeeze(1)\n\n            scores_j = merged_bboxes[inds, 4]\n            bboxes_j = merged_bboxes[inds, :4].view(-1, 4)\n            bboxes_j, scores_j = self.bboxes_vote(bboxes_j, scores_j)\n\n            if len(bboxes_j) > 0:\n                det_bboxes.append(torch.cat([bboxes_j, scores_j[:, None]], dim=1))\n                det_labels.append(torch.full((bboxes_j.shape[0],), j, dtype=torch.int64, device=scores_j.device))\n\n        if len(det_bboxes) > 0:\n            det_bboxes = torch.cat(det_bboxes, dim=0)\n            det_labels = torch.cat(det_labels)\n        else:\n            det_bboxes = merged_bboxes.new_zeros((0, 5))\n            det_labels = merged_bboxes.new_zeros((0,), dtype=torch.long)\n\n        if det_bboxes.shape[0] > 1000 > 0:\n            cls_scores = det_bboxes[:, 4]\n            image_thresh, _ = torch.kthvalue(\n                cls_scores.cpu(),\n                det_bboxes.shape[0] - 1000 + 1\n            )\n            keep = cls_scores >= image_thresh.item()\n            keep = torch.nonzero(keep, as_tuple=False).squeeze(1)\n            det_bboxes = det_bboxes[keep]\n            det_labels = det_labels[keep]\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= img_metas[0][0]['scale_factor']\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        return bbox_results\n\n    def aug_test(self, imgs, img_metas, rescale=False):\n        if self.test_cfg.get(\"method\", \"simple\") == \"simple\":\n            return self.aug_test_simple(imgs, img_metas, rescale)\n        else:\n            return self.aug_test_vote(imgs, img_metas, rescale)"
  },
  {
    "path": "code/mmdet/models/detectors/retinanet.py",
    "content": "from ..builder import DETECTORS\nfrom .single_stage import SingleStageDetector\n\n\n@DETECTORS.register_module()\nclass RetinaNet(SingleStageDetector):\n    \"\"\"Implementation of `RetinaNet <https://arxiv.org/abs/1708.02002>`_\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 bbox_head,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(RetinaNet, self).__init__(backbone, neck, bbox_head, train_cfg,\n                                        test_cfg, pretrained)\n"
  },
  {
    "path": "code/mmdet/models/detectors/rpn.py",
    "content": "import mmcv\n\nfrom mmdet.core import bbox_mapping, tensor2imgs\nfrom ..builder import DETECTORS, build_backbone, build_head, build_neck\nfrom .base import BaseDetector\n\n\n@DETECTORS.register_module()\nclass RPN(BaseDetector):\n    \"\"\"Implementation of Region Proposal Network\"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck,\n                 rpn_head,\n                 train_cfg,\n                 test_cfg,\n                 pretrained=None):\n        super(RPN, self).__init__()\n        self.backbone = build_backbone(backbone)\n        self.neck = build_neck(neck) if neck is not None else None\n        rpn_train_cfg = train_cfg.rpn if train_cfg is not None else None\n        rpn_head.update(train_cfg=rpn_train_cfg)\n        rpn_head.update(test_cfg=test_cfg.rpn)\n        self.rpn_head = build_head(rpn_head)\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n        self.init_weights(pretrained=pretrained)\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in detector\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        super(RPN, self).init_weights(pretrained)\n        self.backbone.init_weights(pretrained=pretrained)\n        if self.with_neck:\n            self.neck.init_weights()\n        self.rpn_head.init_weights()\n\n    def extract_feat(self, img):\n        \"\"\"Extract features\n\n        Args:\n            img (torch.Tensor): Image tensor with shape (n, c, h ,w).\n\n        Returns:\n            list[torch.Tensor]: Multi-level features that may have\n                different resolutions.\n        \"\"\"\n        x = self.backbone(img)\n        if self.with_neck:\n            x = self.neck(x)\n        return x\n\n    def forward_dummy(self, img):\n        \"\"\"Dummy forward function\"\"\"\n        x = self.extract_feat(img)\n        rpn_outs = self.rpn_head(x)\n        return rpn_outs\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes=None,\n                      gt_bboxes_ignore=None):\n        \"\"\"\n        Args:\n            img (Tensor): Input images of shape (N, C, H, W).\n                Typically these should be mean centered and std scaled.\n            img_metas (list[dict]): A List of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                :class:`mmdet.datasets.pipelines.Collect`.\n            gt_bboxes (list[Tensor]): Each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n            gt_bboxes_ignore (None | list[Tensor]): Specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        if self.train_cfg.rpn.get('debug', False):\n            self.rpn_head.debug_imgs = tensor2imgs(img)\n\n        x = self.extract_feat(img)\n        losses = self.rpn_head.forward_train(x, img_metas, gt_bboxes, None,\n                                             gt_bboxes_ignore)\n        return losses\n\n    def simple_test(self, img, img_metas, rescale=False):\n        \"\"\"Test function without test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            np.ndarray: proposals\n        \"\"\"\n        x = self.extract_feat(img)\n        proposal_list = self.rpn_head.simple_test_rpn(x, img_metas)\n        if rescale:\n            for proposals, meta in zip(proposal_list, img_metas):\n                proposals[:, :4] /= proposals.new_tensor(meta['scale_factor'])\n\n        # TODO: remove this restriction\n        return proposal_list[0].cpu().numpy()\n\n    def aug_test(self, imgs, img_metas, rescale=False):\n        \"\"\"Test function with test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            np.ndarray: proposals\n        \"\"\"\n        proposal_list = self.rpn_head.aug_test_rpn(\n            self.extract_feats(imgs), img_metas)\n        if not rescale:\n            for proposals, img_meta in zip(proposal_list, img_metas[0]):\n                img_shape = img_meta['img_shape']\n                scale_factor = img_meta['scale_factor']\n                flip = img_meta['flip']\n                flip_direction = img_meta['flip_direction']\n                proposals[:, :4] = bbox_mapping(proposals[:, :4], img_shape,\n                                                scale_factor, flip,\n                                                flip_direction)\n        # TODO: remove this restriction\n        return proposal_list[0].cpu().numpy()\n\n    def show_result(self, data, result, dataset=None, top_k=20):\n        \"\"\"Show RPN proposals on the image.\n\n        Although we assume batch size is 1, this method supports arbitrary\n        batch size.\n        \"\"\"\n        img_tensor = data['img'][0]\n        img_metas = data['img_metas'][0].data[0]\n        imgs = tensor2imgs(img_tensor, **img_metas[0]['img_norm_cfg'])\n        assert len(imgs) == len(img_metas)\n        for img, img_meta in zip(imgs, img_metas):\n            h, w, _ = img_meta['img_shape']\n            img_show = img[:h, :w, :]\n            mmcv.imshow_bboxes(img_show, result, top_k=top_k)\n"
  },
  {
    "path": "code/mmdet/models/detectors/single_stage.py",
    "content": "import torch.nn as nn\n\nfrom mmdet.core import bbox2result\nfrom ..builder import DETECTORS, build_backbone, build_head, build_neck\nfrom .base import BaseDetector\n\n\n@DETECTORS.register_module()\nclass SingleStageDetector(BaseDetector):\n    \"\"\"Base class for single-stage detectors.\n\n    Single-stage detectors directly and densely predict bounding boxes on the\n    output features of the backbone+neck.\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck=None,\n                 bbox_head=None,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(SingleStageDetector, self).__init__()\n        self.backbone = build_backbone(backbone)\n        if neck is not None:\n            self.neck = build_neck(neck)\n        bbox_head.update(train_cfg=train_cfg)\n        bbox_head.update(test_cfg=test_cfg)\n        self.bbox_head = build_head(bbox_head)\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n        self.init_weights(pretrained=pretrained)\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in detector\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        super(SingleStageDetector, self).init_weights(pretrained)\n        self.backbone.init_weights(pretrained=pretrained)\n        if self.with_neck:\n            if isinstance(self.neck, nn.Sequential):\n                for m in self.neck:\n                    m.init_weights()\n            else:\n                self.neck.init_weights()\n        self.bbox_head.init_weights()\n\n    def extract_feat(self, img):\n        \"\"\"Directly extract features from the backbone+neck\n        \"\"\"\n        x = self.backbone(img)\n        if self.with_neck:\n            x = self.neck(x)\n        return x\n\n    def forward_dummy(self, img):\n        \"\"\"Used for computing network flops.\n\n        See `mmdetection/tools/get_flops.py`\n        \"\"\"\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x)\n        return outs\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None):\n        \"\"\"\n        Args:\n            img (Tensor): Input images of shape (N, C, H, W).\n                Typically these should be mean centered and std scaled.\n            img_metas (list[dict]): A List of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                :class:`mmdet.datasets.pipelines.Collect`.\n            gt_bboxes (list[Tensor]): Each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): Class indices corresponding to each box\n            gt_bboxes_ignore (None | list[Tensor]): Specify which bounding\n                boxes can be ignored when computing the loss.\n\n        Returns:\n            dict[str, Tensor]: A dictionary of loss components.\n        \"\"\"\n        x = self.extract_feat(img)\n        losses = self.bbox_head.forward_train(x, img_metas, gt_bboxes,\n                                              gt_labels, gt_bboxes_ignore)\n        return losses\n\n    def simple_test(self, img, img_metas, rescale=False):\n        \"\"\"Test function without test time augmentation\n\n        Args:\n            imgs (list[torch.Tensor]): List of multiple images\n            img_metas (list[dict]): List of image information.\n            rescale (bool, optional): Whether to rescale the results.\n                Defaults to False.\n\n        Returns:\n            np.ndarray: proposals\n        \"\"\"\n        x = self.extract_feat(img)\n        outs = self.bbox_head(x)\n        bbox_list = self.bbox_head.get_bboxes(\n            *outs, img_metas, rescale=rescale)\n        bbox_results = [\n            bbox2result(det_bboxes, det_labels, self.bbox_head.num_classes)\n            for det_bboxes, det_labels in bbox_list\n        ]\n        return bbox_results[0]\n\n    def aug_test(self, imgs, img_metas, rescale=False):\n        \"\"\"Test function with test time augmentation\"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "code/mmdet/models/detectors/two_stage.py",
    "content": "import torch\nimport torch.nn as nn\n\n# from mmdet.core import bbox2result, bbox2roi, build_assigner, build_sampler\nfrom ..builder import DETECTORS, build_backbone, build_head, build_neck\nfrom .base import BaseDetector\n\n\n@DETECTORS.register_module()\nclass TwoStageDetector(BaseDetector):\n    \"\"\"Base class for two-stage detectors.\n\n    Two-stage detectors typically consisting of a region proposal network and a\n    task-specific regression head.\n    \"\"\"\n\n    def __init__(self,\n                 backbone,\n                 neck=None,\n                 rpn_head=None,\n                 roi_head=None,\n                 train_cfg=None,\n                 test_cfg=None,\n                 pretrained=None):\n        super(TwoStageDetector, self).__init__()\n        self.backbone = build_backbone(backbone)\n\n        if neck is not None:\n            self.neck = build_neck(neck)\n\n        if rpn_head is not None:\n            rpn_train_cfg = train_cfg.rpn if train_cfg is not None else None\n            rpn_head_ = rpn_head.copy()\n            rpn_head_.update(train_cfg=rpn_train_cfg, test_cfg=test_cfg.rpn)\n            self.rpn_head = build_head(rpn_head_)\n\n        if roi_head is not None:\n            # update train and test cfg here for now\n            # TODO: refactor assigner & sampler\n            rcnn_train_cfg = train_cfg.rcnn if train_cfg is not None else None\n            roi_head.update(train_cfg=rcnn_train_cfg)\n            roi_head.update(test_cfg=test_cfg.rcnn)\n            self.roi_head = build_head(roi_head)\n\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n\n        self.init_weights(pretrained=pretrained)\n\n    @property\n    def with_rpn(self):\n        \"\"\"bool: whether the detector has RPN\"\"\"\n        return hasattr(self, 'rpn_head') and self.rpn_head is not None\n\n    @property\n    def with_roi_head(self):\n        \"\"\"bool: whether the detector has a RoI head\"\"\"\n        return hasattr(self, 'roi_head') and self.roi_head is not None\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in detector\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        super(TwoStageDetector, self).init_weights(pretrained)\n        self.backbone.init_weights(pretrained=pretrained)\n        if self.with_neck:\n            if isinstance(self.neck, nn.Sequential):\n                for m in self.neck:\n                    m.init_weights()\n            else:\n                self.neck.init_weights()\n        if self.with_rpn:\n            self.rpn_head.init_weights()\n        if self.with_roi_head:\n            self.roi_head.init_weights(pretrained)\n\n    def extract_feat(self, img):\n        \"\"\"Directly extract features from the backbone+neck\n        \"\"\"\n        x = self.backbone(img)\n        if self.with_neck:\n            x = self.neck(x)\n        return x\n\n    def forward_dummy(self, img):\n        \"\"\"Used for computing network flops.\n\n        See `mmdetection/tools/get_flops.py`\n        \"\"\"\n        outs = ()\n        # backbone\n        x = self.extract_feat(img)\n        # rpn\n        if self.with_rpn:\n            rpn_outs = self.rpn_head(x)\n            outs = outs + (rpn_outs, )\n        proposals = torch.randn(1000, 4).to(img.device)\n        # roi_head\n        roi_outs = self.roi_head.forward_dummy(x, proposals)\n        outs = outs + (roi_outs, )\n        return outs\n\n    def forward_train(self,\n                      img,\n                      img_metas,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None,\n                      proposals=None,\n                      **kwargs):\n        \"\"\"\n        Args:\n            img (Tensor): of shape (N, C, H, W) encoding input images.\n                Typically these should be mean centered and std scaled.\n\n            img_metas (list[dict]): list of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                `mmdet/datasets/pipelines/formatting.py:Collect`.\n\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n\n            gt_labels (list[Tensor]): class indices corresponding to each box\n\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n            gt_masks (None | Tensor) : true segmentation masks for each box\n                used if the architecture supports a segmentation task.\n\n            proposals : override rpn proposals with custom proposals. Use when\n                `with_rpn` is False.\n\n        Returns:\n            dict[str, Tensor]: a dictionary of loss components\n        \"\"\"\n        x = self.extract_feat(img)\n\n        losses = dict()\n\n        # RPN forward and loss\n        if self.with_rpn:\n            proposal_cfg = self.train_cfg.get('rpn_proposal',\n                                              self.test_cfg.rpn)\n            rpn_losses, proposal_list = self.rpn_head.forward_train(\n                x,\n                img_metas,\n                gt_bboxes,\n                gt_labels=None,\n                gt_bboxes_ignore=gt_bboxes_ignore,\n                proposal_cfg=proposal_cfg)\n            losses.update(rpn_losses)\n        else:\n            proposal_list = proposals\n\n        roi_losses = self.roi_head.forward_train(x, img_metas, proposal_list,\n                                                 gt_bboxes, gt_labels,\n                                                 gt_bboxes_ignore, gt_masks,\n                                                 **kwargs)\n        losses.update(roi_losses)\n\n        return losses\n\n    async def async_simple_test(self,\n                                img,\n                                img_meta,\n                                proposals=None,\n                                rescale=False):\n        \"\"\"Async test without augmentation.\"\"\"\n        assert self.with_bbox, 'Bbox head must be implemented.'\n        x = self.extract_feat(img)\n\n        if proposals is None:\n            proposal_list = await self.rpn_head.async_simple_test_rpn(\n                x, img_meta)\n        else:\n            proposal_list = proposals\n\n        return await self.roi_head.async_simple_test(\n            x, proposal_list, img_meta, rescale=rescale)\n\n    def simple_test(self, img, img_metas, proposals=None, rescale=False):\n        \"\"\"Test without augmentation.\"\"\"\n        assert self.with_bbox, 'Bbox head must be implemented.'\n\n        x = self.extract_feat(img)\n\n        if proposals is None:\n            proposal_list = self.rpn_head.simple_test_rpn(x, img_metas)\n        else:\n            proposal_list = proposals\n\n        return self.roi_head.simple_test(\n            x, proposal_list, img_metas, rescale=rescale)\n\n    def aug_test(self, imgs, img_metas, rescale=False):\n        \"\"\"Test with augmentations.\n\n        If rescale is False, then returned bboxes and masks will fit the scale\n        of imgs[0].\n        \"\"\"\n        # recompute feats to save memory\n        x = self.extract_feats(imgs)\n        proposal_list = self.rpn_head.aug_test_rpn(x, img_metas)\n        return self.roi_head.aug_test(\n            x, proposal_list, img_metas, rescale=rescale)\n"
  },
  {
    "path": "code/mmdet/models/losses/__init__.py",
    "content": "from .accuracy import Accuracy, accuracy\nfrom .ae_loss import AssociativeEmbeddingLoss\nfrom .balanced_l1_loss import BalancedL1Loss, balanced_l1_loss\nfrom .cross_entropy_loss import (CrossEntropyLoss, binary_cross_entropy,\n                                 cross_entropy, mask_cross_entropy)\nfrom .chamfer_loss import ChamferLoss2D\nfrom .focal_loss import FocalLoss, sigmoid_focal_loss, SEPFocalLoss\nfrom .gaussian_focal_loss import GaussianFocalLoss\nfrom .gfocal_loss import DistributionFocalLoss, QualityFocalLoss\nfrom .ghm_loss import GHMC, GHMR\nfrom .iou_loss import (BoundedIoULoss, CIoULoss, DIoULoss, GIoULoss, IoULoss, bounded_iou_loss,\n                       iou_loss)\nfrom .mse_loss import MSELoss, mse_loss\nfrom .pisa_loss import carl_loss, isr_p\nfrom .smooth_l1_loss import L1Loss, SmoothL1Loss, l1_loss, smooth_l1_loss\nfrom .utils import reduce_loss, weight_reduce_loss, weighted_loss\nfrom .cross_iou_loss import (CrossIOULoss, cross_iou_loss)\n\n__all__ = [\n    'accuracy', 'Accuracy', 'cross_entropy', 'binary_cross_entropy',\n    'mask_cross_entropy', 'CrossEntropyLoss', 'sigmoid_focal_loss',\n    'FocalLoss', 'smooth_l1_loss', 'SmoothL1Loss', 'balanced_l1_loss',\n    'BalancedL1Loss', 'mse_loss', 'MSELoss', 'iou_loss', 'bounded_iou_loss',\n    'IoULoss', 'BoundedIoULoss', 'GIoULoss', 'CIoULoss', 'DIoULoss', 'GHMC', 'GHMR', 'reduce_loss',\n    'weight_reduce_loss', 'weighted_loss', 'L1Loss', 'l1_loss', 'isr_p',\n    'carl_loss', 'AssociativeEmbeddingLoss', 'GaussianFocalLoss',\n    'QualityFocalLoss', 'DistributionFocalLoss', 'SEPFocalLoss', 'ChamferLoss2D',\n    'CrossIOULoss', 'cross_iou_loss'\n]\n"
  },
  {
    "path": "code/mmdet/models/losses/accuracy.py",
    "content": "import torch.nn as nn\n\n\ndef accuracy(pred, target, topk=1):\n    \"\"\"Calculate accuracy according to the prediction and target\n\n    Args:\n        pred (torch.Tensor): The model prediction.\n        target (torch.Tensor): The target of each prediction\n        topk (int | tuple[int], optional): If the predictions in ``topk``\n            matches the target, the predictions will be regarded as\n            correct ones. Defaults to 1.\n\n    Returns:\n        float | tuple[float]: If the input ``topk`` is a single integer,\n            the function will return a single float as accuracy. If\n            ``topk`` is a tuple containing multiple integers, the\n            function will return a tuple containing accuracies of\n            each ``topk`` number.\n    \"\"\"\n    assert isinstance(topk, (int, tuple))\n    if isinstance(topk, int):\n        topk = (topk, )\n        return_single = True\n    else:\n        return_single = False\n\n    maxk = max(topk)\n    _, pred_label = pred.topk(maxk, dim=1)\n    pred_label = pred_label.t()\n    correct = pred_label.eq(target.view(1, -1).expand_as(pred_label))\n\n    res = []\n    for k in topk:\n        correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)\n        res.append(correct_k.mul_(100.0 / pred.size(0)))\n    return res[0] if return_single else res\n\n\nclass Accuracy(nn.Module):\n\n    def __init__(self, topk=(1, )):\n        \"\"\"Module to calculate the accuracy\n\n        Args:\n            topk (tuple, optional): The criterion used to calculate the\n                accuracy. Defaults to (1,).\n        \"\"\"\n        super().__init__()\n        self.topk = topk\n\n    def forward(self, pred, target):\n        \"\"\"Forward function to calculate accuracy\n\n        Args:\n            pred (torch.Tensor): Prediction of models.\n            target (torch.Tensor): Target for each prediction.\n\n        Returns:\n            tuple[float]: The accuracies under different topk criterions.\n        \"\"\"\n        return accuracy(pred, target, self.topk)\n"
  },
  {
    "path": "code/mmdet/models/losses/ae_loss.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..builder import LOSSES\n\n\ndef ae_loss_per_image(tl_preds, br_preds, match):\n    \"\"\"Associative Embedding Loss in one image.\n\n    Associative Embedding Loss including two parts: pull loss and push loss.\n    Pull loss makes embedding vectors from same object closer to each other.\n    Push loss distinguish embedding vector from different objects, and makes\n        the gap between them is large enough.\n\n    During computing, usually there are 3 cases:\n        - no object in image: both pull loss and push loss will be 0.\n        - one object in image: push loss will be 0 and pull loss is computed\n            by the two corner of the only object.\n        - more than one objects in image: pull loss is computed by corner pairs\n            from each object, push loss is computed by each object with all\n            other objects. We use confusion matrix with 0 in diagonal to\n            compute the push loss.\n\n    Args:\n        tl_preds (tensor): Embedding feature map of left-top corner.\n        br_preds (tensor): Embedding feature map of bottim-right corner.\n        match (list): Downsampled coordinates pair of each ground truth box.\n    \"\"\"\n\n    tl_list, br_list, me_list = [], [], []\n    if len(match) == 0:  # no object in image\n        pull_loss = tl_preds.sum()[None] * 0.\n        push_loss = tl_preds.sum()[None] * 0.\n    else:\n        for m in match:\n            [tl_y, tl_x], [br_y, br_x] = m\n            tl_e = tl_preds[:, tl_y, tl_x].view(-1, 1)\n            br_e = br_preds[:, br_y, br_x].view(-1, 1)\n            tl_list.append(tl_e)\n            br_list.append(br_e)\n            me_list.append((tl_e + br_e) / 2.0)\n\n        tl_list = torch.cat(tl_list)\n        br_list = torch.cat(br_list)\n        me_list = torch.cat(me_list)\n\n        assert tl_list.size() == br_list.size()\n\n        # N is object number in image, M is dimension of embedding vector\n        N, M = tl_list.size()\n\n        pull_loss = (tl_list - me_list).pow(2) + (br_list - me_list).pow(2)\n        pull_loss = pull_loss.sum() / N\n\n        margin = 1  # exp setting of CornerNet, details in section 3.3 of paper\n\n        # confusion matrix of push loss\n        conf_mat = me_list.expand((N, N, M)).permute(1, 0, 2) - me_list\n        conf_weight = 1 - torch.eye(N).type_as(me_list)\n        conf_mat = conf_weight * (margin - conf_mat.sum(-1).abs())\n\n        if N > 1:  # more than one object in current image\n            push_loss = F.relu(conf_mat).sum() / (N * (N - 1))\n\n    return pull_loss, push_loss\n\n\n@LOSSES.register_module()\nclass AssociativeEmbeddingLoss(nn.Module):\n    \"\"\"Associative Embedding Loss.\n\n    More details can be found in\n    `Associative Embedding <https://arxiv.org/abs/1611.05424>`_ and\n    `CornerNet <https://arxiv.org/abs/1808.01244>`_ .\n    Code is modified from `kp_utils.py <https://github.com/princeton-vl/CornerNet/blob/master/models/py_utils/kp_utils.py#L180>`_  # noqa: E501\n\n    Args:\n        pull_weight (float): Loss weight for corners from same object.\n        push_weight (float): Loss weight for corners from different object.\n    \"\"\"\n\n    def __init__(self, pull_weight=0.25, push_weight=0.25):\n        super(AssociativeEmbeddingLoss, self).__init__()\n        self.pull_weight = pull_weight\n        self.push_weight = push_weight\n\n    def forward(self, pred, target, match):\n        \"\"\"Forward function\"\"\"\n        batch = pred.size(0)\n        pull_all, push_all = 0.0, 0.0\n        for i in range(batch):\n            pull, push = ae_loss_per_image(pred[i], target[i], match[i])\n\n            pull_all += self.pull_weight * pull\n            push_all += self.push_weight * push\n\n        return pull_all, push_all\n"
  },
  {
    "path": "code/mmdet/models/losses/balanced_l1_loss.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n\n@weighted_loss\ndef balanced_l1_loss(pred,\n                     target,\n                     beta=1.0,\n                     alpha=0.5,\n                     gamma=1.5,\n                     reduction='mean'):\n    \"\"\"Calculate balanced L1 loss\n\n    Please see the `Libra R-CNN <https://arxiv.org/pdf/1904.02701.pdf>`_\n\n    Args:\n        pred (torch.Tensor): The prediction with shape (N, 4).\n        target (torch.Tensor): The learning target of the prediction with\n            shape (N, 4).\n        beta (float): The loss is a piecewise function of prediction and target\n            and ``beta`` serves as a threshold for the difference between the\n            prediction and target. Defaults to 1.0.\n        alpha (float): The denominator ``alpha`` in the balanced L1 loss.\n            Defaults to 0.5.\n        gamma (float): The ``gamma`` in the balanced L1 loss.\n            Defaults to 1.5.\n        reduction (str, optional): The method that reduces the loss to a\n            scalar. Options are \"none\", \"mean\" and \"sum\".\n\n    Returns:\n        torch.Tensor: The calculated loss\n    \"\"\"\n    assert beta > 0\n    assert pred.size() == target.size() and target.numel() > 0\n\n    diff = torch.abs(pred - target)\n    b = np.e**(gamma / alpha) - 1\n    loss = torch.where(\n        diff < beta, alpha / b *\n        (b * diff + 1) * torch.log(b * diff / beta + 1) - alpha * diff,\n        gamma * diff + gamma / b - alpha * beta)\n\n    return loss\n\n\n@LOSSES.register_module()\nclass BalancedL1Loss(nn.Module):\n    \"\"\"Balanced L1 Loss\n\n    arXiv: https://arxiv.org/pdf/1904.02701.pdf (CVPR 2019)\n\n    Args:\n        alpha (float): The denominator ``alpha`` in the balanced L1 loss.\n            Defaults to 0.5.\n        gamma (float): The ``gamma`` in the balanced L1 loss. Defaults to 1.5.\n        beta (float, optional): The loss is a piecewise function of prediction\n            and target. ``beta`` serves as a threshold for the difference\n            between the prediction and target. Defaults to 1.0.\n        reduction (str, optional): The method that reduces the loss to a\n            scalar. Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float, optional): The weight of the loss. Defaults to 1.0\n\n    \"\"\"\n\n    def __init__(self,\n                 alpha=0.5,\n                 gamma=1.5,\n                 beta=1.0,\n                 reduction='mean',\n                 loss_weight=1.0):\n        super(BalancedL1Loss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.beta = beta\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        \"\"\"Forward function of loss\n\n        Args:\n            pred (torch.Tensor): The prediction with shape (N, 4).\n            target (torch.Tensor): The learning target of the prediction with\n                shape (N, 4).\n            weight (torch.Tensor, optional): Sample-wise loss weight with\n                shape (N, ).\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Options are \"none\", \"mean\" and \"sum\".\n\n        Returns:\n            torch.Tensor: The calculated loss\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_bbox = self.loss_weight * balanced_l1_loss(\n            pred,\n            target,\n            weight,\n            alpha=self.alpha,\n            gamma=self.gamma,\n            beta=self.beta,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss_bbox\n"
  },
  {
    "path": "code/mmdet/models/losses/chamfer_loss.py",
    "content": "import torch\nimport torch.nn as nn\n\nfrom mmdet.ops.chamfer_2d import Chamfer2D\nfrom ..builder import LOSSES\n\n\n@LOSSES.register_module()\nclass ChamferLoss2D(nn.Module):\n    def __init__(self, use_cuda=True, loss_weight=1.0, eps=1e-12):\n        super(ChamferLoss2D, self).__init__()\n        self.use_cuda = use_cuda\n        self.loss_weight = loss_weight\n        self.eps = eps\n\n    def forward(self, point_set_1, point_set_2):\n        \"\"\"\n        Computation of optimal transport distance via sinkhorn algorithm.\n        - Input:\n            - point_set_1:\ttorch.Tensor\t[..., num_points_1, point_dim] e.g. [bs, h, w, 1000, 2]; [bs, 1000, 2]; [1000, 2]\n            - point_set_2:\ttorch.Tensor\t[..., num_points_2, point_dim]\n                    (the dimensions of point_set_2 except the last two should be the same as point_set_1)\n        - Output:\n            - distance:\ttorch.Tensor\t[...] e.g. [bs, h, w]; [bs]; []\n        \"\"\"\n        chamfer = Chamfer2D() if self.use_cuda else ChamferDistancePytorch()\n\n        assert point_set_1.dim() == point_set_2.dim()\n        assert point_set_1.shape[-1] == point_set_2.shape[-1]\n        if point_set_1.dim() <= 3:\n            if self.use_cuda:\n                dist1, dist2, _, _ = chamfer(point_set_1, point_set_2)\n                dist1 = torch.sqrt(torch.clamp(dist1, self.eps))\n                dist2 = torch.sqrt(torch.clamp(dist2, self.eps))\n                dist = (dist1.mean(-1) + dist2.mean(-1)) / 2.0\n            else:\n                dist = chamfer(point_set_1, point_set_2)\n        else:\n            point_dim = point_set_1.shape[-1]\n            num_points_1, num_points_2 = point_set_1.shape[-2], point_set_2.shape[-2]\n            point_set_1t = point_set_1.reshape((-1, num_points_1, point_dim))\n            point_set_2t = point_set_2.reshape((-1, num_points_2, point_dim))\n            if self.use_cuda:\n                dist1, dist2, _, _ = chamfer(point_set_1, point_set_2)\n                dist1 = torch.sqrt(torch.clamp(dist1, self.eps))\n                dist2 = torch.sqrt(torch.clamp(dist2, self.eps))\n                dist_t = (dist1.mean(-1) + dist2.mean(-1)) / 2.0\n            else:\n                dist_t = chamfer(point_set_1t, point_set_2t)\n            dist_dim = point_set_1.shape[:-2]\n            dist = dist_t.reshape(dist_dim)\n        return dist * self.loss_weight\n\n\n# Adapted from https://github.com/dfdazac/wassdistance\nclass ChamferDistancePytorch(nn.Module):\n    r\"\"\"\n    Shape:\n        - Input: :math:`(N, P_1, D_1)`, :math:`(N, P_2, D_2)`\n        - Output: :math:`(N)` or :math:`()`, depending on `reduction`\n    \"\"\"\n\n    def __init__(self, reduction='none'):\n        super(ChamferDistancePytorch, self).__init__()\n        self.reduction = reduction\n\n    def forward(self, x, y):\n        if x.shape[0] == 0:\n            return x.sum()\n        # The Sinkhorn algorithm takes as input three variables :\n        C = self._cost_matrix(x, y)  # Wasserstein cost function\n\n        # compute chamfer loss\n        min_x2y, _ = C.min(-1)\n        d1 = min_x2y.mean(-1)\n        min_y2x, _ = C.min(-2)\n        d2 = min_y2x.mean(-1)\n        cost = (d1 + d2) / 2.0\n\n        if self.reduction == 'mean':\n            cost = cost.mean()\n        elif self.reduction == 'sum':\n            cost = cost.sum()\n        return cost\n\n    @staticmethod\n    def _cost_matrix(x, y, p=2):\n        \"Returns the matrix of $|x_i-y_j|^p$.\"\n        x_col = x.unsqueeze(-2)\n        y_lin = y.unsqueeze(-3)\n        C = torch.norm(x_col - y_lin, 2, -1)\n        return C\n"
  },
  {
    "path": "code/mmdet/models/losses/cross_entropy_loss.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..builder import LOSSES\nfrom .utils import weight_reduce_loss\n\n\ndef cross_entropy(pred,\n                  label,\n                  weight=None,\n                  reduction='mean',\n                  avg_factor=None,\n                  class_weight=None):\n    \"\"\"Calculate the CrossEntropy loss.\n\n    Args:\n        pred (torch.Tensor): The prediction with shape (N, C), C is the number\n            of classes.\n        label (torch.Tensor): The learning label of the prediction.\n        weight (torch.Tensor, optional): Sample-wise loss weight.\n        reduction (str, optional): The method used to reduce the loss.\n        avg_factor (int, optional): Average factor that is used to average\n            the loss. Defaults to None.\n        class_weight (list[float], optional): The weight for each class.\n\n    Returns:\n        torch.Tensor: The calculated loss\n    \"\"\"\n    # element-wise losses\n    loss = F.cross_entropy(pred, label, weight=class_weight, reduction='none')\n\n    # apply weights and do the reduction\n    if weight is not None:\n        weight = weight.float()\n    loss = weight_reduce_loss(\n        loss, weight=weight, reduction=reduction, avg_factor=avg_factor)\n\n    return loss\n\n\ndef _expand_binary_labels(labels, label_weights, label_channels):\n    # Caution: this function should only be used in RPN\n    # in other files such as in ghm_loss, the _expand_binary_labels\n    # is used for multi-class classification.\n    bin_labels = labels.new_full((labels.size(0), label_channels), 0)\n    inds = torch.nonzero(labels >= 1, as_tuple=False).squeeze()\n    if inds.numel() > 0:\n        bin_labels[inds, labels[inds] - 1] = 1\n    if label_weights is None:\n        bin_label_weights = None\n    else:\n        bin_label_weights = label_weights.view(-1, 1).expand(\n            label_weights.size(0), label_channels)\n    return bin_labels, bin_label_weights\n\n\ndef binary_cross_entropy(pred,\n                         label,\n                         weight=None,\n                         reduction='mean',\n                         avg_factor=None,\n                         class_weight=None):\n    \"\"\"Calculate the binary CrossEntropy loss.\n\n    Args:\n        pred (torch.Tensor): The prediction with shape (N, 1).\n        label (torch.Tensor): The learning label of the prediction.\n        weight (torch.Tensor, optional): Sample-wise loss weight.\n        reduction (str, optional): The method used to reduce the loss.\n            Options are \"none\", \"mean\" and \"sum\".\n        avg_factor (int, optional): Average factor that is used to average\n            the loss. Defaults to None.\n        class_weight (list[float], optional): The weight for each class.\n\n    Returns:\n        torch.Tensor: The calculated loss\n    \"\"\"\n    if pred.dim() != label.dim():\n        label, weight = _expand_binary_labels(label, weight, pred.size(-1))\n\n    # weighted element-wise losses\n    if weight is not None:\n        weight = weight.float()\n    loss = F.binary_cross_entropy_with_logits(\n        pred, label.float(), weight=class_weight, reduction='none')\n    # do the reduction for the weighted loss\n    loss = weight_reduce_loss(\n        loss, weight, reduction=reduction, avg_factor=avg_factor)\n\n    return loss\n\n\ndef mask_cross_entropy(pred,\n                       target,\n                       label,\n                       reduction='mean',\n                       avg_factor=None,\n                       class_weight=None):\n    \"\"\"Calculate the CrossEntropy loss for masks.\n\n    Args:\n        pred (torch.Tensor): The prediction with shape (N, C), C is the number\n            of classes.\n        target (torch.Tensor): The learning label of the prediction.\n        label (torch.Tensor): ``label`` indicates the class label of the mask'\n            corresponding object. This will be used to select the mask in the\n            of the class which the object belongs to when the mask prediction\n            if not class-agnostic.\n        reduction (str, optional): The method used to reduce the loss.\n            Options are \"none\", \"mean\" and \"sum\".\n        avg_factor (int, optional): Average factor that is used to average\n            the loss. Defaults to None.\n        class_weight (list[float], optional): The weight for each class.\n\n    Returns:\n        torch.Tensor: The calculated loss\n    \"\"\"\n    # TODO: handle these two reserved arguments\n    assert reduction == 'mean' and avg_factor is None\n    num_rois = pred.size()[0]\n    inds = torch.arange(0, num_rois, dtype=torch.long, device=pred.device)\n    pred_slice = pred[inds, label].squeeze(1)\n    return F.binary_cross_entropy_with_logits(\n        pred_slice, target, weight=class_weight, reduction='mean')[None]\n\n\n@LOSSES.register_module()\nclass CrossEntropyLoss(nn.Module):\n\n    def __init__(self,\n                 use_sigmoid=False,\n                 use_mask=False,\n                 reduction='mean',\n                 class_weight=None,\n                 loss_weight=1.0):\n        \"\"\"CrossEntropyLoss\n\n        Args:\n            use_sigmoid (bool, optional): Whether the prediction uses sigmoid\n                of softmax. Defaults to False.\n            use_mask (bool, optional): Whether to use mask cross entropy loss.\n                Defaults to False.\n            reduction (str, optional): . Defaults to 'mean'.\n                Options are \"none\", \"mean\" and \"sum\".\n            class_weight (list[float], optional): Weight of each class.\n                Defaults to None.\n            loss_weight (float, optional): Weight of the loss. Defaults to 1.0.\n        \"\"\"\n        super(CrossEntropyLoss, self).__init__()\n        assert (use_sigmoid is False) or (use_mask is False)\n        self.use_sigmoid = use_sigmoid\n        self.use_mask = use_mask\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n        self.class_weight = class_weight\n\n        if self.use_sigmoid:\n            self.cls_criterion = binary_cross_entropy\n        elif self.use_mask:\n            self.cls_criterion = mask_cross_entropy\n        else:\n            self.cls_criterion = cross_entropy\n\n    def forward(self,\n                cls_score,\n                label,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        \"\"\"Forward function.\n\n        Args:\n            cls_score (torch.Tensor): The prediction.\n            label (torch.Tensor): The learning label of the prediction.\n            weight (torch.Tensor, optional): Sample-wise loss weight.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction (str, optional): The method used to reduce the loss.\n                Options are \"none\", \"mean\" and \"sum\".\n        Returns:\n            torch.Tensor: The calculated loss\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if self.class_weight is not None:\n            class_weight = cls_score.new_tensor(self.class_weight)\n        else:\n            class_weight = None\n        loss_cls = self.loss_weight * self.cls_criterion(\n            cls_score,\n            label,\n            weight,\n            class_weight=class_weight,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss_cls\n"
  },
  {
    "path": "code/mmdet/models/losses/cross_iou_loss.py",
    "content": "import pdb\nimport math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\ndef get_bbox_from_extreme(pred, anchor_pts):\n    pred_reshape = pred.view(pred.shape[0], -1, 2)\n    pred_pts, inds = torch.max(pred_reshape, dim=2)\n    neg_inds = inds == 0\n    pred_pts[neg_inds] *= -1\n    pred_pts = pred_pts.view(pred_pts.shape[0], -1, 2)\n    pred_y = pred_pts[:,:,0]\n    pred_x = pred_pts[:,:,1]\n    pred_pts_ = torch.stack([pred_x, pred_y], -1).reshape(-1, 10)\n\n    anchor_pts_repeat = anchor_pts[:, :2].repeat(1, 5)\n    pred_pts_ += anchor_pts_repeat\n    pred_pts_reshape = pred_pts_.view(pred_pts_.shape[0], -1, 2)\n\n    pred_pts_x = pred_pts_reshape[:,:,0]\n    pred_pts_y = pred_pts_reshape[:,:,1]\n\n    bbox_left   = pred_pts_x[:,1].unsqueeze(1)\n    bbox_right  = pred_pts_x[:,3].unsqueeze(1)\n    bbox_up     = pred_pts_y[:,0].unsqueeze(1)\n    bbox_bottom = pred_pts_y[:,2].unsqueeze(1)\n\n    bbox_pred = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], dim=1)\n    return bbox_pred\n\ndef get_bbox_from_polygon(pred, anchor_pts):\n    num_poly_pts = int(pred[:,:-4, ...].size(1)/4)\n    pred_reshape = pred[:,:-4,...].view(pred.shape[0], -1, 2, *pred.shape[2:])\n    pred_pts, inds = torch.max(pred_reshape, dim=2)\n    neg_inds = inds == 0\n    pred_pts[neg_inds] *= -1\n    pred_pts = pred_pts.view(pred_pts.shape[0], -1, 2)\n    pred_y = pred_pts[:,:,0]\n    pred_x = pred_pts[:,:,1]\n    pred_pts_ = torch.stack([pred_x, pred_y], -1).reshape(-1, num_poly_pts*2)\n\n    anchor_pts_repeat = anchor_pts[:, :2].repeat(1, num_poly_pts)\n    pred_pts_ += anchor_pts_repeat\n\n    pred_pts_reshape = pred_pts_.view(pred_pts_.shape[0], -1, 2)\n    pred_pts_x = pred_pts_reshape[:,:,0]\n    pred_pts_y = pred_pts_reshape[:,:,1]\n\n    polys_xmin = pred_pts_x.min(1)[0]\n    polys_ymin = pred_pts_y.min(1)[0]\n    polys_xmax = pred_pts_x.max(1)[0]\n    polys_ymax = pred_pts_y.max(1)[0]\n\n    bbox_pred = torch.stack([polys_xmin, polys_ymin, polys_xmax, polys_ymax], 1)\n    return bbox_pred\n\n@weighted_loss\ndef cross_iou_loss(pred, target, loss_type=None, anchor_pts=None, vs=None,\n                   bbox_gt=None, pos_inds=None, eps=1e-6, alpha=0.2, stride=9):\n    \n    neg_inds = ~pos_inds\n    target[neg_inds] = alpha*target[pos_inds]\n\n    if loss_type == 'polygon':\n        overlaps = []\n        total = torch.stack([pred, target], -1)\n        total_reshape = total.reshape(total.size(0), -1, 4, total.size(-1))\n        for i in range(stride):\n            total_ = total_reshape[:, i::stride, ...].reshape(total.size(0), -1, total.size(-1))\n            l_max = total_.max(dim=2)[0]\n            l_min = total_.min(dim=2)[0]\n            overlaps.append(l_min.sum(dim=1)/l_max.sum(dim=1))\n        overlaps = torch.stack(overlaps, -1).sum(-1)/stride\n\n    elif loss_type == 'bbox':\n        total = torch.stack([pred, target], -1)\n        l_max = total.max(dim=2)[0]\n        l_min = total.min(dim=2)[0]\n\n        overlaps = l_min.sum(dim=1)/l_max.sum(dim=1)\n    else: #keypoint\n        target_reshape = target.reshape(target.size(0), -1, 2)\n        pred_reshape   = pred.reshape(pred.size(0), -1, 2)\n        total = torch.stack([pred_reshape, target_reshape], -1)\n        l_max = total.max(dim=-1)[0].clamp(min=eps)\n        l_min = total.min(dim=-1)[0]\n        overlaps = l_min.sum(dim=-1)/l_max.sum(dim=-1)\n        vs[vs>0]=1\n        vs_stack = torch.stack((vs, vs), 2).reshape(vs.size(0), -1)\n        overlaps[:,:-2] *= vs_stack\n\n        overlaps = overlaps.sum(-1)/total.size(1)\n\n    if loss_type == 'bbox':\n        bbox_pred = get_bbox_from_extreme(pred, anchor_pts)\n    elif loss_type == 'polygon':\n        bbox_pred = get_bbox_from_polygon(pred, anchor_pts)\n\n    if loss_type != 'keypoint':\n        enclose_x1y1 = torch.min(bbox_pred[:, :2], bbox_gt[:, :2])\n        enclose_x2y2 = torch.max(bbox_pred[:, 2:], bbox_gt[:, 2:])\n        enclose_wh   = (enclose_x2y2 - enclose_x1y1).clamp(min=0)\n\n        cw = enclose_wh[:, 0]\n        ch = enclose_wh[:, 1]\n\n        c2 = cw**2 + ch**2 +eps\n\n        b1_x1, b1_y1 = bbox_pred[:, 0], bbox_pred[:, 1]\n        b1_x2, b1_y2 = bbox_pred[:, 2], bbox_pred[:, 3]\n        b2_x1, b2_y1 = bbox_gt[:, 0], bbox_gt[:, 1]\n        b2_x2, b2_y2 = bbox_gt[:, 2], bbox_gt[:, 3]\n\n        w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps\n        w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps\n\n        left = ((b2_x1 + b2_x2) - (b1_x1 + b1_x2))**2 / 4\n        right = ((b2_y1 + b2_y2) - (b1_y1 + b1_y2))**2 / 4\n        rho2 = left + right\n\n        factor = 4 / math.pi**2\n        v = factor * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)\n\n        loss = 1 - (overlaps - (rho2 / c2 + v**2 / (1 - overlaps +v)))\n    else:\n        loss = 1 - overlaps\n\n    return loss\n\n@LOSSES.register_module()\nclass CrossIOULoss(nn.Module):\n\n    def __init__(self, eps=1e-6, reduction='mean', loss_weight=1.0, loss_type='bbox', alpha=0.2, stride=9):\n        super(CrossIOULoss, self).__init__()\n        self.eps = eps\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n        self.loss_type = loss_type\n        self.alpha = alpha\n        self.stride =stride\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        if weight is not None and not torch.any(weight>0):\n            return (pred * weight).sum() #0\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if weight is not None and weight.dim() > 1:\n            assert weight.shape == pred.shape\n            weight = weight.mean(-1)\n        loss = self.loss_weight * cross_iou_loss(\n            pred,\n            target,\n            weight,\n            loss_type=self.loss_type,\n            eps=self.eps,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            alpha = self.alpha,\n            stride = self.stride,\n            **kwargs)\n        return loss\n"
  },
  {
    "path": "code/mmdet/models/losses/focal_loss.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom mmdet.ops import sigmoid_focal_loss as _sigmoid_focal_loss\nfrom ..builder import LOSSES\nfrom .utils import weight_reduce_loss\n\n\n# This method is only for debugging\ndef py_sigmoid_focal_loss(pred,\n                          target,\n                          weight=None,\n                          gamma=2.0,\n                          alpha=0.25,\n                          reduction='mean',\n                          avg_factor=None):\n    \"\"\"PyTorch version of `Focal Loss <https://arxiv.org/abs/1708.02002>`_\n\n    Args:\n        pred (torch.Tensor): The prediction with shape (N, C), C is the\n            number of classes\n        target (torch.Tensor): The learning label of the prediction.\n        weight (torch.Tensor, optional): Sample-wise loss weight.\n        gamma (float, optional): The gamma for calculating the modulating\n            factor. Defaults to 2.0.\n        alpha (float, optional): A balanced form for Focal Loss.\n            Defaults to 0.25.\n        reduction (str, optional): The method used to reduce the loss into\n            a scalar. Defaults to 'mean'.\n        avg_factor (int, optional): Average factor that is used to average\n            the loss. Defaults to None.\n    \"\"\"\n    pred_sigmoid = pred.sigmoid()\n    target = target.type_as(pred)\n    pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target)\n    focal_weight = (alpha * target + (1 - alpha) *\n                    (1 - target)) * pt.pow(gamma)\n    loss = F.binary_cross_entropy_with_logits(\n        pred, target, reduction='none') * focal_weight\n    loss = weight_reduce_loss(loss, weight, reduction, avg_factor)\n    return loss\n\n\ndef separate_sigmoid_focal_loss(pred,\n                                target,\n                                weight=None,\n                                gamma=2.0,\n                                alpha=0.25,\n                                reduction='mean',\n                                avg_factor=None):\n    pred_sigmoid = pred.sigmoid()\n    target = target.type_as(pred)\n\n    pos_inds = target.eq(1)\n    neg_inds = target.lt(1)\n\n    pos_weights = weight[pos_inds]\n\n    pos_pred = pred_sigmoid[pos_inds]\n    neg_pred = pred_sigmoid[neg_inds]\n\n    pos_loss = -torch.log(pos_pred) * torch.pow(1 - pos_pred, gamma) * pos_weights * alpha\n    neg_loss = -torch.log(1 - neg_pred) * torch.pow(neg_pred, gamma) * (1 - alpha)\n\n    if pos_pred.nelement() == 0:\n        loss = neg_loss.sum() / avg_factor\n    else:\n        loss = pos_loss.sum() / pos_weights.sum() + neg_loss.sum() / avg_factor\n\n    return loss\n\n\ndef sigmoid_focal_loss(pred,\n                       target,\n                       weight=None,\n                       gamma=2.0,\n                       alpha=0.25,\n                       reduction='mean',\n                       avg_factor=None):\n    \"\"\"A warpper of cuda version\n    `Focal Loss <https://arxiv.org/abs/1708.02002>`_\n\n    Args:\n        pred (torch.Tensor): The prediction with shape (N, C), C is the number\n            of classes.\n        target (torch.Tensor): The learning label of the prediction.\n        weight (torch.Tensor, optional): Sample-wise loss weight.\n        gamma (float, optional): The gamma for calculating the modulating\n            factor. Defaults to 2.0.\n        alpha (float, optional): A balanced form for Focal Loss.\n            Defaults to 0.25.\n        reduction (str, optional): The method used to reduce the loss into\n            a scalar. Defaults to 'mean'. Options are \"none\", \"mean\" and \"sum\".\n        avg_factor (int, optional): Average factor that is used to average\n            the loss. Defaults to None.\n    \"\"\"\n    # Function.apply does not accept keyword arguments, so the decorator\n    # \"weighted_loss\" is not applicable\n    loss = _sigmoid_focal_loss(pred, target, gamma, alpha)\n    if weight is not None:\n        if weight.shape != loss.shape:\n            if weight.size(0) == loss.size(0):\n                # For most cases, weight is of shape (num_priors, ),\n                #  which means it does not have the second axis num_class\n                weight = weight.view(-1, 1)\n            else:\n                # Sometimes, weight per anchor per class is also needed. e.g.\n                #  in FSAF. But it may be flattened of shape\n                #  (num_priors x num_class, ), while loss is still of shape\n                #  (num_priors, num_class).\n                assert weight.numel() == loss.numel()\n                weight = weight.view(loss.size(0), -1)\n        assert weight.ndim == loss.ndim\n    loss = weight_reduce_loss(loss, weight, reduction, avg_factor)\n    return loss\n\n\n@LOSSES.register_module()\nclass FocalLoss(nn.Module):\n\n    def __init__(self,\n                 use_sigmoid=True,\n                 gamma=2.0,\n                 alpha=0.25,\n                 reduction='mean',\n                 loss_weight=1.0):\n        \"\"\"`Focal Loss <https://arxiv.org/abs/1708.02002>`_\n\n        Args:\n            use_sigmoid (bool, optional): Whether to the prediction is\n                used for sigmoid or softmax. Defaults to True.\n            gamma (float, optional): The gamma for calculating the modulating\n                factor. Defaults to 2.0.\n            alpha (float, optional): A balanced form for Focal Loss.\n                Defaults to 0.25.\n            reduction (str, optional): The method used to reduce the loss into\n                a scalar. Defaults to 'mean'. Options are \"none\", \"mean\" and\n                \"sum\".\n            loss_weight (float, optional): Weight of loss. Defaults to 1.0.\n        \"\"\"\n        super(FocalLoss, self).__init__()\n        assert use_sigmoid is True, 'Only sigmoid focal loss supported now.'\n        self.use_sigmoid = use_sigmoid\n        self.gamma = gamma\n        self.alpha = alpha\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        \"\"\"Forward function.\n\n        Args:\n            pred (torch.Tensor): The prediction.\n            target (torch.Tensor): The learning label of the prediction.\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Options are \"none\", \"mean\" and \"sum\".\n\n        Returns:\n            torch.Tensor: The calculated loss\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if self.use_sigmoid:\n            loss_cls = self.loss_weight * sigmoid_focal_loss(\n                pred,\n                target,\n                weight,\n                gamma=self.gamma,\n                alpha=self.alpha,\n                reduction=reduction,\n                avg_factor=avg_factor)\n        else:\n            raise NotImplementedError\n        return loss_cls\n\n\n@LOSSES.register_module()\nclass SEPFocalLoss(nn.Module):\n\n    def __init__(self,\n                 gamma=2.0,\n                 alpha=0.25,\n                 reduction='mean',\n                 loss_weight=1.0):\n        super(SEPFocalLoss, self).__init__()\n        self.gamma = gamma\n        self.alpha = alpha\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_cls = self.loss_weight * separate_sigmoid_focal_loss(\n            pred,\n            target,\n            weight,\n            gamma=self.gamma,\n            alpha=self.alpha,\n            reduction=reduction,\n            avg_factor=avg_factor)\n\n        return loss_cls\n"
  },
  {
    "path": "code/mmdet/models/losses/gaussian_focal_loss.py",
    "content": "import torch.nn as nn\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n\n@weighted_loss\ndef gaussian_focal_loss(pred, gaussian_target, alpha=2.0, gamma=4.0):\n    \"\"\"`Focal Loss <https://arxiv.org/abs/1708.02002>`_ for targets in\n    gaussian distribution.\n\n    Args:\n        pred (torch.Tensor): The prediction.\n        gaussian_target (torch.Tensor): The learning target of the prediction\n            in gaussian distribution.\n        alpha (float, optional): A balanced form for Focal Loss.\n            Defaults to 2.0.\n        gamma (float, optional): The gamma for calculating the modulating\n            factor. Defaults to 4.0.\n    \"\"\"\n    eps = 1e-12\n    pos_weights = gaussian_target.eq(1)\n    neg_weights = (1 - gaussian_target).pow(gamma)\n    pos_loss = -(pred + eps).log() * (1 - pred).pow(alpha) * pos_weights\n    neg_loss = -(1 - pred + eps).log() * pred.pow(alpha) * neg_weights\n    return pos_loss + neg_loss\n\n\n@LOSSES.register_module()\nclass GaussianFocalLoss(nn.Module):\n    \"\"\"GaussianFocalLoss is a variant of focal loss.\n\n    More details can be found in the `paper\n    <https://arxiv.org/abs/1808.01244>`_\n    Code is modified from `kp_utils.py\n    <https://github.com/princeton-vl/CornerNet/blob/master/models/py_utils/kp_utils.py#L152>`_  # noqa: E501\n    Please notice that the target in GaussianFocalLoss is a gaussian heatmap,\n    not 0/1 binary target.\n\n    Args:\n        alpha (float): Power of prediction.\n        gamma (float): Power of target for negtive samples.\n        reduction (str): Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float): Loss weight of current loss.\n    \"\"\"\n\n    def __init__(self,\n                 alpha=2.0,\n                 gamma=4.0,\n                 reduction='mean',\n                 loss_weight=1.0):\n        super(GaussianFocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        \"\"\"Forward function\n\n        Args:\n            pred (torch.Tensor): The prediction.\n            target (torch.Tensor): The learning target of the prediction\n                in gaussian distribution.\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Defaults to None.\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_reg = self.loss_weight * gaussian_focal_loss(\n            pred,\n            target,\n            weight,\n            alpha=self.alpha,\n            gamma=self.gamma,\n            reduction=reduction,\n            avg_factor=avg_factor)\n        return loss_reg\n"
  },
  {
    "path": "code/mmdet/models/losses/gfocal_loss.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n\n@weighted_loss\ndef quality_focal_loss(pred, target, beta=2.0):\n    \"\"\"Quality Focal Loss (QFL) is from\n    Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes\n    for Dense Object Detection\n    https://arxiv.org/abs/2006.04388\n\n    Args:\n        pred (torch.Tensor): Predicted joint representation of classification\n            and quality (IoU) estimation with shape (N, C), C is the number of\n            classes.\n        target (tuple([torch.Tensor])): Target category label with shape (N,)\n            and target quality label with shape (N,).\n        beta (float): The beta parameter for calculating the modulating factor.\n            Defaults to 2.0.\n\n    Return:\n        torch.Tensor: Loss tensor with shape (N,).\n    \"\"\"\n    assert len(target) == 2, \"\"\"target for QFL must be a tuple of two elements,\n        including category label and quality label, respectively\"\"\"\n    # label denotes the category id, score denotes the quality score\n    label, score = target\n\n    # negatives are supervised by 0 quality score\n    pred_sigmoid = pred.sigmoid()\n    scale_factor = pred_sigmoid\n    zerolabel = scale_factor.new_zeros(pred.shape)\n    loss = F.binary_cross_entropy_with_logits(\n        pred, zerolabel, reduction='none') * scale_factor.pow(beta)\n\n    # FG cat_id: [0, num_classes -1], BG cat_id: num_classes\n    bg_class_ind = pred.size(1)\n    pos = ((label >= 0) & (label < bg_class_ind)).nonzero().squeeze(1)\n    pos_label = label[pos].long()\n    # positives are supervised by bbox quality (IoU) score\n    scale_factor = score[pos] - pred_sigmoid[pos, pos_label]\n    loss[pos, pos_label] = F.binary_cross_entropy_with_logits(\n        pred[pos, pos_label], score[pos],\n        reduction='none') * scale_factor.abs().pow(beta)\n\n    loss = loss.sum(dim=1, keepdim=False)\n    return loss\n\n\n@weighted_loss\ndef distribution_focal_loss(pred, label):\n    \"\"\"Distribution Focal Loss (DFL) is from\n    Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes\n    for Dense Object Detection\n    https://arxiv.org/abs/2006.04388\n\n    Args:\n        pred (torch.Tensor): Predicted general distribution of bounding boxes\n            (before softmax) with shape (N, n+1), n is the max value of the\n            integral set `{0, ..., n}` in paper.\n        label (torch.Tensor): Target distance label for bounding boxes with\n            shape (N,).\n\n    Return:\n        torch.Tensor: Loss tensor with shape (N,).\n    \"\"\"\n    dis_left = label.long()\n    dis_right = dis_left + 1\n    weight_left = dis_right.float() - label\n    weight_right = label - dis_left.float()\n    loss = F.cross_entropy(pred, dis_left, reduction='none') * weight_left \\\n        + F.cross_entropy(pred, dis_right, reduction='none') * weight_right\n    return loss\n\n\n@LOSSES.register_module()\nclass QualityFocalLoss(nn.Module):\n    \"\"\"Quality Focal Loss (QFL) is a variant of\n    Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes\n    for Dense Object Detection\n    https://arxiv.org/abs/2006.04388\n\n    Args:\n        use_sigmoid (bool): Whether sigmoid operation is conducted in QFL.\n            Defaults to True.\n        beta (float): The beta parameter for calculating the modulating factor.\n            Defaults to 2.0.\n        reduction (str): Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float): Loss weight of current loss.\n    \"\"\"\n\n    def __init__(self,\n                 use_sigmoid=True,\n                 beta=2.0,\n                 reduction='mean',\n                 loss_weight=1.0):\n        super(QualityFocalLoss, self).__init__()\n        assert use_sigmoid is True, 'Only sigmoid in QFL supported now.'\n        self.use_sigmoid = use_sigmoid\n        self.beta = beta\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        \"\"\"Forward function\n\n        Args:\n            pred (torch.Tensor): Predicted joint representation of\n                classification and quality (IoU) estimation with shape (N, C),\n                C is the number of classes.\n            target (tuple([torch.Tensor])): Target category label with shape\n                (N,) and target quality label with shape (N,).\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Defaults to None.\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if self.use_sigmoid:\n            loss_cls = self.loss_weight * quality_focal_loss(\n                pred,\n                target,\n                weight,\n                beta=self.beta,\n                reduction=reduction,\n                avg_factor=avg_factor)\n        else:\n            raise NotImplementedError\n        return loss_cls\n\n\n@LOSSES.register_module()\nclass DistributionFocalLoss(nn.Module):\n    \"\"\"Distribution Focal Loss (DFL) is a variant of\n    Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes\n    for Dense Object Detection\n    https://arxiv.org/abs/2006.04388\n\n    Args:\n        reduction (str): Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float): Loss weight of current loss.\n    \"\"\"\n\n    def __init__(self, reduction='mean', loss_weight=1.0):\n        super(DistributionFocalLoss, self).__init__()\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        \"\"\"Forward function\n\n        Args:\n            pred (torch.Tensor): Predicted general distribution of bounding\n                boxes (before softmax) with shape (N, n+1), n is the max value\n                of the integral set `{0, ..., n}` in paper.\n            target (torch.Tensor): Target distance label for bounding boxes\n                with shape (N,).\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Defaults to None.\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_cls = self.loss_weight * distribution_focal_loss(\n            pred, target, weight, reduction=reduction, avg_factor=avg_factor)\n        return loss_cls\n"
  },
  {
    "path": "code/mmdet/models/losses/ghm_loss.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..builder import LOSSES\n\n\ndef _expand_onehot_labels(labels, label_weights, label_channels):\n    bin_labels = labels.new_full((labels.size(0), label_channels), 0)\n    inds = torch.nonzero(\n        (labels >= 0) & (labels < label_channels), as_tuple=False).squeeze()\n    if inds.numel() > 0:\n        bin_labels[inds, labels[inds]] = 1\n    bin_label_weights = label_weights.view(-1, 1).expand(\n        label_weights.size(0), label_channels)\n    return bin_labels, bin_label_weights\n\n\n# TODO: code refactoring to make it consistent with other losses\n@LOSSES.register_module()\nclass GHMC(nn.Module):\n    \"\"\"GHM Classification Loss.\n\n    Details of the theorem can be viewed in the paper\n    \"Gradient Harmonized Single-stage Detector\".\n    https://arxiv.org/abs/1811.05181\n\n    Args:\n        bins (int): Number of the unit regions for distribution calculation.\n        momentum (float): The parameter for moving average.\n        use_sigmoid (bool): Can only be true for BCE based loss now.\n        loss_weight (float): The weight of the total GHM-C loss.\n    \"\"\"\n\n    def __init__(self, bins=10, momentum=0, use_sigmoid=True, loss_weight=1.0):\n        super(GHMC, self).__init__()\n        self.bins = bins\n        self.momentum = momentum\n        edges = torch.arange(bins + 1).float() / bins\n        self.register_buffer('edges', edges)\n        self.edges[-1] += 1e-6\n        if momentum > 0:\n            acc_sum = torch.zeros(bins)\n            self.register_buffer('acc_sum', acc_sum)\n        self.use_sigmoid = use_sigmoid\n        if not self.use_sigmoid:\n            raise NotImplementedError\n        self.loss_weight = loss_weight\n\n    def forward(self, pred, target, label_weight, *args, **kwargs):\n        \"\"\"Calculate the GHM-C loss.\n\n        Args:\n            pred (float tensor of size [batch_num, class_num]):\n                The direct prediction of classification fc layer.\n            target (float tensor of size [batch_num, class_num]):\n                Binary class target for each sample.\n            label_weight (float tensor of size [batch_num, class_num]):\n                the value is 1 if the sample is valid and 0 if ignored.\n        Returns:\n            The gradient harmonized loss.\n        \"\"\"\n        # the target should be binary class label\n        if pred.dim() != target.dim():\n            target, label_weight = _expand_onehot_labels(\n                target, label_weight, pred.size(-1))\n        target, label_weight = target.float(), label_weight.float()\n        edges = self.edges\n        mmt = self.momentum\n        weights = torch.zeros_like(pred)\n\n        # gradient length\n        g = torch.abs(pred.sigmoid().detach() - target)\n\n        valid = label_weight > 0\n        tot = max(valid.float().sum().item(), 1.0)\n        n = 0  # n valid bins\n        for i in range(self.bins):\n            inds = (g >= edges[i]) & (g < edges[i + 1]) & valid\n            num_in_bin = inds.sum().item()\n            if num_in_bin > 0:\n                if mmt > 0:\n                    self.acc_sum[i] = mmt * self.acc_sum[i] \\\n                        + (1 - mmt) * num_in_bin\n                    weights[inds] = tot / self.acc_sum[i]\n                else:\n                    weights[inds] = tot / num_in_bin\n                n += 1\n        if n > 0:\n            weights = weights / n\n\n        loss = F.binary_cross_entropy_with_logits(\n            pred, target, weights, reduction='sum') / tot\n        return loss * self.loss_weight\n\n\n# TODO: code refactoring to make it consistent with other losses\n@LOSSES.register_module()\nclass GHMR(nn.Module):\n    \"\"\"GHM Regression Loss.\n\n    Details of the theorem can be viewed in the paper\n    \"Gradient Harmonized Single-stage Detector\"\n    https://arxiv.org/abs/1811.05181\n\n    Args:\n        mu (float): The parameter for the Authentic Smooth L1 loss.\n        bins (int): Number of the unit regions for distribution calculation.\n        momentum (float): The parameter for moving average.\n        loss_weight (float): The weight of the total GHM-R loss.\n    \"\"\"\n\n    def __init__(self, mu=0.02, bins=10, momentum=0, loss_weight=1.0):\n        super(GHMR, self).__init__()\n        self.mu = mu\n        self.bins = bins\n        edges = torch.arange(bins + 1).float() / bins\n        self.register_buffer('edges', edges)\n        self.edges[-1] = 1e3\n        self.momentum = momentum\n        if momentum > 0:\n            acc_sum = torch.zeros(bins)\n            self.register_buffer('acc_sum', acc_sum)\n        self.loss_weight = loss_weight\n\n    # TODO: support reduction parameter\n    def forward(self, pred, target, label_weight, avg_factor=None):\n        \"\"\"Calculate the GHM-R loss.\n\n        Args:\n            pred (float tensor of size [batch_num, 4 (* class_num)]):\n                The prediction of box regression layer. Channel number can be 4\n                or 4 * class_num depending on whether it is class-agnostic.\n            target (float tensor of size [batch_num, 4 (* class_num)]):\n                The target regression values with the same size of pred.\n            label_weight (float tensor of size [batch_num, 4 (* class_num)]):\n                The weight of each sample, 0 if ignored.\n        Returns:\n            The gradient harmonized loss.\n        \"\"\"\n        mu = self.mu\n        edges = self.edges\n        mmt = self.momentum\n\n        # ASL1 loss\n        diff = pred - target\n        loss = torch.sqrt(diff * diff + mu * mu) - mu\n\n        # gradient length\n        g = torch.abs(diff / torch.sqrt(mu * mu + diff * diff)).detach()\n        weights = torch.zeros_like(g)\n\n        valid = label_weight > 0\n        tot = max(label_weight.float().sum().item(), 1.0)\n        n = 0  # n: valid bins\n        for i in range(self.bins):\n            inds = (g >= edges[i]) & (g < edges[i + 1]) & valid\n            num_in_bin = inds.sum().item()\n            if num_in_bin > 0:\n                n += 1\n                if mmt > 0:\n                    self.acc_sum[i] = mmt * self.acc_sum[i] \\\n                        + (1 - mmt) * num_in_bin\n                    weights[inds] = tot / self.acc_sum[i]\n                else:\n                    weights[inds] = tot / num_in_bin\n        if n > 0:\n            weights /= n\n\n        loss = loss * weights\n        loss = loss.sum() / tot\n        return loss * self.loss_weight\n"
  },
  {
    "path": "code/mmdet/models/losses/iou_loss.py",
    "content": "import pdb\nimport math\nimport torch\nimport torch.nn as nn\n\nfrom mmdet.core import bbox_overlaps\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n\n@weighted_loss\ndef iou_loss(pred, target, eps=1e-6):\n    \"\"\"IoU loss.\n\n    Computing the IoU loss between a set of predicted bboxes and target bboxes.\n    The loss is calculated as negative log of IoU.\n\n    Args:\n        pred (torch.Tensor): Predicted bboxes of format (x1, y1, x2, y2),\n            shape (n, 4).\n        target (torch.Tensor): Corresponding gt bboxes, shape (n, 4).\n        eps (float): Eps to avoid log(0).\n\n    Return:\n        torch.Tensor: Loss tensor.\n    \"\"\"\n    ious = bbox_overlaps(pred, target, is_aligned=True).clamp(min=eps)\n    loss = -ious.log()\n    return loss\n\n@weighted_loss\ndef bounded_iou_loss(pred, target, beta=0.2, eps=1e-3):\n    \"\"\"Improving Object Localization with Fitness NMS and Bounded IoU Loss,\n    https://arxiv.org/abs/1711.00164.\n\n    Args:\n        pred (torch.Tensor): Predicted bboxes.\n        target (torch.Tensor): Target bboxes.\n        beta (float): beta parameter in smoothl1.\n        eps (float): eps to avoid NaN.\n    \"\"\"\n    pred_ctrx = (pred[:, 0] + pred[:, 2]) * 0.5\n    pred_ctry = (pred[:, 1] + pred[:, 3]) * 0.5\n    pred_w = pred[:, 2] - pred[:, 0]\n    pred_h = pred[:, 3] - pred[:, 1]\n    with torch.no_grad():\n        target_ctrx = (target[:, 0] + target[:, 2]) * 0.5\n        target_ctry = (target[:, 1] + target[:, 3]) * 0.5\n        target_w = target[:, 2] - target[:, 0]\n        target_h = target[:, 3] - target[:, 1]\n\n    dx = target_ctrx - pred_ctrx\n    dy = target_ctry - pred_ctry\n\n    loss_dx = 1 - torch.max(\n        (target_w - 2 * dx.abs()) /\n        (target_w + 2 * dx.abs() + eps), torch.zeros_like(dx))\n    loss_dy = 1 - torch.max(\n        (target_h - 2 * dy.abs()) /\n        (target_h + 2 * dy.abs() + eps), torch.zeros_like(dy))\n    loss_dw = 1 - torch.min(target_w / (pred_w + eps), pred_w /\n                            (target_w + eps))\n    loss_dh = 1 - torch.min(target_h / (pred_h + eps), pred_h /\n                            (target_h + eps))\n    loss_comb = torch.stack([loss_dx, loss_dy, loss_dw, loss_dh],\n                            dim=-1).view(loss_dx.size(0), -1)\n\n    loss = torch.where(loss_comb < beta, 0.5 * loss_comb * loss_comb / beta,\n                       loss_comb - 0.5 * beta)\n    return loss\n\n\n@weighted_loss\ndef giou_loss(pred, target, eps=1e-7):\n    \"\"\"\n    Generalized Intersection over Union: A Metric and A Loss for\n    Bounding Box Regression\n    https://arxiv.org/abs/1902.09630\n\n    code refer to:\n    https://github.com/sfzhang15/ATSS/blob/master/atss_core/modeling/rpn/atss/loss.py#L36\n\n    Args:\n        pred (torch.Tensor): Predicted bboxes of format (x1, y1, x2, y2),\n            shape (n, 4).\n        target (torch.Tensor): Corresponding gt bboxes, shape (n, 4).\n        eps (float): Eps to avoid log(0).\n\n    Return:\n        Tensor: Loss tensor.\n    \"\"\"\n    # overlap\n    lt = torch.max(pred[:, :2], target[:, :2])\n    rb = torch.min(pred[:, 2:], target[:, 2:])\n    wh = (rb - lt + 1).clamp(min=0)\n    overlap = wh[:, 0] * wh[:, 1]\n\n    # union\n    ap = (pred[:, 2] - pred[:, 0] + 1) * (pred[:, 3] - pred[:, 1] + 1)\n    ag = (target[:, 2] - target[:, 0] + 1) * (target[:, 3] - target[:, 1] + 1)\n    union = ap + ag - overlap + eps\n\n    # IoU\n    ious = overlap / union\n\n    # enclose area\n    enclose_x1y1 = torch.min(pred[:, :2], target[:, :2])\n    enclose_x2y2 = torch.max(pred[:, 2:], target[:, 2:])\n    enclose_wh = (enclose_x2y2 - enclose_x1y1 + 1).clamp(min=0)\n    enclose_area = enclose_wh[:, 0] * enclose_wh[:, 1] + eps\n\n    # GIoU\n    gious = ious - (enclose_area - union) / enclose_area\n    loss = 1 - gious\n    return loss\n\n\n@weighted_loss\ndef diou_loss(pred, target, eps=1e-7):\n    r\"\"\"`Implementation of Distance-IoU Loss: Faster and Better\n    Learning for Bounding Box Regression, https://arxiv.org/abs/1911.08287`_.\n    Code is modified from https://github.com/Zzh-tju/DIoU.\n    Args:\n        pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2),\n            shape (n, 4).\n        target (Tensor): Corresponding gt bboxes, shape (n, 4).\n        eps (float): Eps to avoid log(0).\n    Return:\n        Tensor: Loss tensor.\n    \"\"\"\n    # overlap\n    lt = torch.max(pred[:, :2], target[:, :2])\n    rb = torch.min(pred[:, 2:], target[:, 2:])\n    wh = (rb - lt).clamp(min=0)\n    overlap = wh[:, 0] * wh[:, 1]\n\n    # union\n    ap = (pred[:, 2] - pred[:, 0]) * (pred[:, 3] - pred[:, 1])\n    ag = (target[:, 2] - target[:, 0]) * (target[:, 3] - target[:, 1])\n    union = ap + ag - overlap + eps\n\n    # IoU\n    ious = overlap / union\n\n    # enclose area\n    enclose_x1y1 = torch.min(pred[:, :2], target[:, :2])\n    enclose_x2y2 = torch.max(pred[:, 2:], target[:, 2:])\n    enclose_wh = (enclose_x2y2 - enclose_x1y1).clamp(min=0)\n\n    cw = enclose_wh[:, 0]\n    ch = enclose_wh[:, 1]\n\n    c2 = cw**2 + ch**2 + eps\n\n    b1_x1, b1_y1 = pred[:, 0], pred[:, 1]\n    b1_x2, b1_y2 = pred[:, 2], pred[:, 3]\n    b2_x1, b2_y1 = target[:, 0], target[:, 1]\n    b2_x2, b2_y2 = target[:, 2], target[:, 3]\n\n    left = ((b2_x1 + b2_x2) - (b1_x1 + b1_x2))**2 / 4\n    right = ((b2_y1 + b2_y2) - (b1_y1 + b1_y2))**2 / 4\n    rho2 = left + right\n\n    # DIoU\n    dious = ious - rho2 / c2\n    loss = 1 - dious\n    return loss\n\n\n@weighted_loss\ndef ciou_loss(pred, target, eps=1e-7):\n    r\"\"\"`Implementation of paper `Enhancing Geometric Factors into\n    Model Learning and Inference for Object Detection and Instance\n    Segmentation <https://arxiv.org/abs/2005.03572>`_.\n    Code is modified from https://github.com/Zzh-tju/CIoU.\n    Args:\n        pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2),\n            shape (n, 4).\n        target (Tensor): Corresponding gt bboxes, shape (n, 4).\n        eps (float): Eps to avoid log(0).\n    Return:\n        Tensor: Loss tensor.\n    \"\"\"\n    # overlap\n    lt = torch.max(pred[:, :2], target[:, :2])\n    rb = torch.min(pred[:, 2:], target[:, 2:])\n    wh = (rb - lt).clamp(min=0)\n    overlap = wh[:, 0] * wh[:, 1]\n\n    # union\n    ap = (pred[:, 2] - pred[:, 0]) * (pred[:, 3] - pred[:, 1])\n    ag = (target[:, 2] - target[:, 0]) * (target[:, 3] - target[:, 1])\n    union = ap + ag - overlap + eps\n\n    # IoU\n    ious = overlap / union\n\n    # enclose area\n    enclose_x1y1 = torch.min(pred[:, :2], target[:, :2])\n    enclose_x2y2 = torch.max(pred[:, 2:], target[:, 2:])\n    enclose_wh = (enclose_x2y2 - enclose_x1y1).clamp(min=0)\n\n    cw = enclose_wh[:, 0]\n    ch = enclose_wh[:, 1]\n\n    c2 = cw**2 + ch**2 + eps\n\n    b1_x1, b1_y1 = pred[:, 0], pred[:, 1]\n    b1_x2, b1_y2 = pred[:, 2], pred[:, 3]\n    b2_x1, b2_y1 = target[:, 0], target[:, 1]\n    b2_x2, b2_y2 = target[:, 2], target[:, 3]\n\n    w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps\n    w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps\n\n    left = ((b2_x1 + b2_x2) - (b1_x1 + b1_x2))**2 / 4\n    right = ((b2_y1 + b2_y2) - (b1_y1 + b1_y2))**2 / 4\n    rho2 = left + right\n\n    factor = 4 / math.pi**2\n    v = factor * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)\n\n    # CIoU\n    cious = ious - (rho2 / c2 + v**2 / (1 - ious + v))\n    loss = 1 - cious\n    return loss\n\n\n@LOSSES.register_module()\nclass IoULoss(nn.Module):\n    \"\"\"IoULoss\n\n    Computing the IoU loss between a set of predicted bboxes and target bboxes.\n\n    Args:\n        eps (float): Eps to avoid log(0).\n        reduction (str): Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float): Weight of loss.\n    \"\"\"\n\n    def __init__(self, eps=1e-6, reduction='mean', loss_weight=1.0):\n        super(IoULoss, self).__init__()\n        self.eps = eps\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        \"\"\"Forward function\n\n        Args:\n            pred (torch.Tensor): The prediction.\n            target (torch.Tensor): The learning target of the prediction.\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Defaults to None. Options are \"none\", \"mean\" and \"sum\".\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if (weight is not None) and (not torch.any(weight > 0)) and (\n                reduction != 'none'):\n            return (pred * weight).sum()  # 0\n        if weight is not None and weight.dim() > 1:\n            # TODO: remove this in the future\n            # reduce the weight of shape (n, 4) to (n,) to match the\n            # iou_loss of shape (n,)\n            assert weight.shape == pred.shape\n            weight = weight.mean(-1)\n        loss = self.loss_weight * iou_loss(\n            pred,\n            target,\n            weight,\n            eps=self.eps,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss\n\n\n@LOSSES.register_module()\nclass BoundedIoULoss(nn.Module):\n\n    def __init__(self, beta=0.2, eps=1e-3, reduction='mean', loss_weight=1.0):\n        super(BoundedIoULoss, self).__init__()\n        self.beta = beta\n        self.eps = eps\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        if weight is not None and not torch.any(weight > 0):\n            return (pred * weight).sum()  # 0\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss = self.loss_weight * bounded_iou_loss(\n            pred,\n            target,\n            weight,\n            beta=self.beta,\n            eps=self.eps,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss\n\n\n@LOSSES.register_module()\nclass GIoULoss(nn.Module):\n\n    def __init__(self, eps=1e-6, reduction='mean', loss_weight=1.0):\n        super(GIoULoss, self).__init__()\n        self.eps = eps\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        if weight is not None and not torch.any(weight > 0):\n            return (pred * weight).sum()  # 0\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if weight is not None and weight.dim() > 1:\n            # TODO: remove this in the future\n            # reduce the weight of shape (n, 4) to (n,) to match the\n            # giou_loss of shape (n,)\n            assert weight.shape == pred.shape\n            weight = weight.mean(-1)\n        loss = self.loss_weight * giou_loss(\n            pred,\n            target,\n            weight,\n            eps=self.eps,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss\n\n@LOSSES.register_module()\nclass DIoULoss(nn.Module):\n\n    def __init__(self, eps=1e-6, reduction='mean', loss_weight=1.0):\n        super(DIoULoss, self).__init__()\n        self.eps = eps\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        if weight is not None and not torch.any(weight > 0):\n            return (pred * weight).sum()  # 0\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if weight is not None and weight.dim() > 1:\n            # TODO: remove this in the future\n            # reduce the weight of shape (n, 4) to (n,) to match the\n            # giou_loss of shape (n,)\n            assert weight.shape == pred.shape\n            weight = weight.mean(-1)\n        loss = self.loss_weight * diou_loss(\n            pred,\n            target,\n            weight,\n            eps=self.eps,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss\n\n\n@LOSSES.register_module()\nclass CIoULoss(nn.Module):\n\n    def __init__(self, eps=1e-6, reduction='mean', loss_weight=1.0):\n        super(CIoULoss, self).__init__()\n        self.eps = eps\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        if weight is not None and not torch.any(weight > 0):\n            return (pred * weight).sum()  # 0\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        if weight is not None and weight.dim() > 1:\n            # TODO: remove this in the future\n            # reduce the weight of shape (n, 4) to (n,) to match the\n            # giou_loss of shape (n,)\n            assert weight.shape == pred.shape\n            weight = weight.mean(-1)\n        loss = self.loss_weight * ciou_loss(\n            pred,\n            target,\n            weight,\n            eps=self.eps,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss"
  },
  {
    "path": "code/mmdet/models/losses/mse_loss.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n\n@weighted_loss\ndef mse_loss(pred, target):\n    \"\"\"Warpper of mse loss\"\"\"\n    return F.mse_loss(pred, target, reduction='none')\n\n\n@LOSSES.register_module()\nclass MSELoss(nn.Module):\n    \"\"\"MSELoss\n\n    Args:\n        reduction (str, optional): The method that reduces the loss to a\n            scalar. Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float, optional): The weight of the loss. Defaults to 1.0\n    \"\"\"\n\n    def __init__(self, reduction='mean', loss_weight=1.0):\n        super().__init__()\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self, pred, target, weight=None, avg_factor=None):\n        \"\"\"Forward function of loss\n\n        Args:\n            pred (torch.Tensor): The prediction.\n            target (torch.Tensor): The learning target of the prediction.\n            weight (torch.Tensor, optional): Weight of the loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n\n        Returns:\n            torch.Tensor: The calculated loss\n        \"\"\"\n        loss = self.loss_weight * mse_loss(\n            pred,\n            target,\n            weight,\n            reduction=self.reduction,\n            avg_factor=avg_factor)\n        return loss\n"
  },
  {
    "path": "code/mmdet/models/losses/pisa_loss.py",
    "content": "import torch\n\nfrom mmdet.core import bbox_overlaps\n\n\ndef isr_p(cls_score,\n          bbox_pred,\n          bbox_targets,\n          rois,\n          sampling_results,\n          loss_cls,\n          bbox_coder,\n          k=2,\n          bias=0,\n          num_class=80):\n    \"\"\"Importance-based Sample Reweighting (ISR_P), positive part.\n\n    Args:\n        cls_score (Tensor): Predicted classification scores.\n        bbox_pred (Tensor): Predicted bbox deltas.\n        bbox_targets (tuple[Tensor]): A tuple of bbox targets, the are\n            labels, label_weights, bbox_targets, bbox_weights, respectively.\n        rois (Tensor): Anchors (single_stage) in shape (n, 4) or RoIs\n            (two_stage) in shape (n, 5).\n        sampling_results (obj): Sampling results.\n        loss_cls (func): Classification loss func of the head.\n        bbox_coder (obj): BBox coder of the head.\n        k (float): Power of the non-linear mapping.\n        bias (float): Shift of the non-linear mapping.\n        num_class (int): Number of classes, default: 80.\n\n    Return:\n        tuple([Tensor]): labels, imp_based_label_weights, bbox_targets,\n            bbox_target_weights\n    \"\"\"\n\n    labels, label_weights, bbox_targets, bbox_weights = bbox_targets\n    pos_label_inds = ((labels >= 0) &\n                      (labels < num_class)).nonzero().reshape(-1)\n    pos_labels = labels[pos_label_inds]\n\n    # if no positive samples, return the original targets\n    num_pos = float(pos_label_inds.size(0))\n    if num_pos == 0:\n        return labels, label_weights, bbox_targets, bbox_weights\n\n    # merge pos_assigned_gt_inds of per image to a single tensor\n    gts = list()\n    last_max_gt = 0\n    for i in range(len(sampling_results)):\n        gt_i = sampling_results[i].pos_assigned_gt_inds\n        gts.append(gt_i + last_max_gt)\n        if len(gt_i) != 0:\n            last_max_gt = gt_i.max() + 1\n    gts = torch.cat(gts)\n    assert len(gts) == num_pos\n\n    cls_score = cls_score.detach()\n    bbox_pred = bbox_pred.detach()\n\n    # For single stage detectors, rois here indicate anchors, in shape (N, 4)\n    # For two stage detectors, rois are in shape (N, 5)\n    if rois.size(-1) == 5:\n        pos_rois = rois[pos_label_inds][:, 1:]\n    else:\n        pos_rois = rois[pos_label_inds]\n\n    if bbox_pred.size(-1) > 4:\n        bbox_pred = bbox_pred.view(bbox_pred.size(0), -1, 4)\n        pos_delta_pred = bbox_pred[pos_label_inds, pos_labels].view(-1, 4)\n    else:\n        pos_delta_pred = bbox_pred[pos_label_inds].view(-1, 4)\n\n    # compute iou of the predicted bbox and the corresponding GT\n    pos_delta_target = bbox_targets[pos_label_inds].view(-1, 4)\n    pos_bbox_pred = bbox_coder.decode(pos_rois, pos_delta_pred)\n    target_bbox_pred = bbox_coder.decode(pos_rois, pos_delta_target)\n    ious = bbox_overlaps(pos_bbox_pred, target_bbox_pred, is_aligned=True)\n\n    pos_imp_weights = label_weights[pos_label_inds]\n    # Two steps to compute IoU-HLR. Samples are first sorted by IoU locally,\n    # then sorted again within the same-rank group\n    max_l_num = pos_labels.bincount().max()\n    for label in pos_labels.unique():\n        l_inds = (pos_labels == label).nonzero().view(-1)\n        l_gts = gts[l_inds]\n        for t in l_gts.unique():\n            t_inds = l_inds[l_gts == t]\n            t_ious = ious[t_inds]\n            _, t_iou_rank_idx = t_ious.sort(descending=True)\n            _, t_iou_rank = t_iou_rank_idx.sort()\n            ious[t_inds] += max_l_num - t_iou_rank.float()\n        l_ious = ious[l_inds]\n        _, l_iou_rank_idx = l_ious.sort(descending=True)\n        _, l_iou_rank = l_iou_rank_idx.sort()  # IoU-HLR\n        # linearly map HLR to label weights\n        pos_imp_weights[l_inds] *= (max_l_num - l_iou_rank.float()) / max_l_num\n\n    pos_imp_weights = (bias + pos_imp_weights * (1 - bias)).pow(k)\n\n    # normalize to make the new weighted loss value equal to the original loss\n    pos_loss_cls = loss_cls(\n        cls_score[pos_label_inds], pos_labels, reduction_override='none')\n    if pos_loss_cls.dim() > 1:\n        ori_pos_loss_cls = pos_loss_cls * label_weights[pos_label_inds][:,\n                                                                        None]\n        new_pos_loss_cls = pos_loss_cls * pos_imp_weights[:, None]\n    else:\n        ori_pos_loss_cls = pos_loss_cls * label_weights[pos_label_inds]\n        new_pos_loss_cls = pos_loss_cls * pos_imp_weights\n    pos_loss_cls_ratio = ori_pos_loss_cls.sum() / new_pos_loss_cls.sum()\n    pos_imp_weights = pos_imp_weights * pos_loss_cls_ratio\n    label_weights[pos_label_inds] = pos_imp_weights\n\n    bbox_targets = labels, label_weights, bbox_targets, bbox_weights\n    return bbox_targets\n\n\ndef carl_loss(cls_score,\n              labels,\n              bbox_pred,\n              bbox_targets,\n              loss_bbox,\n              k=1,\n              bias=0.2,\n              avg_factor=None,\n              sigmoid=False,\n              num_class=80):\n    \"\"\"Classification-Aware Regression Loss (CARL).\n\n    Args:\n        cls_score (Tensor): Predicted classification scores.\n        labels (Tensor): Targets of classification.\n        bbox_pred (Tensor): Predicted bbox deltas.\n        bbox_targets (Tensor): Target of bbox regression.\n        loss_bbox (func): Regression loss func of the head.\n        bbox_coder (obj): BBox coder of the head.\n        k (float): Power of the non-linear mapping.\n        bias (float): Shift of the non-linear mapping.\n        avg_factor (int): Average factor used in regression loss.\n        sigmoid (bool): Activation of the classification score.\n        num_class (int): Number of classes, default: 80.\n\n    Return:\n        dict: CARL loss dict.\n    \"\"\"\n    pos_label_inds = ((labels >= 0) &\n                      (labels < num_class)).nonzero().reshape(-1)\n    if pos_label_inds.numel() == 0:\n        return dict(loss_carl=cls_score.sum()[None] * 0.)\n    pos_labels = labels[pos_label_inds]\n\n    # multiply pos_cls_score with the corresponding bbox weight\n    # and remain gradient\n    if sigmoid:\n        pos_cls_score = cls_score.sigmoid()[pos_label_inds, pos_labels]\n    else:\n        pos_cls_score = cls_score.softmax(-1)[pos_label_inds, pos_labels]\n    carl_loss_weights = (bias + (1 - bias) * pos_cls_score).pow(k)\n\n    # normalize carl_loss_weight to make its sum equal to num positive\n    num_pos = float(pos_cls_score.size(0))\n    weight_ratio = num_pos / carl_loss_weights.sum()\n    carl_loss_weights *= weight_ratio\n\n    if avg_factor is None:\n        avg_factor = bbox_targets.size(0)\n    # if is class agnostic, bbox pred is in shape (N, 4)\n    # otherwise, bbox pred is in shape (N, #classes, 4)\n    if bbox_pred.size(-1) > 4:\n        bbox_pred = bbox_pred.view(bbox_pred.size(0), -1, 4)\n        pos_bbox_preds = bbox_pred[pos_label_inds, pos_labels]\n    else:\n        pos_bbox_preds = bbox_pred[pos_label_inds]\n    ori_loss_reg = loss_bbox(\n        pos_bbox_preds,\n        bbox_targets[pos_label_inds],\n        reduction_override='none') / avg_factor\n    loss_carl = (ori_loss_reg * carl_loss_weights[:, None]).sum()\n    return dict(loss_carl=loss_carl[None])\n"
  },
  {
    "path": "code/mmdet/models/losses/smooth_l1_loss.py",
    "content": "import torch\nimport torch.nn as nn\n\nfrom ..builder import LOSSES\nfrom .utils import weighted_loss\n\n\n@weighted_loss\ndef smooth_l1_loss(pred, target, beta=1.0):\n    \"\"\"Smooth L1 loss\n\n    Args:\n        pred (torch.Tensor): The prediction.\n        target (torch.Tensor): The learning target of the prediction.\n        beta (float, optional): The threshold in the piecewise function.\n            Defaults to 1.0.\n\n    Returns:\n        torch.Tensor: Calculated loss\n    \"\"\"\n    assert beta > 0\n    assert pred.size() == target.size() and target.numel() > 0\n    diff = torch.abs(pred - target)\n    loss = torch.where(diff < beta, 0.5 * diff * diff / beta,\n                       diff - 0.5 * beta)\n    return loss\n\n\n@weighted_loss\ndef l1_loss(pred, target):\n    \"\"\"L1 loss\n\n    Args:\n        pred (torch.Tensor): The prediction.\n        target (torch.Tensor): The learning target of the prediction.\n\n    Returns:\n        torch.Tensor: Calculated loss\n    \"\"\"\n    assert pred.size() == target.size() and target.numel() > 0\n    loss = torch.abs(pred - target)\n    return loss\n\n\n@LOSSES.register_module()\nclass SmoothL1Loss(nn.Module):\n    \"\"\"Smooth L1 loss\n\n    Args:\n        beta (float, optional): The threshold in the piecewise function.\n            Defaults to 1.0.\n        reduction (str, optional): The method to reduce the loss.\n            Options are \"none\", \"mean\" and \"sum\". Defaults to \"mean\".\n        loss_weight (float, optional): The weight of loss.\n    \"\"\"\n\n    def __init__(self, beta=1.0, reduction='mean', loss_weight=1.0):\n        super(SmoothL1Loss, self).__init__()\n        self.beta = beta\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None,\n                **kwargs):\n        \"\"\"Forward function\n\n        Args:\n            pred (torch.Tensor): The prediction.\n            target (torch.Tensor): The learning target of the prediction.\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Defaults to None.\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_bbox = self.loss_weight * smooth_l1_loss(\n            pred,\n            target,\n            weight,\n            beta=self.beta,\n            reduction=reduction,\n            avg_factor=avg_factor,\n            **kwargs)\n        return loss_bbox\n\n\n@LOSSES.register_module()\nclass L1Loss(nn.Module):\n    \"\"\"L1 loss\n\n    Args:\n        reduction (str, optional): The method to reduce the loss.\n            Options are \"none\", \"mean\" and \"sum\".\n        loss_weight (float, optional): The weight of loss.\n    \"\"\"\n\n    def __init__(self, reduction='mean', loss_weight=1.0):\n        super(L1Loss, self).__init__()\n        self.reduction = reduction\n        self.loss_weight = loss_weight\n\n    def forward(self,\n                pred,\n                target,\n                weight=None,\n                avg_factor=None,\n                reduction_override=None):\n        \"\"\"Forward function\n\n        Args:\n            pred (torch.Tensor): The prediction.\n            target (torch.Tensor): The learning target of the prediction.\n            weight (torch.Tensor, optional): The weight of loss for each\n                prediction. Defaults to None.\n            avg_factor (int, optional): Average factor that is used to average\n                the loss. Defaults to None.\n            reduction_override (str, optional): The reduction method used to\n                override the original reduction method of the loss.\n                Defaults to None.\n        \"\"\"\n        assert reduction_override in (None, 'none', 'mean', 'sum')\n        reduction = (\n            reduction_override if reduction_override else self.reduction)\n        loss_bbox = self.loss_weight * l1_loss(\n            pred, target, weight, reduction=reduction, avg_factor=avg_factor)\n        return loss_bbox\n"
  },
  {
    "path": "code/mmdet/models/losses/utils.py",
    "content": "import functools\n\nimport torch.nn.functional as F\n\n\ndef reduce_loss(loss, reduction):\n    \"\"\"Reduce loss as specified.\n\n    Args:\n        loss (Tensor): Elementwise loss tensor.\n        reduction (str): Options are \"none\", \"mean\" and \"sum\".\n\n    Return:\n        Tensor: Reduced loss tensor.\n    \"\"\"\n    reduction_enum = F._Reduction.get_enum(reduction)\n    # none: 0, elementwise_mean:1, sum: 2\n    if reduction_enum == 0:\n        return loss\n    elif reduction_enum == 1:\n        return loss.mean()\n    elif reduction_enum == 2:\n        return loss.sum()\n\n\ndef weight_reduce_loss(loss, weight=None, reduction='mean', avg_factor=None):\n    \"\"\"Apply element-wise weight and reduce loss.\n\n    Args:\n        loss (Tensor): Element-wise loss.\n        weight (Tensor): Element-wise weights.\n        reduction (str): Same as built-in losses of PyTorch.\n        avg_factor (float): Avarage factor when computing the mean of losses.\n\n    Returns:\n        Tensor: Processed loss values.\n    \"\"\"\n    # if weight is specified, apply element-wise weight\n    if weight is not None:\n        loss = loss * weight\n\n    # if avg_factor is not specified, just reduce the loss\n    if avg_factor is None:\n        loss = reduce_loss(loss, reduction)\n    else:\n        # if reduction is mean, then average the loss by avg_factor\n        if reduction == 'mean':\n            loss = loss.sum() / avg_factor\n        # if reduction is 'none', then do nothing, otherwise raise an error\n        elif reduction != 'none':\n            raise ValueError('avg_factor can not be used with reduction=\"sum\"')\n    return loss\n\n\ndef weighted_loss(loss_func):\n    \"\"\"Create a weighted version of a given loss function.\n\n    To use this decorator, the loss function must have the signature like\n    `loss_func(pred, target, **kwargs)`. The function only needs to compute\n    element-wise loss without any reduction. This decorator will add weight\n    and reduction arguments to the function. The decorated function will have\n    the signature like `loss_func(pred, target, weight=None, reduction='mean',\n    avg_factor=None, **kwargs)`.\n\n    :Example:\n\n    >>> import torch\n    >>> @weighted_loss\n    >>> def l1_loss(pred, target):\n    >>>     return (pred - target).abs()\n\n    >>> pred = torch.Tensor([0, 2, 3])\n    >>> target = torch.Tensor([1, 1, 1])\n    >>> weight = torch.Tensor([1, 0, 1])\n\n    >>> l1_loss(pred, target)\n    tensor(1.3333)\n    >>> l1_loss(pred, target, weight)\n    tensor(1.)\n    >>> l1_loss(pred, target, reduction='none')\n    tensor([1., 1., 2.])\n    >>> l1_loss(pred, target, weight, avg_factor=2)\n    tensor(1.5000)\n    \"\"\"\n\n    @functools.wraps(loss_func)\n    def wrapper(pred,\n                target,\n                weight=None,\n                reduction='mean',\n                avg_factor=None,\n                **kwargs):\n        # get element-wise loss\n        loss = loss_func(pred, target, **kwargs)\n        loss = weight_reduce_loss(loss, weight, reduction, avg_factor)\n        return loss\n\n    return wrapper\n"
  },
  {
    "path": "code/mmdet/models/necks/__init__.py",
    "content": "from .bfp import BFP\nfrom .fpn import FPN\nfrom .fpn_carafe import FPN_CARAFE\nfrom .hrfpn import HRFPN\nfrom .nas_fpn import NASFPN\nfrom .nasfcos_fpn import NASFCOS_FPN\nfrom .pafpn import PAFPN\nfrom .rfp import RFP\n\n__all__ = [\n    'FPN', 'BFP', 'HRFPN', 'NASFPN', 'FPN_CARAFE', 'PAFPN', 'NASFCOS_FPN',\n    'RFP'\n]\n"
  },
  {
    "path": "code/mmdet/models/necks/bfp.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, xavier_init\n\nfrom mmdet.ops import NonLocal2D\nfrom ..builder import NECKS\n\n\n@NECKS.register_module()\nclass BFP(nn.Module):\n    \"\"\"BFP (Balanced Feature Pyrmamids)\n\n    BFP takes multi-level features as inputs and gather them into a single one,\n    then refine the gathered feature and scatter the refined results to\n    multi-level features. This module is used in Libra R-CNN (CVPR 2019), see\n    https://arxiv.org/pdf/1904.02701.pdf for details.\n\n    Args:\n        in_channels (int): Number of input channels (feature maps of all levels\n            should have the same channels).\n        num_levels (int): Number of input feature levels.\n        conv_cfg (dict): The config dict for convolution layers.\n        norm_cfg (dict): The config dict for normalization layers.\n        refine_level (int): Index of integration and refine level of BSF in\n            multi-level features from bottom to top.\n        refine_type (str): Type of the refine op, currently support\n            [None, 'conv', 'non_local'].\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 num_levels,\n                 refine_level=2,\n                 refine_type=None,\n                 conv_cfg=None,\n                 norm_cfg=None):\n        super(BFP, self).__init__()\n        assert refine_type in [None, 'conv', 'non_local']\n\n        self.in_channels = in_channels\n        self.num_levels = num_levels\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n\n        self.refine_level = refine_level\n        self.refine_type = refine_type\n        assert 0 <= self.refine_level < self.num_levels\n\n        if self.refine_type == 'conv':\n            self.refine = ConvModule(\n                self.in_channels,\n                self.in_channels,\n                3,\n                padding=1,\n                conv_cfg=self.conv_cfg,\n                norm_cfg=self.norm_cfg)\n        elif self.refine_type == 'non_local':\n            self.refine = NonLocal2D(\n                self.in_channels,\n                reduction=1,\n                use_scale=False,\n                conv_cfg=self.conv_cfg,\n                norm_cfg=self.norm_cfg)\n\n    def init_weights(self):\n        \"\"\"Initialize the weights of FPN module\"\"\"\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                xavier_init(m, distribution='uniform')\n\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        assert len(inputs) == self.num_levels\n\n        # step 1: gather multi-level features by resize and average\n        feats = []\n        gather_size = inputs[self.refine_level].size()[2:]\n        for i in range(self.num_levels):\n            if i < self.refine_level:\n                gathered = F.adaptive_max_pool2d(\n                    inputs[i], output_size=gather_size)\n            else:\n                gathered = F.interpolate(\n                    inputs[i], size=gather_size, mode='nearest')\n            feats.append(gathered)\n\n        bsf = sum(feats) / len(feats)\n\n        # step 2: refine gathered features\n        if self.refine_type is not None:\n            bsf = self.refine(bsf)\n\n        # step 3: scatter refined features to multi-levels by a residual path\n        outs = []\n        for i in range(self.num_levels):\n            out_size = inputs[i].size()[2:]\n            if i < self.refine_level:\n                residual = F.interpolate(bsf, size=out_size, mode='nearest')\n            else:\n                residual = F.adaptive_max_pool2d(bsf, output_size=out_size)\n            outs.append(residual + inputs[i])\n\n        return tuple(outs)\n"
  },
  {
    "path": "code/mmdet/models/necks/fpn.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, xavier_init\n\nfrom mmdet.core import auto_fp16\nfrom ..builder import NECKS\n\n\n@NECKS.register_module()\nclass FPN(nn.Module):\n    \"\"\"\n    Feature Pyramid Network.\n\n    This is an implementation of - Feature Pyramid Networks for Object\n    Detection (https://arxiv.org/abs/1612.03144)\n\n    Args:\n        in_channels (List[int]): Number of input channels per scale.\n        out_channels (int): Number of output channels (used at each scale)\n        num_outs (int): Number of output scales.\n        start_level (int): Index of the start input backbone level used to\n            build the feature pyramid. Default: 0.\n        end_level (int): Index of the end input backbone level (exclusive) to\n            build the feature pyramid. Default: -1, which means the last level.\n        add_extra_convs (bool | str): If bool, it decides whether to add conv\n            layers on top of the original feature maps. Default to False.\n            If True, its actual mode is specified by `extra_convs_on_inputs`.\n            If str, it specifies the source feature map of the extra convs.\n            Only the following options are allowed\n\n            - 'on_input': Last feat map of neck inputs (i.e. backbone feature).\n            - 'on_lateral':  Last feature map after lateral convs.\n            - 'on_output': The last output feature map after fpn convs.\n        extra_convs_on_inputs (bool, deprecated): Whether to apply extra convs\n            on the original feature from the backbone. If True,\n            it is equivalent to `add_extra_convs='on_input'`. If False, it is\n            equivalent to set `add_extra_convs='on_output'`. Default to True.\n        relu_before_extra_convs (bool): Whether to apply relu before the extra\n            conv. Default: False.\n        no_norm_on_lateral (bool): Whether to apply norm on lateral.\n            Default: False.\n        conv_cfg (dict): Config dict for convolution layer. Default: None.\n        norm_cfg (dict): Config dict for normalization layer. Default: None.\n        act_cfg (str): Config dict for activation layer in ConvModule.\n            Default: None.\n        upsample_cfg (dict): Config dict for interpolate layer.\n            Default: `dict(mode='nearest')`\n\n    Example:\n        >>> import torch\n        >>> in_channels = [2, 3, 5, 7]\n        >>> scales = [340, 170, 84, 43]\n        >>> inputs = [torch.rand(1, c, s, s)\n        ...           for c, s in zip(in_channels, scales)]\n        >>> self = FPN(in_channels, 11, len(in_channels)).eval()\n        >>> outputs = self.forward(inputs)\n        >>> for i in range(len(outputs)):\n        ...     print(f'outputs[{i}].shape = {outputs[i].shape}')\n        outputs[0].shape = torch.Size([1, 11, 340, 340])\n        outputs[1].shape = torch.Size([1, 11, 170, 170])\n        outputs[2].shape = torch.Size([1, 11, 84, 84])\n        outputs[3].shape = torch.Size([1, 11, 43, 43])\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 num_outs,\n                 start_level=0,\n                 end_level=-1,\n                 add_extra_convs=False,\n                 extra_convs_on_inputs=True,\n                 relu_before_extra_convs=False,\n                 no_norm_on_lateral=False,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 act_cfg=None,\n                 upsample_cfg=dict(mode='nearest')):\n        super(FPN, self).__init__()\n        assert isinstance(in_channels, list)\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.num_ins = len(in_channels)\n        self.num_outs = num_outs\n        self.relu_before_extra_convs = relu_before_extra_convs\n        self.no_norm_on_lateral = no_norm_on_lateral\n        self.fp16_enabled = False\n        self.upsample_cfg = upsample_cfg.copy()\n\n        if end_level == -1:\n            self.backbone_end_level = self.num_ins\n            assert num_outs >= self.num_ins - start_level\n        else:\n            # if end_level < inputs, no extra level is allowed\n            self.backbone_end_level = end_level\n            assert end_level <= len(in_channels)\n            assert num_outs == end_level - start_level\n        self.start_level = start_level\n        self.end_level = end_level\n        self.add_extra_convs = add_extra_convs\n        assert isinstance(add_extra_convs, (str, bool))\n        if isinstance(add_extra_convs, str):\n            # Extra_convs_source choices: 'on_input', 'on_lateral', 'on_output'\n            assert add_extra_convs in ('on_input', 'on_lateral', 'on_output')\n        elif add_extra_convs:  # True\n            if extra_convs_on_inputs:\n                # For compatibility with previous release\n                # TODO: deprecate `extra_convs_on_inputs`\n                self.add_extra_convs = 'on_input'\n            else:\n                self.add_extra_convs = 'on_output'\n\n        self.lateral_convs = nn.ModuleList()\n        self.fpn_convs = nn.ModuleList()\n\n        for i in range(self.start_level, self.backbone_end_level):\n            l_conv = ConvModule(\n                in_channels[i],\n                out_channels,\n                1,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg if not self.no_norm_on_lateral else None,\n                act_cfg=act_cfg,\n                inplace=False)\n            fpn_conv = ConvModule(\n                out_channels,\n                out_channels,\n                3,\n                padding=1,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                act_cfg=act_cfg,\n                inplace=False)\n\n            self.lateral_convs.append(l_conv)\n            self.fpn_convs.append(fpn_conv)\n\n        # add extra conv layers (e.g., RetinaNet)\n        extra_levels = num_outs - self.backbone_end_level + self.start_level\n        if self.add_extra_convs and extra_levels >= 1:\n            for i in range(extra_levels):\n                if i == 0 and self.add_extra_convs == 'on_input':\n                    in_channels = self.in_channels[self.backbone_end_level - 1]\n                else:\n                    in_channels = out_channels\n                extra_fpn_conv = ConvModule(\n                    in_channels,\n                    out_channels,\n                    3,\n                    stride=2,\n                    padding=1,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=norm_cfg,\n                    act_cfg=act_cfg,\n                    inplace=False)\n                self.fpn_convs.append(extra_fpn_conv)\n\n    # default init_weights for conv(msra) and norm in ConvModule\n    def init_weights(self):\n        \"\"\"Initialize the weights of FPN module\"\"\"\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                xavier_init(m, distribution='uniform')\n\n    @auto_fp16()\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        assert len(inputs) == len(self.in_channels)\n\n        # build laterals\n        laterals = [\n            lateral_conv(inputs[i + self.start_level])\n            for i, lateral_conv in enumerate(self.lateral_convs)\n        ]\n\n        # build top-down path\n        used_backbone_levels = len(laterals)\n        for i in range(used_backbone_levels - 1, 0, -1):\n            # In some cases, fixing `scale factor` (e.g. 2) is preferred, but\n            #  it cannot co-exist with `size` in `F.interpolate`.\n            if 'scale_factor' in self.upsample_cfg:\n                laterals[i - 1] += F.interpolate(laterals[i],\n                                                 **self.upsample_cfg)\n            else:\n                prev_shape = laterals[i - 1].shape[2:]\n                laterals[i - 1] += F.interpolate(\n                    laterals[i], size=prev_shape, **self.upsample_cfg)\n\n        # build outputs\n        # part 1: from original levels\n        outs = [\n            self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)\n        ]\n        # part 2: add extra levels\n        if self.num_outs > len(outs):\n            # use max pool to get more levels on top of outputs\n            # (e.g., Faster R-CNN, Mask R-CNN)\n            if not self.add_extra_convs:\n                for i in range(self.num_outs - used_backbone_levels):\n                    outs.append(F.max_pool2d(outs[-1], 1, stride=2))\n            # add conv layers on top of original feature maps (RetinaNet)\n            else:\n                if self.add_extra_convs == 'on_input':\n                    extra_source = inputs[self.backbone_end_level - 1]\n                elif self.add_extra_convs == 'on_lateral':\n                    extra_source = laterals[-1]\n                elif self.add_extra_convs == 'on_output':\n                    extra_source = outs[-1]\n                else:\n                    raise NotImplementedError\n                outs.append(self.fpn_convs[used_backbone_levels](extra_source))\n                for i in range(used_backbone_levels + 1, self.num_outs):\n                    if self.relu_before_extra_convs:\n                        outs.append(self.fpn_convs[i](F.relu(outs[-1])))\n                    else:\n                        outs.append(self.fpn_convs[i](outs[-1]))\n        return tuple(outs)\n"
  },
  {
    "path": "code/mmdet/models/necks/fpn_carafe.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, build_upsample_layer, xavier_init\n\nfrom mmdet.ops.carafe import CARAFEPack\nfrom ..builder import NECKS\n\n\n@NECKS.register_module()\nclass FPN_CARAFE(nn.Module):\n    \"\"\"FPN_CARAFE is a more flexible implementation of FPN.\n    It allows more choice for upsample methods during the top-down pathway.\n\n    It can reproduce the preformance of ICCV 2019 paper\n    CARAFE: Content-Aware ReAssembly of FEatures\n    Please refer to https://arxiv.org/abs/1905.02188 for more details.\n\n    Args:\n        in_channels (list[int]): Number of channels for each input feature map.\n        out_channels (int): Output channels of feature pyramids.\n        num_outs (int): Number of output stages.\n        start_level (int): Start level of feature pyramids.\n            (Default: 0)\n        end_level (int): End level of feature pyramids.\n            (Default: -1 indicates the last level).\n        norm_cfg (dict): Dictionary to construct and config norm layer.\n        activate (str): Type of activation function in ConvModule\n            (Default: None indicates w/o activation).\n        order (dict): Order of components in ConvModule.\n        upsample (str): Type of upsample layer.\n        upsample_cfg (dict): Dictionary to construct and config upsample layer.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 num_outs,\n                 start_level=0,\n                 end_level=-1,\n                 norm_cfg=None,\n                 act_cfg=None,\n                 order=('conv', 'norm', 'act'),\n                 upsample_cfg=dict(\n                     type='carafe',\n                     up_kernel=5,\n                     up_group=1,\n                     encoder_kernel=3,\n                     encoder_dilation=1)):\n        super(FPN_CARAFE, self).__init__()\n        assert isinstance(in_channels, list)\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.num_ins = len(in_channels)\n        self.num_outs = num_outs\n        self.norm_cfg = norm_cfg\n        self.act_cfg = act_cfg\n        self.with_bias = norm_cfg is None\n        self.upsample_cfg = upsample_cfg.copy()\n        self.upsample = self.upsample_cfg.get('type')\n        self.relu = nn.ReLU(inplace=False)\n\n        self.order = order\n        assert order in [('conv', 'norm', 'act'), ('act', 'conv', 'norm')]\n\n        assert self.upsample in [\n            'nearest', 'bilinear', 'deconv', 'pixel_shuffle', 'carafe', None\n        ]\n        if self.upsample in ['deconv', 'pixel_shuffle']:\n            assert hasattr(\n                self.upsample_cfg,\n                'upsample_kernel') and self.upsample_cfg.upsample_kernel > 0\n            self.upsample_kernel = self.upsample_cfg.pop('upsample_kernel')\n\n        if end_level == -1:\n            self.backbone_end_level = self.num_ins\n            assert num_outs >= self.num_ins - start_level\n        else:\n            # if end_level < inputs, no extra level is allowed\n            self.backbone_end_level = end_level\n            assert end_level <= len(in_channels)\n            assert num_outs == end_level - start_level\n        self.start_level = start_level\n        self.end_level = end_level\n\n        self.lateral_convs = nn.ModuleList()\n        self.fpn_convs = nn.ModuleList()\n        self.upsample_modules = nn.ModuleList()\n\n        for i in range(self.start_level, self.backbone_end_level):\n            l_conv = ConvModule(\n                in_channels[i],\n                out_channels,\n                1,\n                norm_cfg=norm_cfg,\n                bias=self.with_bias,\n                act_cfg=act_cfg,\n                inplace=False,\n                order=self.order)\n            fpn_conv = ConvModule(\n                out_channels,\n                out_channels,\n                3,\n                padding=1,\n                norm_cfg=self.norm_cfg,\n                bias=self.with_bias,\n                act_cfg=act_cfg,\n                inplace=False,\n                order=self.order)\n            if i != self.backbone_end_level - 1:\n                upsample_cfg_ = self.upsample_cfg.copy()\n                if self.upsample == 'deconv':\n                    upsample_cfg_.update(\n                        in_channels=out_channels,\n                        out_channels=out_channels,\n                        kernel_size=self.upsample_kernel,\n                        stride=2,\n                        padding=(self.upsample_kernel - 1) // 2,\n                        output_padding=(self.upsample_kernel - 1) // 2)\n                elif self.upsample == 'pixel_shuffle':\n                    upsample_cfg_.update(\n                        in_channels=out_channels,\n                        out_channels=out_channels,\n                        scale_factor=2,\n                        upsample_kernel=self.upsample_kernel)\n                elif self.upsample == 'carafe':\n                    upsample_cfg_.update(channels=out_channels, scale_factor=2)\n                else:\n                    # suppress warnings\n                    align_corners = (None\n                                     if self.upsample == 'nearest' else False)\n                    upsample_cfg_.update(\n                        scale_factor=2,\n                        mode=self.upsample,\n                        align_corners=align_corners)\n                upsample_module = build_upsample_layer(upsample_cfg_)\n                self.upsample_modules.append(upsample_module)\n            self.lateral_convs.append(l_conv)\n            self.fpn_convs.append(fpn_conv)\n\n        # add extra conv layers (e.g., RetinaNet)\n        extra_out_levels = (\n            num_outs - self.backbone_end_level + self.start_level)\n        if extra_out_levels >= 1:\n            for i in range(extra_out_levels):\n                in_channels = (\n                    self.in_channels[self.backbone_end_level -\n                                     1] if i == 0 else out_channels)\n                extra_l_conv = ConvModule(\n                    in_channels,\n                    out_channels,\n                    3,\n                    stride=2,\n                    padding=1,\n                    norm_cfg=norm_cfg,\n                    bias=self.with_bias,\n                    act_cfg=act_cfg,\n                    inplace=False,\n                    order=self.order)\n                if self.upsample == 'deconv':\n                    upsampler_cfg_ = dict(\n                        in_channels=out_channels,\n                        out_channels=out_channels,\n                        kernel_size=self.upsample_kernel,\n                        stride=2,\n                        padding=(self.upsample_kernel - 1) // 2,\n                        output_padding=(self.upsample_kernel - 1) // 2)\n                elif self.upsample == 'pixel_shuffle':\n                    upsampler_cfg_ = dict(\n                        in_channels=out_channels,\n                        out_channels=out_channels,\n                        scale_factor=2,\n                        upsample_kernel=self.upsample_kernel)\n                elif self.upsample == 'carafe':\n                    upsampler_cfg_ = dict(\n                        channels=out_channels,\n                        scale_factor=2,\n                        **self.upsample_cfg)\n                else:\n                    # suppress warnings\n                    align_corners = (None\n                                     if self.upsample == 'nearest' else False)\n                    upsampler_cfg_ = dict(\n                        scale_factor=2,\n                        mode=self.upsample,\n                        align_corners=align_corners)\n                upsampler_cfg_['type'] = self.upsample\n                upsample_module = build_upsample_layer(upsampler_cfg_)\n                extra_fpn_conv = ConvModule(\n                    out_channels,\n                    out_channels,\n                    3,\n                    padding=1,\n                    norm_cfg=self.norm_cfg,\n                    bias=self.with_bias,\n                    act_cfg=act_cfg,\n                    inplace=False,\n                    order=self.order)\n                self.upsample_modules.append(upsample_module)\n                self.fpn_convs.append(extra_fpn_conv)\n                self.lateral_convs.append(extra_l_conv)\n\n    # default init_weights for conv(msra) and norm in ConvModule\n    def init_weights(self):\n        \"\"\"Initialize the weights of module\"\"\"\n        for m in self.modules():\n            if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):\n                xavier_init(m, distribution='uniform')\n        for m in self.modules():\n            if isinstance(m, CARAFEPack):\n                m.init_weights()\n\n    def slice_as(self, src, dst):\n        \"\"\"Slice ``src`` as ``dst``\n\n        Note:\n            ``src`` should have the same or larger size than ``dst``.\n\n        Args:\n            src (torch.Tensor): Tensors to be sliced.\n            dst (torch.Tensor): ``src`` will be sliced to have the same\n                size as ``dst``.\n\n        Returns:\n            torch.Tensor: Sliced tensor.\n        \"\"\"\n        assert (src.size(2) >= dst.size(2)) and (src.size(3) >= dst.size(3))\n        if src.size(2) == dst.size(2) and src.size(3) == dst.size(3):\n            return src\n        else:\n            return src[:, :, :dst.size(2), :dst.size(3)]\n\n    def tensor_add(self, a, b):\n        \"\"\"Add tensors ``a`` and ``b`` that might have different sizes\"\"\"\n        if a.size() == b.size():\n            c = a + b\n        else:\n            c = a + self.slice_as(b, a)\n        return c\n\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        assert len(inputs) == len(self.in_channels)\n\n        # build laterals\n        laterals = []\n        for i, lateral_conv in enumerate(self.lateral_convs):\n            if i <= self.backbone_end_level - self.start_level:\n                input = inputs[min(i + self.start_level, len(inputs) - 1)]\n            else:\n                input = laterals[-1]\n            lateral = lateral_conv(input)\n            laterals.append(lateral)\n\n        # build top-down path\n        for i in range(len(laterals) - 1, 0, -1):\n            if self.upsample is not None:\n                upsample_feat = self.upsample_modules[i - 1](laterals[i])\n            else:\n                upsample_feat = laterals[i]\n            laterals[i - 1] = self.tensor_add(laterals[i - 1], upsample_feat)\n\n        # build outputs\n        num_conv_outs = len(self.fpn_convs)\n        outs = []\n        for i in range(num_conv_outs):\n            out = self.fpn_convs[i](laterals[i])\n            outs.append(out)\n        return tuple(outs)\n"
  },
  {
    "path": "code/mmdet/models/necks/hrfpn.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, caffe2_xavier_init\nfrom torch.utils.checkpoint import checkpoint\n\nfrom ..builder import NECKS\n\n\n@NECKS.register_module()\nclass HRFPN(nn.Module):\n    \"\"\"HRFPN (High Resolution Feature Pyrmamids)\n\n    arXiv: https://arxiv.org/abs/1904.04514\n\n    Args:\n        in_channels (list): number of channels for each branch.\n        out_channels (int): output channels of feature pyramids.\n        num_outs (int): number of output stages.\n        pooling_type (str): pooling for generating feature pyramids\n            from {MAX, AVG}.\n        conv_cfg (dict): dictionary to construct and config conv layer.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n        with_cp  (bool): Use checkpoint or not. Using checkpoint will save some\n            memory while slowing down the training speed.\n        stride (int): stride of 3x3 convolutional layers\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 num_outs=5,\n                 pooling_type='AVG',\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 with_cp=False,\n                 stride=1):\n        super(HRFPN, self).__init__()\n        assert isinstance(in_channels, list)\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.num_ins = len(in_channels)\n        self.num_outs = num_outs\n        self.with_cp = with_cp\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n\n        self.reduction_conv = ConvModule(\n            sum(in_channels),\n            out_channels,\n            kernel_size=1,\n            conv_cfg=self.conv_cfg,\n            act_cfg=None)\n\n        self.fpn_convs = nn.ModuleList()\n        for i in range(self.num_outs):\n            self.fpn_convs.append(\n                ConvModule(\n                    out_channels,\n                    out_channels,\n                    kernel_size=3,\n                    padding=1,\n                    stride=stride,\n                    conv_cfg=self.conv_cfg,\n                    act_cfg=None))\n\n        if pooling_type == 'MAX':\n            self.pooling = F.max_pool2d\n        else:\n            self.pooling = F.avg_pool2d\n\n    def init_weights(self):\n        \"\"\"Initialize the weights of module\"\"\"\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                caffe2_xavier_init(m)\n\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        assert len(inputs) == self.num_ins\n        outs = [inputs[0]]\n        for i in range(1, self.num_ins):\n            outs.append(\n                F.interpolate(inputs[i], scale_factor=2**i, mode='bilinear'))\n        out = torch.cat(outs, dim=1)\n        if out.requires_grad and self.with_cp:\n            out = checkpoint(self.reduction_conv, out)\n        else:\n            out = self.reduction_conv(out)\n        outs = [out]\n        for i in range(1, self.num_outs):\n            outs.append(self.pooling(out, kernel_size=2**i, stride=2**i))\n        outputs = []\n\n        for i in range(self.num_outs):\n            if outs[i].requires_grad and self.with_cp:\n                tmp_out = checkpoint(self.fpn_convs[i], outs[i])\n            else:\n                tmp_out = self.fpn_convs[i](outs[i])\n            outputs.append(tmp_out)\n        return tuple(outputs)\n"
  },
  {
    "path": "code/mmdet/models/necks/nas_fpn.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, caffe2_xavier_init\n\nfrom mmdet.ops.merge_cells import GlobalPoolingCell, SumCell\nfrom ..builder import NECKS\n\n\n@NECKS.register_module()\nclass NASFPN(nn.Module):\n    \"\"\"NAS-FPN.\n\n    Implementation of `NAS-FPN: Learning Scalable Feature Pyramid Architecture\n    for Object Detection <https://arxiv.org/abs/1904.07392>`_\n\n    Args:\n        in_channels (List[int]): Number of input channels per scale.\n        out_channels (int): Number of output channels (used at each scale)\n        num_outs (int): Number of output scales.\n        stack_times (int): The number of times the pyramid architecture will\n            be stacked.\n        start_level (int): Index of the start input backbone level used to\n            build the feature pyramid. Default: 0.\n        end_level (int): Index of the end input backbone level (exclusive) to\n            build the feature pyramid. Default: -1, which means the last level.\n        add_extra_convs (bool): It decides whether to add conv\n            layers on top of the original feature maps. Default to False.\n            If True, its actual mode is specified by `extra_convs_on_inputs`.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 num_outs,\n                 stack_times,\n                 start_level=0,\n                 end_level=-1,\n                 add_extra_convs=False,\n                 norm_cfg=None):\n        super(NASFPN, self).__init__()\n        assert isinstance(in_channels, list)\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.num_ins = len(in_channels)  # num of input feature levels\n        self.num_outs = num_outs  # num of output feature levels\n        self.stack_times = stack_times\n        self.norm_cfg = norm_cfg\n\n        if end_level == -1:\n            self.backbone_end_level = self.num_ins\n            assert num_outs >= self.num_ins - start_level\n        else:\n            # if end_level < inputs, no extra level is allowed\n            self.backbone_end_level = end_level\n            assert end_level <= len(in_channels)\n            assert num_outs == end_level - start_level\n        self.start_level = start_level\n        self.end_level = end_level\n        self.add_extra_convs = add_extra_convs\n\n        # add lateral connections\n        self.lateral_convs = nn.ModuleList()\n        for i in range(self.start_level, self.backbone_end_level):\n            l_conv = ConvModule(\n                in_channels[i],\n                out_channels,\n                1,\n                norm_cfg=norm_cfg,\n                act_cfg=None)\n            self.lateral_convs.append(l_conv)\n\n        # add extra downsample layers (stride-2 pooling or conv)\n        extra_levels = num_outs - self.backbone_end_level + self.start_level\n        self.extra_downsamples = nn.ModuleList()\n        for i in range(extra_levels):\n            extra_conv = ConvModule(\n                out_channels, out_channels, 1, norm_cfg=norm_cfg, act_cfg=None)\n            self.extra_downsamples.append(\n                nn.Sequential(extra_conv, nn.MaxPool2d(2, 2)))\n\n        # add NAS FPN connections\n        self.fpn_stages = nn.ModuleList()\n        for _ in range(self.stack_times):\n            stage = nn.ModuleDict()\n            # gp(p6, p4) -> p4_1\n            stage['gp_64_4'] = GlobalPoolingCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            # sum(p4_1, p4) -> p4_2\n            stage['sum_44_4'] = SumCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            # sum(p4_2, p3) -> p3_out\n            stage['sum_43_3'] = SumCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            # sum(p3_out, p4_2) -> p4_out\n            stage['sum_34_4'] = SumCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            # sum(p5, gp(p4_out, p3_out)) -> p5_out\n            stage['gp_43_5'] = GlobalPoolingCell(with_out_conv=False)\n            stage['sum_55_5'] = SumCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            # sum(p7, gp(p5_out, p4_2)) -> p7_out\n            stage['gp_54_7'] = GlobalPoolingCell(with_out_conv=False)\n            stage['sum_77_7'] = SumCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            # gp(p7_out, p5_out) -> p6_out\n            stage['gp_75_6'] = GlobalPoolingCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                out_norm_cfg=norm_cfg)\n            self.fpn_stages.append(stage)\n\n    def init_weights(self):\n        \"\"\"Initialize the weights of module\"\"\"\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                caffe2_xavier_init(m)\n\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        # build P3-P5\n        feats = [\n            lateral_conv(inputs[i + self.start_level])\n            for i, lateral_conv in enumerate(self.lateral_convs)\n        ]\n        # build P6-P7 on top of P5\n        for downsample in self.extra_downsamples:\n            feats.append(downsample(feats[-1]))\n\n        p3, p4, p5, p6, p7 = feats\n\n        for stage in self.fpn_stages:\n            # gp(p6, p4) -> p4_1\n            p4_1 = stage['gp_64_4'](p6, p4, out_size=p4.shape[-2:])\n            # sum(p4_1, p4) -> p4_2\n            p4_2 = stage['sum_44_4'](p4_1, p4, out_size=p4.shape[-2:])\n            # sum(p4_2, p3) -> p3_out\n            p3 = stage['sum_43_3'](p4_2, p3, out_size=p3.shape[-2:])\n            # sum(p3_out, p4_2) -> p4_out\n            p4 = stage['sum_34_4'](p3, p4_2, out_size=p4.shape[-2:])\n            # sum(p5, gp(p4_out, p3_out)) -> p5_out\n            p5_tmp = stage['gp_43_5'](p4, p3, out_size=p5.shape[-2:])\n            p5 = stage['sum_55_5'](p5, p5_tmp, out_size=p5.shape[-2:])\n            # sum(p7, gp(p5_out, p4_2)) -> p7_out\n            p7_tmp = stage['gp_54_7'](p5, p4_2, out_size=p7.shape[-2:])\n            p7 = stage['sum_77_7'](p7, p7_tmp, out_size=p7.shape[-2:])\n            # gp(p7_out, p5_out) -> p6_out\n            p6 = stage['gp_75_6'](p7, p5, out_size=p6.shape[-2:])\n\n        return p3, p4, p5, p6, p7\n"
  },
  {
    "path": "code/mmdet/models/necks/nasfcos_fpn.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, caffe2_xavier_init\n\nfrom mmdet.ops.merge_cells import ConcatCell\nfrom ..builder import NECKS\n\n\n@NECKS.register_module()\nclass NASFCOS_FPN(nn.Module):\n    \"\"\"FPN structure in NASFPN\n\n    Implementation of paper `NAS-FCOS: Fast Neural Architecture Search for\n    Object Detection <https://arxiv.org/abs/1906.04423>`_\n\n    Args:\n        in_channels (List[int]): Number of input channels per scale.\n        out_channels (int): Number of output channels (used at each scale)\n        num_outs (int): Number of output scales.\n        start_level (int): Index of the start input backbone level used to\n            build the feature pyramid. Default: 0.\n        end_level (int): Index of the end input backbone level (exclusive) to\n            build the feature pyramid. Default: -1, which means the last level.\n        add_extra_convs (bool): It decides whether to add conv\n            layers on top of the original feature maps. Default to False.\n            If True, its actual mode is specified by `extra_convs_on_inputs`.\n        conv_cfg (dict): dictionary to construct and config conv layer.\n        norm_cfg (dict): dictionary to construct and config norm layer.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 num_outs,\n                 start_level=1,\n                 end_level=-1,\n                 add_extra_convs=False,\n                 conv_cfg=None,\n                 norm_cfg=None):\n        super(NASFCOS_FPN, self).__init__()\n        assert isinstance(in_channels, list)\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.num_ins = len(in_channels)\n        self.num_outs = num_outs\n        self.norm_cfg = norm_cfg\n        self.conv_cfg = conv_cfg\n\n        if end_level == -1:\n            self.backbone_end_level = self.num_ins\n            assert num_outs >= self.num_ins - start_level\n        else:\n            self.backbone_end_level = end_level\n            assert end_level <= len(in_channels)\n            assert num_outs == end_level - start_level\n        self.start_level = start_level\n        self.end_level = end_level\n        self.add_extra_convs = add_extra_convs\n\n        self.adapt_convs = nn.ModuleList()\n        for i in range(self.start_level, self.backbone_end_level):\n            adapt_conv = ConvModule(\n                in_channels[i],\n                out_channels,\n                1,\n                stride=1,\n                padding=0,\n                bias=False,\n                norm_cfg=dict(type='BN'),\n                act_cfg=dict(type='ReLU', inplace=False))\n            self.adapt_convs.append(adapt_conv)\n\n        # C2 is omitted according to the paper\n        extra_levels = num_outs - self.backbone_end_level + self.start_level\n\n        def build_concat_cell(with_input1_conv, with_input2_conv):\n            cell_conv_cfg = dict(\n                kernel_size=1, padding=0, bias=False, groups=out_channels)\n            return ConcatCell(\n                in_channels=out_channels,\n                out_channels=out_channels,\n                with_out_conv=True,\n                out_conv_cfg=cell_conv_cfg,\n                out_norm_cfg=dict(type='BN'),\n                out_conv_order=('norm', 'act', 'conv'),\n                with_input1_conv=with_input1_conv,\n                with_input2_conv=with_input2_conv,\n                input_conv_cfg=conv_cfg,\n                input_norm_cfg=norm_cfg,\n                upsample_mode='nearest')\n\n        # Denote c3=f0, c4=f1, c5=f2 for convince\n        self.fpn = nn.ModuleDict()\n        self.fpn['c22_1'] = build_concat_cell(True, True)\n        self.fpn['c22_2'] = build_concat_cell(True, True)\n        self.fpn['c32'] = build_concat_cell(True, False)\n        self.fpn['c02'] = build_concat_cell(True, False)\n        self.fpn['c42'] = build_concat_cell(True, True)\n        self.fpn['c36'] = build_concat_cell(True, True)\n        self.fpn['c61'] = build_concat_cell(True, True)  # f9\n        self.extra_downsamples = nn.ModuleList()\n        for i in range(extra_levels):\n            extra_act_cfg = None if i == 0 \\\n                else dict(type='ReLU', inplace=False)\n            self.extra_downsamples.append(\n                ConvModule(\n                    out_channels,\n                    out_channels,\n                    3,\n                    stride=2,\n                    padding=1,\n                    act_cfg=extra_act_cfg,\n                    order=('act', 'norm', 'conv')))\n\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        feats = [\n            adapt_conv(inputs[i + self.start_level])\n            for i, adapt_conv in enumerate(self.adapt_convs)\n        ]\n\n        for (i, module_name) in enumerate(self.fpn):\n            idx_1, idx_2 = int(module_name[1]), int(module_name[2])\n            res = self.fpn[module_name](feats[idx_1], feats[idx_2])\n            feats.append(res)\n\n        ret = []\n        for (idx, input_idx) in zip([9, 8, 7], [1, 2, 3]):  # add P3, P4, P5\n            feats1, feats2 = feats[idx], feats[5]\n            feats2_resize = F.interpolate(\n                feats2,\n                size=feats1.size()[2:],\n                mode='bilinear',\n                align_corners=False)\n\n            feats_sum = feats1 + feats2_resize\n            ret.append(\n                F.interpolate(\n                    feats_sum,\n                    size=inputs[input_idx].size()[2:],\n                    mode='bilinear',\n                    align_corners=False))\n\n        for submodule in self.extra_downsamples:\n            ret.append(submodule(ret[-1]))\n\n        return tuple(ret)\n\n    def init_weights(self):\n        \"\"\"Initialize the weights of module\"\"\"\n        for module in self.fpn.values():\n            if hasattr(module, 'conv_out'):\n                caffe2_xavier_init(module.out_conv.conv)\n\n        for modules in [\n                self.adapt_convs.modules(),\n                self.extra_downsamples.modules()\n        ]:\n            for module in modules:\n                if isinstance(module, nn.Conv2d):\n                    caffe2_xavier_init(module)\n"
  },
  {
    "path": "code/mmdet/models/necks/pafpn.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule\n\nfrom mmdet.core import auto_fp16\nfrom ..builder import NECKS\nfrom .fpn import FPN\n\n\n@NECKS.register_module()\nclass PAFPN(FPN):\n    \"\"\"Path Aggregation Network for Instance Segmentation.\n\n    This is an implementation of the `PAFPN in Path Aggregation Network\n    <https://arxiv.org/abs/1803.01534>`_.\n\n    Args:\n        in_channels (List[int]): Number of input channels per scale.\n        out_channels (int): Number of output channels (used at each scale)\n        num_outs (int): Number of output scales.\n        start_level (int): Index of the start input backbone level used to\n            build the feature pyramid. Default: 0.\n        end_level (int): Index of the end input backbone level (exclusive) to\n            build the feature pyramid. Default: -1, which means the last level.\n        add_extra_convs (bool): Whether to add conv layers on top of the\n            original feature maps. Default: False.\n        extra_convs_on_inputs (bool): Whether to apply extra conv on\n            the original feature from the backbone. Default: False.\n        relu_before_extra_convs (bool): Whether to apply relu before the extra\n            conv. Default: False.\n        no_norm_on_lateral (bool): Whether to apply norm on lateral.\n            Default: False.\n        conv_cfg (dict): Config dict for convolution layer. Default: None.\n        norm_cfg (dict): Config dict for normalization layer. Default: None.\n        act_cfg (str): Config dict for activation layer in ConvModule.\n            Default: None.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 num_outs,\n                 start_level=0,\n                 end_level=-1,\n                 add_extra_convs=False,\n                 extra_convs_on_inputs=True,\n                 relu_before_extra_convs=False,\n                 no_norm_on_lateral=False,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 act_cfg=None):\n        super(PAFPN,\n              self).__init__(in_channels, out_channels, num_outs, start_level,\n                             end_level, add_extra_convs, extra_convs_on_inputs,\n                             relu_before_extra_convs, no_norm_on_lateral,\n                             conv_cfg, norm_cfg, act_cfg)\n        # add extra bottom up pathway\n        self.downsample_convs = nn.ModuleList()\n        self.pafpn_convs = nn.ModuleList()\n        for i in range(self.start_level + 1, self.backbone_end_level):\n            d_conv = ConvModule(\n                out_channels,\n                out_channels,\n                3,\n                stride=2,\n                padding=1,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                act_cfg=act_cfg,\n                inplace=False)\n            pafpn_conv = ConvModule(\n                out_channels,\n                out_channels,\n                3,\n                padding=1,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                act_cfg=act_cfg,\n                inplace=False)\n            self.downsample_convs.append(d_conv)\n            self.pafpn_convs.append(pafpn_conv)\n\n    @auto_fp16()\n    def forward(self, inputs):\n        \"\"\"Forward function\"\"\"\n        assert len(inputs) == len(self.in_channels)\n\n        # build laterals\n        laterals = [\n            lateral_conv(inputs[i + self.start_level])\n            for i, lateral_conv in enumerate(self.lateral_convs)\n        ]\n\n        # build top-down path\n        used_backbone_levels = len(laterals)\n        for i in range(used_backbone_levels - 1, 0, -1):\n            prev_shape = laterals[i - 1].shape[2:]\n            laterals[i - 1] += F.interpolate(\n                laterals[i], size=prev_shape, mode='nearest')\n\n        # build outputs\n        # part 1: from original levels\n        inter_outs = [\n            self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)\n        ]\n\n        # part 2: add bottom-up path\n        for i in range(0, used_backbone_levels - 1):\n            inter_outs[i + 1] += self.downsample_convs[i](inter_outs[i])\n\n        outs = []\n        outs.append(inter_outs[0])\n        outs.extend([\n            self.pafpn_convs[i - 1](inter_outs[i])\n            for i in range(1, used_backbone_levels)\n        ])\n\n        # part 3: add extra levels\n        if self.num_outs > len(outs):\n            # use max pool to get more levels on top of outputs\n            # (e.g., Faster R-CNN, Mask R-CNN)\n            if not self.add_extra_convs:\n                for i in range(self.num_outs - used_backbone_levels):\n                    outs.append(F.max_pool2d(outs[-1], 1, stride=2))\n            # add conv layers on top of original feature maps (RetinaNet)\n            else:\n                if self.extra_convs_on_inputs:\n                    orig = inputs[self.backbone_end_level - 1]\n                    outs.append(self.fpn_convs[used_backbone_levels](orig))\n                else:\n                    outs.append(self.fpn_convs[used_backbone_levels](outs[-1]))\n                for i in range(used_backbone_levels + 1, self.num_outs):\n                    if self.relu_before_extra_convs:\n                        outs.append(self.fpn_convs[i](F.relu(outs[-1])))\n                    else:\n                        outs.append(self.fpn_convs[i](outs[-1]))\n        return tuple(outs)\n"
  },
  {
    "path": "code/mmdet/models/necks/rfp.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import constant_init, kaiming_init\n\nfrom ..builder import NECKS, build_backbone\nfrom .fpn import FPN\n\n\nclass ASPP(nn.Module):\n    \"\"\"ASPP (Atrous Spatial Pyramid Pooling)\n\n    This is an implementation of the ASPP module used in DetectoRS\n    (https://arxiv.org/pdf/2006.02334.pdf)\n\n    Args:\n        in_channels (int): Number of input channels.\n        out_channels (int): Number of channels produced by this module\n        dilations (tuple[int]): Dilations of the four branches.\n            Default: (1, 3, 6, 1)\n    \"\"\"\n\n    def __init__(self, in_channels, out_channels, dilations=(1, 3, 6, 1)):\n        super().__init__()\n        assert dilations[-1] == 1\n        self.aspp = nn.ModuleList()\n        for dilation in dilations:\n            kernel_size = 3 if dilation > 1 else 1\n            padding = dilation if dilation > 1 else 0\n            conv = nn.Conv2d(\n                in_channels,\n                out_channels,\n                kernel_size=kernel_size,\n                stride=1,\n                dilation=dilation,\n                padding=padding,\n                bias=True)\n            self.aspp.append(conv)\n        self.gap = nn.AdaptiveAvgPool2d(1)\n        self.init_weights()\n\n    def init_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                kaiming_init(m)\n\n    def forward(self, x):\n        avg_x = self.gap(x)\n        out = []\n        for aspp_idx in range(len(self.aspp)):\n            inp = avg_x if (aspp_idx == len(self.aspp) - 1) else x\n            out.append(F.relu_(self.aspp[aspp_idx](inp)))\n        out[-1] = out[-1].expand_as(out[-2])\n        out = torch.cat(out, dim=1)\n        return out\n\n\n@NECKS.register_module()\nclass RFP(FPN):\n    \"\"\"RFP (Recursive Feature Pyramid)\n\n    This is an implementation of RFP in `DetectoRS\n    <https://arxiv.org/pdf/2006.02334.pdf>`_. Different from standard FPN, the\n    input of RFP should be multi level features along with origin input image\n    of backbone.\n\n    Args:\n        rfp_steps (int): Number of unrolled steps of RFP.\n        rfp_backbone (dict): Configuration of the backbone for RFP.\n        aspp_out_channels (int): Number of output channels of ASPP module.\n        aspp_dilations (tuple[int]): Dilation rates of four branches.\n            Default: (1, 3, 6, 1)\n    \"\"\"\n\n    def __init__(self,\n                 rfp_steps,\n                 rfp_backbone,\n                 aspp_out_channels,\n                 aspp_dilations=(1, 3, 6, 1),\n                 **kwargs):\n        super().__init__(**kwargs)\n        self.rfp_steps = rfp_steps\n        self.rfp_modules = nn.ModuleList()\n        for rfp_idx in range(1, rfp_steps):\n            rfp_module = build_backbone(rfp_backbone)\n            self.rfp_modules.append(rfp_module)\n        self.rfp_aspp = ASPP(self.out_channels, aspp_out_channels,\n                             aspp_dilations)\n        self.rfp_weight = nn.Conv2d(\n            self.out_channels,\n            1,\n            kernel_size=1,\n            stride=1,\n            padding=0,\n            bias=True)\n\n    def init_weights(self):\n        super().init_weights()\n        for rfp_idx in range(self.rfp_steps - 1):\n            self.rfp_modules[rfp_idx].init_weights(\n                self.rfp_modules[rfp_idx].pretrained)\n        constant_init(self.rfp_weight, 0)\n\n    def forward(self, inputs):\n        inputs = list(inputs)\n        assert len(inputs) == len(self.in_channels) + 1  # +1 for input image\n        img = inputs.pop(0)\n        # FPN forward\n        x = super().forward(tuple(inputs))\n        for rfp_idx in range(self.rfp_steps - 1):\n            rfp_feats = [x[0]] + list(\n                self.rfp_aspp(x[i]) for i in range(1, len(x)))\n            x_idx = self.rfp_modules[rfp_idx].rfp_forward(img, rfp_feats)\n            # FPN forward\n            x_idx = super().forward(x_idx)\n            x_new = []\n            for ft_idx in range(len(x_idx)):\n                add_weight = torch.sigmoid(self.rfp_weight(x_idx[ft_idx]))\n                x_new.append(add_weight * x_idx[ft_idx] +\n                             (1 - add_weight) * x[ft_idx])\n            x = x_new\n        return x\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/__init__.py",
    "content": "from .base_roi_head import BaseRoIHead\nfrom .bbox_heads import (BBoxHead, ConvFCBBoxHead, DoubleConvFCBBoxHead,\n                         Shared2FCBBoxHead, Shared4Conv1FCBBoxHead)\nfrom .cascade_roi_head import CascadeRoIHead\nfrom .double_roi_head import DoubleHeadRoIHead\nfrom .dynamic_roi_head import DynamicRoIHead\nfrom .grid_roi_head import GridRoIHead\nfrom .htc_roi_head import HybridTaskCascadeRoIHead\nfrom .mask_heads import (CoarseMaskHead, FCNMaskHead, FusedSemanticHead,\n                         GridHead, HTCMaskHead, MaskIoUHead, MaskPointHead)\nfrom .mask_scoring_roi_head import MaskScoringRoIHead\nfrom .pisa_roi_head import PISARoIHead\nfrom .point_rend_roi_head import PointRendRoIHead\nfrom .roi_extractors import SingleRoIExtractor\nfrom .shared_heads import ResLayer\n\n__all__ = [\n    'BaseRoIHead', 'CascadeRoIHead', 'DoubleHeadRoIHead', 'MaskScoringRoIHead',\n    'HybridTaskCascadeRoIHead', 'GridRoIHead', 'ResLayer', 'BBoxHead',\n    'ConvFCBBoxHead', 'Shared2FCBBoxHead', 'Shared4Conv1FCBBoxHead',\n    'DoubleConvFCBBoxHead', 'FCNMaskHead', 'HTCMaskHead', 'FusedSemanticHead',\n    'GridHead', 'MaskIoUHead', 'SingleRoIExtractor', 'PISARoIHead',\n    'PointRendRoIHead', 'MaskPointHead', 'CoarseMaskHead', 'DynamicRoIHead'\n]\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/base_roi_head.py",
    "content": "from abc import ABCMeta, abstractmethod\n\nimport torch.nn as nn\n\nfrom ..builder import build_shared_head\n\n\nclass BaseRoIHead(nn.Module, metaclass=ABCMeta):\n    \"\"\"Base class for RoIHeads\"\"\"\n\n    def __init__(self,\n                 bbox_roi_extractor=None,\n                 bbox_head=None,\n                 mask_roi_extractor=None,\n                 mask_head=None,\n                 shared_head=None,\n                 train_cfg=None,\n                 test_cfg=None):\n        super(BaseRoIHead, self).__init__()\n        self.train_cfg = train_cfg\n        self.test_cfg = test_cfg\n        if shared_head is not None:\n            self.shared_head = build_shared_head(shared_head)\n\n        if bbox_head is not None:\n            self.init_bbox_head(bbox_roi_extractor, bbox_head)\n\n        if mask_head is not None:\n            self.init_mask_head(mask_roi_extractor, mask_head)\n\n        self.init_assigner_sampler()\n\n    @property\n    def with_bbox(self):\n        \"\"\"bool: whether the RoI head contains a `bbox_head`\"\"\"\n        return hasattr(self, 'bbox_head') and self.bbox_head is not None\n\n    @property\n    def with_mask(self):\n        \"\"\"bool: whether the RoI head contains a `mask_head`\"\"\"\n        return hasattr(self, 'mask_head') and self.mask_head is not None\n\n    @property\n    def with_shared_head(self):\n        \"\"\"bool: whether the RoI head contains a `shared_head`\"\"\"\n        return hasattr(self, 'shared_head') and self.shared_head is not None\n\n    @abstractmethod\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def init_bbox_head(self):\n        \"\"\"Initialize ``bbox_head``\"\"\"\n        pass\n\n    @abstractmethod\n    def init_mask_head(self):\n        \"\"\"Initialize ``mask_head``\"\"\"\n        pass\n\n    @abstractmethod\n    def init_assigner_sampler(self):\n        \"\"\"Initialize assigner and sampler\"\"\"\n        pass\n\n    @abstractmethod\n    def forward_train(self,\n                      x,\n                      img_meta,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None,\n                      **kwargs):\n        \"\"\"Forward function during training\"\"\"\n        pass\n\n    async def async_simple_test(self, x, img_meta, **kwargs):\n        \"\"\"Asynchronized test function\"\"\"\n        raise NotImplementedError\n\n    def simple_test(self,\n                    x,\n                    proposal_list,\n                    img_meta,\n                    proposals=None,\n                    rescale=False,\n                    **kwargs):\n        \"\"\"Test without augmentation.\"\"\"\n        pass\n\n    def aug_test(self, x, proposal_list, img_metas, rescale=False, **kwargs):\n        \"\"\"Test with augmentations.\n\n        If rescale is False, then returned bboxes and masks will fit the scale\n        of imgs[0].\n        \"\"\"\n        pass\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/bbox_heads/__init__.py",
    "content": "from .bbox_head import BBoxHead\nfrom .convfc_bbox_head import (ConvFCBBoxHead, Shared2FCBBoxHead,\n                               Shared4Conv1FCBBoxHead)\nfrom .double_bbox_head import DoubleConvFCBBoxHead\n\n__all__ = [\n    'BBoxHead', 'ConvFCBBoxHead', 'Shared2FCBBoxHead',\n    'Shared4Conv1FCBBoxHead', 'DoubleConvFCBBoxHead'\n]\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/bbox_heads/bbox_head.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.nn.modules.utils import _pair\n\nfrom mmdet.core import (auto_fp16, build_bbox_coder, force_fp32, multi_apply,\n                        multiclass_nms)\nfrom mmdet.models.builder import HEADS, build_loss\nfrom mmdet.models.losses import accuracy\n\n\n@HEADS.register_module()\nclass BBoxHead(nn.Module):\n    \"\"\"Simplest RoI head, with only two fc layers for classification and\n    regression respectively\"\"\"\n\n    def __init__(self,\n                 with_avg_pool=False,\n                 with_cls=True,\n                 with_reg=True,\n                 roi_feat_size=7,\n                 in_channels=256,\n                 num_classes=80,\n                 bbox_coder=dict(\n                     type='DeltaXYWHBBoxCoder',\n                     target_means=[0., 0., 0., 0.],\n                     target_stds=[0.1, 0.1, 0.2, 0.2]),\n                 reg_class_agnostic=False,\n                 reg_decoded_bbox=False,\n                 loss_cls=dict(\n                     type='CrossEntropyLoss',\n                     use_sigmoid=False,\n                     loss_weight=1.0),\n                 loss_bbox=dict(\n                     type='SmoothL1Loss', beta=1.0, loss_weight=1.0)):\n        super(BBoxHead, self).__init__()\n        assert with_cls or with_reg\n        self.with_avg_pool = with_avg_pool\n        self.with_cls = with_cls\n        self.with_reg = with_reg\n        self.roi_feat_size = _pair(roi_feat_size)\n        self.roi_feat_area = self.roi_feat_size[0] * self.roi_feat_size[1]\n        self.in_channels = in_channels\n        self.num_classes = num_classes\n        self.reg_class_agnostic = reg_class_agnostic\n        self.reg_decoded_bbox = reg_decoded_bbox\n        self.fp16_enabled = False\n\n        self.bbox_coder = build_bbox_coder(bbox_coder)\n        self.loss_cls = build_loss(loss_cls)\n        self.loss_bbox = build_loss(loss_bbox)\n\n        in_channels = self.in_channels\n        if self.with_avg_pool:\n            self.avg_pool = nn.AvgPool2d(self.roi_feat_size)\n        else:\n            in_channels *= self.roi_feat_area\n        if self.with_cls:\n            # need to add background class\n            self.fc_cls = nn.Linear(in_channels, num_classes + 1)\n        if self.with_reg:\n            out_dim_reg = 4 if reg_class_agnostic else 4 * num_classes\n            self.fc_reg = nn.Linear(in_channels, out_dim_reg)\n        self.debug_imgs = None\n\n    def init_weights(self):\n        # conv layers are already initialized by ConvModule\n        if self.with_cls:\n            nn.init.normal_(self.fc_cls.weight, 0, 0.01)\n            nn.init.constant_(self.fc_cls.bias, 0)\n        if self.with_reg:\n            nn.init.normal_(self.fc_reg.weight, 0, 0.001)\n            nn.init.constant_(self.fc_reg.bias, 0)\n\n    @auto_fp16()\n    def forward(self, x):\n        if self.with_avg_pool:\n            x = self.avg_pool(x)\n        x = x.view(x.size(0), -1)\n        cls_score = self.fc_cls(x) if self.with_cls else None\n        bbox_pred = self.fc_reg(x) if self.with_reg else None\n        return cls_score, bbox_pred\n\n    def _get_target_single(self, pos_bboxes, neg_bboxes, pos_gt_bboxes,\n                           pos_gt_labels, cfg):\n        num_pos = pos_bboxes.size(0)\n        num_neg = neg_bboxes.size(0)\n        num_samples = num_pos + num_neg\n\n        # original implementation uses new_zeros since BG are set to be 0\n        # now use empty & fill because BG cat_id = num_classes,\n        # FG cat_id = [0, num_classes-1]\n        labels = pos_bboxes.new_full((num_samples, ),\n                                     self.num_classes,\n                                     dtype=torch.long)\n        label_weights = pos_bboxes.new_zeros(num_samples)\n        bbox_targets = pos_bboxes.new_zeros(num_samples, 4)\n        bbox_weights = pos_bboxes.new_zeros(num_samples, 4)\n        if num_pos > 0:\n            labels[:num_pos] = pos_gt_labels\n            pos_weight = 1.0 if cfg.pos_weight <= 0 else cfg.pos_weight\n            label_weights[:num_pos] = pos_weight\n            if not self.reg_decoded_bbox:\n                pos_bbox_targets = self.bbox_coder.encode(\n                    pos_bboxes, pos_gt_bboxes)\n            else:\n                pos_bbox_targets = pos_gt_bboxes\n            bbox_targets[:num_pos, :] = pos_bbox_targets\n            bbox_weights[:num_pos, :] = 1\n        if num_neg > 0:\n            label_weights[-num_neg:] = 1.0\n\n        return labels, label_weights, bbox_targets, bbox_weights\n\n    def get_targets(self,\n                    sampling_results,\n                    gt_bboxes,\n                    gt_labels,\n                    rcnn_train_cfg,\n                    concat=True):\n        pos_bboxes_list = [res.pos_bboxes for res in sampling_results]\n        neg_bboxes_list = [res.neg_bboxes for res in sampling_results]\n        pos_gt_bboxes_list = [res.pos_gt_bboxes for res in sampling_results]\n        pos_gt_labels_list = [res.pos_gt_labels for res in sampling_results]\n        labels, label_weights, bbox_targets, bbox_weights = multi_apply(\n            self._get_target_single,\n            pos_bboxes_list,\n            neg_bboxes_list,\n            pos_gt_bboxes_list,\n            pos_gt_labels_list,\n            cfg=rcnn_train_cfg)\n\n        if concat:\n            labels = torch.cat(labels, 0)\n            label_weights = torch.cat(label_weights, 0)\n            bbox_targets = torch.cat(bbox_targets, 0)\n            bbox_weights = torch.cat(bbox_weights, 0)\n        return labels, label_weights, bbox_targets, bbox_weights\n\n    @force_fp32(apply_to=('cls_score', 'bbox_pred'))\n    def loss(self,\n             cls_score,\n             bbox_pred,\n             rois,\n             labels,\n             label_weights,\n             bbox_targets,\n             bbox_weights,\n             reduction_override=None):\n        losses = dict()\n        if cls_score is not None:\n            avg_factor = max(torch.sum(label_weights > 0).float().item(), 1.)\n            if cls_score.numel() > 0:\n                losses['loss_cls'] = self.loss_cls(\n                    cls_score,\n                    labels,\n                    label_weights,\n                    avg_factor=avg_factor,\n                    reduction_override=reduction_override)\n                losses['acc'] = accuracy(cls_score, labels)\n        if bbox_pred is not None:\n            bg_class_ind = self.num_classes\n            # 0~self.num_classes-1 are FG, self.num_classes is BG\n            pos_inds = (labels >= 0) & (labels < bg_class_ind)\n            # do not perform bounding box regression for BG anymore.\n            if pos_inds.any():\n                if self.reg_decoded_bbox:\n                    bbox_pred = self.bbox_coder.decode(rois[:, 1:], bbox_pred)\n                if self.reg_class_agnostic:\n                    pos_bbox_pred = bbox_pred.view(\n                        bbox_pred.size(0), 4)[pos_inds.type(torch.bool)]\n                else:\n                    pos_bbox_pred = bbox_pred.view(\n                        bbox_pred.size(0), -1,\n                        4)[pos_inds.type(torch.bool),\n                           labels[pos_inds.type(torch.bool)]]\n                losses['loss_bbox'] = self.loss_bbox(\n                    pos_bbox_pred,\n                    bbox_targets[pos_inds.type(torch.bool)],\n                    bbox_weights[pos_inds.type(torch.bool)],\n                    avg_factor=bbox_targets.size(0),\n                    reduction_override=reduction_override)\n            else:\n                losses['loss_bbox'] = bbox_pred.sum() * 0\n        return losses\n\n    @force_fp32(apply_to=('cls_score', 'bbox_pred'))\n    def get_bboxes(self,\n                   rois,\n                   cls_score,\n                   bbox_pred,\n                   img_shape,\n                   scale_factor,\n                   rescale=False,\n                   cfg=None):\n        if isinstance(cls_score, list):\n            cls_score = sum(cls_score) / float(len(cls_score))\n        scores = F.softmax(cls_score, dim=1) if cls_score is not None else None\n\n        if bbox_pred is not None:\n            bboxes = self.bbox_coder.decode(\n                rois[:, 1:], bbox_pred, max_shape=img_shape)\n        else:\n            bboxes = rois[:, 1:].clone()\n            if img_shape is not None:\n                bboxes[:, [0, 2]].clamp_(min=0, max=img_shape[1])\n                bboxes[:, [1, 3]].clamp_(min=0, max=img_shape[0])\n\n        if rescale:\n            if isinstance(scale_factor, float):\n                bboxes /= scale_factor\n            else:\n                scale_factor = bboxes.new_tensor(scale_factor)\n                bboxes = (bboxes.view(bboxes.size(0), -1, 4) /\n                          scale_factor).view(bboxes.size()[0], -1)\n\n        if cfg is None:\n            return bboxes, scores\n        else:\n            det_bboxes, det_labels = multiclass_nms(bboxes, scores,\n                                                    cfg.score_thr, cfg.nms,\n                                                    cfg.max_per_img)\n\n            return det_bboxes, det_labels\n\n    @force_fp32(apply_to=('bbox_preds', ))\n    def refine_bboxes(self, rois, labels, bbox_preds, pos_is_gts, img_metas):\n        \"\"\"Refine bboxes during training.\n\n        Args:\n            rois (Tensor): Shape (n*bs, 5), where n is image number per GPU,\n                and bs is the sampled RoIs per image. The first column is\n                the image id and the next 4 columns are x1, y1, x2, y2.\n            labels (Tensor): Shape (n*bs, ).\n            bbox_preds (Tensor): Shape (n*bs, 4) or (n*bs, 4*#class).\n            pos_is_gts (list[Tensor]): Flags indicating if each positive bbox\n                is a gt bbox.\n            img_metas (list[dict]): Meta info of each image.\n\n        Returns:\n            list[Tensor]: Refined bboxes of each image in a mini-batch.\n\n        Example:\n            >>> # xdoctest: +REQUIRES(module:kwarray)\n            >>> import kwarray\n            >>> import numpy as np\n            >>> from mmdet.core.bbox.demodata import random_boxes\n            >>> self = BBoxHead(reg_class_agnostic=True)\n            >>> n_roi = 2\n            >>> n_img = 4\n            >>> scale = 512\n            >>> rng = np.random.RandomState(0)\n            >>> img_metas = [{'img_shape': (scale, scale)}\n            ...              for _ in range(n_img)]\n            >>> # Create rois in the expected format\n            >>> roi_boxes = random_boxes(n_roi, scale=scale, rng=rng)\n            >>> img_ids = torch.randint(0, n_img, (n_roi,))\n            >>> img_ids = img_ids.float()\n            >>> rois = torch.cat([img_ids[:, None], roi_boxes], dim=1)\n            >>> # Create other args\n            >>> labels = torch.randint(0, 2, (n_roi,)).long()\n            >>> bbox_preds = random_boxes(n_roi, scale=scale, rng=rng)\n            >>> # For each image, pretend random positive boxes are gts\n            >>> is_label_pos = (labels.numpy() > 0).astype(np.int)\n            >>> lbl_per_img = kwarray.group_items(is_label_pos,\n            ...                                   img_ids.numpy())\n            >>> pos_per_img = [sum(lbl_per_img.get(gid, []))\n            ...                for gid in range(n_img)]\n            >>> pos_is_gts = [\n            >>>     torch.randint(0, 2, (npos,)).byte().sort(\n            >>>         descending=True)[0]\n            >>>     for npos in pos_per_img\n            >>> ]\n            >>> bboxes_list = self.refine_bboxes(rois, labels, bbox_preds,\n            >>>                    pos_is_gts, img_metas)\n            >>> print(bboxes_list)\n        \"\"\"\n        img_ids = rois[:, 0].long().unique(sorted=True)\n        assert img_ids.numel() <= len(img_metas)\n\n        bboxes_list = []\n        for i in range(len(img_metas)):\n            inds = torch.nonzero(\n                rois[:, 0] == i, as_tuple=False).squeeze(dim=1)\n            num_rois = inds.numel()\n\n            bboxes_ = rois[inds, 1:]\n            label_ = labels[inds]\n            bbox_pred_ = bbox_preds[inds]\n            img_meta_ = img_metas[i]\n            pos_is_gts_ = pos_is_gts[i]\n\n            bboxes = self.regress_by_class(bboxes_, label_, bbox_pred_,\n                                           img_meta_)\n\n            # filter gt bboxes\n            pos_keep = 1 - pos_is_gts_\n            keep_inds = pos_is_gts_.new_ones(num_rois)\n            keep_inds[:len(pos_is_gts_)] = pos_keep\n\n            bboxes_list.append(bboxes[keep_inds.type(torch.bool)])\n\n        return bboxes_list\n\n    @force_fp32(apply_to=('bbox_pred', ))\n    def regress_by_class(self, rois, label, bbox_pred, img_meta):\n        \"\"\"Regress the bbox for the predicted class. Used in Cascade R-CNN.\n\n        Args:\n            rois (Tensor): shape (n, 4) or (n, 5)\n            label (Tensor): shape (n, )\n            bbox_pred (Tensor): shape (n, 4*(#class)) or (n, 4)\n            img_meta (dict): Image meta info.\n\n        Returns:\n            Tensor: Regressed bboxes, the same shape as input rois.\n        \"\"\"\n        assert rois.size(1) == 4 or rois.size(1) == 5, repr(rois.shape)\n\n        if not self.reg_class_agnostic:\n            label = label * 4\n            inds = torch.stack((label, label + 1, label + 2, label + 3), 1)\n            bbox_pred = torch.gather(bbox_pred, 1, inds)\n        assert bbox_pred.size(1) == 4\n\n        if rois.size(1) == 4:\n            new_rois = self.bbox_coder.decode(\n                rois, bbox_pred, max_shape=img_meta['img_shape'])\n        else:\n            bboxes = self.bbox_coder.decode(\n                rois[:, 1:], bbox_pred, max_shape=img_meta['img_shape'])\n            new_rois = torch.cat((rois[:, [0]], bboxes), dim=1)\n\n        return new_rois\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule\n\nfrom mmdet.models.builder import HEADS\nfrom .bbox_head import BBoxHead\n\n\n@HEADS.register_module()\nclass ConvFCBBoxHead(BBoxHead):\n    r\"\"\"More general bbox head, with shared conv and fc layers and two optional\n    separated branches.\n\n    .. code-block:: none\n\n                                    /-> cls convs -> cls fcs -> cls\n        shared convs -> shared fcs\n                                    \\-> reg convs -> reg fcs -> reg\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_shared_convs=0,\n                 num_shared_fcs=0,\n                 num_cls_convs=0,\n                 num_cls_fcs=0,\n                 num_reg_convs=0,\n                 num_reg_fcs=0,\n                 conv_out_channels=256,\n                 fc_out_channels=1024,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 *args,\n                 **kwargs):\n        super(ConvFCBBoxHead, self).__init__(*args, **kwargs)\n        assert (num_shared_convs + num_shared_fcs + num_cls_convs +\n                num_cls_fcs + num_reg_convs + num_reg_fcs > 0)\n        if num_cls_convs > 0 or num_reg_convs > 0:\n            assert num_shared_fcs == 0\n        if not self.with_cls:\n            assert num_cls_convs == 0 and num_cls_fcs == 0\n        if not self.with_reg:\n            assert num_reg_convs == 0 and num_reg_fcs == 0\n        self.num_shared_convs = num_shared_convs\n        self.num_shared_fcs = num_shared_fcs\n        self.num_cls_convs = num_cls_convs\n        self.num_cls_fcs = num_cls_fcs\n        self.num_reg_convs = num_reg_convs\n        self.num_reg_fcs = num_reg_fcs\n        self.conv_out_channels = conv_out_channels\n        self.fc_out_channels = fc_out_channels\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n\n        # add shared convs and fcs\n        self.shared_convs, self.shared_fcs, last_layer_dim = \\\n            self._add_conv_fc_branch(\n                self.num_shared_convs, self.num_shared_fcs, self.in_channels,\n                True)\n        self.shared_out_channels = last_layer_dim\n\n        # add cls specific branch\n        self.cls_convs, self.cls_fcs, self.cls_last_dim = \\\n            self._add_conv_fc_branch(\n                self.num_cls_convs, self.num_cls_fcs, self.shared_out_channels)\n\n        # add reg specific branch\n        self.reg_convs, self.reg_fcs, self.reg_last_dim = \\\n            self._add_conv_fc_branch(\n                self.num_reg_convs, self.num_reg_fcs, self.shared_out_channels)\n\n        if self.num_shared_fcs == 0 and not self.with_avg_pool:\n            if self.num_cls_fcs == 0:\n                self.cls_last_dim *= self.roi_feat_area\n            if self.num_reg_fcs == 0:\n                self.reg_last_dim *= self.roi_feat_area\n\n        self.relu = nn.ReLU(inplace=True)\n        # reconstruct fc_cls and fc_reg since input channels are changed\n        if self.with_cls:\n            self.fc_cls = nn.Linear(self.cls_last_dim, self.num_classes + 1)\n        if self.with_reg:\n            out_dim_reg = (4 if self.reg_class_agnostic else 4 *\n                           self.num_classes)\n            self.fc_reg = nn.Linear(self.reg_last_dim, out_dim_reg)\n\n    def _add_conv_fc_branch(self,\n                            num_branch_convs,\n                            num_branch_fcs,\n                            in_channels,\n                            is_shared=False):\n        \"\"\"Add shared or separable branch\n\n        convs -> avg pool (optional) -> fcs\n        \"\"\"\n        last_layer_dim = in_channels\n        # add branch specific conv layers\n        branch_convs = nn.ModuleList()\n        if num_branch_convs > 0:\n            for i in range(num_branch_convs):\n                conv_in_channels = (\n                    last_layer_dim if i == 0 else self.conv_out_channels)\n                branch_convs.append(\n                    ConvModule(\n                        conv_in_channels,\n                        self.conv_out_channels,\n                        3,\n                        padding=1,\n                        conv_cfg=self.conv_cfg,\n                        norm_cfg=self.norm_cfg))\n            last_layer_dim = self.conv_out_channels\n        # add branch specific fc layers\n        branch_fcs = nn.ModuleList()\n        if num_branch_fcs > 0:\n            # for shared branch, only consider self.with_avg_pool\n            # for separated branches, also consider self.num_shared_fcs\n            if (is_shared\n                    or self.num_shared_fcs == 0) and not self.with_avg_pool:\n                last_layer_dim *= self.roi_feat_area\n            for i in range(num_branch_fcs):\n                fc_in_channels = (\n                    last_layer_dim if i == 0 else self.fc_out_channels)\n                branch_fcs.append(\n                    nn.Linear(fc_in_channels, self.fc_out_channels))\n            last_layer_dim = self.fc_out_channels\n        return branch_convs, branch_fcs, last_layer_dim\n\n    def init_weights(self):\n        super(ConvFCBBoxHead, self).init_weights()\n        # conv layers are already initialized by ConvModule\n        for module_list in [self.shared_fcs, self.cls_fcs, self.reg_fcs]:\n            for m in module_list.modules():\n                if isinstance(m, nn.Linear):\n                    nn.init.xavier_uniform_(m.weight)\n                    nn.init.constant_(m.bias, 0)\n\n    def forward(self, x):\n        # shared part\n        if self.num_shared_convs > 0:\n            for conv in self.shared_convs:\n                x = conv(x)\n\n        if self.num_shared_fcs > 0:\n            if self.with_avg_pool:\n                x = self.avg_pool(x)\n\n            x = x.flatten(1)\n\n            for fc in self.shared_fcs:\n                x = self.relu(fc(x))\n        # separate branches\n        x_cls = x\n        x_reg = x\n\n        for conv in self.cls_convs:\n            x_cls = conv(x_cls)\n        if x_cls.dim() > 2:\n            if self.with_avg_pool:\n                x_cls = self.avg_pool(x_cls)\n            x_cls = x_cls.flatten(1)\n        for fc in self.cls_fcs:\n            x_cls = self.relu(fc(x_cls))\n\n        for conv in self.reg_convs:\n            x_reg = conv(x_reg)\n        if x_reg.dim() > 2:\n            if self.with_avg_pool:\n                x_reg = self.avg_pool(x_reg)\n            x_reg = x_reg.flatten(1)\n        for fc in self.reg_fcs:\n            x_reg = self.relu(fc(x_reg))\n\n        cls_score = self.fc_cls(x_cls) if self.with_cls else None\n        bbox_pred = self.fc_reg(x_reg) if self.with_reg else None\n        return cls_score, bbox_pred\n\n\n@HEADS.register_module()\nclass Shared2FCBBoxHead(ConvFCBBoxHead):\n\n    def __init__(self, fc_out_channels=1024, *args, **kwargs):\n        super(Shared2FCBBoxHead, self).__init__(\n            num_shared_convs=0,\n            num_shared_fcs=2,\n            num_cls_convs=0,\n            num_cls_fcs=0,\n            num_reg_convs=0,\n            num_reg_fcs=0,\n            fc_out_channels=fc_out_channels,\n            *args,\n            **kwargs)\n\n\n@HEADS.register_module()\nclass Shared4Conv1FCBBoxHead(ConvFCBBoxHead):\n\n    def __init__(self, fc_out_channels=1024, *args, **kwargs):\n        super(Shared4Conv1FCBBoxHead, self).__init__(\n            num_shared_convs=4,\n            num_shared_fcs=1,\n            num_cls_convs=0,\n            num_cls_fcs=0,\n            num_reg_convs=0,\n            num_reg_fcs=0,\n            fc_out_channels=fc_out_channels,\n            *args,\n            **kwargs)\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/bbox_heads/double_bbox_head.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, normal_init, xavier_init\n\nfrom mmdet.models.backbones.resnet import Bottleneck\nfrom mmdet.models.builder import HEADS\nfrom .bbox_head import BBoxHead\n\n\nclass BasicResBlock(nn.Module):\n    \"\"\"Basic residual block.\n\n    This block is a little different from the block in the ResNet backbone.\n    The kernel size of conv1 is 1 in this block while 3 in ResNet BasicBlock.\n\n    Args:\n        in_channels (int): Channels of the input feature map.\n        out_channels (int): Channels of the output feature map.\n        conv_cfg (dict): The config dict for convolution layers.\n        norm_cfg (dict): The config dict for normalization layers.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN')):\n        super(BasicResBlock, self).__init__()\n\n        # main path\n        self.conv1 = ConvModule(\n            in_channels,\n            in_channels,\n            kernel_size=3,\n            padding=1,\n            bias=False,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg)\n        self.conv2 = ConvModule(\n            in_channels,\n            out_channels,\n            kernel_size=1,\n            bias=False,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg,\n            act_cfg=None)\n\n        # identity path\n        self.conv_identity = ConvModule(\n            in_channels,\n            out_channels,\n            kernel_size=1,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg,\n            act_cfg=None)\n\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, x):\n        identity = x\n\n        x = self.conv1(x)\n        x = self.conv2(x)\n\n        identity = self.conv_identity(identity)\n        out = x + identity\n\n        out = self.relu(out)\n        return out\n\n\n@HEADS.register_module()\nclass DoubleConvFCBBoxHead(BBoxHead):\n    r\"\"\"Bbox head used in Double-Head R-CNN\n\n    .. code-block:: none\n\n                                          /-> cls\n                      /-> shared convs ->\n                                          \\-> reg\n        roi features\n                                          /-> cls\n                      \\-> shared fc    ->\n                                          \\-> reg\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_convs=0,\n                 num_fcs=0,\n                 conv_out_channels=1024,\n                 fc_out_channels=1024,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 **kwargs):\n        kwargs.setdefault('with_avg_pool', True)\n        super(DoubleConvFCBBoxHead, self).__init__(**kwargs)\n        assert self.with_avg_pool\n        assert num_convs > 0\n        assert num_fcs > 0\n        self.num_convs = num_convs\n        self.num_fcs = num_fcs\n        self.conv_out_channels = conv_out_channels\n        self.fc_out_channels = fc_out_channels\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n\n        # increase the channel of input features\n        self.res_block = BasicResBlock(self.in_channels,\n                                       self.conv_out_channels)\n\n        # add conv heads\n        self.conv_branch = self._add_conv_branch()\n        # add fc heads\n        self.fc_branch = self._add_fc_branch()\n\n        out_dim_reg = 4 if self.reg_class_agnostic else 4 * self.num_classes\n        self.fc_reg = nn.Linear(self.conv_out_channels, out_dim_reg)\n\n        self.fc_cls = nn.Linear(self.fc_out_channels, self.num_classes + 1)\n        self.relu = nn.ReLU(inplace=True)\n\n    def _add_conv_branch(self):\n        \"\"\"Add the fc branch which consists of a sequential of conv layers\"\"\"\n        branch_convs = nn.ModuleList()\n        for i in range(self.num_convs):\n            branch_convs.append(\n                Bottleneck(\n                    inplanes=self.conv_out_channels,\n                    planes=self.conv_out_channels // 4,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        return branch_convs\n\n    def _add_fc_branch(self):\n        \"\"\"Add the fc branch which consists of a sequential of fc layers\"\"\"\n        branch_fcs = nn.ModuleList()\n        for i in range(self.num_fcs):\n            fc_in_channels = (\n                self.in_channels *\n                self.roi_feat_area if i == 0 else self.fc_out_channels)\n            branch_fcs.append(nn.Linear(fc_in_channels, self.fc_out_channels))\n        return branch_fcs\n\n    def init_weights(self):\n        # conv layers are already initialized by ConvModule\n        normal_init(self.fc_cls, std=0.01)\n        normal_init(self.fc_reg, std=0.001)\n\n        for m in self.fc_branch.modules():\n            if isinstance(m, nn.Linear):\n                xavier_init(m, distribution='uniform')\n\n    def forward(self, x_cls, x_reg):\n        # conv head\n        x_conv = self.res_block(x_reg)\n\n        for conv in self.conv_branch:\n            x_conv = conv(x_conv)\n\n        if self.with_avg_pool:\n            x_conv = self.avg_pool(x_conv)\n\n        x_conv = x_conv.view(x_conv.size(0), -1)\n        bbox_pred = self.fc_reg(x_conv)\n\n        # fc head\n        x_fc = x_cls.view(x_cls.size(0), -1)\n        for fc in self.fc_branch:\n            x_fc = self.relu(fc(x_fc))\n\n        cls_score = self.fc_cls(x_fc)\n\n        return cls_score, bbox_pred\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/cascade_roi_head.py",
    "content": "import torch\nimport torch.nn as nn\n\nfrom mmdet.core import (bbox2result, bbox2roi, bbox_mapping, build_assigner,\n                        build_sampler, merge_aug_bboxes, merge_aug_masks,\n                        multiclass_nms)\nfrom ..builder import HEADS, build_head, build_roi_extractor\nfrom .base_roi_head import BaseRoIHead\nfrom .test_mixins import BBoxTestMixin, MaskTestMixin\n\n\n@HEADS.register_module()\nclass CascadeRoIHead(BaseRoIHead, BBoxTestMixin, MaskTestMixin):\n    \"\"\"Cascade roi head including one bbox head and one mask head.\n\n    https://arxiv.org/abs/1712.00726\n    \"\"\"\n\n    def __init__(self,\n                 num_stages,\n                 stage_loss_weights,\n                 bbox_roi_extractor=None,\n                 bbox_head=None,\n                 mask_roi_extractor=None,\n                 mask_head=None,\n                 shared_head=None,\n                 train_cfg=None,\n                 test_cfg=None):\n        assert bbox_roi_extractor is not None\n        assert bbox_head is not None\n        assert shared_head is None, \\\n            'Shared head is not supported in Cascade RCNN anymore'\n        self.num_stages = num_stages\n        self.stage_loss_weights = stage_loss_weights\n        super(CascadeRoIHead, self).__init__(\n            bbox_roi_extractor=bbox_roi_extractor,\n            bbox_head=bbox_head,\n            mask_roi_extractor=mask_roi_extractor,\n            mask_head=mask_head,\n            shared_head=shared_head,\n            train_cfg=train_cfg,\n            test_cfg=test_cfg)\n\n    def init_bbox_head(self, bbox_roi_extractor, bbox_head):\n        \"\"\"Initialize box head and box roi extractor\n\n        Args:\n            bbox_roi_extractor (dict): Config of box roi extractor.\n            bbox_head (dict): Config of box in box head.\n        \"\"\"\n        self.bbox_roi_extractor = nn.ModuleList()\n        self.bbox_head = nn.ModuleList()\n        if not isinstance(bbox_roi_extractor, list):\n            bbox_roi_extractor = [\n                bbox_roi_extractor for _ in range(self.num_stages)\n            ]\n        if not isinstance(bbox_head, list):\n            bbox_head = [bbox_head for _ in range(self.num_stages)]\n        assert len(bbox_roi_extractor) == len(bbox_head) == self.num_stages\n        for roi_extractor, head in zip(bbox_roi_extractor, bbox_head):\n            self.bbox_roi_extractor.append(build_roi_extractor(roi_extractor))\n            self.bbox_head.append(build_head(head))\n\n    def init_mask_head(self, mask_roi_extractor, mask_head):\n        \"\"\"Initialize mask head and mask roi extractor\n\n        Args:\n            mask_roi_extractor (dict): Config of mask roi extractor.\n            mask_head (dict): Config of mask in mask head.\n        \"\"\"\n        self.mask_head = nn.ModuleList()\n        if not isinstance(mask_head, list):\n            mask_head = [mask_head for _ in range(self.num_stages)]\n        assert len(mask_head) == self.num_stages\n        for head in mask_head:\n            self.mask_head.append(build_head(head))\n        if mask_roi_extractor is not None:\n            self.share_roi_extractor = False\n            self.mask_roi_extractor = nn.ModuleList()\n            if not isinstance(mask_roi_extractor, list):\n                mask_roi_extractor = [\n                    mask_roi_extractor for _ in range(self.num_stages)\n                ]\n            assert len(mask_roi_extractor) == self.num_stages\n            for roi_extractor in mask_roi_extractor:\n                self.mask_roi_extractor.append(\n                    build_roi_extractor(roi_extractor))\n        else:\n            self.share_roi_extractor = True\n            self.mask_roi_extractor = self.bbox_roi_extractor\n\n    def init_assigner_sampler(self):\n        \"\"\"Initialize assigner and sampler for each stage\"\"\"\n        self.bbox_assigner = []\n        self.bbox_sampler = []\n        if self.train_cfg is not None:\n            for rcnn_train_cfg in self.train_cfg:\n                self.bbox_assigner.append(\n                    build_assigner(rcnn_train_cfg.assigner))\n                self.bbox_sampler.append(build_sampler(rcnn_train_cfg.sampler))\n\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if self.with_shared_head:\n            self.shared_head.init_weights(pretrained=pretrained)\n        for i in range(self.num_stages):\n            if self.with_bbox:\n                self.bbox_roi_extractor[i].init_weights()\n                self.bbox_head[i].init_weights()\n            if self.with_mask:\n                if not self.share_roi_extractor:\n                    self.mask_roi_extractor[i].init_weights()\n                self.mask_head[i].init_weights()\n\n    def forward_dummy(self, x, proposals):\n        \"\"\"Dummy forward function\"\"\"\n        # bbox head\n        outs = ()\n        rois = bbox2roi([proposals])\n        if self.with_bbox:\n            for i in range(self.num_stages):\n                bbox_results = self._bbox_forward(i, x, rois)\n                outs = outs + (bbox_results['cls_score'],\n                               bbox_results['bbox_pred'])\n        # mask heads\n        if self.with_mask:\n            mask_rois = rois[:100]\n            for i in range(self.num_stages):\n                mask_results = self._mask_forward(i, x, mask_rois)\n                outs = outs + (mask_results['mask_pred'], )\n        return outs\n\n    def _bbox_forward(self, stage, x, rois):\n        \"\"\"Box head forward function used in both training and testing\"\"\"\n        bbox_roi_extractor = self.bbox_roi_extractor[stage]\n        bbox_head = self.bbox_head[stage]\n        bbox_feats = bbox_roi_extractor(x[:bbox_roi_extractor.num_inputs],\n                                        rois)\n        # do not support caffe_c4 model anymore\n        cls_score, bbox_pred = bbox_head(bbox_feats)\n\n        bbox_results = dict(\n            cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats)\n        return bbox_results\n\n    def _bbox_forward_train(self, stage, x, sampling_results, gt_bboxes,\n                            gt_labels, rcnn_train_cfg):\n        \"\"\"Run forward function and calculate loss for box head in training\"\"\"\n        rois = bbox2roi([res.bboxes for res in sampling_results])\n        bbox_results = self._bbox_forward(stage, x, rois)\n        bbox_targets = self.bbox_head[stage].get_targets(\n            sampling_results, gt_bboxes, gt_labels, rcnn_train_cfg)\n        loss_bbox = self.bbox_head[stage].loss(bbox_results['cls_score'],\n                                               bbox_results['bbox_pred'], rois,\n                                               *bbox_targets)\n\n        bbox_results.update(\n            loss_bbox=loss_bbox, rois=rois, bbox_targets=bbox_targets)\n        return bbox_results\n\n    def _mask_forward(self, stage, x, rois):\n        \"\"\"Mask head forward function used in both training and testing\"\"\"\n        mask_roi_extractor = self.mask_roi_extractor[stage]\n        mask_head = self.mask_head[stage]\n        mask_feats = mask_roi_extractor(x[:mask_roi_extractor.num_inputs],\n                                        rois)\n        # do not support caffe_c4 model anymore\n        mask_pred = mask_head(mask_feats)\n\n        mask_results = dict(mask_pred=mask_pred)\n        return mask_results\n\n    def _mask_forward_train(self,\n                            stage,\n                            x,\n                            sampling_results,\n                            gt_masks,\n                            rcnn_train_cfg,\n                            bbox_feats=None):\n        \"\"\"Run forward function and calculate loss for mask head in training\"\"\"\n        pos_rois = bbox2roi([res.pos_bboxes for res in sampling_results])\n        if len(pos_rois) == 0:\n            # If there are no predicted and/or truth boxes, then we cannot\n            # compute head / mask losses\n            return dict(loss_mask=None)\n        mask_results = self._mask_forward(stage, x, pos_rois)\n\n        mask_targets = self.mask_head[stage].get_targets(\n            sampling_results, gt_masks, rcnn_train_cfg)\n        pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results])\n        loss_mask = self.mask_head[stage].loss(mask_results['mask_pred'],\n                                               mask_targets, pos_labels)\n\n        mask_results.update(loss_mask=loss_mask)\n        return mask_results\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None):\n        \"\"\"\n        Args:\n            x (list[Tensor]): list of multi-level img features.\n            img_metas (list[dict]): list of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                `mmdet/datasets/pipelines/formatting.py:Collect`.\n            proposals (list[Tensors]): list of region proposals.\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n            gt_masks (None | Tensor) : true segmentation masks for each box\n                used if the architecture supports a segmentation task.\n\n        Returns:\n            dict[str, Tensor]: a dictionary of loss components\n        \"\"\"\n        losses = dict()\n        for i in range(self.num_stages):\n            self.current_stage = i\n            rcnn_train_cfg = self.train_cfg[i]\n            lw = self.stage_loss_weights[i]\n\n            # assign gts and sample proposals\n            sampling_results = []\n            if self.with_bbox or self.with_mask:\n                bbox_assigner = self.bbox_assigner[i]\n                bbox_sampler = self.bbox_sampler[i]\n                num_imgs = len(img_metas)\n                if gt_bboxes_ignore is None:\n                    gt_bboxes_ignore = [None for _ in range(num_imgs)]\n\n                for j in range(num_imgs):\n                    assign_result = bbox_assigner.assign(\n                        proposal_list[j], gt_bboxes[j], gt_bboxes_ignore[j],\n                        gt_labels[j])\n                    sampling_result = bbox_sampler.sample(\n                        assign_result,\n                        proposal_list[j],\n                        gt_bboxes[j],\n                        gt_labels[j],\n                        feats=[lvl_feat[j][None] for lvl_feat in x])\n                    sampling_results.append(sampling_result)\n\n            # bbox head forward and loss\n            bbox_results = self._bbox_forward_train(i, x, sampling_results,\n                                                    gt_bboxes, gt_labels,\n                                                    rcnn_train_cfg)\n\n            for name, value in bbox_results['loss_bbox'].items():\n                losses[f's{i}.{name}'] = (\n                    value * lw if 'loss' in name else value)\n\n            # mask head forward and loss\n            if self.with_mask:\n                mask_results = self._mask_forward_train(\n                    i, x, sampling_results, gt_masks, rcnn_train_cfg,\n                    bbox_results['bbox_feats'])\n                # TODO: Support empty tensor input. #2280\n                if mask_results['loss_mask'] is not None:\n                    for name, value in mask_results['loss_mask'].items():\n                        losses[f's{i}.{name}'] = (\n                            value * lw if 'loss' in name else value)\n\n            # refine bboxes\n            if i < self.num_stages - 1:\n                pos_is_gts = [res.pos_is_gt for res in sampling_results]\n                # bbox_targets is a tuple\n                roi_labels = bbox_results['bbox_targets'][0]\n                with torch.no_grad():\n                    proposal_list = self.bbox_head[i].refine_bboxes(\n                        bbox_results['rois'], roi_labels,\n                        bbox_results['bbox_pred'], pos_is_gts, img_metas)\n\n        return losses\n\n    def simple_test(self, x, proposal_list, img_metas, rescale=False):\n        \"\"\"Test without augmentation.\"\"\"\n        assert self.with_bbox, 'Bbox head must be implemented.'\n        img_shape = img_metas[0]['img_shape']\n        ori_shape = img_metas[0]['ori_shape']\n        scale_factor = img_metas[0]['scale_factor']\n\n        # \"ms\" in variable names means multi-stage\n        ms_bbox_result = {}\n        ms_segm_result = {}\n        ms_scores = []\n        rcnn_test_cfg = self.test_cfg\n\n        rois = bbox2roi(proposal_list)\n        for i in range(self.num_stages):\n            bbox_results = self._bbox_forward(i, x, rois)\n            ms_scores.append(bbox_results['cls_score'])\n\n            if i < self.num_stages - 1:\n                bbox_label = bbox_results['cls_score'].argmax(dim=1)\n                rois = self.bbox_head[i].regress_by_class(\n                    rois, bbox_label, bbox_results['bbox_pred'], img_metas[0])\n\n        cls_score = sum(ms_scores) / self.num_stages\n        det_bboxes, det_labels = self.bbox_head[-1].get_bboxes(\n            rois,\n            cls_score,\n            bbox_results['bbox_pred'],\n            img_shape,\n            scale_factor,\n            rescale=rescale,\n            cfg=rcnn_test_cfg)\n        bbox_result = bbox2result(det_bboxes, det_labels,\n                                  self.bbox_head[-1].num_classes)\n        ms_bbox_result['ensemble'] = bbox_result\n\n        if self.with_mask:\n            if det_bboxes.shape[0] == 0:\n                mask_classes = self.mask_head[-1].num_classes\n                segm_result = [[] for _ in range(mask_classes)]\n            else:\n                _bboxes = (\n                    det_bboxes[:, :4] * det_bboxes.new_tensor(scale_factor)\n                    if rescale else det_bboxes)\n\n                mask_rois = bbox2roi([_bboxes])\n                aug_masks = []\n                for i in range(self.num_stages):\n                    mask_results = self._mask_forward(i, x, mask_rois)\n                    aug_masks.append(\n                        mask_results['mask_pred'].sigmoid().cpu().numpy())\n                merged_masks = merge_aug_masks(aug_masks,\n                                               [img_metas] * self.num_stages,\n                                               self.test_cfg)\n                segm_result = self.mask_head[-1].get_seg_masks(\n                    merged_masks, _bboxes, det_labels, rcnn_test_cfg,\n                    ori_shape, scale_factor, rescale)\n            ms_segm_result['ensemble'] = segm_result\n\n        if self.with_mask:\n            results = (ms_bbox_result['ensemble'], ms_segm_result['ensemble'])\n        else:\n            results = ms_bbox_result['ensemble']\n\n        return results\n\n    def aug_test(self, features, proposal_list, img_metas, rescale=False):\n        \"\"\"Test with augmentations.\n\n        If rescale is False, then returned bboxes and masks will fit the scale\n        of imgs[0].\n        \"\"\"\n        rcnn_test_cfg = self.test_cfg\n        aug_bboxes = []\n        aug_scores = []\n        for x, img_meta in zip(features, img_metas):\n            # only one image in the batch\n            img_shape = img_meta[0]['img_shape']\n            scale_factor = img_meta[0]['scale_factor']\n            flip = img_meta[0]['flip']\n            flip_direction = img_meta[0]['flip_direction']\n\n            proposals = bbox_mapping(proposal_list[0][:, :4], img_shape,\n                                     scale_factor, flip, flip_direction)\n            # \"ms\" in variable names means multi-stage\n            ms_scores = []\n\n            rois = bbox2roi([proposals])\n            for i in range(self.num_stages):\n                bbox_results = self._bbox_forward(i, x, rois)\n                ms_scores.append(bbox_results['cls_score'])\n\n                if i < self.num_stages - 1:\n                    bbox_label = bbox_results['cls_score'].argmax(dim=1)\n                    rois = self.bbox_head[i].regress_by_class(\n                        rois, bbox_label, bbox_results['bbox_pred'],\n                        img_meta[0])\n\n            cls_score = sum(ms_scores) / float(len(ms_scores))\n            bboxes, scores = self.bbox_head[-1].get_bboxes(\n                rois,\n                cls_score,\n                bbox_results['bbox_pred'],\n                img_shape,\n                scale_factor,\n                rescale=False,\n                cfg=None)\n            aug_bboxes.append(bboxes)\n            aug_scores.append(scores)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = merge_aug_bboxes(\n            aug_bboxes, aug_scores, img_metas, rcnn_test_cfg)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                rcnn_test_cfg.score_thr,\n                                                rcnn_test_cfg.nms,\n                                                rcnn_test_cfg.max_per_img)\n\n        bbox_result = bbox2result(det_bboxes, det_labels,\n                                  self.bbox_head[-1].num_classes)\n\n        if self.with_mask:\n            if det_bboxes.shape[0] == 0:\n                segm_result = [[]\n                               for _ in range(self.mask_head[-1].num_classes)]\n            else:\n                aug_masks = []\n                aug_img_metas = []\n                for x, img_meta in zip(features, img_metas):\n                    img_shape = img_meta[0]['img_shape']\n                    scale_factor = img_meta[0]['scale_factor']\n                    flip = img_meta[0]['flip']\n                    flip_direction = img_meta[0]['flip_direction']\n                    _bboxes = bbox_mapping(det_bboxes[:, :4], img_shape,\n                                           scale_factor, flip, flip_direction)\n                    mask_rois = bbox2roi([_bboxes])\n                    for i in range(self.num_stages):\n                        mask_results = self._mask_forward(i, x, mask_rois)\n                        aug_masks.append(\n                            mask_results['mask_pred'].sigmoid().cpu().numpy())\n                        aug_img_metas.append(img_meta)\n                merged_masks = merge_aug_masks(aug_masks, aug_img_metas,\n                                               self.test_cfg)\n\n                ori_shape = img_metas[0][0]['ori_shape']\n                segm_result = self.mask_head[-1].get_seg_masks(\n                    merged_masks,\n                    det_bboxes,\n                    det_labels,\n                    rcnn_test_cfg,\n                    ori_shape,\n                    scale_factor=1.0,\n                    rescale=False)\n            return bbox_result, segm_result\n        else:\n            return bbox_result\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/double_roi_head.py",
    "content": "from ..builder import HEADS\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass DoubleHeadRoIHead(StandardRoIHead):\n    \"\"\"RoI head for Double Head RCNN\n\n    https://arxiv.org/abs/1904.06493\n    \"\"\"\n\n    def __init__(self, reg_roi_scale_factor, **kwargs):\n        super(DoubleHeadRoIHead, self).__init__(**kwargs)\n        self.reg_roi_scale_factor = reg_roi_scale_factor\n\n    def _bbox_forward(self, x, rois):\n        \"\"\"Box head forward function used in both training and testing time\"\"\"\n        bbox_cls_feats = self.bbox_roi_extractor(\n            x[:self.bbox_roi_extractor.num_inputs], rois)\n        bbox_reg_feats = self.bbox_roi_extractor(\n            x[:self.bbox_roi_extractor.num_inputs],\n            rois,\n            roi_scale_factor=self.reg_roi_scale_factor)\n        if self.with_shared_head:\n            bbox_cls_feats = self.shared_head(bbox_cls_feats)\n            bbox_reg_feats = self.shared_head(bbox_reg_feats)\n        cls_score, bbox_pred = self.bbox_head(bbox_cls_feats, bbox_reg_feats)\n\n        bbox_results = dict(\n            cls_score=cls_score,\n            bbox_pred=bbox_pred,\n            bbox_feats=bbox_cls_feats)\n        return bbox_results\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/dynamic_roi_head.py",
    "content": "import numpy as np\nimport torch\n\nfrom mmdet.core import bbox2roi\nfrom mmdet.models.losses import SmoothL1Loss\nfrom ..builder import HEADS\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass DynamicRoIHead(StandardRoIHead):\n    \"\"\"RoI head for `Dynamic R-CNN <https://arxiv.org/abs/2004.06002>`_.\"\"\"\n\n    def __init__(self, **kwargs):\n        super(DynamicRoIHead, self).__init__(**kwargs)\n        assert isinstance(self.bbox_head.loss_bbox, SmoothL1Loss)\n        # the IoU history of the past `update_iter_interval` iterations\n        self.iou_history = []\n        # the beta history of the past `update_iter_interval` iterations\n        self.beta_history = []\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None):\n        \"\"\"\n        Args:\n            x (list[Tensor]): list of multi-level img features.\n\n            img_metas (list[dict]): list of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                `mmdet/datasets/pipelines/formatting.py:Collect`.\n\n            proposals (list[Tensors]): list of region proposals.\n\n            gt_bboxes (list[Tensor]): each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n\n            gt_labels (list[Tensor]): class indices corresponding to each box\n\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n            gt_masks (None | Tensor) : true segmentation masks for each box\n                used if the architecture supports a segmentation task.\n\n        Returns:\n            dict[str, Tensor]: a dictionary of loss components\n        \"\"\"\n        # assign gts and sample proposals\n        if self.with_bbox or self.with_mask:\n            num_imgs = len(img_metas)\n            if gt_bboxes_ignore is None:\n                gt_bboxes_ignore = [None for _ in range(num_imgs)]\n            sampling_results = []\n            cur_iou = []\n            for i in range(num_imgs):\n                assign_result = self.bbox_assigner.assign(\n                    proposal_list[i], gt_bboxes[i], gt_bboxes_ignore[i],\n                    gt_labels[i])\n                sampling_result = self.bbox_sampler.sample(\n                    assign_result,\n                    proposal_list[i],\n                    gt_bboxes[i],\n                    gt_labels[i],\n                    feats=[lvl_feat[i][None] for lvl_feat in x])\n                # record the `iou_topk`-th largest IoU in an image\n                iou_topk = min(self.train_cfg.dynamic_rcnn.iou_topk,\n                               len(assign_result.max_overlaps))\n                ious, _ = torch.topk(assign_result.max_overlaps, iou_topk)\n                cur_iou.append(ious[-1].item())\n                sampling_results.append(sampling_result)\n            # average the current IoUs over images\n            cur_iou = np.mean(cur_iou)\n            self.iou_history.append(cur_iou)\n\n        losses = dict()\n        # bbox head forward and loss\n        if self.with_bbox:\n            bbox_results = self._bbox_forward_train(x, sampling_results,\n                                                    gt_bboxes, gt_labels,\n                                                    img_metas)\n            losses.update(bbox_results['loss_bbox'])\n\n        # mask head forward and loss\n        if self.with_mask:\n            mask_results = self._mask_forward_train(x, sampling_results,\n                                                    bbox_results['bbox_feats'],\n                                                    gt_masks, img_metas)\n            # TODO: Support empty tensor input. #2280\n            if mask_results['loss_mask'] is not None:\n                losses.update(mask_results['loss_mask'])\n\n        # update IoU threshold and SmoothL1 beta\n        update_iter_interval = self.train_cfg.dynamic_rcnn.update_iter_interval\n        if len(self.iou_history) % update_iter_interval == 0:\n            new_iou_thr, new_beta = self.update_hyperparameters()\n\n        return losses\n\n    def _bbox_forward_train(self, x, sampling_results, gt_bboxes, gt_labels,\n                            img_metas):\n        num_imgs = len(img_metas)\n        rois = bbox2roi([res.bboxes for res in sampling_results])\n        bbox_results = self._bbox_forward(x, rois)\n\n        bbox_targets = self.bbox_head.get_targets(sampling_results, gt_bboxes,\n                                                  gt_labels, self.train_cfg)\n        # record the `beta_topk`-th smallest target\n        # `bbox_targets[2]` and `bbox_targets[3]` stand for bbox_targets\n        # and bbox_weights, respectively\n        pos_inds = bbox_targets[3][:, 0].nonzero().squeeze(1)\n        num_pos = len(pos_inds)\n        cur_target = bbox_targets[2][pos_inds, :2].abs().mean(dim=1)\n        beta_topk = min(self.train_cfg.dynamic_rcnn.beta_topk * num_imgs,\n                        num_pos)\n        cur_target = torch.kthvalue(cur_target, beta_topk)[0].item()\n        self.beta_history.append(cur_target)\n        loss_bbox = self.bbox_head.loss(bbox_results['cls_score'],\n                                        bbox_results['bbox_pred'], rois,\n                                        *bbox_targets)\n\n        bbox_results.update(loss_bbox=loss_bbox)\n        return bbox_results\n\n    def update_hyperparameters(self):\n        \"\"\"\n        Update hyperparameters like `iou_thr` and `SmoothL1 beta` based\n        on the training statistics.\n\n        Returns:\n            tuple[float]: the updated `iou_thr` and `SmoothL1 beta`\n        \"\"\"\n        new_iou_thr = max(self.train_cfg.dynamic_rcnn.initial_iou,\n                          np.mean(self.iou_history))\n        self.iou_history = []\n        self.bbox_assigner.pos_iou_thr = new_iou_thr\n        self.bbox_assigner.neg_iou_thr = new_iou_thr\n        self.bbox_assigner.min_pos_iou = new_iou_thr\n        new_beta = min(self.train_cfg.dynamic_rcnn.initial_beta,\n                       np.median(self.beta_history))\n        self.beta_history = []\n        self.bbox_head.loss_bbox.beta = new_beta\n        return new_iou_thr, new_beta\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/grid_roi_head.py",
    "content": "import torch\n\nfrom mmdet.core import bbox2result, bbox2roi\nfrom ..builder import HEADS, build_head, build_roi_extractor\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass GridRoIHead(StandardRoIHead):\n    \"\"\"Grid roi head for Grid R-CNN.\n\n    https://arxiv.org/abs/1811.12030\n    \"\"\"\n\n    def __init__(self, grid_roi_extractor, grid_head, **kwargs):\n        assert grid_head is not None\n        super(GridRoIHead, self).__init__(**kwargs)\n        if grid_roi_extractor is not None:\n            self.grid_roi_extractor = build_roi_extractor(grid_roi_extractor)\n            self.share_roi_extractor = False\n        else:\n            self.share_roi_extractor = True\n            self.grid_roi_extractor = self.bbox_roi_extractor\n        self.grid_head = build_head(grid_head)\n\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        super(GridRoIHead, self).init_weights(pretrained)\n        self.grid_head.init_weights()\n        if not self.share_roi_extractor:\n            self.grid_roi_extractor.init_weights()\n\n    def _random_jitter(self, sampling_results, img_metas, amplitude=0.15):\n        \"\"\"Ramdom jitter positive proposals for training.\"\"\"\n        for sampling_result, img_meta in zip(sampling_results, img_metas):\n            bboxes = sampling_result.pos_bboxes\n            random_offsets = bboxes.new_empty(bboxes.shape[0], 4).uniform_(\n                -amplitude, amplitude)\n            # before jittering\n            cxcy = (bboxes[:, 2:4] + bboxes[:, :2]) / 2\n            wh = (bboxes[:, 2:4] - bboxes[:, :2]).abs()\n            # after jittering\n            new_cxcy = cxcy + wh * random_offsets[:, :2]\n            new_wh = wh * (1 + random_offsets[:, 2:])\n            # xywh to xyxy\n            new_x1y1 = (new_cxcy - new_wh / 2)\n            new_x2y2 = (new_cxcy + new_wh / 2)\n            new_bboxes = torch.cat([new_x1y1, new_x2y2], dim=1)\n            # clip bboxes\n            max_shape = img_meta['img_shape']\n            if max_shape is not None:\n                new_bboxes[:, 0::2].clamp_(min=0, max=max_shape[1] - 1)\n                new_bboxes[:, 1::2].clamp_(min=0, max=max_shape[0] - 1)\n\n            sampling_result.pos_bboxes = new_bboxes\n        return sampling_results\n\n    def forward_dummy(self, x, proposals):\n        \"\"\"Dummy forward function\"\"\"\n        # bbox head\n        outs = ()\n        rois = bbox2roi([proposals])\n        if self.with_bbox:\n            bbox_results = self._bbox_forward(x, rois)\n            outs = outs + (bbox_results['cls_score'],\n                           bbox_results['bbox_pred'])\n\n        # grid head\n        grid_rois = rois[:100]\n        grid_feats = self.grid_roi_extractor(\n            x[:self.grid_roi_extractor.num_inputs], grid_rois)\n        if self.with_shared_head:\n            grid_feats = self.shared_head(grid_feats)\n        grid_pred = self.grid_head(grid_feats)\n        outs = outs + (grid_pred, )\n\n        # mask head\n        if self.with_mask:\n            mask_rois = rois[:100]\n            mask_results = self._mask_forward(x, mask_rois)\n            outs = outs + (mask_results['mask_pred'], )\n        return outs\n\n    def _bbox_forward_train(self, x, sampling_results, gt_bboxes, gt_labels,\n                            img_metas):\n        \"\"\"Run forward function and calculate loss for box head in training\"\"\"\n        bbox_results = super(GridRoIHead,\n                             self)._bbox_forward_train(x, sampling_results,\n                                                       gt_bboxes, gt_labels,\n                                                       img_metas)\n\n        # Grid head forward and loss\n        sampling_results = self._random_jitter(sampling_results, img_metas)\n        pos_rois = bbox2roi([res.pos_bboxes for res in sampling_results])\n\n        # GN in head does not support zero shape input\n        if pos_rois.shape[0] == 0:\n            return bbox_results\n\n        grid_feats = self.grid_roi_extractor(\n            x[:self.grid_roi_extractor.num_inputs], pos_rois)\n        if self.with_shared_head:\n            grid_feats = self.shared_head(grid_feats)\n        # Accelerate training\n        max_sample_num_grid = self.train_cfg.get('max_num_grid', 192)\n        sample_idx = torch.randperm(\n            grid_feats.shape[0])[:min(grid_feats.shape[0], max_sample_num_grid\n                                      )]\n        grid_feats = grid_feats[sample_idx]\n\n        grid_pred = self.grid_head(grid_feats)\n\n        grid_targets = self.grid_head.get_targets(sampling_results,\n                                                  self.train_cfg)\n        grid_targets = grid_targets[sample_idx]\n\n        loss_grid = self.grid_head.loss(grid_pred, grid_targets)\n\n        bbox_results['loss_bbox'].update(loss_grid)\n        return bbox_results\n\n    def simple_test(self,\n                    x,\n                    proposal_list,\n                    img_metas,\n                    proposals=None,\n                    rescale=False):\n        \"\"\"Test without augmentation.\"\"\"\n        assert self.with_bbox, 'Bbox head must be implemented.'\n\n        det_bboxes, det_labels = self.simple_test_bboxes(\n            x, img_metas, proposal_list, self.test_cfg, rescale=False)\n        # pack rois into bboxes\n        grid_rois = bbox2roi([det_bboxes[:, :4]])\n        grid_feats = self.grid_roi_extractor(\n            x[:len(self.grid_roi_extractor.featmap_strides)], grid_rois)\n        if grid_rois.shape[0] != 0:\n            self.grid_head.test_mode = True\n            grid_pred = self.grid_head(grid_feats)\n            det_bboxes = self.grid_head.get_bboxes(det_bboxes,\n                                                   grid_pred['fused'],\n                                                   img_metas)\n            if rescale:\n                scale_factor = img_metas[0]['scale_factor']\n                if not isinstance(scale_factor, (float, torch.Tensor)):\n                    scale_factor = det_bboxes.new_tensor(scale_factor)\n                det_bboxes[:, :4] /= scale_factor\n        else:\n            det_bboxes = torch.Tensor([])\n\n        bbox_results = bbox2result(det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n\n        if not self.with_mask:\n            return bbox_results\n        else:\n            segm_results = self.simple_test_mask(\n                x, img_metas, det_bboxes, det_labels, rescale=rescale)\n            return bbox_results, segm_results\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/htc_roi_head.py",
    "content": "import torch\nimport torch.nn.functional as F\n\nfrom mmdet.core import (bbox2result, bbox2roi, bbox_mapping, merge_aug_bboxes,\n                        merge_aug_masks, multiclass_nms)\nfrom ..builder import HEADS, build_head, build_roi_extractor\nfrom .cascade_roi_head import CascadeRoIHead\n\n\n@HEADS.register_module()\nclass HybridTaskCascadeRoIHead(CascadeRoIHead):\n    \"\"\"Hybrid task cascade roi head including one bbox head and one mask head.\n\n    https://arxiv.org/abs/1901.07518\n    \"\"\"\n\n    def __init__(self,\n                 num_stages,\n                 stage_loss_weights,\n                 semantic_roi_extractor=None,\n                 semantic_head=None,\n                 semantic_fusion=('bbox', 'mask'),\n                 interleaved=True,\n                 mask_info_flow=True,\n                 **kwargs):\n        super(HybridTaskCascadeRoIHead,\n              self).__init__(num_stages, stage_loss_weights, **kwargs)\n        assert self.with_bbox and self.with_mask\n        assert not self.with_shared_head  # shared head is not supported\n\n        if semantic_head is not None:\n            self.semantic_roi_extractor = build_roi_extractor(\n                semantic_roi_extractor)\n            self.semantic_head = build_head(semantic_head)\n\n        self.semantic_fusion = semantic_fusion\n        self.interleaved = interleaved\n        self.mask_info_flow = mask_info_flow\n\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        super(HybridTaskCascadeRoIHead, self).init_weights(pretrained)\n        if self.with_semantic:\n            self.semantic_head.init_weights()\n\n    @property\n    def with_semantic(self):\n        \"\"\"bool: whether the head has semantic head\"\"\"\n        if hasattr(self, 'semantic_head') and self.semantic_head is not None:\n            return True\n        else:\n            return False\n\n    def forward_dummy(self, x, proposals):\n        \"\"\"Dummy forward function\"\"\"\n        outs = ()\n        # semantic head\n        if self.with_semantic:\n            _, semantic_feat = self.semantic_head(x)\n        else:\n            semantic_feat = None\n        # bbox heads\n        rois = bbox2roi([proposals])\n        for i in range(self.num_stages):\n            bbox_results = self._bbox_forward(\n                i, x, rois, semantic_feat=semantic_feat)\n            outs = outs + (bbox_results['cls_score'],\n                           bbox_results['bbox_pred'])\n        # mask heads\n        if self.with_mask:\n            mask_rois = rois[:100]\n            mask_roi_extractor = self.mask_roi_extractor[-1]\n            mask_feats = mask_roi_extractor(\n                x[:len(mask_roi_extractor.featmap_strides)], mask_rois)\n            if self.with_semantic and 'mask' in self.semantic_fusion:\n                mask_semantic_feat = self.semantic_roi_extractor(\n                    [semantic_feat], mask_rois)\n                mask_feats += mask_semantic_feat\n            last_feat = None\n            for i in range(self.num_stages):\n                mask_head = self.mask_head[i]\n                if self.mask_info_flow:\n                    mask_pred, last_feat = mask_head(mask_feats, last_feat)\n                else:\n                    mask_pred = mask_head(mask_feats)\n                outs = outs + (mask_pred, )\n        return outs\n\n    def _bbox_forward_train(self,\n                            stage,\n                            x,\n                            sampling_results,\n                            gt_bboxes,\n                            gt_labels,\n                            rcnn_train_cfg,\n                            semantic_feat=None):\n        \"\"\"Run forward function and calculate loss for box head in training\"\"\"\n        bbox_head = self.bbox_head[stage]\n        rois = bbox2roi([res.bboxes for res in sampling_results])\n        bbox_results = self._bbox_forward(\n            stage, x, rois, semantic_feat=semantic_feat)\n\n        bbox_targets = bbox_head.get_targets(sampling_results, gt_bboxes,\n                                             gt_labels, rcnn_train_cfg)\n        loss_bbox = bbox_head.loss(bbox_results['cls_score'],\n                                   bbox_results['bbox_pred'], rois,\n                                   *bbox_targets)\n\n        bbox_results.update(\n            loss_bbox=loss_bbox,\n            rois=rois,\n            bbox_targets=bbox_targets,\n        )\n        return bbox_results\n\n    def _mask_forward_train(self,\n                            stage,\n                            x,\n                            sampling_results,\n                            gt_masks,\n                            rcnn_train_cfg,\n                            semantic_feat=None):\n        \"\"\"Run forward function and calculate loss for mask head in training\"\"\"\n        mask_roi_extractor = self.mask_roi_extractor[stage]\n        mask_head = self.mask_head[stage]\n        pos_rois = bbox2roi([res.pos_bboxes for res in sampling_results])\n        mask_feats = mask_roi_extractor(x[:mask_roi_extractor.num_inputs],\n                                        pos_rois)\n\n        # semantic feature fusion\n        # element-wise sum for original features and pooled semantic features\n        if self.with_semantic and 'mask' in self.semantic_fusion:\n            mask_semantic_feat = self.semantic_roi_extractor([semantic_feat],\n                                                             pos_rois)\n            if mask_semantic_feat.shape[-2:] != mask_feats.shape[-2:]:\n                mask_semantic_feat = F.adaptive_avg_pool2d(\n                    mask_semantic_feat, mask_feats.shape[-2:])\n            mask_feats += mask_semantic_feat\n\n        # mask information flow\n        # forward all previous mask heads to obtain last_feat, and fuse it\n        # with the normal mask feature\n        if self.mask_info_flow:\n            last_feat = None\n            for i in range(stage):\n                last_feat = self.mask_head[i](\n                    mask_feats, last_feat, return_logits=False)\n            mask_pred = mask_head(mask_feats, last_feat, return_feat=False)\n        else:\n            mask_pred = mask_head(mask_feats, return_feat=False)\n\n        mask_targets = mask_head.get_targets(sampling_results, gt_masks,\n                                             rcnn_train_cfg)\n        pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results])\n        loss_mask = mask_head.loss(mask_pred, mask_targets, pos_labels)\n\n        mask_results = dict(loss_mask=loss_mask)\n        return mask_results\n\n    def _bbox_forward(self, stage, x, rois, semantic_feat=None):\n        \"\"\"Box head forward function used in both training and testing\"\"\"\n        bbox_roi_extractor = self.bbox_roi_extractor[stage]\n        bbox_head = self.bbox_head[stage]\n        bbox_feats = bbox_roi_extractor(\n            x[:len(bbox_roi_extractor.featmap_strides)], rois)\n        if self.with_semantic and 'bbox' in self.semantic_fusion:\n            bbox_semantic_feat = self.semantic_roi_extractor([semantic_feat],\n                                                             rois)\n            if bbox_semantic_feat.shape[-2:] != bbox_feats.shape[-2:]:\n                bbox_semantic_feat = F.adaptive_avg_pool2d(\n                    bbox_semantic_feat, bbox_feats.shape[-2:])\n            bbox_feats += bbox_semantic_feat\n        cls_score, bbox_pred = bbox_head(bbox_feats)\n\n        bbox_results = dict(cls_score=cls_score, bbox_pred=bbox_pred)\n        return bbox_results\n\n    def _mask_forward_test(self, stage, x, bboxes, semantic_feat=None):\n        \"\"\"Mask head forward function for testing\"\"\"\n        mask_roi_extractor = self.mask_roi_extractor[stage]\n        mask_head = self.mask_head[stage]\n        mask_rois = bbox2roi([bboxes])\n        mask_feats = mask_roi_extractor(\n            x[:len(mask_roi_extractor.featmap_strides)], mask_rois)\n        if self.with_semantic and 'mask' in self.semantic_fusion:\n            mask_semantic_feat = self.semantic_roi_extractor([semantic_feat],\n                                                             mask_rois)\n            if mask_semantic_feat.shape[-2:] != mask_feats.shape[-2:]:\n                mask_semantic_feat = F.adaptive_avg_pool2d(\n                    mask_semantic_feat, mask_feats.shape[-2:])\n            mask_feats += mask_semantic_feat\n        if self.mask_info_flow:\n            last_feat = None\n            last_pred = None\n            for i in range(stage):\n                mask_pred, last_feat = self.mask_head[i](mask_feats, last_feat)\n                if last_pred is not None:\n                    mask_pred = mask_pred + last_pred\n                last_pred = mask_pred\n            mask_pred = mask_head(mask_feats, last_feat, return_feat=False)\n            if last_pred is not None:\n                mask_pred = mask_pred + last_pred\n        else:\n            mask_pred = mask_head(mask_feats)\n        return mask_pred\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None,\n                      gt_semantic_seg=None):\n        \"\"\"\n        Args:\n            x (list[Tensor]): list of multi-level img features.\n\n            img_metas (list[dict]): list of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                `mmdet/datasets/pipelines/formatting.py:Collect`.\n\n            proposal_list (list[Tensors]): list of region proposals.\n\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n\n            gt_labels (list[Tensor]): class indices corresponding to each box\n\n            gt_bboxes_ignore (None, list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n\n            gt_masks (None, Tensor) : true segmentation masks for each box\n                used if the architecture supports a segmentation task.\n\n            gt_semantic_seg (None, list[Tensor]): semantic segmentation masks\n                used if the architecture supports semantic segmentation task.\n\n        Returns:\n            dict[str, Tensor]: a dictionary of loss components\n        \"\"\"\n        # semantic segmentation part\n        # 2 outputs: segmentation prediction and embedded features\n        losses = dict()\n        if self.with_semantic:\n            semantic_pred, semantic_feat = self.semantic_head(x)\n            loss_seg = self.semantic_head.loss(semantic_pred, gt_semantic_seg)\n            losses['loss_semantic_seg'] = loss_seg\n        else:\n            semantic_feat = None\n\n        for i in range(self.num_stages):\n            self.current_stage = i\n            rcnn_train_cfg = self.train_cfg[i]\n            lw = self.stage_loss_weights[i]\n\n            # assign gts and sample proposals\n            sampling_results = []\n            bbox_assigner = self.bbox_assigner[i]\n            bbox_sampler = self.bbox_sampler[i]\n            num_imgs = len(img_metas)\n            if gt_bboxes_ignore is None:\n                gt_bboxes_ignore = [None for _ in range(num_imgs)]\n\n            for j in range(num_imgs):\n                assign_result = bbox_assigner.assign(proposal_list[j],\n                                                     gt_bboxes[j],\n                                                     gt_bboxes_ignore[j],\n                                                     gt_labels[j])\n                sampling_result = bbox_sampler.sample(\n                    assign_result,\n                    proposal_list[j],\n                    gt_bboxes[j],\n                    gt_labels[j],\n                    feats=[lvl_feat[j][None] for lvl_feat in x])\n                sampling_results.append(sampling_result)\n\n            # bbox head forward and loss\n            bbox_results = \\\n                self._bbox_forward_train(\n                    i, x, sampling_results, gt_bboxes, gt_labels,\n                    rcnn_train_cfg, semantic_feat)\n            roi_labels = bbox_results['bbox_targets'][0]\n\n            for name, value in bbox_results['loss_bbox'].items():\n                losses[f's{i}.{name}'] = (\n                    value * lw if 'loss' in name else value)\n\n            # mask head forward and loss\n            if self.with_mask:\n                # interleaved execution: use regressed bboxes by the box branch\n                # to train the mask branch\n                if self.interleaved:\n                    pos_is_gts = [res.pos_is_gt for res in sampling_results]\n                    with torch.no_grad():\n                        proposal_list = self.bbox_head[i].refine_bboxes(\n                            bbox_results['rois'], roi_labels,\n                            bbox_results['bbox_pred'], pos_is_gts, img_metas)\n                        # re-assign and sample 512 RoIs from 512 RoIs\n                        sampling_results = []\n                        for j in range(num_imgs):\n                            assign_result = bbox_assigner.assign(\n                                proposal_list[j], gt_bboxes[j],\n                                gt_bboxes_ignore[j], gt_labels[j])\n                            sampling_result = bbox_sampler.sample(\n                                assign_result,\n                                proposal_list[j],\n                                gt_bboxes[j],\n                                gt_labels[j],\n                                feats=[lvl_feat[j][None] for lvl_feat in x])\n                            sampling_results.append(sampling_result)\n                mask_results = self._mask_forward_train(\n                    i, x, sampling_results, gt_masks, rcnn_train_cfg,\n                    semantic_feat)\n                for name, value in mask_results['loss_mask'].items():\n                    losses[f's{i}.{name}'] = (\n                        value * lw if 'loss' in name else value)\n\n            # refine bboxes (same as Cascade R-CNN)\n            if i < self.num_stages - 1 and not self.interleaved:\n                pos_is_gts = [res.pos_is_gt for res in sampling_results]\n                with torch.no_grad():\n                    proposal_list = self.bbox_head[i].refine_bboxes(\n                        bbox_results['rois'], roi_labels,\n                        bbox_results['bbox_pred'], pos_is_gts, img_metas)\n\n        return losses\n\n    def simple_test(self, x, proposal_list, img_metas, rescale=False):\n        \"\"\"Test without augmentation.\"\"\"\n        if self.with_semantic:\n            _, semantic_feat = self.semantic_head(x)\n        else:\n            semantic_feat = None\n\n        img_shape = img_metas[0]['img_shape']\n        ori_shape = img_metas[0]['ori_shape']\n        scale_factor = img_metas[0]['scale_factor']\n\n        # \"ms\" in variable names means multi-stage\n        ms_bbox_result = {}\n        ms_segm_result = {}\n        ms_scores = []\n        rcnn_test_cfg = self.test_cfg\n\n        rois = bbox2roi(proposal_list)\n        for i in range(self.num_stages):\n            bbox_head = self.bbox_head[i]\n            bbox_results = self._bbox_forward(\n                i, x, rois, semantic_feat=semantic_feat)\n            ms_scores.append(bbox_results['cls_score'])\n\n            if i < self.num_stages - 1:\n                bbox_label = bbox_results['cls_score'].argmax(dim=1)\n                rois = bbox_head.regress_by_class(rois, bbox_label,\n                                                  bbox_results['bbox_pred'],\n                                                  img_metas[0])\n\n        cls_score = sum(ms_scores) / float(len(ms_scores))\n        det_bboxes, det_labels = self.bbox_head[-1].get_bboxes(\n            rois,\n            cls_score,\n            bbox_results['bbox_pred'],\n            img_shape,\n            scale_factor,\n            rescale=rescale,\n            cfg=rcnn_test_cfg)\n        bbox_result = bbox2result(det_bboxes, det_labels,\n                                  self.bbox_head[-1].num_classes)\n        ms_bbox_result['ensemble'] = bbox_result\n\n        if self.with_mask:\n            if det_bboxes.shape[0] == 0:\n                mask_classes = self.mask_head[-1].num_classes\n                segm_result = [[] for _ in range(mask_classes)]\n            else:\n                _bboxes = (\n                    det_bboxes[:, :4] * det_bboxes.new_tensor(scale_factor)\n                    if rescale else det_bboxes)\n\n                mask_rois = bbox2roi([_bboxes])\n                aug_masks = []\n                mask_roi_extractor = self.mask_roi_extractor[-1]\n                mask_feats = mask_roi_extractor(\n                    x[:len(mask_roi_extractor.featmap_strides)], mask_rois)\n                if self.with_semantic and 'mask' in self.semantic_fusion:\n                    mask_semantic_feat = self.semantic_roi_extractor(\n                        [semantic_feat], mask_rois)\n                    mask_feats += mask_semantic_feat\n                last_feat = None\n                for i in range(self.num_stages):\n                    mask_head = self.mask_head[i]\n                    if self.mask_info_flow:\n                        mask_pred, last_feat = mask_head(mask_feats, last_feat)\n                    else:\n                        mask_pred = mask_head(mask_feats)\n                    aug_masks.append(mask_pred.sigmoid().cpu().numpy())\n                merged_masks = merge_aug_masks(aug_masks,\n                                               [img_metas] * self.num_stages,\n                                               self.test_cfg)\n                segm_result = self.mask_head[-1].get_seg_masks(\n                    merged_masks, _bboxes, det_labels, rcnn_test_cfg,\n                    ori_shape, scale_factor, rescale)\n            ms_segm_result['ensemble'] = segm_result\n\n        if self.with_mask:\n            results = (ms_bbox_result['ensemble'], ms_segm_result['ensemble'])\n        else:\n            results = ms_bbox_result['ensemble']\n\n        return results\n\n    def aug_test(self, img_feats, proposal_list, img_metas, rescale=False):\n        \"\"\"Test with augmentations.\n\n        If rescale is False, then returned bboxes and masks will fit the scale\n        of imgs[0].\n        \"\"\"\n        if self.with_semantic:\n            semantic_feats = [\n                self.semantic_head(feat)[1] for feat in img_feats\n            ]\n        else:\n            semantic_feats = [None] * len(img_metas)\n\n        rcnn_test_cfg = self.test_cfg\n        aug_bboxes = []\n        aug_scores = []\n        for x, img_meta, semantic in zip(img_feats, img_metas, semantic_feats):\n            # only one image in the batch\n            img_shape = img_meta[0]['img_shape']\n            scale_factor = img_meta[0]['scale_factor']\n            flip = img_meta[0]['flip']\n            flip_direction = img_meta[0]['flip_direction']\n\n            proposals = bbox_mapping(proposal_list[0][:, :4], img_shape,\n                                     scale_factor, flip, flip_direction)\n            # \"ms\" in variable names means multi-stage\n            ms_scores = []\n\n            rois = bbox2roi([proposals])\n            for i in range(self.num_stages):\n                bbox_head = self.bbox_head[i]\n                bbox_results = self._bbox_forward(\n                    i, x, rois, semantic_feat=semantic)\n                ms_scores.append(bbox_results['cls_score'])\n\n                if i < self.num_stages - 1:\n                    bbox_label = bbox_results['cls_score'].argmax(dim=1)\n                    rois = bbox_head.regress_by_class(\n                        rois, bbox_label, bbox_results['bbox_pred'],\n                        img_meta[0])\n\n            cls_score = sum(ms_scores) / float(len(ms_scores))\n            bboxes, scores = self.bbox_head[-1].get_bboxes(\n                rois,\n                cls_score,\n                bbox_results['bbox_pred'],\n                img_shape,\n                scale_factor,\n                rescale=False,\n                cfg=None)\n            aug_bboxes.append(bboxes)\n            aug_scores.append(scores)\n\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = merge_aug_bboxes(\n            aug_bboxes, aug_scores, img_metas, rcnn_test_cfg)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                rcnn_test_cfg.score_thr,\n                                                rcnn_test_cfg.nms,\n                                                rcnn_test_cfg.max_per_img)\n\n        bbox_result = bbox2result(det_bboxes, det_labels,\n                                  self.bbox_head[-1].num_classes)\n\n        if self.with_mask:\n            if det_bboxes.shape[0] == 0:\n                segm_result = [[]\n                               for _ in range(self.mask_head[-1].num_classes -\n                                              1)]\n            else:\n                aug_masks = []\n                aug_img_metas = []\n                for x, img_meta, semantic in zip(img_feats, img_metas,\n                                                 semantic_feats):\n                    img_shape = img_meta[0]['img_shape']\n                    scale_factor = img_meta[0]['scale_factor']\n                    flip = img_meta[0]['flip']\n                    flip_direction = img_meta[0]['flip_direction']\n                    _bboxes = bbox_mapping(det_bboxes[:, :4], img_shape,\n                                           scale_factor, flip, flip_direction)\n                    mask_rois = bbox2roi([_bboxes])\n                    mask_feats = self.mask_roi_extractor[-1](\n                        x[:len(self.mask_roi_extractor[-1].featmap_strides)],\n                        mask_rois)\n                    if self.with_semantic:\n                        semantic_feat = semantic\n                        mask_semantic_feat = self.semantic_roi_extractor(\n                            [semantic_feat], mask_rois)\n                        if mask_semantic_feat.shape[-2:] != mask_feats.shape[\n                                -2:]:\n                            mask_semantic_feat = F.adaptive_avg_pool2d(\n                                mask_semantic_feat, mask_feats.shape[-2:])\n                        mask_feats += mask_semantic_feat\n                    last_feat = None\n                    for i in range(self.num_stages):\n                        mask_head = self.mask_head[i]\n                        if self.mask_info_flow:\n                            mask_pred, last_feat = mask_head(\n                                mask_feats, last_feat)\n                        else:\n                            mask_pred = mask_head(mask_feats)\n                        aug_masks.append(mask_pred.sigmoid().cpu().numpy())\n                        aug_img_metas.append(img_meta)\n                merged_masks = merge_aug_masks(aug_masks, aug_img_metas,\n                                               self.test_cfg)\n\n                ori_shape = img_metas[0][0]['ori_shape']\n                segm_result = self.mask_head[-1].get_seg_masks(\n                    merged_masks,\n                    det_bboxes,\n                    det_labels,\n                    rcnn_test_cfg,\n                    ori_shape,\n                    scale_factor=1.0,\n                    rescale=False)\n            return bbox_result, segm_result\n        else:\n            return bbox_result\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/__init__.py",
    "content": "from .coarse_mask_head import CoarseMaskHead\nfrom .fcn_mask_head import FCNMaskHead\nfrom .fused_semantic_head import FusedSemanticHead\nfrom .grid_head import GridHead\nfrom .htc_mask_head import HTCMaskHead\nfrom .mask_point_head import MaskPointHead\nfrom .maskiou_head import MaskIoUHead\n\n__all__ = [\n    'FCNMaskHead', 'HTCMaskHead', 'FusedSemanticHead', 'GridHead',\n    'MaskIoUHead', 'CoarseMaskHead', 'MaskPointHead'\n]\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/coarse_mask_head.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import ConvModule, constant_init, xavier_init\n\nfrom mmdet.core import auto_fp16\nfrom mmdet.models.builder import HEADS\nfrom .fcn_mask_head import FCNMaskHead\n\n\n@HEADS.register_module()\nclass CoarseMaskHead(FCNMaskHead):\n    \"\"\"Coarse mask head used in PointRend.\n\n    Compared with standard ``FCNMaskHead``, ``CoarseMaskHead`` will downsample\n    the input feature map instead of upsample it.\n\n    Args:\n        num_convs (int): Number of conv layers in the head. Default: 0.\n        num_fcs (int): Number of fc layers in the head. Default: 2.\n        fc_out_channels (int): Number of output channels of fc layer.\n            Default: 1024.\n        downsample_factor (int): The factor that feature map is downsampled by.\n            Default: 2.\n    \"\"\"\n\n    def __init__(self,\n                 num_convs=0,\n                 num_fcs=2,\n                 fc_out_channels=1024,\n                 downsample_factor=2,\n                 *arg,\n                 **kwarg):\n        super(CoarseMaskHead, self).__init__(\n            *arg, num_convs=num_convs, upsample_cfg=dict(type=None), **kwarg)\n        self.num_fcs = num_fcs\n        assert self.num_fcs > 0\n        self.fc_out_channels = fc_out_channels\n        self.downsample_factor = downsample_factor\n        assert self.downsample_factor >= 1\n        # remove conv_logit\n        delattr(self, 'conv_logits')\n\n        if downsample_factor > 1:\n            downsample_in_channels = (\n                self.conv_out_channels\n                if self.num_convs > 0 else self.in_channels)\n            self.downsample_conv = ConvModule(\n                downsample_in_channels,\n                self.conv_out_channels,\n                kernel_size=downsample_factor,\n                stride=downsample_factor,\n                padding=0,\n                conv_cfg=self.conv_cfg,\n                norm_cfg=self.norm_cfg)\n        else:\n            self.downsample_conv = None\n\n        self.output_size = (self.roi_feat_size[0] // downsample_factor,\n                            self.roi_feat_size[1] // downsample_factor)\n        self.output_area = self.output_size[0] * self.output_size[1]\n\n        last_layer_dim = self.conv_out_channels * self.output_area\n\n        self.fcs = nn.ModuleList()\n        for i in range(num_fcs):\n            fc_in_channels = (\n                last_layer_dim if i == 0 else self.fc_out_channels)\n            self.fcs.append(nn.Linear(fc_in_channels, self.fc_out_channels))\n        last_layer_dim = self.fc_out_channels\n        output_channels = self.num_classes * self.output_area\n        self.fc_logits = nn.Linear(last_layer_dim, output_channels)\n\n    def init_weights(self):\n        for m in self.fcs.modules():\n            if isinstance(m, nn.Linear):\n                xavier_init(m)\n        constant_init(self.fc_logits, 0.001)\n\n    @auto_fp16()\n    def forward(self, x):\n        for conv in self.convs:\n            x = conv(x)\n\n        if self.downsample_conv is not None:\n            x = self.downsample_conv(x)\n\n        x = x.flatten(1)\n        for fc in self.fcs:\n            x = self.relu(fc(x))\n        mask_pred = self.fc_logits(x).view(\n            x.size(0), self.num_classes, *self.output_size)\n        return mask_pred\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/fcn_mask_head.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, build_upsample_layer\nfrom torch.nn.modules.utils import _pair\n\nfrom mmdet.core import auto_fp16, force_fp32, mask_target\nfrom mmdet.models.builder import HEADS, build_loss\nfrom mmdet.ops import Conv2d\nfrom mmdet.ops.carafe import CARAFEPack\n\nBYTES_PER_FLOAT = 4\n# TODO: This memory limit may be too much or too little. It would be better to\n# determine it based on available resources.\nGPU_MEM_LIMIT = 1024**3  # 1 GB memory limit\n\n\n@HEADS.register_module()\nclass FCNMaskHead(nn.Module):\n\n    def __init__(self,\n                 num_convs=4,\n                 roi_feat_size=14,\n                 in_channels=256,\n                 conv_kernel_size=3,\n                 conv_out_channels=256,\n                 num_classes=80,\n                 class_agnostic=False,\n                 upsample_cfg=dict(type='deconv', scale_factor=2),\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 loss_mask=dict(\n                     type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)):\n        super(FCNMaskHead, self).__init__()\n        self.upsample_cfg = upsample_cfg.copy()\n        if self.upsample_cfg['type'] not in [\n                None, 'deconv', 'nearest', 'bilinear', 'carafe'\n        ]:\n            raise ValueError(\n                f'Invalid upsample method {self.upsample_cfg[\"type\"]}, '\n                'accepted methods are \"deconv\", \"nearest\", \"bilinear\", '\n                '\"carafe\"')\n        self.num_convs = num_convs\n        # WARN: roi_feat_size is reserved and not used\n        self.roi_feat_size = _pair(roi_feat_size)\n        self.in_channels = in_channels\n        self.conv_kernel_size = conv_kernel_size\n        self.conv_out_channels = conv_out_channels\n        self.upsample_method = self.upsample_cfg.get('type')\n        self.scale_factor = self.upsample_cfg.pop('scale_factor', None)\n        self.num_classes = num_classes\n        self.class_agnostic = class_agnostic\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.fp16_enabled = False\n        self.loss_mask = build_loss(loss_mask)\n\n        self.convs = nn.ModuleList()\n        for i in range(self.num_convs):\n            in_channels = (\n                self.in_channels if i == 0 else self.conv_out_channels)\n            padding = (self.conv_kernel_size - 1) // 2\n            self.convs.append(\n                ConvModule(\n                    in_channels,\n                    self.conv_out_channels,\n                    self.conv_kernel_size,\n                    padding=padding,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=norm_cfg))\n        upsample_in_channels = (\n            self.conv_out_channels if self.num_convs > 0 else in_channels)\n        upsample_cfg_ = self.upsample_cfg.copy()\n        if self.upsample_method is None:\n            self.upsample = None\n        elif self.upsample_method == 'deconv':\n            upsample_cfg_.update(\n                in_channels=upsample_in_channels,\n                out_channels=self.conv_out_channels,\n                kernel_size=self.scale_factor,\n                stride=self.scale_factor)\n            self.upsample = build_upsample_layer(upsample_cfg_)\n        elif self.upsample_method == 'carafe':\n            upsample_cfg_.update(\n                channels=upsample_in_channels, scale_factor=self.scale_factor)\n            self.upsample = build_upsample_layer(upsample_cfg_)\n        else:\n            # suppress warnings\n            align_corners = (None\n                             if self.upsample_method == 'nearest' else False)\n            upsample_cfg_.update(\n                scale_factor=self.scale_factor,\n                mode=self.upsample_method,\n                align_corners=align_corners)\n            self.upsample = build_upsample_layer(upsample_cfg_)\n\n        out_channels = 1 if self.class_agnostic else self.num_classes\n        logits_in_channel = (\n            self.conv_out_channels\n            if self.upsample_method == 'deconv' else upsample_in_channels)\n        self.conv_logits = Conv2d(logits_in_channel, out_channels, 1)\n        self.relu = nn.ReLU(inplace=True)\n        self.debug_imgs = None\n\n    def init_weights(self):\n        for m in [self.upsample, self.conv_logits]:\n            if m is None:\n                continue\n            elif isinstance(m, CARAFEPack):\n                m.init_weights()\n            else:\n                nn.init.kaiming_normal_(\n                    m.weight, mode='fan_out', nonlinearity='relu')\n                nn.init.constant_(m.bias, 0)\n\n    @auto_fp16()\n    def forward(self, x):\n        for conv in self.convs:\n            x = conv(x)\n        if self.upsample is not None:\n            x = self.upsample(x)\n            if self.upsample_method == 'deconv':\n                x = self.relu(x)\n        mask_pred = self.conv_logits(x)\n        return mask_pred\n\n    def get_targets(self, sampling_results, gt_masks, rcnn_train_cfg):\n        pos_proposals = [res.pos_bboxes for res in sampling_results]\n        pos_assigned_gt_inds = [\n            res.pos_assigned_gt_inds for res in sampling_results\n        ]\n        mask_targets = mask_target(pos_proposals, pos_assigned_gt_inds,\n                                   gt_masks, rcnn_train_cfg)\n        return mask_targets\n\n    @force_fp32(apply_to=('mask_pred', ))\n    def loss(self, mask_pred, mask_targets, labels):\n        loss = dict()\n        if mask_pred.size(0) == 0:\n            loss_mask = mask_pred.sum() * 0\n        else:\n            if self.class_agnostic:\n                loss_mask = self.loss_mask(mask_pred, mask_targets,\n                                           torch.zeros_like(labels))\n            else:\n                loss_mask = self.loss_mask(mask_pred, mask_targets, labels)\n        loss['loss_mask'] = loss_mask\n        return loss\n\n    def get_seg_masks(self, mask_pred, det_bboxes, det_labels, rcnn_test_cfg,\n                      ori_shape, scale_factor, rescale):\n        \"\"\"Get segmentation masks from mask_pred and bboxes.\n\n        Args:\n            mask_pred (Tensor or ndarray): shape (n, #class, h, w).\n                For single-scale testing, mask_pred is the direct output of\n                model, whose type is Tensor, while for multi-scale testing,\n                it will be converted to numpy array outside of this method.\n            det_bboxes (Tensor): shape (n, 4/5)\n            det_labels (Tensor): shape (n, )\n            img_shape (Tensor): shape (3, )\n            rcnn_test_cfg (dict): rcnn testing config\n            ori_shape: original image size\n\n        Returns:\n            list[list]: encoded masks\n        \"\"\"\n        if isinstance(mask_pred, torch.Tensor):\n            mask_pred = mask_pred.sigmoid()\n        else:\n            mask_pred = det_bboxes.new_tensor(mask_pred)\n\n        device = mask_pred.device\n        cls_segms = [[] for _ in range(self.num_classes)\n                     ]  # BG is not included in num_classes\n        bboxes = det_bboxes[:, :4]\n        labels = det_labels\n\n        if rescale:\n            img_h, img_w = ori_shape[:2]\n        else:\n            img_h = np.round(ori_shape[0] * scale_factor).astype(np.int32)\n            img_w = np.round(ori_shape[1] * scale_factor).astype(np.int32)\n            scale_factor = 1.0\n\n        if not isinstance(scale_factor, (float, torch.Tensor)):\n            scale_factor = bboxes.new_tensor(scale_factor)\n        bboxes = bboxes / scale_factor\n\n        N = len(mask_pred)\n        # The actual implementation split the input into chunks,\n        # and paste them chunk by chunk.\n        if device.type == 'cpu':\n            # CPU is most efficient when they are pasted one by one with\n            # skip_empty=True, so that it performs minimal number of\n            # operations.\n            num_chunks = N\n        else:\n            # GPU benefits from parallelism for larger chunks,\n            # but may have memory issue\n            num_chunks = int(\n                np.ceil(N * img_h * img_w * BYTES_PER_FLOAT / GPU_MEM_LIMIT))\n            assert (num_chunks <=\n                    N), 'Default GPU_MEM_LIMIT is too small; try increasing it'\n        chunks = torch.chunk(torch.arange(N, device=device), num_chunks)\n\n        threshold = rcnn_test_cfg.mask_thr_binary\n        im_mask = torch.zeros(\n            N,\n            img_h,\n            img_w,\n            device=device,\n            dtype=torch.bool if threshold >= 0 else torch.uint8)\n\n        if not self.class_agnostic:\n            mask_pred = mask_pred[range(N), labels][:, None]\n\n        for inds in chunks:\n            masks_chunk, spatial_inds = _do_paste_mask(\n                mask_pred[inds],\n                bboxes[inds],\n                img_h,\n                img_w,\n                skip_empty=device.type == 'cpu')\n\n            if threshold >= 0:\n                masks_chunk = (masks_chunk >= threshold).to(dtype=torch.bool)\n            else:\n                # for visualization and debugging\n                masks_chunk = (masks_chunk * 255).to(dtype=torch.uint8)\n\n            im_mask[(inds, ) + spatial_inds] = masks_chunk\n\n        for i in range(N):\n            cls_segms[labels[i]].append(im_mask[i].cpu().numpy())\n        return cls_segms\n\n\ndef _do_paste_mask(masks, boxes, img_h, img_w, skip_empty=True):\n    \"\"\"Paste instance masks acoording to boxes.\n\n    This implementation is modified from\n    https://github.com/facebookresearch/detectron2/\n\n    Args:\n        masks (Tensor): N, 1, H, W\n        boxes (Tensor): N, 4\n        img_h (int): Height of the image to be pasted.\n        img_w (int): Width of the image to be pasted.\n        skip_empty (bool): Only paste masks within the region that\n            tightly bound all boxes, and returns the results this region only.\n            An important optimization for CPU.\n\n    Returns:\n        tuple: (Tensor, tuple). The first item is mask tensor, the second one\n            is the slice object.\n        If skip_empty == False, the whole image will be pasted. It will\n            return a mask of shape (N, img_h, img_w) and an empty tuple.\n        If skip_empty == True, only area around the mask will be pasted.\n            A mask of shape (N, h', w') and its start and end coordinates\n            in the original image will be returned.\n    \"\"\"\n    # On GPU, paste all masks together (up to chunk size)\n    # by using the entire image to sample the masks\n    # Compared to pasting them one by one,\n    # this has more operations but is faster on COCO-scale dataset.\n    device = masks.device\n    if skip_empty:\n        x0_int, y0_int = torch.clamp(\n            boxes.min(dim=0).values.floor()[:2] - 1,\n            min=0).to(dtype=torch.int32)\n        x1_int = torch.clamp(\n            boxes[:, 2].max().ceil() + 1, max=img_w).to(dtype=torch.int32)\n        y1_int = torch.clamp(\n            boxes[:, 3].max().ceil() + 1, max=img_h).to(dtype=torch.int32)\n    else:\n        x0_int, y0_int = 0, 0\n        x1_int, y1_int = img_w, img_h\n    x0, y0, x1, y1 = torch.split(boxes, 1, dim=1)  # each is Nx1\n\n    N = masks.shape[0]\n\n    img_y = torch.arange(\n        y0_int, y1_int, device=device, dtype=torch.float32) + 0.5\n    img_x = torch.arange(\n        x0_int, x1_int, device=device, dtype=torch.float32) + 0.5\n    img_y = (img_y - y0) / (y1 - y0) * 2 - 1\n    img_x = (img_x - x0) / (x1 - x0) * 2 - 1\n    # img_x, img_y have shapes (N, w), (N, h)\n    if torch.isinf(img_x).any():\n        inds = torch.where(torch.isinf(img_x))\n        img_x[inds] = 0\n    if torch.isinf(img_y).any():\n        inds = torch.where(torch.isinf(img_y))\n        img_y[inds] = 0\n\n    gx = img_x[:, None, :].expand(N, img_y.size(1), img_x.size(1))\n    gy = img_y[:, :, None].expand(N, img_y.size(1), img_x.size(1))\n    grid = torch.stack([gx, gy], dim=3)\n\n    img_masks = F.grid_sample(\n        masks.to(dtype=torch.float32), grid, align_corners=False)\n\n    if skip_empty:\n        return img_masks[:, 0], (slice(y0_int, y1_int), slice(x0_int, x1_int))\n    else:\n        return img_masks[:, 0], ()\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/fused_semantic_head.py",
    "content": "import torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, kaiming_init\n\nfrom mmdet.core import auto_fp16, force_fp32\nfrom mmdet.models.builder import HEADS\n\n\n@HEADS.register_module()\nclass FusedSemanticHead(nn.Module):\n    r\"\"\"Multi-level fused semantic segmentation head.\n\n    .. code-block:: none\n\n        in_1 -> 1x1 conv ---\n                            |\n        in_2 -> 1x1 conv -- |\n                           ||\n        in_3 -> 1x1 conv - ||\n                          |||                  /-> 1x1 conv (mask prediction)\n        in_4 -> 1x1 conv -----> 3x3 convs (*4)\n                            |                  \\-> 1x1 conv (feature)\n        in_5 -> 1x1 conv ---\n    \"\"\"  # noqa: W605\n\n    def __init__(self,\n                 num_ins,\n                 fusion_level,\n                 num_convs=4,\n                 in_channels=256,\n                 conv_out_channels=256,\n                 num_classes=183,\n                 ignore_label=255,\n                 loss_weight=0.2,\n                 conv_cfg=None,\n                 norm_cfg=None):\n        super(FusedSemanticHead, self).__init__()\n        self.num_ins = num_ins\n        self.fusion_level = fusion_level\n        self.num_convs = num_convs\n        self.in_channels = in_channels\n        self.conv_out_channels = conv_out_channels\n        self.num_classes = num_classes\n        self.ignore_label = ignore_label\n        self.loss_weight = loss_weight\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.fp16_enabled = False\n\n        self.lateral_convs = nn.ModuleList()\n        for i in range(self.num_ins):\n            self.lateral_convs.append(\n                ConvModule(\n                    self.in_channels,\n                    self.in_channels,\n                    1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg,\n                    inplace=False))\n\n        self.convs = nn.ModuleList()\n        for i in range(self.num_convs):\n            in_channels = self.in_channels if i == 0 else conv_out_channels\n            self.convs.append(\n                ConvModule(\n                    in_channels,\n                    conv_out_channels,\n                    3,\n                    padding=1,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg))\n        self.conv_embedding = ConvModule(\n            conv_out_channels,\n            conv_out_channels,\n            1,\n            conv_cfg=self.conv_cfg,\n            norm_cfg=self.norm_cfg)\n        self.conv_logits = nn.Conv2d(conv_out_channels, self.num_classes, 1)\n\n        self.criterion = nn.CrossEntropyLoss(ignore_index=ignore_label)\n\n    def init_weights(self):\n        kaiming_init(self.conv_logits)\n\n    @auto_fp16()\n    def forward(self, feats):\n        x = self.lateral_convs[self.fusion_level](feats[self.fusion_level])\n        fused_size = tuple(x.shape[-2:])\n        for i, feat in enumerate(feats):\n            if i != self.fusion_level:\n                feat = F.interpolate(\n                    feat, size=fused_size, mode='bilinear', align_corners=True)\n                x += self.lateral_convs[i](feat)\n\n        for i in range(self.num_convs):\n            x = self.convs[i](x)\n\n        mask_pred = self.conv_logits(x)\n        x = self.conv_embedding(x)\n        return mask_pred, x\n\n    @force_fp32(apply_to=('mask_pred', ))\n    def loss(self, mask_pred, labels):\n        labels = labels.squeeze(1).long()\n        loss_semantic_seg = self.criterion(mask_pred, labels)\n        loss_semantic_seg *= self.loss_weight\n        return loss_semantic_seg\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/grid_head.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule, kaiming_init, normal_init\n\nfrom mmdet.models.builder import HEADS, build_loss\n\n\n@HEADS.register_module()\nclass GridHead(nn.Module):\n\n    def __init__(self,\n                 grid_points=9,\n                 num_convs=8,\n                 roi_feat_size=14,\n                 in_channels=256,\n                 conv_kernel_size=3,\n                 point_feat_channels=64,\n                 deconv_kernel_size=4,\n                 class_agnostic=False,\n                 loss_grid=dict(\n                     type='CrossEntropyLoss', use_sigmoid=True,\n                     loss_weight=15),\n                 conv_cfg=None,\n                 norm_cfg=dict(type='GN', num_groups=36)):\n        super(GridHead, self).__init__()\n        self.grid_points = grid_points\n        self.num_convs = num_convs\n        self.roi_feat_size = roi_feat_size\n        self.in_channels = in_channels\n        self.conv_kernel_size = conv_kernel_size\n        self.point_feat_channels = point_feat_channels\n        self.conv_out_channels = self.point_feat_channels * self.grid_points\n        self.class_agnostic = class_agnostic\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        if isinstance(norm_cfg, dict) and norm_cfg['type'] == 'GN':\n            assert self.conv_out_channels % norm_cfg['num_groups'] == 0\n\n        assert self.grid_points >= 4\n        self.grid_size = int(np.sqrt(self.grid_points))\n        if self.grid_size * self.grid_size != self.grid_points:\n            raise ValueError('grid_points must be a square number')\n\n        # the predicted heatmap is half of whole_map_size\n        if not isinstance(self.roi_feat_size, int):\n            raise ValueError('Only square RoIs are supporeted in Grid R-CNN')\n        self.whole_map_size = self.roi_feat_size * 4\n\n        # compute point-wise sub-regions\n        self.sub_regions = self.calc_sub_regions()\n\n        self.convs = []\n        for i in range(self.num_convs):\n            in_channels = (\n                self.in_channels if i == 0 else self.conv_out_channels)\n            stride = 2 if i == 0 else 1\n            padding = (self.conv_kernel_size - 1) // 2\n            self.convs.append(\n                ConvModule(\n                    in_channels,\n                    self.conv_out_channels,\n                    self.conv_kernel_size,\n                    stride=stride,\n                    padding=padding,\n                    conv_cfg=self.conv_cfg,\n                    norm_cfg=self.norm_cfg,\n                    bias=True))\n        self.convs = nn.Sequential(*self.convs)\n\n        self.deconv1 = nn.ConvTranspose2d(\n            self.conv_out_channels,\n            self.conv_out_channels,\n            kernel_size=deconv_kernel_size,\n            stride=2,\n            padding=(deconv_kernel_size - 2) // 2,\n            groups=grid_points)\n        self.norm1 = nn.GroupNorm(grid_points, self.conv_out_channels)\n        self.deconv2 = nn.ConvTranspose2d(\n            self.conv_out_channels,\n            grid_points,\n            kernel_size=deconv_kernel_size,\n            stride=2,\n            padding=(deconv_kernel_size - 2) // 2,\n            groups=grid_points)\n\n        # find the 4-neighbor of each grid point\n        self.neighbor_points = []\n        grid_size = self.grid_size\n        for i in range(grid_size):  # i-th column\n            for j in range(grid_size):  # j-th row\n                neighbors = []\n                if i > 0:  # left: (i - 1, j)\n                    neighbors.append((i - 1) * grid_size + j)\n                if j > 0:  # up: (i, j - 1)\n                    neighbors.append(i * grid_size + j - 1)\n                if j < grid_size - 1:  # down: (i, j + 1)\n                    neighbors.append(i * grid_size + j + 1)\n                if i < grid_size - 1:  # right: (i + 1, j)\n                    neighbors.append((i + 1) * grid_size + j)\n                self.neighbor_points.append(tuple(neighbors))\n        # total edges in the grid\n        self.num_edges = sum([len(p) for p in self.neighbor_points])\n\n        self.forder_trans = nn.ModuleList()  # first-order feature transition\n        self.sorder_trans = nn.ModuleList()  # second-order feature transition\n        for neighbors in self.neighbor_points:\n            fo_trans = nn.ModuleList()\n            so_trans = nn.ModuleList()\n            for _ in range(len(neighbors)):\n                # each transition module consists of a 5x5 depth-wise conv and\n                # 1x1 conv.\n                fo_trans.append(\n                    nn.Sequential(\n                        nn.Conv2d(\n                            self.point_feat_channels,\n                            self.point_feat_channels,\n                            5,\n                            stride=1,\n                            padding=2,\n                            groups=self.point_feat_channels),\n                        nn.Conv2d(self.point_feat_channels,\n                                  self.point_feat_channels, 1)))\n                so_trans.append(\n                    nn.Sequential(\n                        nn.Conv2d(\n                            self.point_feat_channels,\n                            self.point_feat_channels,\n                            5,\n                            1,\n                            2,\n                            groups=self.point_feat_channels),\n                        nn.Conv2d(self.point_feat_channels,\n                                  self.point_feat_channels, 1)))\n            self.forder_trans.append(fo_trans)\n            self.sorder_trans.append(so_trans)\n\n        self.loss_grid = build_loss(loss_grid)\n\n    def init_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):\n                # TODO: compare mode = \"fan_in\" or \"fan_out\"\n                kaiming_init(m)\n        for m in self.modules():\n            if isinstance(m, nn.ConvTranspose2d):\n                normal_init(m, std=0.001)\n        nn.init.constant_(self.deconv2.bias, -np.log(0.99 / 0.01))\n\n    def forward(self, x):\n        assert x.shape[-1] == x.shape[-2] == self.roi_feat_size\n        # RoI feature transformation, downsample 2x\n        x = self.convs(x)\n\n        c = self.point_feat_channels\n        # first-order fusion\n        x_fo = [None for _ in range(self.grid_points)]\n        for i, points in enumerate(self.neighbor_points):\n            x_fo[i] = x[:, i * c:(i + 1) * c]\n            for j, point_idx in enumerate(points):\n                x_fo[i] = x_fo[i] + self.forder_trans[i][j](\n                    x[:, point_idx * c:(point_idx + 1) * c])\n\n        # second-order fusion\n        x_so = [None for _ in range(self.grid_points)]\n        for i, points in enumerate(self.neighbor_points):\n            x_so[i] = x[:, i * c:(i + 1) * c]\n            for j, point_idx in enumerate(points):\n                x_so[i] = x_so[i] + self.sorder_trans[i][j](x_fo[point_idx])\n\n        # predicted heatmap with fused features\n        x2 = torch.cat(x_so, dim=1)\n        x2 = self.deconv1(x2)\n        x2 = F.relu(self.norm1(x2), inplace=True)\n        heatmap = self.deconv2(x2)\n\n        # predicted heatmap with original features (applicable during training)\n        if self.training:\n            x1 = x\n            x1 = self.deconv1(x1)\n            x1 = F.relu(self.norm1(x1), inplace=True)\n            heatmap_unfused = self.deconv2(x1)\n        else:\n            heatmap_unfused = heatmap\n\n        return dict(fused=heatmap, unfused=heatmap_unfused)\n\n    def calc_sub_regions(self):\n        \"\"\"Compute point specific representation regions.\n\n        See Grid R-CNN Plus (https://arxiv.org/abs/1906.05688) for details.\n        \"\"\"\n        # to make it consistent with the original implementation, half_size\n        # is computed as 2 * quarter_size, which is smaller\n        half_size = self.whole_map_size // 4 * 2\n        sub_regions = []\n        for i in range(self.grid_points):\n            x_idx = i // self.grid_size\n            y_idx = i % self.grid_size\n            if x_idx == 0:\n                sub_x1 = 0\n            elif x_idx == self.grid_size - 1:\n                sub_x1 = half_size\n            else:\n                ratio = x_idx / (self.grid_size - 1) - 0.25\n                sub_x1 = max(int(ratio * self.whole_map_size), 0)\n\n            if y_idx == 0:\n                sub_y1 = 0\n            elif y_idx == self.grid_size - 1:\n                sub_y1 = half_size\n            else:\n                ratio = y_idx / (self.grid_size - 1) - 0.25\n                sub_y1 = max(int(ratio * self.whole_map_size), 0)\n            sub_regions.append(\n                (sub_x1, sub_y1, sub_x1 + half_size, sub_y1 + half_size))\n        return sub_regions\n\n    def get_targets(self, sampling_results, rcnn_train_cfg):\n        # mix all samples (across images) together.\n        pos_bboxes = torch.cat([res.pos_bboxes for res in sampling_results],\n                               dim=0).cpu()\n        pos_gt_bboxes = torch.cat(\n            [res.pos_gt_bboxes for res in sampling_results], dim=0).cpu()\n        assert pos_bboxes.shape == pos_gt_bboxes.shape\n\n        # expand pos_bboxes to 2x of original size\n        x1 = pos_bboxes[:, 0] - (pos_bboxes[:, 2] - pos_bboxes[:, 0]) / 2\n        y1 = pos_bboxes[:, 1] - (pos_bboxes[:, 3] - pos_bboxes[:, 1]) / 2\n        x2 = pos_bboxes[:, 2] + (pos_bboxes[:, 2] - pos_bboxes[:, 0]) / 2\n        y2 = pos_bboxes[:, 3] + (pos_bboxes[:, 3] - pos_bboxes[:, 1]) / 2\n        pos_bboxes = torch.stack([x1, y1, x2, y2], dim=-1)\n        pos_bbox_ws = (pos_bboxes[:, 2] - pos_bboxes[:, 0]).unsqueeze(-1)\n        pos_bbox_hs = (pos_bboxes[:, 3] - pos_bboxes[:, 1]).unsqueeze(-1)\n\n        num_rois = pos_bboxes.shape[0]\n        map_size = self.whole_map_size\n        # this is not the final target shape\n        targets = torch.zeros((num_rois, self.grid_points, map_size, map_size),\n                              dtype=torch.float)\n\n        # pre-compute interpolation factors for all grid points.\n        # the first item is the factor of x-dim, and the second is y-dim.\n        # for a 9-point grid, factors are like (1, 0), (0.5, 0.5), (0, 1)\n        factors = []\n        for j in range(self.grid_points):\n            x_idx = j // self.grid_size\n            y_idx = j % self.grid_size\n            factors.append((1 - x_idx / (self.grid_size - 1),\n                            1 - y_idx / (self.grid_size - 1)))\n\n        radius = rcnn_train_cfg.pos_radius\n        radius2 = radius**2\n        for i in range(num_rois):\n            # ignore small bboxes\n            if (pos_bbox_ws[i] <= self.grid_size\n                    or pos_bbox_hs[i] <= self.grid_size):\n                continue\n            # for each grid point, mark a small circle as positive\n            for j in range(self.grid_points):\n                factor_x, factor_y = factors[j]\n                gridpoint_x = factor_x * pos_gt_bboxes[i, 0] + (\n                    1 - factor_x) * pos_gt_bboxes[i, 2]\n                gridpoint_y = factor_y * pos_gt_bboxes[i, 1] + (\n                    1 - factor_y) * pos_gt_bboxes[i, 3]\n\n                cx = int((gridpoint_x - pos_bboxes[i, 0]) / pos_bbox_ws[i] *\n                         map_size)\n                cy = int((gridpoint_y - pos_bboxes[i, 1]) / pos_bbox_hs[i] *\n                         map_size)\n\n                for x in range(cx - radius, cx + radius + 1):\n                    for y in range(cy - radius, cy + radius + 1):\n                        if x >= 0 and x < map_size and y >= 0 and y < map_size:\n                            if (x - cx)**2 + (y - cy)**2 <= radius2:\n                                targets[i, j, y, x] = 1\n        # reduce the target heatmap size by a half\n        # proposed in Grid R-CNN Plus (https://arxiv.org/abs/1906.05688).\n        sub_targets = []\n        for i in range(self.grid_points):\n            sub_x1, sub_y1, sub_x2, sub_y2 = self.sub_regions[i]\n            sub_targets.append(targets[:, [i], sub_y1:sub_y2, sub_x1:sub_x2])\n        sub_targets = torch.cat(sub_targets, dim=1)\n        sub_targets = sub_targets.to(sampling_results[0].pos_bboxes.device)\n        return sub_targets\n\n    def loss(self, grid_pred, grid_targets):\n        loss_fused = self.loss_grid(grid_pred['fused'], grid_targets)\n        loss_unfused = self.loss_grid(grid_pred['unfused'], grid_targets)\n        loss_grid = loss_fused + loss_unfused\n        return dict(loss_grid=loss_grid)\n\n    def get_bboxes(self, det_bboxes, grid_pred, img_metas):\n        # TODO: refactoring\n        assert det_bboxes.shape[0] == grid_pred.shape[0]\n        det_bboxes = det_bboxes.cpu()\n        cls_scores = det_bboxes[:, [4]]\n        det_bboxes = det_bboxes[:, :4]\n        grid_pred = grid_pred.sigmoid().cpu()\n\n        R, c, h, w = grid_pred.shape\n        half_size = self.whole_map_size // 4 * 2\n        assert h == w == half_size\n        assert c == self.grid_points\n\n        # find the point with max scores in the half-sized heatmap\n        grid_pred = grid_pred.view(R * c, h * w)\n        pred_scores, pred_position = grid_pred.max(dim=1)\n        xs = pred_position % w\n        ys = pred_position // w\n\n        # get the position in the whole heatmap instead of half-sized heatmap\n        for i in range(self.grid_points):\n            xs[i::self.grid_points] += self.sub_regions[i][0]\n            ys[i::self.grid_points] += self.sub_regions[i][1]\n\n        # reshape to (num_rois, grid_points)\n        pred_scores, xs, ys = tuple(\n            map(lambda x: x.view(R, c), [pred_scores, xs, ys]))\n\n        # get expanded pos_bboxes\n        widths = (det_bboxes[:, 2] - det_bboxes[:, 0]).unsqueeze(-1)\n        heights = (det_bboxes[:, 3] - det_bboxes[:, 1]).unsqueeze(-1)\n        x1 = (det_bboxes[:, 0, None] - widths / 2)\n        y1 = (det_bboxes[:, 1, None] - heights / 2)\n        # map the grid point to the absolute coordinates\n        abs_xs = (xs.float() + 0.5) / w * widths + x1\n        abs_ys = (ys.float() + 0.5) / h * heights + y1\n\n        # get the grid points indices that fall on the bbox boundaries\n        x1_inds = [i for i in range(self.grid_size)]\n        y1_inds = [i * self.grid_size for i in range(self.grid_size)]\n        x2_inds = [\n            self.grid_points - self.grid_size + i\n            for i in range(self.grid_size)\n        ]\n        y2_inds = [(i + 1) * self.grid_size - 1 for i in range(self.grid_size)]\n\n        # voting of all grid points on some boundary\n        bboxes_x1 = (abs_xs[:, x1_inds] * pred_scores[:, x1_inds]).sum(\n            dim=1, keepdim=True) / (\n                pred_scores[:, x1_inds].sum(dim=1, keepdim=True))\n        bboxes_y1 = (abs_ys[:, y1_inds] * pred_scores[:, y1_inds]).sum(\n            dim=1, keepdim=True) / (\n                pred_scores[:, y1_inds].sum(dim=1, keepdim=True))\n        bboxes_x2 = (abs_xs[:, x2_inds] * pred_scores[:, x2_inds]).sum(\n            dim=1, keepdim=True) / (\n                pred_scores[:, x2_inds].sum(dim=1, keepdim=True))\n        bboxes_y2 = (abs_ys[:, y2_inds] * pred_scores[:, y2_inds]).sum(\n            dim=1, keepdim=True) / (\n                pred_scores[:, y2_inds].sum(dim=1, keepdim=True))\n\n        bbox_res = torch.cat(\n            [bboxes_x1, bboxes_y1, bboxes_x2, bboxes_y2, cls_scores], dim=1)\n        bbox_res[:, [0, 2]].clamp_(min=0, max=img_metas[0]['img_shape'][1])\n        bbox_res[:, [1, 3]].clamp_(min=0, max=img_metas[0]['img_shape'][0])\n\n        return bbox_res\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/htc_mask_head.py",
    "content": "from mmcv.cnn import ConvModule\n\nfrom mmdet.models.builder import HEADS\nfrom .fcn_mask_head import FCNMaskHead\n\n\n@HEADS.register_module()\nclass HTCMaskHead(FCNMaskHead):\n\n    def __init__(self, with_conv_res=True, *args, **kwargs):\n        super(HTCMaskHead, self).__init__(*args, **kwargs)\n        self.with_conv_res = with_conv_res\n        if self.with_conv_res:\n            self.conv_res = ConvModule(\n                self.conv_out_channels,\n                self.conv_out_channels,\n                1,\n                conv_cfg=self.conv_cfg,\n                norm_cfg=self.norm_cfg)\n\n    def init_weights(self):\n        super(HTCMaskHead, self).init_weights()\n        if self.with_conv_res:\n            self.conv_res.init_weights()\n\n    def forward(self, x, res_feat=None, return_logits=True, return_feat=True):\n        if res_feat is not None:\n            assert self.with_conv_res\n            res_feat = self.conv_res(res_feat)\n            x = x + res_feat\n        for conv in self.convs:\n            x = conv(x)\n        res_feat = x\n        outs = []\n        if return_logits:\n            x = self.upsample(x)\n            if self.upsample_method == 'deconv':\n                x = self.relu(x)\n            mask_pred = self.conv_logits(x)\n            outs.append(mask_pred)\n        if return_feat:\n            outs.append(res_feat)\n        return outs if len(outs) > 1 else outs[0]\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/mask_point_head.py",
    "content": "# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend/point_head/point_head.py  # noqa\n\nimport torch\nimport torch.nn as nn\nfrom mmcv.cnn import ConvModule, normal_init\n\nfrom mmdet.models.builder import HEADS, build_loss\nfrom mmdet.ops import point_sample, rel_roi_point_to_rel_img_point\n\n\n@HEADS.register_module()\nclass MaskPointHead(nn.Module):\n    \"\"\"A mask point head use in PointRend.\n\n    ``MaskPointHead`` use shared multi-layer perceptron (equivalent to\n    nn.Conv1d) to predict the logit of input points. The fine-grained feature\n    and coarse feature will be concatenate together for predication.\n\n    Args:\n        num_fcs (int): Number of fc layers in the head. Default: 3.\n        in_channels (int): Number of input channels. Default: 256.\n        fc_channels (int): Number of fc channels. Default: 256.\n        num_classes (int): Number of classes for logits. Default: 80.\n        class_agnostic (bool): Whether use class agnostic classification.\n            If so, the output channels of logits will be 1. Default: False.\n        coarse_pred_each_layer (bool): Whether concatenate coarse feature with\n            the output of each fc layer. Default: True.\n        conv_cfg (dict | None): Dictionary to construct and config conv layer.\n            Default: dict(type='Conv1d'))\n        norm_cfg (dict | None): Dictionary to construct and config norm layer.\n            Default: None.\n        loss_point (dict): Dictionary to construct and config loss layer of\n            point head. Default: dict(type='CrossEntropyLoss', use_mask=True,\n            loss_weight=1.0).\n    \"\"\"\n\n    def __init__(self,\n                 num_classes,\n                 num_fcs=3,\n                 in_channels=256,\n                 fc_channels=256,\n                 class_agnostic=False,\n                 coarse_pred_each_layer=True,\n                 conv_cfg=dict(type='Conv1d'),\n                 norm_cfg=None,\n                 act_cfg=dict(type='ReLU'),\n                 loss_point=dict(\n                     type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)):\n        super().__init__()\n        self.num_fcs = num_fcs\n        self.in_channels = in_channels\n        self.fc_channles = fc_channels\n        self.num_classes = num_classes\n        self.class_agnostic = class_agnostic\n        self.coarse_pred_each_layer = coarse_pred_each_layer\n        self.conv_cfg = conv_cfg\n        self.norm_cfg = norm_cfg\n        self.loss_point = build_loss(loss_point)\n\n        fc_in_channels = in_channels + num_classes\n        self.fcs = nn.ModuleList()\n        for _ in range(num_fcs):\n            fc = ConvModule(\n                fc_in_channels,\n                fc_channels,\n                kernel_size=1,\n                stride=1,\n                padding=0,\n                conv_cfg=conv_cfg,\n                norm_cfg=norm_cfg,\n                act_cfg=act_cfg)\n            self.fcs.append(fc)\n            fc_in_channels = fc_channels\n            fc_in_channels += num_classes if self.coarse_pred_each_layer else 0\n\n        out_channels = 1 if self.class_agnostic else self.num_classes\n        self.fc_logits = nn.Conv1d(\n            fc_in_channels, out_channels, kernel_size=1, stride=1, padding=0)\n\n    def init_weights(self):\n        \"\"\"Initialize last classification layer of MaskPointHead, conv layers are\n        already initialized by ConvModule\"\"\"\n        normal_init(self.fc_logits, std=0.001)\n\n    def forward(self, fine_grained_feats, coarse_feats):\n        \"\"\"Classify each point base on fine grained and coarse feats.\n\n        Args:\n            fine_grained_feats (Tensor): Fine grained feature sampled from FPN,\n                shape (num_rois, in_channels, num_points).\n            coarse_feats (Tensor): Coarse feature sampled from CoarseMaskHead,\n                shape (num_rois, num_classes, num_points).\n\n        Returns:\n            Tensor: Point classification results,\n                shape (num_rois, num_class, num_points).\n        \"\"\"\n\n        x = torch.cat([fine_grained_feats, coarse_feats], dim=1)\n        for fc in self.fcs:\n            x = fc(x)\n            if self.coarse_pred_each_layer:\n                x = torch.cat((x, coarse_feats), dim=1)\n        return self.fc_logits(x)\n\n    def get_targets(self, rois, rel_roi_points, sampling_results, gt_masks,\n                    cfg):\n        \"\"\"Get training targets of MaskPointHead for all images.\n\n        Args:\n            rois (Tensor): Region of Interest, shape (num_rois, 5).\n            rel_roi_points: Points coordinates relative to RoI, shape\n                (num_rois, num_points, 2).\n            sampling_results (:obj:`SamplingResult`): Sampling result after\n                sampling and assignment.\n            gt_masks (Tensor) : Ground truth segmentation masks of\n                corresponding boxes, shape (num_rois, height, width).\n            cfg (dict): Training cfg.\n\n        Returns:\n            Tensor: Point target, shape (num_rois, num_points).\n        \"\"\"\n\n        num_imgs = len(sampling_results)\n        rois_list = []\n        rel_roi_points_list = []\n        for batch_ind in range(num_imgs):\n            inds = (rois[:, 0] == batch_ind)\n            rois_list.append(rois[inds])\n            rel_roi_points_list.append(rel_roi_points[inds])\n        pos_assigned_gt_inds_list = [\n            res.pos_assigned_gt_inds for res in sampling_results\n        ]\n        cfg_list = [cfg for _ in range(num_imgs)]\n\n        point_targets = map(self._get_target_single, rois_list,\n                            rel_roi_points_list, pos_assigned_gt_inds_list,\n                            gt_masks, cfg_list)\n        point_targets = list(point_targets)\n\n        if len(point_targets) > 0:\n            point_targets = torch.cat(point_targets)\n\n        return point_targets\n\n    def _get_target_single(self, rois, rel_roi_points, pos_assigned_gt_inds,\n                           gt_masks, cfg):\n        \"\"\"Get training target of MaskPointHead for each image.\"\"\"\n        num_pos = rois.size(0)\n        num_points = cfg.num_points\n        if num_pos > 0:\n            gt_masks_th = (\n                gt_masks.to_tensor(rois.dtype, rois.device).index_select(\n                    0, pos_assigned_gt_inds))\n            gt_masks_th = gt_masks_th.unsqueeze(1)\n            rel_img_points = rel_roi_point_to_rel_img_point(\n                rois, rel_roi_points, gt_masks_th.shape[2:])\n            point_targets = point_sample(gt_masks_th,\n                                         rel_img_points).squeeze(1)\n        else:\n            point_targets = rois.new_zeros((0, num_points))\n        return point_targets\n\n    def loss(self, point_pred, point_targets, labels):\n        \"\"\"Calculate loss for MaskPointHead\n\n        Args:\n            point_pred (Tensor): Point predication result, shape\n                (num_rois, num_classes, num_points).\n            point_targets (Tensor): Point targets, shape (num_roi, num_points).\n            labels (Tensor): Class label of corresponding boxes,\n                shape (num_rois, )\n\n        Returns:\n            dict[str, Tensor]: a dictionary of point loss components\n        \"\"\"\n\n        loss = dict()\n        if self.class_agnostic:\n            loss_point = self.loss_point(point_pred, point_targets,\n                                         torch.zeros_like(labels))\n        else:\n            loss_point = self.loss_point(point_pred, point_targets, labels)\n        loss['loss_point'] = loss_point\n        return loss\n\n    def _get_uncertainty(self, mask_pred, labels):\n        \"\"\"Estimate uncertainty based on pred logits\n\n        We estimate uncertainty as L1 distance between 0.0 and the logits\n        prediction in 'mask_pred' for the foreground class in `classes`.\n\n        Args:\n            mask_pred (Tensor): mask predication logits, shape (num_rois,\n                num_classes, mask_height, mask_width).\n\n            labels (list[Tensor]): Either predicted or ground truth label for\n                each predicted mask, of length num_rois.\n\n        Returns:\n            scores (Tensor): Uncertainty scores with the most uncertain\n                locations having the highest uncertainty score,\n                shape (num_rois, 1, mask_height, mask_width)\n        \"\"\"\n        if mask_pred.shape[1] == 1:\n            gt_class_logits = mask_pred.clone()\n        else:\n            inds = torch.arange(mask_pred.shape[0], device=mask_pred.device)\n            gt_class_logits = mask_pred[inds, labels].unsqueeze(1)\n        return -torch.abs(gt_class_logits)\n\n    def get_roi_rel_points_train(self, mask_pred, labels, cfg):\n        \"\"\"Get ``num_points`` most uncertain points with random points during\n        train.\n\n        Sample points in [0, 1] x [0, 1] coordinate space based on their\n        uncertainty. The uncertainties are calculated for each point using\n        '_get_uncertainty()' function that takes point's logit prediction as\n        input.\n\n        Args:\n            mask_pred (Tensor): A tensor of shape (num_rois, num_classes,\n                mask_height, mask_width) for class-specific or class-agnostic\n                prediction.\n            labels (list): The ground truth class for each instance.\n            cfg (dict): Training config of point head.\n\n        Returns:\n            point_coords (Tensor): A tensor of shape (num_rois, num_points, 2)\n                that contains the coordinates sampled points.\n        \"\"\"\n        num_points = cfg.num_points\n        oversample_ratio = cfg.oversample_ratio\n        importance_sample_ratio = cfg.importance_sample_ratio\n        assert oversample_ratio >= 1\n        assert 0 <= importance_sample_ratio <= 1\n        batch_size = mask_pred.shape[0]\n        num_sampled = int(num_points * oversample_ratio)\n        point_coords = torch.rand(\n            batch_size, num_sampled, 2, device=mask_pred.device)\n        point_logits = point_sample(mask_pred, point_coords)\n        # It is crucial to calculate uncertainty based on the sampled\n        # prediction value for the points. Calculating uncertainties of the\n        # coarse predictions first and sampling them for points leads to\n        # incorrect results.  To illustrate this: assume uncertainty func(\n        # logits)=-abs(logits), a sampled point between two coarse\n        # predictions with -1 and 1 logits has 0 logits, and therefore 0\n        # uncertainty value. However, if we calculate uncertainties for the\n        # coarse predictions first, both will have -1 uncertainty,\n        # and sampled point will get -1 uncertainty.\n        point_uncertainties = self._get_uncertainty(point_logits, labels)\n        num_uncertain_points = int(importance_sample_ratio * num_points)\n        num_random_points = num_points - num_uncertain_points\n        idx = torch.topk(\n            point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1]\n        shift = num_sampled * torch.arange(\n            batch_size, dtype=torch.long, device=mask_pred.device)\n        idx += shift[:, None]\n        point_coords = point_coords.view(-1, 2)[idx.view(-1), :].view(\n            batch_size, num_uncertain_points, 2)\n        if num_random_points > 0:\n            rand_roi_coords = torch.rand(\n                batch_size, num_random_points, 2, device=mask_pred.device)\n            point_coords = torch.cat((point_coords, rand_roi_coords), dim=1)\n        return point_coords\n\n    def get_roi_rel_points_test(self, mask_pred, pred_label, cfg):\n        \"\"\"Get ``num_points`` most uncertain points during test.\n\n        Args:\n            mask_pred (Tensor): A tensor of shape (num_rois, num_classes,\n                mask_height, mask_width) for class-specific or class-agnostic\n                prediction.\n            pred_label (list): The predication class for each instance.\n            cfg (dict): Testing config of point head.\n\n        Returns:\n            point_indices (Tensor): A tensor of shape (num_rois, num_points)\n                that contains indices from [0, mask_height x mask_width) of the\n                most uncertain points.\n            point_coords (Tensor): A tensor of shape (num_rois, num_points, 2)\n                that contains [0, 1] x [0, 1] normalized coordinates of the\n                most uncertain points from the [mask_height, mask_width] grid .\n            \"\"\"\n        num_points = cfg.subdivision_num_points\n        uncertainty_map = self._get_uncertainty(mask_pred, pred_label)\n        num_rois, _, mask_height, mask_width = uncertainty_map.shape\n        h_step = 1.0 / mask_height\n        w_step = 1.0 / mask_width\n\n        uncertainty_map = uncertainty_map.view(num_rois,\n                                               mask_height * mask_width)\n        num_points = min(mask_height * mask_width, num_points)\n        point_indices = uncertainty_map.topk(num_points, dim=1)[1]\n        point_coords = uncertainty_map.new_zeros(num_rois, num_points, 2)\n        point_coords[:, :, 0] = w_step / 2.0 + (point_indices %\n                                                mask_width).float() * w_step\n        point_coords[:, :, 1] = h_step / 2.0 + (point_indices //\n                                                mask_width).float() * h_step\n        return point_indices, point_coords\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_heads/maskiou_head.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\nfrom mmcv.cnn import kaiming_init, normal_init\nfrom torch.nn.modules.utils import _pair\n\nfrom mmdet.core import force_fp32\nfrom mmdet.models.builder import HEADS, build_loss\nfrom mmdet.ops import Conv2d, Linear, MaxPool2d\n\n\n@HEADS.register_module()\nclass MaskIoUHead(nn.Module):\n    \"\"\"Mask IoU Head.\n\n    This head predicts the IoU of predicted masks and corresponding gt masks.\n    \"\"\"\n\n    def __init__(self,\n                 num_convs=4,\n                 num_fcs=2,\n                 roi_feat_size=14,\n                 in_channels=256,\n                 conv_out_channels=256,\n                 fc_out_channels=1024,\n                 num_classes=80,\n                 loss_iou=dict(type='MSELoss', loss_weight=0.5)):\n        super(MaskIoUHead, self).__init__()\n        self.in_channels = in_channels\n        self.conv_out_channels = conv_out_channels\n        self.fc_out_channels = fc_out_channels\n        self.num_classes = num_classes\n        self.fp16_enabled = False\n\n        self.convs = nn.ModuleList()\n        for i in range(num_convs):\n            if i == 0:\n                # concatenation of mask feature and mask prediction\n                in_channels = self.in_channels + 1\n            else:\n                in_channels = self.conv_out_channels\n            stride = 2 if i == num_convs - 1 else 1\n            self.convs.append(\n                Conv2d(\n                    in_channels,\n                    self.conv_out_channels,\n                    3,\n                    stride=stride,\n                    padding=1))\n\n        roi_feat_size = _pair(roi_feat_size)\n        pooled_area = (roi_feat_size[0] // 2) * (roi_feat_size[1] // 2)\n        self.fcs = nn.ModuleList()\n        for i in range(num_fcs):\n            in_channels = (\n                self.conv_out_channels *\n                pooled_area if i == 0 else self.fc_out_channels)\n            self.fcs.append(Linear(in_channels, self.fc_out_channels))\n\n        self.fc_mask_iou = Linear(self.fc_out_channels, self.num_classes)\n        self.relu = nn.ReLU()\n        self.max_pool = MaxPool2d(2, 2)\n        self.loss_iou = build_loss(loss_iou)\n\n    def init_weights(self):\n        for conv in self.convs:\n            kaiming_init(conv)\n        for fc in self.fcs:\n            kaiming_init(\n                fc,\n                a=1,\n                mode='fan_in',\n                nonlinearity='leaky_relu',\n                distribution='uniform')\n        normal_init(self.fc_mask_iou, std=0.01)\n\n    def forward(self, mask_feat, mask_pred):\n        mask_pred = mask_pred.sigmoid()\n        mask_pred_pooled = self.max_pool(mask_pred.unsqueeze(1))\n\n        x = torch.cat((mask_feat, mask_pred_pooled), 1)\n\n        for conv in self.convs:\n            x = self.relu(conv(x))\n        x = x.flatten(1)\n        for fc in self.fcs:\n            x = self.relu(fc(x))\n        mask_iou = self.fc_mask_iou(x)\n        return mask_iou\n\n    @force_fp32(apply_to=('mask_iou_pred', ))\n    def loss(self, mask_iou_pred, mask_iou_targets):\n        pos_inds = mask_iou_targets > 0\n        if pos_inds.sum() > 0:\n            loss_mask_iou = self.loss_iou(mask_iou_pred[pos_inds],\n                                          mask_iou_targets[pos_inds])\n        else:\n            loss_mask_iou = mask_iou_pred.sum() * 0\n        return dict(loss_mask_iou=loss_mask_iou)\n\n    @force_fp32(apply_to=('mask_pred', ))\n    def get_targets(self, sampling_results, gt_masks, mask_pred, mask_targets,\n                    rcnn_train_cfg):\n        \"\"\"Compute target of mask IoU.\n\n        Mask IoU target is the IoU of the predicted mask (inside a bbox) and\n        the gt mask of corresponding gt mask (the whole instance).\n        The intersection area is computed inside the bbox, and the gt mask area\n        is computed with two steps, firstly we compute the gt area inside the\n        bbox, then divide it by the area ratio of gt area inside the bbox and\n        the gt area of the whole instance.\n\n        Args:\n            sampling_results (list[:obj:`SamplingResult`]): sampling results.\n            gt_masks (BitmapMask | PolygonMask): Gt masks (the whole instance)\n                of each image, with the same shape of the input image.\n            mask_pred (Tensor): Predicted masks of each positive proposal,\n                shape (num_pos, h, w).\n            mask_targets (Tensor): Gt mask of each positive proposal,\n                binary map of the shape (num_pos, h, w).\n            rcnn_train_cfg (dict): Training config for R-CNN part.\n\n        Returns:\n            Tensor: mask iou target (length == num positive).\n        \"\"\"\n        pos_proposals = [res.pos_bboxes for res in sampling_results]\n        pos_assigned_gt_inds = [\n            res.pos_assigned_gt_inds for res in sampling_results\n        ]\n\n        # compute the area ratio of gt areas inside the proposals and\n        # the whole instance\n        area_ratios = map(self._get_area_ratio, pos_proposals,\n                          pos_assigned_gt_inds, gt_masks)\n        area_ratios = torch.cat(list(area_ratios))\n        assert mask_targets.size(0) == area_ratios.size(0)\n\n        mask_pred = (mask_pred > rcnn_train_cfg.mask_thr_binary).float()\n        mask_pred_areas = mask_pred.sum((-1, -2))\n\n        # mask_pred and mask_targets are binary maps\n        overlap_areas = (mask_pred * mask_targets).sum((-1, -2))\n\n        # compute the mask area of the whole instance\n        gt_full_areas = mask_targets.sum((-1, -2)) / (area_ratios + 1e-7)\n\n        mask_iou_targets = overlap_areas / (\n            mask_pred_areas + gt_full_areas - overlap_areas)\n        return mask_iou_targets\n\n    def _get_area_ratio(self, pos_proposals, pos_assigned_gt_inds, gt_masks):\n        \"\"\"Compute area ratio of the gt mask inside the proposal and the gt\n        mask of the corresponding instance\"\"\"\n        num_pos = pos_proposals.size(0)\n        if num_pos > 0:\n            area_ratios = []\n            proposals_np = pos_proposals.cpu().numpy()\n            pos_assigned_gt_inds = pos_assigned_gt_inds.cpu().numpy()\n            # compute mask areas of gt instances (batch processing for speedup)\n            gt_instance_mask_area = gt_masks.areas\n            for i in range(num_pos):\n                gt_mask = gt_masks[pos_assigned_gt_inds[i]]\n\n                # crop the gt mask inside the proposal\n                bbox = proposals_np[i, :].astype(np.int32)\n                gt_mask_in_proposal = gt_mask.crop(bbox)\n\n                ratio = gt_mask_in_proposal.areas[0] / (\n                    gt_instance_mask_area[pos_assigned_gt_inds[i]] + 1e-7)\n                area_ratios.append(ratio)\n            area_ratios = torch.from_numpy(np.stack(area_ratios)).float().to(\n                pos_proposals.device)\n        else:\n            area_ratios = pos_proposals.new_zeros((0, ))\n        return area_ratios\n\n    @force_fp32(apply_to=('mask_iou_pred', ))\n    def get_mask_scores(self, mask_iou_pred, det_bboxes, det_labels):\n        \"\"\"Get the mask scores.\n\n        mask_score = bbox_score * mask_iou\n        \"\"\"\n        inds = range(det_labels.size(0))\n        mask_scores = mask_iou_pred[inds, det_labels] * det_bboxes[inds, -1]\n        mask_scores = mask_scores.cpu().numpy()\n        det_labels = det_labels.cpu().numpy()\n        return [mask_scores[det_labels == i] for i in range(self.num_classes)]\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/mask_scoring_roi_head.py",
    "content": "import torch\n\nfrom mmdet.core import bbox2roi\nfrom ..builder import HEADS, build_head\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass MaskScoringRoIHead(StandardRoIHead):\n    \"\"\"Mask Scoring RoIHead for Mask Scoring RCNN.\n\n    https://arxiv.org/abs/1903.00241\n    \"\"\"\n\n    def __init__(self, mask_iou_head, **kwargs):\n        assert mask_iou_head is not None\n        super(MaskScoringRoIHead, self).__init__(**kwargs)\n        self.mask_iou_head = build_head(mask_iou_head)\n\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        super(MaskScoringRoIHead, self).init_weights(pretrained)\n        self.mask_iou_head.init_weights()\n\n    def _mask_forward_train(self, x, sampling_results, bbox_feats, gt_masks,\n                            img_metas):\n        \"\"\"Run forward function and calculate loss for Mask head in training\"\"\"\n        pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results])\n        mask_results = super(MaskScoringRoIHead,\n                             self)._mask_forward_train(x, sampling_results,\n                                                       bbox_feats, gt_masks,\n                                                       img_metas)\n        if mask_results['loss_mask'] is None:\n            return mask_results\n\n        # mask iou head forward and loss\n        pos_mask_pred = mask_results['mask_pred'][\n            range(mask_results['mask_pred'].size(0)), pos_labels]\n        mask_iou_pred = self.mask_iou_head(mask_results['mask_feats'],\n                                           pos_mask_pred)\n        pos_mask_iou_pred = mask_iou_pred[range(mask_iou_pred.size(0)),\n                                          pos_labels]\n\n        mask_iou_targets = self.mask_iou_head.get_targets(\n            sampling_results, gt_masks, pos_mask_pred,\n            mask_results['mask_targets'], self.train_cfg)\n        loss_mask_iou = self.mask_iou_head.loss(pos_mask_iou_pred,\n                                                mask_iou_targets)\n        mask_results['loss_mask'].update(loss_mask_iou)\n        return mask_results\n\n    def simple_test_mask(self,\n                         x,\n                         img_metas,\n                         det_bboxes,\n                         det_labels,\n                         rescale=False):\n        \"\"\"Obtain mask prediction without augmentation\"\"\"\n        # image shape of the first image in the batch (only one)\n        ori_shape = img_metas[0]['ori_shape']\n        scale_factor = img_metas[0]['scale_factor']\n\n        if det_bboxes.shape[0] == 0:\n            segm_result = [[] for _ in range(self.mask_head.num_classes)]\n            mask_scores = [[] for _ in range(self.mask_head.num_classes)]\n        else:\n            # if det_bboxes is rescaled to the original image size, we need to\n            # rescale it back to the testing scale to obtain RoIs.\n            _bboxes = (\n                det_bboxes[:, :4] *\n                det_bboxes.new_tensor(scale_factor) if rescale else det_bboxes)\n            mask_rois = bbox2roi([_bboxes])\n            mask_results = self._mask_forward(x, mask_rois)\n            segm_result = self.mask_head.get_seg_masks(\n                mask_results['mask_pred'], _bboxes, det_labels, self.test_cfg,\n                ori_shape, scale_factor, rescale)\n            # get mask scores with mask iou head\n            mask_iou_pred = self.mask_iou_head(\n                mask_results['mask_feats'],\n                mask_results['mask_pred'][range(det_labels.size(0)),\n                                          det_labels])\n            mask_scores = self.mask_iou_head.get_mask_scores(\n                mask_iou_pred, det_bboxes, det_labels)\n        return segm_result, mask_scores\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/pisa_roi_head.py",
    "content": "from mmdet.core import bbox2roi\nfrom ..builder import HEADS\nfrom ..losses.pisa_loss import carl_loss, isr_p\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass PISARoIHead(StandardRoIHead):\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None):\n        \"\"\" StandardRoIHead with PrIme Sample Attention (PISA),\n        described in `PISA <https://arxiv.org/abs/1904.04821>`_.\n\n        Args:\n            x (list[Tensor]): List of multi-level img features.\n            img_metas (list[dict]): List of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                `mmdet/datasets/pipelines/formatting.py:Collect`.\n            proposals (list[Tensors]): List of region proposals.\n            gt_bboxes (list[Tensor]): Each item are the truth boxes for each\n                image in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): Class indices corresponding to each box\n            gt_bboxes_ignore (list[Tensor], optional): Specify which bounding\n                boxes can be ignored when computing the loss.\n            gt_masks (None | Tensor) : True segmentation masks for each box\n                used if the architecture supports a segmentation task.\n\n        Returns:\n            dict[str, Tensor]: a dictionary of loss components\n        \"\"\"\n        # assign gts and sample proposals\n        if self.with_bbox or self.with_mask:\n            num_imgs = len(img_metas)\n            if gt_bboxes_ignore is None:\n                gt_bboxes_ignore = [None for _ in range(num_imgs)]\n            sampling_results = []\n            neg_label_weights = []\n            for i in range(num_imgs):\n                assign_result = self.bbox_assigner.assign(\n                    proposal_list[i], gt_bboxes[i], gt_bboxes_ignore[i],\n                    gt_labels[i])\n                sampling_result = self.bbox_sampler.sample(\n                    assign_result,\n                    proposal_list[i],\n                    gt_bboxes[i],\n                    gt_labels[i],\n                    feats=[lvl_feat[i][None] for lvl_feat in x])\n                # neg label weight is obtained by sampling when using ISR-N\n                neg_label_weight = None\n                if isinstance(sampling_result, tuple):\n                    sampling_result, neg_label_weight = sampling_result\n                sampling_results.append(sampling_result)\n                neg_label_weights.append(neg_label_weight)\n\n        losses = dict()\n        # bbox head forward and loss\n        if self.with_bbox:\n            bbox_results = self._bbox_forward_train(\n                x,\n                sampling_results,\n                gt_bboxes,\n                gt_labels,\n                img_metas,\n                neg_label_weights=neg_label_weights)\n            losses.update(bbox_results['loss_bbox'])\n\n        # mask head forward and loss\n        if self.with_mask:\n            mask_results = self._mask_forward_train(x, sampling_results,\n                                                    bbox_results['bbox_feats'],\n                                                    gt_masks, img_metas)\n            # TODO: Support empty tensor input. #2280\n            if mask_results['loss_mask'] is not None:\n                losses.update(mask_results['loss_mask'])\n\n        return losses\n\n    def _bbox_forward(self, x, rois):\n        \"\"\"Box forward function used in both training and testing\"\"\"\n        # TODO: a more flexible way to decide which feature maps to use\n        bbox_feats = self.bbox_roi_extractor(\n            x[:self.bbox_roi_extractor.num_inputs], rois)\n        if self.with_shared_head:\n            bbox_feats = self.shared_head(bbox_feats)\n        cls_score, bbox_pred = self.bbox_head(bbox_feats)\n\n        bbox_results = dict(\n            cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats)\n        return bbox_results\n\n    def _bbox_forward_train(self,\n                            x,\n                            sampling_results,\n                            gt_bboxes,\n                            gt_labels,\n                            img_metas,\n                            neg_label_weights=None):\n        \"\"\"Run forward function and calculate loss for box head in training\"\"\"\n        rois = bbox2roi([res.bboxes for res in sampling_results])\n\n        bbox_results = self._bbox_forward(x, rois)\n\n        bbox_targets = self.bbox_head.get_targets(sampling_results, gt_bboxes,\n                                                  gt_labels, self.train_cfg)\n\n        # neg_label_weights obtained by sampler is image-wise, mapping back to\n        # the corresponding location in label weights\n        if neg_label_weights[0] is not None:\n            label_weights = bbox_targets[1]\n            cur_num_rois = 0\n            for i in range(len(sampling_results)):\n                num_pos = sampling_results[i].pos_inds.size(0)\n                num_neg = sampling_results[i].neg_inds.size(0)\n                label_weights[cur_num_rois + num_pos:cur_num_rois + num_pos +\n                              num_neg] = neg_label_weights[i]\n                cur_num_rois += num_pos + num_neg\n\n        cls_score = bbox_results['cls_score']\n        bbox_pred = bbox_results['bbox_pred']\n\n        # Apply ISR-P\n        isr_cfg = self.train_cfg.get('isr', None)\n        if isr_cfg is not None:\n            bbox_targets = isr_p(\n                cls_score,\n                bbox_pred,\n                bbox_targets,\n                rois,\n                sampling_results,\n                self.bbox_head.loss_cls,\n                self.bbox_head.bbox_coder,\n                **isr_cfg,\n                num_class=self.bbox_head.num_classes)\n        loss_bbox = self.bbox_head.loss(cls_score, bbox_pred, rois,\n                                        *bbox_targets)\n\n        # Add CARL Loss\n        carl_cfg = self.train_cfg.get('carl', None)\n        if carl_cfg is not None:\n            loss_carl = carl_loss(\n                cls_score,\n                bbox_targets[0],\n                bbox_pred,\n                bbox_targets[2],\n                self.bbox_head.loss_bbox,\n                **carl_cfg,\n                num_class=self.bbox_head.num_classes)\n            loss_bbox.update(loss_carl)\n\n        bbox_results.update(loss_bbox=loss_bbox)\n        return bbox_results\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/point_rend_roi_head.py",
    "content": "# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend  # noqa\n\nimport torch\nimport torch.nn.functional as F\n\nfrom mmdet.core import bbox2roi, bbox_mapping, merge_aug_masks\nfrom mmdet.ops import point_sample, rel_roi_point_to_rel_img_point\nfrom .. import builder\nfrom ..builder import HEADS\nfrom .standard_roi_head import StandardRoIHead\n\n\n@HEADS.register_module()\nclass PointRendRoIHead(StandardRoIHead):\n    \"\"\"`PointRend <https://arxiv.org/abs/1912.08193>`_.\n    \"\"\"\n\n    def __init__(self, point_head, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        assert self.with_bbox and self.with_mask\n        self.init_point_head(point_head)\n\n    def init_point_head(self, point_head):\n        \"\"\"Initialize ``point_head``\"\"\"\n        self.point_head = builder.build_head(point_head)\n\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n        \"\"\"\n        super().init_weights(pretrained)\n        self.point_head.init_weights()\n\n    def _mask_forward_train(self, x, sampling_results, bbox_feats, gt_masks,\n                            img_metas):\n        \"\"\"Run forward function and calculate loss for mask head and point head\n        in training\"\"\"\n        mask_results = super()._mask_forward_train(x, sampling_results,\n                                                   bbox_feats, gt_masks,\n                                                   img_metas)\n        if mask_results['loss_mask'] is not None:\n            loss_point = self._mask_point_forward_train(\n                x, sampling_results, mask_results['mask_pred'], gt_masks,\n                img_metas)\n            mask_results['loss_mask'].update(loss_point)\n\n        return mask_results\n\n    def _mask_point_forward_train(self, x, sampling_results, mask_pred,\n                                  gt_masks, img_metas):\n        \"\"\"Run forward function and calculate loss for point head in\n        training\"\"\"\n        pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results])\n        rel_roi_points = self.point_head.get_roi_rel_points_train(\n            mask_pred, pos_labels, cfg=self.train_cfg)\n        rois = bbox2roi([res.pos_bboxes for res in sampling_results])\n\n        fine_grained_point_feats = self._get_fine_grained_point_feats(\n            x, rois, rel_roi_points, img_metas)\n        coarse_point_feats = point_sample(mask_pred, rel_roi_points)\n        mask_point_pred = self.point_head(fine_grained_point_feats,\n                                          coarse_point_feats)\n        mask_point_target = self.point_head.get_targets(\n            rois, rel_roi_points, sampling_results, gt_masks, self.train_cfg)\n        loss_mask_point = self.point_head.loss(mask_point_pred,\n                                               mask_point_target, pos_labels)\n\n        return loss_mask_point\n\n    def _get_fine_grained_point_feats(self, x, rois, rel_roi_points,\n                                      img_metas):\n        \"\"\"Sample fine grained feats from each level feature map and\n        concatenate them together.\"\"\"\n        num_imgs = len(img_metas)\n        fine_grained_feats = []\n        for idx in range(self.mask_roi_extractor.num_inputs):\n            feats = x[idx]\n            spatial_scale = 1. / float(\n                self.mask_roi_extractor.featmap_strides[idx])\n            point_feats = []\n            for batch_ind in range(num_imgs):\n                # unravel batch dim\n                feat = feats[batch_ind].unsqueeze(0)\n                inds = (rois[:, 0].long() == batch_ind)\n                if inds.any():\n                    rel_img_points = rel_roi_point_to_rel_img_point(\n                        rois[inds], rel_roi_points[inds], feat.shape[2:],\n                        spatial_scale).unsqueeze(0)\n                    point_feat = point_sample(feat, rel_img_points)\n                    point_feat = point_feat.squeeze(0).transpose(0, 1)\n                    point_feats.append(point_feat)\n            fine_grained_feats.append(torch.cat(point_feats, dim=0))\n        return torch.cat(fine_grained_feats, dim=1)\n\n    def _mask_point_forward_test(self, x, rois, label_pred, mask_pred,\n                                 img_metas):\n        \"\"\"Mask refining process with point head in testing\"\"\"\n        refined_mask_pred = mask_pred.clone()\n        for subdivision_step in range(self.test_cfg.subdivision_steps):\n            refined_mask_pred = F.interpolate(\n                refined_mask_pred,\n                scale_factor=self.test_cfg.scale_factor,\n                mode='bilinear',\n                align_corners=False)\n            # If `subdivision_num_points` is larger or equal to the\n            # resolution of the next step, then we can skip this step\n            num_rois, channels, mask_height, mask_width = \\\n                refined_mask_pred.shape\n            if (self.test_cfg.subdivision_num_points >=\n                    self.test_cfg.scale_factor**2 * mask_height * mask_width\n                    and\n                    subdivision_step < self.test_cfg.subdivision_steps - 1):\n                continue\n            point_indices, rel_roi_points = \\\n                self.point_head.get_roi_rel_points_test(\n                    refined_mask_pred, label_pred, cfg=self.test_cfg)\n            fine_grained_point_feats = self._get_fine_grained_point_feats(\n                x, rois, rel_roi_points, img_metas)\n            coarse_point_feats = point_sample(mask_pred, rel_roi_points)\n            mask_point_pred = self.point_head(fine_grained_point_feats,\n                                              coarse_point_feats)\n\n            point_indices = point_indices.unsqueeze(1).expand(-1, channels, -1)\n            refined_mask_pred = refined_mask_pred.reshape(\n                num_rois, channels, mask_height * mask_width)\n            refined_mask_pred = refined_mask_pred.scatter_(\n                2, point_indices, mask_point_pred)\n            refined_mask_pred = refined_mask_pred.view(num_rois, channels,\n                                                       mask_height, mask_width)\n\n        return refined_mask_pred\n\n    def simple_test_mask(self,\n                         x,\n                         img_metas,\n                         det_bboxes,\n                         det_labels,\n                         rescale=False):\n        \"\"\"Obtain mask prediction without augmentation\"\"\"\n        # image shape of the first image in the batch (only one)\n        ori_shape = img_metas[0]['ori_shape']\n        scale_factor = img_metas[0]['scale_factor']\n        if det_bboxes.shape[0] == 0:\n            segm_result = [[] for _ in range(self.mask_head.num_classes)]\n        else:\n            # if det_bboxes is rescaled to the original image size, we need to\n            # rescale it back to the testing scale to obtain RoIs.\n            if rescale and not isinstance(scale_factor, float):\n                scale_factor = det_bboxes.new_tensor(scale_factor)\n            _bboxes = (\n                det_bboxes[:, :4] * scale_factor if rescale else det_bboxes)\n            mask_rois = bbox2roi([_bboxes])\n            mask_results = self._mask_forward(x, mask_rois)\n            mask_results['mask_pred'] = self._mask_point_forward_test(\n                x, mask_rois, det_labels, mask_results['mask_pred'], img_metas)\n            segm_result = self.mask_head.get_seg_masks(\n                mask_results['mask_pred'], _bboxes, det_labels, self.test_cfg,\n                ori_shape, scale_factor, rescale)\n        return segm_result\n\n    def aug_test_mask(self, feats, img_metas, det_bboxes, det_labels):\n        \"\"\"Test for mask head with test time augmentation.\"\"\"\n        if det_bboxes.shape[0] == 0:\n            segm_result = [[] for _ in range(self.mask_head.num_classes)]\n        else:\n            aug_masks = []\n            for x, img_meta in zip(feats, img_metas):\n                img_shape = img_meta[0]['img_shape']\n                scale_factor = img_meta[0]['scale_factor']\n                flip = img_meta[0]['flip']\n                _bboxes = bbox_mapping(det_bboxes[:, :4], img_shape,\n                                       scale_factor, flip)\n                mask_rois = bbox2roi([_bboxes])\n                mask_results = self._mask_forward(x, mask_rois)\n                mask_results['mask_pred'] = self._mask_point_forward_test(\n                    x, mask_rois, det_labels, mask_results['mask_pred'],\n                    img_metas)\n                # convert to numpy array to save memory\n                aug_masks.append(\n                    mask_results['mask_pred'].sigmoid().cpu().numpy())\n            merged_masks = merge_aug_masks(aug_masks, img_metas, self.test_cfg)\n\n            ori_shape = img_metas[0][0]['ori_shape']\n            segm_result = self.mask_head.get_seg_masks(\n                merged_masks,\n                det_bboxes,\n                det_labels,\n                self.test_cfg,\n                ori_shape,\n                scale_factor=1.0,\n                rescale=False)\n        return segm_result\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/roi_extractors/__init__.py",
    "content": "from .generic_roi_extractor import GenericRoIExtractor\nfrom .single_level_roi_extractor import SingleRoIExtractor\n\n__all__ = [\n    'SingleRoIExtractor',\n    'GenericRoIExtractor',\n]\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/roi_extractors/base_roi_extractor.py",
    "content": "from abc import ABCMeta, abstractmethod\n\nimport torch\nimport torch.nn as nn\n\nfrom mmdet import ops\n\n\nclass BaseRoIExtractor(nn.Module, metaclass=ABCMeta):\n    \"\"\"Base class for RoI extractor.\n\n    Args:\n        roi_layer (dict): Specify RoI layer type and arguments.\n        out_channels (int): Output channels of RoI layers.\n        featmap_strides (int): Strides of input feature maps.\n    \"\"\"\n\n    def __init__(self, roi_layer, out_channels, featmap_strides):\n        super(BaseRoIExtractor, self).__init__()\n        self.roi_layers = self.build_roi_layers(roi_layer, featmap_strides)\n        self.out_channels = out_channels\n        self.featmap_strides = featmap_strides\n        self.fp16_enabled = False\n\n    @property\n    def num_inputs(self):\n        \"\"\"int: Number of input feature maps.\"\"\"\n        return len(self.featmap_strides)\n\n    def init_weights(self):\n        pass\n\n    def build_roi_layers(self, layer_cfg, featmap_strides):\n        \"\"\"Build RoI operator to extract feature from each level feature map.\n\n        Args:\n            layer_cfg (dict): Dictionary to construct and config RoI layer\n                operation. Options are modules under ``mmdet/ops`` such as\n                ``RoIAlign``.\n            featmap_strides (int): The stride of input feature map w.r.t to the\n                original image size, which would be used to scale RoI\n                coordinate (original image coordinate system) to feature\n                coordinate system.\n\n        Returns:\n            nn.ModuleList: The RoI extractor modules for each level feature\n                map.\n        \"\"\"\n\n        cfg = layer_cfg.copy()\n        layer_type = cfg.pop('type')\n        assert hasattr(ops, layer_type)\n        layer_cls = getattr(ops, layer_type)\n        roi_layers = nn.ModuleList(\n            [layer_cls(spatial_scale=1 / s, **cfg) for s in featmap_strides])\n        return roi_layers\n\n    def roi_rescale(self, rois, scale_factor):\n        \"\"\"Scale RoI coordinates by scale factor.\n\n        Args:\n            rois (torch.Tensor): RoI (Region of Interest), shape (n, 5)\n            scale_factor (float): Scale factor that RoI will be multiplied by.\n\n        Returns:\n            torch.Tensor: Scaled RoI.\n        \"\"\"\n\n        cx = (rois[:, 1] + rois[:, 3]) * 0.5\n        cy = (rois[:, 2] + rois[:, 4]) * 0.5\n        w = rois[:, 3] - rois[:, 1]\n        h = rois[:, 4] - rois[:, 2]\n        new_w = w * scale_factor\n        new_h = h * scale_factor\n        x1 = cx - new_w * 0.5\n        x2 = cx + new_w * 0.5\n        y1 = cy - new_h * 0.5\n        y2 = cy + new_h * 0.5\n        new_rois = torch.stack((rois[:, 0], x1, y1, x2, y2), dim=-1)\n        return new_rois\n\n    @abstractmethod\n    def forward(self, feats, rois, roi_scale_factor=None):\n        pass\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/roi_extractors/generic_roi_extractor.py",
    "content": "from mmdet.core import force_fp32\nfrom mmdet.models.builder import ROI_EXTRACTORS\nfrom mmdet.ops.plugin import build_plugin_layer\nfrom .base_roi_extractor import BaseRoIExtractor\n\n\n@ROI_EXTRACTORS.register_module()\nclass GenericRoIExtractor(BaseRoIExtractor):\n    \"\"\"Extract RoI features from all level feature maps levels.\n\n    This is the implementation of `A novel Region of Interest Extraction Layer\n    for Instance Segmentation <https://arxiv.org/abs/2004.13665>`_.\n\n    Args:\n        aggregation (str): The method to aggregate multiple feature maps.\n            Options are 'sum', 'concat'. Default: 'sum'.\n        pre_cfg (dict | None): Specify pre-processing modules. Default: None.\n        post_cfg (dict | None): Specify post-processing modules. Default: None.\n        kwargs (keyword arguments): Arguments that are the same\n            as :class:`BaseRoIExtractor`.\n    \"\"\"\n\n    def __init__(self,\n                 aggregation='sum',\n                 pre_cfg=None,\n                 post_cfg=None,\n                 **kwargs):\n        super(GenericRoIExtractor, self).__init__(**kwargs)\n\n        assert aggregation in ['sum', 'concat']\n\n        self.aggregation = aggregation\n        self.with_post = post_cfg is not None\n        self.with_pre = pre_cfg is not None\n        # build pre/post processing modules\n        if self.with_post:\n            self.post_module = build_plugin_layer(post_cfg, '_post_module')[1]\n        if self.with_pre:\n            self.pre_module = build_plugin_layer(pre_cfg, '_pre_module')[1]\n\n    @force_fp32(apply_to=('feats', ), out_fp16=True)\n    def forward(self, feats, rois, roi_scale_factor=None):\n        \"\"\"Forward function\"\"\"\n        if len(feats) == 1:\n            return self.roi_layers[0](feats[0], rois)\n\n        out_size = self.roi_layers[0].out_size\n        num_levels = len(feats)\n        roi_feats = feats[0].new_zeros(\n            rois.size(0), self.out_channels, *out_size)\n\n        # some times rois is an empty tensor\n        if roi_feats.shape[0] == 0:\n            return roi_feats\n\n        if roi_scale_factor is not None:\n            rois = self.roi_rescale(rois, roi_scale_factor)\n\n        # mark the starting channels for concat mode\n        start_channels = 0\n        for i in range(num_levels):\n            roi_feats_t = self.roi_layers[i](feats[i], rois)\n            end_channels = start_channels + roi_feats_t.size(1)\n            if self.with_pre:\n                # apply pre-processing to a RoI extracted from each layer\n                roi_feats_t = self.pre_module(roi_feats_t)\n            if self.aggregation == 'sum':\n                # and sum them all\n                roi_feats += roi_feats_t\n            else:\n                # and concat them along channel dimension\n                roi_feats[:, start_channels:end_channels] = roi_feats_t\n            # update channels starting position\n            start_channels = end_channels\n        # check if concat channels match at the end\n        if self.aggregation == 'concat':\n            assert start_channels == self.out_channels\n\n        if self.with_post:\n            # apply post-processing before return the result\n            roi_feats = self.post_module(roi_feats)\n        return roi_feats\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/roi_extractors/single_level_roi_extractor.py",
    "content": "import torch\n\nfrom mmdet.core import force_fp32\nfrom mmdet.models.builder import ROI_EXTRACTORS\nfrom .base_roi_extractor import BaseRoIExtractor\n\n\n@ROI_EXTRACTORS.register_module()\nclass SingleRoIExtractor(BaseRoIExtractor):\n    \"\"\"Extract RoI features from a single level feature map.\n\n    If there are multiple input feature levels, each RoI is mapped to a level\n    according to its scale. The mapping rule is proposed in\n    `FPN <https://arxiv.org/abs/1612.03144>`_.\n\n    Args:\n        roi_layer (dict): Specify RoI layer type and arguments.\n        out_channels (int): Output channels of RoI layers.\n        featmap_strides (int): Strides of input feature maps.\n        finest_scale (int): Scale threshold of mapping to level 0. Default: 56.\n    \"\"\"\n\n    def __init__(self,\n                 roi_layer,\n                 out_channels,\n                 featmap_strides,\n                 finest_scale=56):\n        super(SingleRoIExtractor, self).__init__(roi_layer, out_channels,\n                                                 featmap_strides)\n        self.finest_scale = finest_scale\n\n    def map_roi_levels(self, rois, num_levels):\n        \"\"\"Map rois to corresponding feature levels by scales.\n\n        - scale < finest_scale * 2: level 0\n        - finest_scale * 2 <= scale < finest_scale * 4: level 1\n        - finest_scale * 4 <= scale < finest_scale * 8: level 2\n        - scale >= finest_scale * 8: level 3\n\n        Args:\n            rois (Tensor): Input RoIs, shape (k, 5).\n            num_levels (int): Total level number.\n\n        Returns:\n            Tensor: Level index (0-based) of each RoI, shape (k, )\n        \"\"\"\n        scale = torch.sqrt(\n            (rois[:, 3] - rois[:, 1]) * (rois[:, 4] - rois[:, 2]))\n        target_lvls = torch.floor(torch.log2(scale / self.finest_scale + 1e-6))\n        target_lvls = target_lvls.clamp(min=0, max=num_levels - 1).long()\n        return target_lvls\n\n    @force_fp32(apply_to=('feats', ), out_fp16=True)\n    def forward(self, feats, rois, roi_scale_factor=None):\n        \"\"\"Forward function\"\"\"\n        out_size = self.roi_layers[0].out_size\n        num_levels = len(feats)\n        roi_feats = feats[0].new_zeros(\n            rois.size(0), self.out_channels, *out_size)\n\n        if num_levels == 1:\n            if len(rois) == 0:\n                return roi_feats\n            return self.roi_layers[0](feats[0], rois)\n\n        target_lvls = self.map_roi_levels(rois, num_levels)\n        if roi_scale_factor is not None:\n            rois = self.roi_rescale(rois, roi_scale_factor)\n        for i in range(num_levels):\n            inds = target_lvls == i\n            if inds.any():\n                rois_ = rois[inds, :]\n                roi_feats_t = self.roi_layers[i](feats[i], rois_)\n                roi_feats[inds] = roi_feats_t\n            else:\n                roi_feats += sum(x.view(-1)[0] for x in self.parameters()) * 0.\n        return roi_feats\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/shared_heads/__init__.py",
    "content": "from .res_layer import ResLayer\n\n__all__ = ['ResLayer']\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/shared_heads/res_layer.py",
    "content": "import torch.nn as nn\nfrom mmcv.cnn import constant_init, kaiming_init\nfrom mmcv.runner import load_checkpoint\n\nfrom mmdet.core import auto_fp16\nfrom mmdet.models.backbones import ResNet\nfrom mmdet.models.builder import SHARED_HEADS\nfrom mmdet.models.utils import ResLayer as _ResLayer\nfrom mmdet.utils import get_root_logger\n\n\n@SHARED_HEADS.register_module()\nclass ResLayer(nn.Module):\n\n    def __init__(self,\n                 depth,\n                 stage=3,\n                 stride=2,\n                 dilation=1,\n                 style='pytorch',\n                 norm_cfg=dict(type='BN', requires_grad=True),\n                 norm_eval=True,\n                 with_cp=False,\n                 dcn=None):\n        super(ResLayer, self).__init__()\n        self.norm_eval = norm_eval\n        self.norm_cfg = norm_cfg\n        self.stage = stage\n        self.fp16_enabled = False\n        block, stage_blocks = ResNet.arch_settings[depth]\n        stage_block = stage_blocks[stage]\n        planes = 64 * 2**stage\n        inplanes = 64 * 2**(stage - 1) * block.expansion\n\n        res_layer = _ResLayer(\n            block,\n            inplanes,\n            planes,\n            stage_block,\n            stride=stride,\n            dilation=dilation,\n            style=style,\n            with_cp=with_cp,\n            norm_cfg=self.norm_cfg,\n            dcn=dcn)\n        self.add_module(f'layer{stage + 1}', res_layer)\n\n    def init_weights(self, pretrained=None):\n        \"\"\"Initialize the weights in the module\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if isinstance(pretrained, str):\n            logger = get_root_logger()\n            load_checkpoint(self, pretrained, strict=False, logger=logger)\n        elif pretrained is None:\n            for m in self.modules():\n                if isinstance(m, nn.Conv2d):\n                    kaiming_init(m)\n                elif isinstance(m, nn.BatchNorm2d):\n                    constant_init(m, 1)\n        else:\n            raise TypeError('pretrained must be a str or None')\n\n    @auto_fp16()\n    def forward(self, x):\n        res_layer = getattr(self, f'layer{self.stage + 1}')\n        out = res_layer(x)\n        return out\n\n    def train(self, mode=True):\n        super(ResLayer, self).train(mode)\n        if self.norm_eval:\n            for m in self.modules():\n                if isinstance(m, nn.BatchNorm2d):\n                    m.eval()\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/standard_roi_head.py",
    "content": "import torch\n\nfrom mmdet.core import bbox2result, bbox2roi, build_assigner, build_sampler\nfrom ..builder import HEADS, build_head, build_roi_extractor\nfrom .base_roi_head import BaseRoIHead\nfrom .test_mixins import BBoxTestMixin, MaskTestMixin\n\n\n@HEADS.register_module()\nclass StandardRoIHead(BaseRoIHead, BBoxTestMixin, MaskTestMixin):\n    \"\"\"Simplest base roi head including one bbox head and one mask head.\n    \"\"\"\n\n    def init_assigner_sampler(self):\n        \"\"\"Initialize assigner and sampler\"\"\"\n        self.bbox_assigner = None\n        self.bbox_sampler = None\n        if self.train_cfg:\n            self.bbox_assigner = build_assigner(self.train_cfg.assigner)\n            self.bbox_sampler = build_sampler(\n                self.train_cfg.sampler, context=self)\n\n    def init_bbox_head(self, bbox_roi_extractor, bbox_head):\n        \"\"\"Initialize ``bbox_head``\"\"\"\n        self.bbox_roi_extractor = build_roi_extractor(bbox_roi_extractor)\n        self.bbox_head = build_head(bbox_head)\n\n    def init_mask_head(self, mask_roi_extractor, mask_head):\n        \"\"\"Initialize ``mask_head``\"\"\"\n        if mask_roi_extractor is not None:\n            self.mask_roi_extractor = build_roi_extractor(mask_roi_extractor)\n            self.share_roi_extractor = False\n        else:\n            self.share_roi_extractor = True\n            self.mask_roi_extractor = self.bbox_roi_extractor\n        self.mask_head = build_head(mask_head)\n\n    def init_weights(self, pretrained):\n        \"\"\"Initialize the weights in head\n\n        Args:\n            pretrained (str, optional): Path to pre-trained weights.\n                Defaults to None.\n        \"\"\"\n        if self.with_shared_head:\n            self.shared_head.init_weights(pretrained=pretrained)\n        if self.with_bbox:\n            self.bbox_roi_extractor.init_weights()\n            self.bbox_head.init_weights()\n        if self.with_mask:\n            self.mask_head.init_weights()\n            if not self.share_roi_extractor:\n                self.mask_roi_extractor.init_weights()\n\n    def forward_dummy(self, x, proposals):\n        \"\"\"Dummy forward function\"\"\"\n        # bbox head\n        outs = ()\n        rois = bbox2roi([proposals])\n        if self.with_bbox:\n            bbox_results = self._bbox_forward(x, rois)\n            outs = outs + (bbox_results['cls_score'],\n                           bbox_results['bbox_pred'])\n        # mask head\n        if self.with_mask:\n            mask_rois = rois[:100]\n            mask_results = self._mask_forward(x, mask_rois)\n            outs = outs + (mask_results['mask_pred'], )\n        return outs\n\n    def forward_train(self,\n                      x,\n                      img_metas,\n                      proposal_list,\n                      gt_bboxes,\n                      gt_labels,\n                      gt_bboxes_ignore=None,\n                      gt_masks=None):\n        \"\"\"\n        Args:\n            x (list[Tensor]): list of multi-level img features.\n            img_metas (list[dict]): list of image info dict where each dict\n                has: 'img_shape', 'scale_factor', 'flip', and may also contain\n                'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.\n                For details on the values of these keys see\n                `mmdet/datasets/pipelines/formatting.py:Collect`.\n            proposals (list[Tensors]): list of region proposals.\n            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with\n                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.\n            gt_labels (list[Tensor]): class indices corresponding to each box\n            gt_bboxes_ignore (None | list[Tensor]): specify which bounding\n                boxes can be ignored when computing the loss.\n            gt_masks (None | Tensor) : true segmentation masks for each box\n                used if the architecture supports a segmentation task.\n\n        Returns:\n            dict[str, Tensor]: a dictionary of loss components\n        \"\"\"\n        # assign gts and sample proposals\n        if self.with_bbox or self.with_mask:\n            num_imgs = len(img_metas)\n            if gt_bboxes_ignore is None:\n                gt_bboxes_ignore = [None for _ in range(num_imgs)]\n            sampling_results = []\n            for i in range(num_imgs):\n                assign_result = self.bbox_assigner.assign(\n                    proposal_list[i], gt_bboxes[i], gt_bboxes_ignore[i],\n                    gt_labels[i])\n                sampling_result = self.bbox_sampler.sample(\n                    assign_result,\n                    proposal_list[i],\n                    gt_bboxes[i],\n                    gt_labels[i],\n                    feats=[lvl_feat[i][None] for lvl_feat in x])\n                sampling_results.append(sampling_result)\n\n        losses = dict()\n        # bbox head forward and loss\n        if self.with_bbox:\n            bbox_results = self._bbox_forward_train(x, sampling_results,\n                                                    gt_bboxes, gt_labels,\n                                                    img_metas)\n            losses.update(bbox_results['loss_bbox'])\n\n        # mask head forward and loss\n        if self.with_mask:\n            mask_results = self._mask_forward_train(x, sampling_results,\n                                                    bbox_results['bbox_feats'],\n                                                    gt_masks, img_metas)\n            # TODO: Support empty tensor input. #2280\n            if mask_results['loss_mask'] is not None:\n                losses.update(mask_results['loss_mask'])\n\n        return losses\n\n    def _bbox_forward(self, x, rois):\n        \"\"\"Box head forward function used in both training and testing\"\"\"\n        # TODO: a more flexible way to decide which feature maps to use\n        bbox_feats = self.bbox_roi_extractor(\n            x[:self.bbox_roi_extractor.num_inputs], rois)\n        if self.with_shared_head:\n            bbox_feats = self.shared_head(bbox_feats)\n        cls_score, bbox_pred = self.bbox_head(bbox_feats)\n\n        bbox_results = dict(\n            cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats)\n        return bbox_results\n\n    def _bbox_forward_train(self, x, sampling_results, gt_bboxes, gt_labels,\n                            img_metas):\n        \"\"\"Run forward function and calculate loss for box head in training\"\"\"\n        rois = bbox2roi([res.bboxes for res in sampling_results])\n        bbox_results = self._bbox_forward(x, rois)\n\n        bbox_targets = self.bbox_head.get_targets(sampling_results, gt_bboxes,\n                                                  gt_labels, self.train_cfg)\n        loss_bbox = self.bbox_head.loss(bbox_results['cls_score'],\n                                        bbox_results['bbox_pred'], rois,\n                                        *bbox_targets)\n\n        bbox_results.update(loss_bbox=loss_bbox)\n        return bbox_results\n\n    def _mask_forward_train(self, x, sampling_results, bbox_feats, gt_masks,\n                            img_metas):\n        \"\"\"Run forward function and calculate loss for mask head in training\"\"\"\n        if not self.share_roi_extractor:\n            pos_rois = bbox2roi([res.pos_bboxes for res in sampling_results])\n            if pos_rois.shape[0] == 0:\n                return dict(loss_mask=None)\n            mask_results = self._mask_forward(x, pos_rois)\n        else:\n            pos_inds = []\n            device = bbox_feats.device\n            for res in sampling_results:\n                pos_inds.append(\n                    torch.ones(\n                        res.pos_bboxes.shape[0],\n                        device=device,\n                        dtype=torch.uint8))\n                pos_inds.append(\n                    torch.zeros(\n                        res.neg_bboxes.shape[0],\n                        device=device,\n                        dtype=torch.uint8))\n            pos_inds = torch.cat(pos_inds)\n            if pos_inds.shape[0] == 0:\n                return dict(loss_mask=None)\n            mask_results = self._mask_forward(\n                x, pos_inds=pos_inds, bbox_feats=bbox_feats)\n\n        mask_targets = self.mask_head.get_targets(sampling_results, gt_masks,\n                                                  self.train_cfg)\n        pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results])\n        loss_mask = self.mask_head.loss(mask_results['mask_pred'],\n                                        mask_targets, pos_labels)\n\n        mask_results.update(loss_mask=loss_mask, mask_targets=mask_targets)\n        return mask_results\n\n    def _mask_forward(self, x, rois=None, pos_inds=None, bbox_feats=None):\n        \"\"\"Mask head forward function used in both training and testing\"\"\"\n        assert ((rois is not None) ^\n                (pos_inds is not None and bbox_feats is not None))\n        if rois is not None:\n            mask_feats = self.mask_roi_extractor(\n                x[:self.mask_roi_extractor.num_inputs], rois)\n            if self.with_shared_head:\n                mask_feats = self.shared_head(mask_feats)\n        else:\n            assert bbox_feats is not None\n            mask_feats = bbox_feats[pos_inds]\n\n        mask_pred = self.mask_head(mask_feats)\n        mask_results = dict(mask_pred=mask_pred, mask_feats=mask_feats)\n        return mask_results\n\n    async def async_simple_test(self,\n                                x,\n                                proposal_list,\n                                img_metas,\n                                proposals=None,\n                                rescale=False):\n        \"\"\"Async test without augmentation.\"\"\"\n        assert self.with_bbox, 'Bbox head must be implemented.'\n\n        det_bboxes, det_labels = await self.async_test_bboxes(\n            x, img_metas, proposal_list, self.test_cfg, rescale=rescale)\n        bbox_results = bbox2result(det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n        if not self.with_mask:\n            return bbox_results\n        else:\n            segm_results = await self.async_test_mask(\n                x,\n                img_metas,\n                det_bboxes,\n                det_labels,\n                rescale=rescale,\n                mask_test_cfg=self.test_cfg.get('mask'))\n            return bbox_results, segm_results\n\n    def simple_test(self,\n                    x,\n                    proposal_list,\n                    img_metas,\n                    proposals=None,\n                    rescale=False):\n        \"\"\"Test without augmentation.\"\"\"\n        assert self.with_bbox, 'Bbox head must be implemented.'\n\n        det_bboxes, det_labels = self.simple_test_bboxes(\n            x, img_metas, proposal_list, self.test_cfg, rescale=rescale)\n        bbox_results = bbox2result(det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n\n        if not self.with_mask:\n            return bbox_results\n        else:\n            segm_results = self.simple_test_mask(\n                x, img_metas, det_bboxes, det_labels, rescale=rescale)\n            return bbox_results, segm_results\n\n    def aug_test(self, x, proposal_list, img_metas, rescale=False):\n        \"\"\"Test with augmentations.\n\n        If rescale is False, then returned bboxes and masks will fit the scale\n        of imgs[0].\n        \"\"\"\n        # recompute feats to save memory\n        det_bboxes, det_labels = self.aug_test_bboxes(x, img_metas,\n                                                      proposal_list,\n                                                      self.test_cfg)\n\n        if rescale:\n            _det_bboxes = det_bboxes\n        else:\n            _det_bboxes = det_bboxes.clone()\n            _det_bboxes[:, :4] *= det_bboxes.new_tensor(\n                img_metas[0][0]['scale_factor'])\n        bbox_results = bbox2result(_det_bboxes, det_labels,\n                                   self.bbox_head.num_classes)\n\n        # det_bboxes always keep the original scale\n        if self.with_mask:\n            segm_results = self.aug_test_mask(x, img_metas, det_bboxes,\n                                              det_labels)\n            return bbox_results, segm_results\n        else:\n            return bbox_results\n"
  },
  {
    "path": "code/mmdet/models/roi_heads/test_mixins.py",
    "content": "import logging\nimport sys\n\nimport torch\n\nfrom mmdet.core import (bbox2roi, bbox_mapping, merge_aug_bboxes,\n                        merge_aug_masks, multiclass_nms)\n\nlogger = logging.getLogger(__name__)\n\nif sys.version_info >= (3, 7):\n    from mmdet.utils.contextmanagers import completed\n\n\nclass BBoxTestMixin(object):\n\n    if sys.version_info >= (3, 7):\n\n        async def async_test_bboxes(self,\n                                    x,\n                                    img_metas,\n                                    proposals,\n                                    rcnn_test_cfg,\n                                    rescale=False,\n                                    bbox_semaphore=None,\n                                    global_lock=None):\n            \"\"\"Asynchronized test for box head without augmentation.\"\"\"\n            rois = bbox2roi(proposals)\n            roi_feats = self.bbox_roi_extractor(\n                x[:len(self.bbox_roi_extractor.featmap_strides)], rois)\n            if self.with_shared_head:\n                roi_feats = self.shared_head(roi_feats)\n            sleep_interval = rcnn_test_cfg.get('async_sleep_interval', 0.017)\n\n            async with completed(\n                    __name__, 'bbox_head_forward',\n                    sleep_interval=sleep_interval):\n                cls_score, bbox_pred = self.bbox_head(roi_feats)\n\n            img_shape = img_metas[0]['img_shape']\n            scale_factor = img_metas[0]['scale_factor']\n            det_bboxes, det_labels = self.bbox_head.get_bboxes(\n                rois,\n                cls_score,\n                bbox_pred,\n                img_shape,\n                scale_factor,\n                rescale=rescale,\n                cfg=rcnn_test_cfg)\n            return det_bboxes, det_labels\n\n    def simple_test_bboxes(self,\n                           x,\n                           img_metas,\n                           proposals,\n                           rcnn_test_cfg,\n                           rescale=False):\n        \"\"\"Test only det bboxes without augmentation.\"\"\"\n        rois = bbox2roi(proposals)\n        bbox_results = self._bbox_forward(x, rois)\n        img_shape = img_metas[0]['img_shape']\n        scale_factor = img_metas[0]['scale_factor']\n        det_bboxes, det_labels = self.bbox_head.get_bboxes(\n            rois,\n            bbox_results['cls_score'],\n            bbox_results['bbox_pred'],\n            img_shape,\n            scale_factor,\n            rescale=rescale,\n            cfg=rcnn_test_cfg)\n        return det_bboxes, det_labels\n\n    def aug_test_bboxes(self, feats, img_metas, proposal_list, rcnn_test_cfg):\n        \"\"\"Test det bboxes with test time augmentation.\"\"\"\n        aug_bboxes = []\n        aug_scores = []\n        for x, img_meta in zip(feats, img_metas):\n            # only one image in the batch\n            img_shape = img_meta[0]['img_shape']\n            scale_factor = img_meta[0]['scale_factor']\n            flip = img_meta[0]['flip']\n            flip_direction = img_meta[0]['flip_direction']\n            # TODO more flexible\n            proposals = bbox_mapping(proposal_list[0][:, :4], img_shape,\n                                     scale_factor, flip, flip_direction)\n            rois = bbox2roi([proposals])\n            # recompute feature maps to save GPU memory\n            bbox_results = self._bbox_forward(x, rois)\n            bboxes, scores = self.bbox_head.get_bboxes(\n                rois,\n                bbox_results['cls_score'],\n                bbox_results['bbox_pred'],\n                img_shape,\n                scale_factor,\n                rescale=False,\n                cfg=None)\n            aug_bboxes.append(bboxes)\n            aug_scores.append(scores)\n        # after merging, bboxes will be rescaled to the original image size\n        merged_bboxes, merged_scores = merge_aug_bboxes(\n            aug_bboxes, aug_scores, img_metas, rcnn_test_cfg)\n        det_bboxes, det_labels = multiclass_nms(merged_bboxes, merged_scores,\n                                                rcnn_test_cfg.score_thr,\n                                                rcnn_test_cfg.nms,\n                                                rcnn_test_cfg.max_per_img)\n        return det_bboxes, det_labels\n\n\nclass MaskTestMixin(object):\n\n    if sys.version_info >= (3, 7):\n\n        async def async_test_mask(self,\n                                  x,\n                                  img_metas,\n                                  det_bboxes,\n                                  det_labels,\n                                  rescale=False,\n                                  mask_test_cfg=None):\n            \"\"\"Asynchronized test for mask head without augmentation.\"\"\"\n            # image shape of the first image in the batch (only one)\n            ori_shape = img_metas[0]['ori_shape']\n            scale_factor = img_metas[0]['scale_factor']\n            if det_bboxes.shape[0] == 0:\n                segm_result = [[] for _ in range(self.mask_head.num_classes)]\n            else:\n                _bboxes = (\n                    det_bboxes[:, :4] *\n                    scale_factor if rescale else det_bboxes)\n                mask_rois = bbox2roi([_bboxes])\n                mask_feats = self.mask_roi_extractor(\n                    x[:len(self.mask_roi_extractor.featmap_strides)],\n                    mask_rois)\n\n                if self.with_shared_head:\n                    mask_feats = self.shared_head(mask_feats)\n                if mask_test_cfg and mask_test_cfg.get('async_sleep_interval'):\n                    sleep_interval = mask_test_cfg['async_sleep_interval']\n                else:\n                    sleep_interval = 0.035\n                async with completed(\n                        __name__,\n                        'mask_head_forward',\n                        sleep_interval=sleep_interval):\n                    mask_pred = self.mask_head(mask_feats)\n                segm_result = self.mask_head.get_seg_masks(\n                    mask_pred, _bboxes, det_labels, self.test_cfg, ori_shape,\n                    scale_factor, rescale)\n            return segm_result\n\n    def simple_test_mask(self,\n                         x,\n                         img_metas,\n                         det_bboxes,\n                         det_labels,\n                         rescale=False):\n        \"\"\"Simple test for mask head without augmentation.\"\"\"\n        # image shape of the first image in the batch (only one)\n        ori_shape = img_metas[0]['ori_shape']\n        scale_factor = img_metas[0]['scale_factor']\n        if det_bboxes.shape[0] == 0:\n            segm_result = [[] for _ in range(self.mask_head.num_classes)]\n        else:\n            # if det_bboxes is rescaled to the original image size, we need to\n            # rescale it back to the testing scale to obtain RoIs.\n            if rescale and not isinstance(scale_factor, float):\n                scale_factor = torch.from_numpy(scale_factor).to(\n                    det_bboxes.device)\n            _bboxes = (\n                det_bboxes[:, :4] * scale_factor if rescale else det_bboxes)\n            mask_rois = bbox2roi([_bboxes])\n            mask_results = self._mask_forward(x, mask_rois)\n            segm_result = self.mask_head.get_seg_masks(\n                mask_results['mask_pred'], _bboxes, det_labels, self.test_cfg,\n                ori_shape, scale_factor, rescale)\n        return segm_result\n\n    def aug_test_mask(self, feats, img_metas, det_bboxes, det_labels):\n        \"\"\"Test for mask head with test time augmentation.\"\"\"\n        if det_bboxes.shape[0] == 0:\n            segm_result = [[] for _ in range(self.mask_head.num_classes)]\n        else:\n            aug_masks = []\n            for x, img_meta in zip(feats, img_metas):\n                img_shape = img_meta[0]['img_shape']\n                scale_factor = img_meta[0]['scale_factor']\n                flip = img_meta[0]['flip']\n                flip_direction = img_meta[0]['flip_direction']\n                _bboxes = bbox_mapping(det_bboxes[:, :4], img_shape,\n                                       scale_factor, flip, flip_direction)\n                mask_rois = bbox2roi([_bboxes])\n                mask_results = self._mask_forward(x, mask_rois)\n                # convert to numpy array to save memory\n                aug_masks.append(\n                    mask_results['mask_pred'].sigmoid().cpu().numpy())\n            merged_masks = merge_aug_masks(aug_masks, img_metas, self.test_cfg)\n\n            ori_shape = img_metas[0][0]['ori_shape']\n            segm_result = self.mask_head.get_seg_masks(\n                merged_masks,\n                det_bboxes,\n                det_labels,\n                self.test_cfg,\n                ori_shape,\n                scale_factor=1.0,\n                rescale=False)\n        return segm_result\n"
  },
  {
    "path": "code/mmdet/models/utils/__init__.py",
    "content": "from .res_layer import ResLayer\n\n__all__ = ['ResLayer']\n"
  },
  {
    "path": "code/mmdet/models/utils/res_layer.py",
    "content": "from mmcv.cnn import build_conv_layer, build_norm_layer\nfrom torch import nn as nn\n\n\nclass ResLayer(nn.Sequential):\n    \"\"\"ResLayer to build ResNet style backbone.\n\n    Args:\n        block (nn.Module): block used to build ResLayer.\n        inplanes (int): inplanes of block.\n        planes (int): planes of block.\n        num_blocks (int): number of blocks.\n        stride (int): stride of the first block. Default: 1\n        avg_down (bool): Use AvgPool instead of stride conv when\n            downsampling in the bottleneck. Default: False\n        conv_cfg (dict): dictionary to construct and config conv layer.\n            Default: None\n        norm_cfg (dict): dictionary to construct and config norm layer.\n            Default: dict(type='BN')\n        downsample_first (bool): Downsample at the first block or last block.\n            False for Hourglass, True for ResNet. Default: True\n    \"\"\"\n\n    def __init__(self,\n                 block,\n                 inplanes,\n                 planes,\n                 num_blocks,\n                 stride=1,\n                 avg_down=False,\n                 conv_cfg=None,\n                 norm_cfg=dict(type='BN'),\n                 downsample_first=True,\n                 **kwargs):\n        self.block = block\n\n        downsample = None\n        if stride != 1 or inplanes != planes * block.expansion:\n            downsample = []\n            conv_stride = stride\n            if avg_down and stride != 1:\n                conv_stride = 1\n                downsample.append(\n                    nn.AvgPool2d(\n                        kernel_size=stride,\n                        stride=stride,\n                        ceil_mode=True,\n                        count_include_pad=False))\n            downsample.extend([\n                build_conv_layer(\n                    conv_cfg,\n                    inplanes,\n                    planes * block.expansion,\n                    kernel_size=1,\n                    stride=conv_stride,\n                    bias=False),\n                build_norm_layer(norm_cfg, planes * block.expansion)[1]\n            ])\n            downsample = nn.Sequential(*downsample)\n\n        layers = []\n        if downsample_first:\n            layers.append(\n                block(\n                    inplanes=inplanes,\n                    planes=planes,\n                    stride=stride,\n                    downsample=downsample,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=norm_cfg,\n                    **kwargs))\n            inplanes = planes * block.expansion\n            for _ in range(1, num_blocks):\n                layers.append(\n                    block(\n                        inplanes=inplanes,\n                        planes=planes,\n                        stride=1,\n                        conv_cfg=conv_cfg,\n                        norm_cfg=norm_cfg,\n                        **kwargs))\n\n        else:  # downsample_first=False is for HourglassModule\n            for _ in range(num_blocks - 1):\n                layers.append(\n                    block(\n                        inplanes=inplanes,\n                        planes=inplanes,\n                        stride=1,\n                        conv_cfg=conv_cfg,\n                        norm_cfg=norm_cfg,\n                        **kwargs))\n            layers.append(\n                block(\n                    inplanes=inplanes,\n                    planes=planes,\n                    stride=stride,\n                    downsample=downsample,\n                    conv_cfg=conv_cfg,\n                    norm_cfg=norm_cfg,\n                    **kwargs))\n        super(ResLayer, self).__init__(*layers)\n"
  },
  {
    "path": "code/mmdet/ops/__init__.py",
    "content": "from .context_block import ContextBlock\nfrom .conv_ws import ConvWS2d, conv_ws_2d\nfrom .corner_pool import CornerPool, TLPool, BRPool\nfrom .dcn import (DeformConv, DeformConvPack, DeformRoIPooling,\n                  DeformRoIPoolingPack, ModulatedDeformConv,\n                  ModulatedDeformConvPack, ModulatedDeformRoIPoolingPack,\n                  deform_conv, deform_roi_pooling, modulated_deform_conv,\n                  PyramidDeformConv, pyramid_deform_conv)\nfrom .generalized_attention import GeneralizedAttention\nfrom .masked_conv import MaskedConv2d\nfrom .nms import batched_nms, nms, nms_match, soft_nms\nfrom .non_local import NonLocal2D\nfrom .plugin import build_plugin_layer\nfrom .point_sample import (SimpleRoIAlign, point_sample,\n                           rel_roi_point_to_rel_img_point)\nfrom .roi_align import RoIAlign, roi_align\nfrom .roi_pool import RoIPool, roi_pool\nfrom .saconv import SAConv2d\nfrom .sigmoid_focal_loss import SigmoidFocalLoss, sigmoid_focal_loss\nfrom .utils import get_compiler_version, get_compiling_cuda_version\nfrom .wrappers import Conv2d, ConvTranspose2d, Linear, MaxPool2d\n\n__all__ = [\n    'nms', 'soft_nms', 'RoIAlign', 'roi_align', 'RoIPool', 'roi_pool',\n    'DeformConv', 'DeformConvPack', 'DeformRoIPooling', 'DeformRoIPoolingPack',\n    'ModulatedDeformRoIPoolingPack', 'ModulatedDeformConv',\n    'ModulatedDeformConvPack', 'deform_conv', 'modulated_deform_conv',\n    'deform_roi_pooling', 'PyramidDeformConv', 'pyramid_deform_conv','SigmoidFocalLoss', \n    'sigmoid_focal_loss', 'MaskedConv2d', 'ContextBlock', 'GeneralizedAttention', 'NonLocal2D',\n    'get_compiler_version', 'get_compiling_cuda_version', 'ConvWS2d',\n    'conv_ws_2d', 'build_plugin_layer', 'batched_nms', 'Conv2d',\n    'ConvTranspose2d', 'MaxPool2d', 'Linear', 'nms_match', 'CornerPool',\n    'point_sample', 'rel_roi_point_to_rel_img_point', 'SimpleRoIAlign',\n    'SAConv2d', 'TLPool', 'BRPool'\n]\n"
  },
  {
    "path": "code/mmdet/ops/carafe/__init__.py",
    "content": "from .carafe import CARAFE, CARAFENaive, CARAFEPack, carafe, carafe_naive\n\n__all__ = ['carafe', 'carafe_naive', 'CARAFE', 'CARAFENaive', 'CARAFEPack']\n"
  },
  {
    "path": "code/mmdet/ops/carafe/carafe.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import UPSAMPLE_LAYERS, normal_init, xavier_init\nfrom torch.autograd import Function\nfrom torch.nn.modules.module import Module\n\nfrom . import carafe_ext, carafe_naive_ext\n\n\nclass CARAFENaiveFunction(Function):\n\n    @staticmethod\n    def forward(ctx, features, masks, kernel_size, group_size, scale_factor):\n        assert scale_factor >= 1\n        assert masks.size(1) == kernel_size * kernel_size * group_size\n        assert masks.size(-1) == features.size(-1) * scale_factor\n        assert masks.size(-2) == features.size(-2) * scale_factor\n        assert features.size(1) % group_size == 0\n        assert (kernel_size - 1) % 2 == 0 and kernel_size >= 1\n        ctx.kernel_size = kernel_size\n        ctx.group_size = group_size\n        ctx.scale_factor = scale_factor\n        ctx.feature_size = features.size()\n        ctx.mask_size = masks.size()\n\n        n, c, h, w = features.size()\n        output = features.new_zeros((n, c, h * scale_factor, w * scale_factor))\n        if features.is_cuda:\n            carafe_naive_ext.forward(features, masks, kernel_size, group_size,\n                                     scale_factor, output)\n        else:\n            raise NotImplementedError\n\n        if features.requires_grad or masks.requires_grad:\n            ctx.save_for_backward(features, masks)\n        return output\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        assert grad_output.is_cuda\n\n        features, masks = ctx.saved_tensors\n        kernel_size = ctx.kernel_size\n        group_size = ctx.group_size\n        scale_factor = ctx.scale_factor\n\n        grad_input = torch.zeros_like(features)\n        grad_masks = torch.zeros_like(masks)\n        carafe_naive_ext.backward(grad_output.contiguous(), features, masks,\n                                  kernel_size, group_size, scale_factor,\n                                  grad_input, grad_masks)\n\n        return grad_input, grad_masks, None, None, None\n\n\ncarafe_naive = CARAFENaiveFunction.apply\n\n\nclass CARAFENaive(Module):\n\n    def __init__(self, kernel_size, group_size, scale_factor):\n        super(CARAFENaive, self).__init__()\n\n        assert isinstance(kernel_size, int) and isinstance(\n            group_size, int) and isinstance(scale_factor, int)\n        self.kernel_size = kernel_size\n        self.group_size = group_size\n        self.scale_factor = scale_factor\n\n    def forward(self, features, masks):\n        return CARAFENaiveFunction.apply(features, masks, self.kernel_size,\n                                         self.group_size, self.scale_factor)\n\n\nclass CARAFEFunction(Function):\n\n    @staticmethod\n    def forward(ctx, features, masks, kernel_size, group_size, scale_factor):\n        assert scale_factor >= 1\n        assert masks.size(1) == kernel_size * kernel_size * group_size\n        assert masks.size(-1) == features.size(-1) * scale_factor\n        assert masks.size(-2) == features.size(-2) * scale_factor\n        assert features.size(1) % group_size == 0\n        assert (kernel_size - 1) % 2 == 0 and kernel_size >= 1\n        ctx.kernel_size = kernel_size\n        ctx.group_size = group_size\n        ctx.scale_factor = scale_factor\n        ctx.feature_size = features.size()\n        ctx.mask_size = masks.size()\n\n        n, c, h, w = features.size()\n        output = features.new_zeros((n, c, h * scale_factor, w * scale_factor))\n        routput = features.new_zeros(output.size(), requires_grad=False)\n        rfeatures = features.new_zeros(features.size(), requires_grad=False)\n        rmasks = masks.new_zeros(masks.size(), requires_grad=False)\n        if features.is_cuda:\n            carafe_ext.forward(features, rfeatures, masks, rmasks, kernel_size,\n                               group_size, scale_factor, routput, output)\n        else:\n            raise NotImplementedError\n\n        if features.requires_grad or masks.requires_grad:\n            ctx.save_for_backward(features, masks, rfeatures)\n        return output\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        assert grad_output.is_cuda\n\n        features, masks, rfeatures = ctx.saved_tensors\n        kernel_size = ctx.kernel_size\n        group_size = ctx.group_size\n        scale_factor = ctx.scale_factor\n\n        rgrad_output = torch.zeros_like(grad_output, requires_grad=False)\n        rgrad_input_hs = torch.zeros_like(grad_output, requires_grad=False)\n        rgrad_input = torch.zeros_like(features, requires_grad=False)\n        rgrad_masks = torch.zeros_like(masks, requires_grad=False)\n        grad_input = torch.zeros_like(features, requires_grad=False)\n        grad_masks = torch.zeros_like(masks, requires_grad=False)\n        carafe_ext.backward(grad_output.contiguous(), rfeatures, masks,\n                            kernel_size, group_size, scale_factor,\n                            rgrad_output, rgrad_input_hs, rgrad_input,\n                            rgrad_masks, grad_input, grad_masks)\n        return grad_input, grad_masks, None, None, None, None\n\n\ncarafe = CARAFEFunction.apply\n\n\nclass CARAFE(Module):\n    \"\"\" CARAFE: Content-Aware ReAssembly of FEatures\n\n    Please refer to https://arxiv.org/abs/1905.02188 for more details.\n\n    Args:\n        kernel_size (int): reassemble kernel size\n        group_size (int): reassemble group size\n        scale_factor (int): upsample ratio\n\n    Returns:\n        upsampled feature map\n    \"\"\"\n\n    def __init__(self, kernel_size, group_size, scale_factor):\n        super(CARAFE, self).__init__()\n\n        assert isinstance(kernel_size, int) and isinstance(\n            group_size, int) and isinstance(scale_factor, int)\n        self.kernel_size = kernel_size\n        self.group_size = group_size\n        self.scale_factor = scale_factor\n\n    def forward(self, features, masks):\n        return CARAFEFunction.apply(features, masks, self.kernel_size,\n                                    self.group_size, self.scale_factor)\n\n\n@UPSAMPLE_LAYERS.register_module(name='carafe')\nclass CARAFEPack(nn.Module):\n    \"\"\" A unified package of CARAFE upsampler that contains:\n    1) channel compressor 2) content encoder 3) CARAFE op\n\n    Official implementation of ICCV 2019 paper\n    CARAFE: Content-Aware ReAssembly of FEatures\n    Please refer to https://arxiv.org/abs/1905.02188 for more details.\n\n    Args:\n        channels (int): input feature channels\n        scale_factor (int): upsample ratio\n        up_kernel (int): kernel size of CARAFE op\n        up_group (int): group size of CARAFE op\n        encoder_kernel (int): kernel size of content encoder\n        encoder_dilation (int): dilation of content encoder\n        compressed_channels (int): output channels of channels compressor\n\n    Returns:\n        upsampled feature map\n    \"\"\"\n\n    def __init__(self,\n                 channels,\n                 scale_factor,\n                 up_kernel=5,\n                 up_group=1,\n                 encoder_kernel=3,\n                 encoder_dilation=1,\n                 compressed_channels=64):\n        super(CARAFEPack, self).__init__()\n        self.channels = channels\n        self.scale_factor = scale_factor\n        self.up_kernel = up_kernel\n        self.up_group = up_group\n        self.encoder_kernel = encoder_kernel\n        self.encoder_dilation = encoder_dilation\n        self.compressed_channels = compressed_channels\n        self.channel_compressor = nn.Conv2d(channels, self.compressed_channels,\n                                            1)\n        self.content_encoder = nn.Conv2d(\n            self.compressed_channels,\n            self.up_kernel * self.up_kernel * self.up_group *\n            self.scale_factor * self.scale_factor,\n            self.encoder_kernel,\n            padding=int((self.encoder_kernel - 1) * self.encoder_dilation / 2),\n            dilation=self.encoder_dilation,\n            groups=1)\n        self.init_weights()\n\n    def init_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                xavier_init(m, distribution='uniform')\n        normal_init(self.content_encoder, std=0.001)\n\n    def kernel_normalizer(self, mask):\n        mask = F.pixel_shuffle(mask, self.scale_factor)\n        n, mask_c, h, w = mask.size()\n        mask_channel = int(mask_c / (self.up_kernel * self.up_kernel))\n        mask = mask.view(n, mask_channel, -1, h, w)\n\n        mask = F.softmax(mask, dim=2)\n        mask = mask.view(n, mask_c, h, w).contiguous()\n\n        return mask\n\n    def feature_reassemble(self, x, mask):\n        x = carafe(x, mask, self.up_kernel, self.up_group, self.scale_factor)\n        return x\n\n    def forward(self, x):\n        compressed_x = self.channel_compressor(x)\n        mask = self.content_encoder(compressed_x)\n        mask = self.kernel_normalizer(mask)\n\n        x = self.feature_reassemble(x, mask)\n        return x\n"
  },
  {
    "path": "code/mmdet/ops/carafe/grad_check.py",
    "content": "import os.path as osp\nimport sys\n\nimport mmcv\nimport torch\nfrom torch.autograd import gradcheck\n\nsys.path.append(osp.abspath(osp.join(__file__, '../../')))\nfrom mmdet.ops.carafe import CARAFE, CARAFENaive  # noqa: E402, isort:skip\nfrom mmdet.ops.carafe import carafe, carafe_naive  # noqa: E402, isort:skip\n\nfeat = torch.randn(2, 64, 3, 3, requires_grad=True, device='cuda:0').double()\nmask = torch.randn(\n    2, 100, 6, 6, requires_grad=True, device='cuda:0').sigmoid().double()\n\nprint('Gradcheck for carafe...')\ntest = gradcheck(CARAFE(5, 4, 2), (feat, mask), atol=1e-4, eps=1e-4)\nprint(test)\n\nprint('Gradcheck for carafe naive...')\ntest = gradcheck(CARAFENaive(5, 4, 2), (feat, mask), atol=1e-4, eps=1e-4)\nprint(test)\n\nfeat = torch.randn(\n    2, 1024, 100, 100, requires_grad=True, device='cuda:0').float()\nmask = torch.randn(\n    2, 25, 200, 200, requires_grad=True, device='cuda:0').sigmoid().float()\nloop_num = 500\n\ntime_forward = 0\ntime_backward = 0\nbar = mmcv.ProgressBar(loop_num)\ntimer = mmcv.Timer()\nfor i in range(loop_num):\n    x = carafe(feat.clone(), mask.clone(), 5, 1, 2)\n    torch.cuda.synchronize()\n    time_forward += timer.since_last_check()\n    x.sum().backward(retain_graph=True)\n    torch.cuda.synchronize()\n    time_backward += timer.since_last_check()\n    bar.update()\nforward_speed = (time_forward + 1e-3) * 1e3 / loop_num\nbackward_speed = (time_backward + 1e-3) * 1e3 / loop_num\nprint(f'\\nCARAFE time forward: {forward_speed} '\n      f'ms/iter | time backward: {backward_speed} ms/iter')\n\ntime_naive_forward = 0\ntime_naive_backward = 0\nbar = mmcv.ProgressBar(loop_num)\ntimer = mmcv.Timer()\nfor i in range(loop_num):\n    x = carafe_naive(feat.clone(), mask.clone(), 5, 1, 2)\n    torch.cuda.synchronize()\n    time_naive_forward += timer.since_last_check()\n    x.sum().backward(retain_graph=True)\n    torch.cuda.synchronize()\n    time_naive_backward += timer.since_last_check()\n    bar.update()\nforward_speed = (time_naive_forward + 1e-3) * 1e3 / loop_num\nbackward_speed = (time_naive_backward + 1e-3) * 1e3 / loop_num\nprint('\\nCARAFE naive time forward: '\n      f'{forward_speed} ms/iter | time backward: {backward_speed} ms/iter')\n"
  },
  {
    "path": "code/mmdet/ops/carafe/setup.py",
    "content": "from setuptools import setup\n\nfrom torch.utils.cpp_extension import BuildExtension, CUDAExtension\n\nNVCC_ARGS = [\n    '-D__CUDA_NO_HALF_OPERATORS__',\n    '-D__CUDA_NO_HALF_CONVERSIONS__',\n    '-D__CUDA_NO_HALF2_OPERATORS__',\n]\n\nsetup(\n    name='carafe',\n    ext_modules=[\n        CUDAExtension(\n            'carafe_ext', [\n                'src/cuda/carafe_cuda.cpp', 'src/cuda/carafe_cuda_kernel.cu',\n                'src/carafe_ext.cpp'\n            ],\n            define_macros=[('WITH_CUDA', None)],\n            extra_compile_args={\n                'cxx': [],\n                'nvcc': NVCC_ARGS\n            }),\n        CUDAExtension(\n            'carafe_naive_ext', [\n                'src/cuda/carafe_naive_cuda.cpp',\n                'src/cuda/carafe_naive_cuda_kernel.cu',\n                'src/carafe_naive_ext.cpp'\n            ],\n            define_macros=[('WITH_CUDA', None)],\n            extra_compile_args={\n                'cxx': [],\n                'nvcc': NVCC_ARGS\n            })\n    ],\n    cmdclass={'build_ext': BuildExtension})\n"
  },
  {
    "path": "code/mmdet/ops/carafe/src/carafe_ext.cpp",
    "content": "#include <ATen/ATen.h>\n#include <torch/extension.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nint carafe_forward_cuda(at::Tensor features, at::Tensor rfeatures,\n                        at::Tensor masks, at::Tensor rmasks, int kernel_size,\n                        int group_size, int scale_factor, at::Tensor routput,\n                        at::Tensor output);\n\nint carafe_backward_cuda(at::Tensor top_grad, at::Tensor rfeatures,\n                         at::Tensor masks, int kernel_size, int group_size,\n                         int scale_factor, at::Tensor rtop_grad,\n                         at::Tensor rbottom_grad_hs, at::Tensor rbottom_grad,\n                         at::Tensor rmask_grad, at::Tensor bottom_grad,\n                         at::Tensor mask_grad);\n#endif\n\nint carafe_forward(at::Tensor features, at::Tensor rfeatures,\n                   at::Tensor masks, at::Tensor rmasks, int kernel_size,\n                   int group_size, int scale_factor, at::Tensor routput,\n                   at::Tensor output) {\n  if (features.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return carafe_forward_cuda(features, rfeatures, masks, rmasks, kernel_size,\n                               group_size, scale_factor, routput, output);\n#else\n    AT_ERROR(\"carafe is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"carafe is not implemented on CPU\");\n}\n\nint carafe_backward(at::Tensor top_grad, at::Tensor rfeatures,\n                    at::Tensor masks, int kernel_size, int group_size,\n                    int scale_factor, at::Tensor rtop_grad,\n                    at::Tensor rbottom_grad_hs, at::Tensor rbottom_grad,\n                    at::Tensor rmask_grad, at::Tensor bottom_grad,\n                    at::Tensor mask_grad) {\n  if (top_grad.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return carafe_backward_cuda(top_grad, rfeatures, masks, kernel_size,\n        group_size, scale_factor, rtop_grad, rbottom_grad_hs, rbottom_grad,\n        rmask_grad, bottom_grad, mask_grad);\n#else\n    AT_ERROR(\"carafe is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"carafe is not implemented on CPU\");\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"forward\", &carafe_forward, \"carafe forward\");\n  m.def(\"backward\", &carafe_backward, \"carafe backward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/carafe/src/carafe_naive_ext.cpp",
    "content": "#include <ATen/ATen.h>\n#include <torch/torch.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nint carafe_naive_forward_cuda(at::Tensor features, at::Tensor masks,\n                              int kernel_size, int group_size, int scale_factor,\n                              at::Tensor output);\n\nint carafe_naive_backward_cuda(at::Tensor top_grad, at::Tensor features,\n                               at::Tensor masks, int kernel_size,\n                               int group_size, int scale_factor,\n                               at::Tensor bottom_grad, at::Tensor mask_grad);\n#endif\n\nint carafe_naive_forward(at::Tensor features, at::Tensor masks,\n                         int kernel_size, int group_size, int scale_factor,\n                         at::Tensor output) {\n  if (features.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return carafe_naive_forward_cuda(features, masks, kernel_size,\n        group_size, scale_factor, output);\n#else\n    AT_ERROR(\"carafe naive is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"carafe naive is not implemented on CPU\");\n}\n\nint carafe_naive_backward(at::Tensor top_grad, at::Tensor features,\n                               at::Tensor masks, int kernel_size,\n                               int group_size, int scale_factor,\n                               at::Tensor bottom_grad, at::Tensor mask_grad) {\n  if (top_grad.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return carafe_naive_backward_cuda(top_grad, features, masks, kernel_size,\n        group_size, scale_factor, bottom_grad, mask_grad);\n#else\n    AT_ERROR(\"carafe naive is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"carafe naive is not implemented on CPU\");\n\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"forward\", &carafe_naive_forward, \"carafe_naive forward\");\n  m.def(\"backward\", &carafe_naive_backward, \"carafe_naive backward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/carafe/src/cuda/carafe_cuda.cpp",
    "content": "#include <ATen/ATen.h>\n#include <torch/extension.h>\n\n#include <cmath>\n#include <vector>\n\nint CARAFEForwardLaucher(const at::Tensor features, const at::Tensor masks,\n                         const int kernel_size, const int group_size,\n                         const int scale_factor, const int batch_size,\n                         const int channels, const int input_height,\n                         const int input_width, const int output_height,\n                         const int output_width, const int mask_channels,\n                         at::Tensor rfeatures, at::Tensor routput,\n                         at::Tensor rmasks, at::Tensor output);\n\nint CARAFEBackwardLaucher(const at::Tensor top_grad, const at::Tensor rfeatures,\n                          const at::Tensor masks, const int kernel_size,\n                          const int group_size, const int scale_factor,\n                          const int batch_size, const int channels,\n                          const int input_height, const int input_width,\n                          const int output_height, const int output_width,\n                          const int mask_channels, at::Tensor rtop_grad,\n                          at::Tensor rbottom_grad_hs, at::Tensor rbottom_grad,\n                          at::Tensor rmask_grad, at::Tensor bottom_grad,\n                          at::Tensor mask_grad);\n\n#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x, \" must be a CUDAtensor \")\n#define CHECK_CONTIGUOUS(x) \\\n  TORCH_CHECK(x.is_contiguous(), #x, \" must be contiguous \")\n#define CHECK_INPUT(x) \\\n  CHECK_CUDA(x);       \\\n  CHECK_CONTIGUOUS(x)\n\nint carafe_forward_cuda(at::Tensor features, at::Tensor rfeatures,\n                        at::Tensor masks, at::Tensor rmasks, int kernel_size,\n                        int group_size, int scale_factor, at::Tensor routput,\n                        at::Tensor output) {\n  CHECK_INPUT(features);\n  CHECK_INPUT(rfeatures);\n  CHECK_INPUT(masks);\n  CHECK_INPUT(rmasks);\n  CHECK_INPUT(output);\n  CHECK_INPUT(routput);\n  at::DeviceGuard guard(features.device());\n\n  const int batch_size = output.size(0);\n  const int num_channels = output.size(1);\n  const int output_height = output.size(2);\n  const int output_width = output.size(3);\n\n  const int input_height = features.size(2);\n  const int input_width = features.size(3);\n\n  const int mask_channels = masks.size(1);\n\n  rfeatures.resize_({batch_size, input_height, input_width, num_channels});\n  routput.resize_({batch_size, output_height, output_width, num_channels});\n  rmasks.resize_({batch_size, output_height, output_width, mask_channels});\n\n  CARAFEForwardLaucher(features, masks, kernel_size, group_size, scale_factor,\n                       batch_size, num_channels, input_height, input_width,\n                       output_height, output_width, mask_channels, rfeatures,\n                       routput, rmasks, output);\n\n  return 1;\n}\n\nint carafe_backward_cuda(at::Tensor top_grad, at::Tensor rfeatures,\n                         at::Tensor masks, int kernel_size, int group_size,\n                         int scale_factor, at::Tensor rtop_grad,\n                         at::Tensor rbottom_grad_hs, at::Tensor rbottom_grad,\n                         at::Tensor rmask_grad, at::Tensor bottom_grad,\n                         at::Tensor mask_grad) {\n  CHECK_INPUT(top_grad);\n  CHECK_INPUT(rfeatures);\n  CHECK_INPUT(masks);\n  CHECK_INPUT(rtop_grad);\n  CHECK_INPUT(rbottom_grad_hs);\n  CHECK_INPUT(rbottom_grad);\n  CHECK_INPUT(rmask_grad);\n  CHECK_INPUT(bottom_grad);\n  CHECK_INPUT(mask_grad);\n  at::DeviceGuard guard(top_grad.device());\n\n  const int batch_size = top_grad.size(0);\n  const int num_channels = top_grad.size(1);\n  const int output_height = top_grad.size(2);\n  const int output_width = top_grad.size(3);\n\n  const int input_height = bottom_grad.size(2);\n  const int input_width = bottom_grad.size(3);\n\n  const int mask_channels = masks.size(1);\n\n  rtop_grad.resize_({batch_size, output_height, output_width, num_channels});\n  rbottom_grad.resize_({batch_size, input_height, input_width, num_channels});\n  rbottom_grad_hs.resize_(\n      {batch_size, output_height, output_width, num_channels});\n  rmask_grad.resize_({batch_size, output_height, output_width, mask_channels});\n\n  CARAFEBackwardLaucher(top_grad, rfeatures, masks, kernel_size, group_size,\n                        scale_factor, batch_size, num_channels, input_height,\n                        input_width, output_height, output_width, mask_channels,\n                        rtop_grad, rbottom_grad_hs, rbottom_grad, rmask_grad,\n                        bottom_grad, mask_grad);\n\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/carafe/src/cuda/carafe_cuda_kernel.cu",
    "content": "#include <ATen/ATen.h>\n#include <ATen/TensorUtils.h>\n#include <ATen/Utils.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <ATen/cuda/CUDAApplyUtils.cuh>\n#include <THC/THCAtomics.cuh>\n#include <cmath>\n\nusing namespace at;\n\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\n#define THREADS_PER_BLOCK 1024  // 32 * 32\n#define WARP_SIZE 32\n#define THREADS_PER_PIXEL 32\n#define MAX_SHARED_MEMORY 49152\n#define MAX_SHARED_SCALAR_T 6144  // 49152 / 8 = 6144\n#define MAXIMIZE_KERNEL_SIZE true\n#define kTileDim 32\n#define kBlockRows 8\n#define FULL_MASK 0xffffffff\n\ninline int divideUP(const int x, const int y) { return (((x) + (y)-1) / (y)); }\n\n__device__ inline int Loc2Index(const int n, const int c, const int h,\n                                const int w, const int channel_num,\n                                const int height, const int width) {\n  int index = w + (h + (c + n * channel_num) * height) * width;\n  return index;\n}\n/* TODO: move this to a common place */\ntemplate <typename scalar_t>\n__device__ inline scalar_t min(scalar_t a, scalar_t b) {\n  return a < b ? a : b;\n}\n\ntemplate <typename scalar_t>\n__device__ inline scalar_t max(scalar_t a, scalar_t b) {\n  return a > b ? a : b;\n}\n\ntemplate <typename scalar_t>\n__device__ __forceinline__ scalar_t WARP_SHFL_DOWN(scalar_t val, int offset)\n{\n    return __shfl_down_sync(FULL_MASK, val, offset);\n}\n\ntemplate<>\n__device__ __forceinline__ c10::Half WARP_SHFL_DOWN<c10::Half>(c10::Half val, int offset)\n{\n  return c10::Half(WARP_SHFL_DOWN<unsigned short>(val.x, offset), c10::Half::from_bits_t{});\n}\n\n\ntemplate <typename scalar_t>\n__device__ __forceinline__ scalar_t warpReduceSum(scalar_t val) {\n  for (int offset = 16; offset > 0; offset /= 2)\n    // val += __shfl_down_sync(FULL_MASK, val, offset);\n    val += WARP_SHFL_DOWN(val, offset);\n  return val;\n}\n\n// Splits the original matrix into submatrices with size 32 * 32.\n// Each block transposes one submatrix by loading it into shared memory.\n// Reference https://devblogs.nvidia.com/efficient-matrix-transpose-cuda-cc/\ntemplate <typename scalar_t>\n__global__ void BatchTranspose2DCUDAKernel(const int N, const int H,\n                                           const int W, const int dh,\n                                           const int dw,\n                                           const scalar_t *__restrict__ X,\n                                           scalar_t *__restrict__ Y) {\n  __shared__ scalar_t tile[kTileDim][kTileDim + 1];\n  const int n = blockIdx.x / (dh * dw);\n  const int k = blockIdx.x % (dh * dw);\n  const int r = k / dw;\n  const int c = k % dw;\n  const int offset = n * H * W;\n  int x = c * kTileDim + threadIdx.x;\n  int y = r * kTileDim + threadIdx.y;\n  if (x < W) {\n    for (int i = 0; threadIdx.y + i < kTileDim && y + i < H; i += kBlockRows) {\n      tile[threadIdx.y + i][threadIdx.x] = X[offset + (y + i) * W + x];\n    }\n  }\n  __syncthreads();\n  x = r * kTileDim + threadIdx.x;\n  y = c * kTileDim + threadIdx.y;\n  if (x < H) {\n    for (int i = 0; threadIdx.y + i < kTileDim && y + i < W; i += kBlockRows) {\n      Y[offset + (y + i) * H + x] = tile[threadIdx.x][threadIdx.y + i];\n    }\n  }\n}\ntemplate <typename scalar_t>\n__global__ void CARAFEForward(\n    const int num_kernels, const scalar_t *__restrict__ bottom_data,\n    const scalar_t *__restrict__ bottom_masks, const int kernel_size,\n    const int group_size, const int scale_factor, const int channels,\n    const int down_height, const int down_width, const int height,\n    const int width, const int mask_channels, scalar_t *__restrict__ top_data) {\n#if MAXIMIZE_KERNEL_SIZE\n  __shared__ float shared_mask[MAX_SHARED_SCALAR_T * 2];\n#else\n  __shared__ scalar_t shared_mask[MAX_SHARED_SCALAR_T];\n#endif\n\n  int index = threadIdx.x + blockIdx.x * blockDim.x;\n  if (index > num_kernels - 1) {\n    return;\n  }\n  const int pixel_id = threadIdx.x / THREADS_PER_PIXEL;\n  const int split_id = threadIdx.x % THREADS_PER_PIXEL;\n  index = index / THREADS_PER_PIXEL;\n  const int pw = index % width;\n  const int ph = (index / width) % height;\n  const int n = index / width / height;\n\n  const int down_pw = pw / scale_factor;\n  const int down_ph = ph / scale_factor;\n\n  const int start_w = down_pw - (kernel_size - 1) / 2;\n  const int end_w = down_pw + (kernel_size - 1) / 2 + 1;\n  const int start_h = down_ph - (kernel_size - 1) / 2;\n  const int end_h = down_ph + (kernel_size - 1) / 2 + 1;\n  for (int c = split_id; c < mask_channels; c += THREADS_PER_PIXEL) {\n    int mask_index = Loc2Index(n, ph, pw, c, height, width, mask_channels);\n    shared_mask[c * WARP_SIZE + pixel_id] = bottom_masks[mask_index];\n  }\n  __syncthreads();\n\n  const int channels_per_group = ceilf(channels / (float)group_size);\n#pragma unroll\n  for (int c = split_id; c < channels; c += THREADS_PER_PIXEL) {\n    int mask_group = c / channels_per_group;\n    scalar_t output_val = 0;\n#pragma unroll\n    for (int iy = start_h; iy < end_h; iy++) {\n#pragma unroll\n      for (int ix = start_w; ix < end_w; ix++) {\n        if (iy < 0 || iy > down_height - 1 || ix < 0 || ix > down_width - 1) {\n          continue;\n        }\n        int mask_iy = iy - down_ph + (kernel_size - 1) / 2;\n        int mask_ix = ix - down_pw + (kernel_size - 1) / 2;\n        int mask_c =\n            (mask_group * kernel_size + mask_iy) * kernel_size + mask_ix;\n        int feat_index =\n            Loc2Index(n, iy, ix, c, down_height, down_width, channels);\n\n        output_val += bottom_data[feat_index] *\n                      shared_mask[mask_c * WARP_SIZE + pixel_id];\n      }\n    }\n\n    int top_index = Loc2Index(n, ph, pw, c, height, width, channels);\n    top_data[top_index] = output_val;\n  }\n}\n\nint CARAFEForwardLaucher(const at::Tensor features, const at::Tensor masks,\n                         const int kernel_size, const int group_size,\n                         const int scale_factor, const int batch_size,\n                         const int channels, const int input_height,\n                         const int input_width, const int output_height,\n                         const int output_width, const int mask_channels,\n                         at::Tensor rfeatures, at::Tensor routput,\n                         at::Tensor rmasks, at::Tensor output) {\n  // one warp per pixel\n  cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"NCHW2NHWC_Feature\", ([&] {\n        const scalar_t *bottom_data = features.data_ptr<scalar_t>();\n        scalar_t *top_data = rfeatures.data_ptr<scalar_t>();\n        const int dh = divideUP(channels, kTileDim);\n        const int dw = divideUP(input_height * input_width, kTileDim);\n        BatchTranspose2DCUDAKernel<scalar_t>\n            <<<batch_size * dh * dw, dim3(kTileDim, kBlockRows), 0, stream>>>(\n                batch_size, channels, input_height * input_width, dh, dw,\n                bottom_data, top_data);\n      }));\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"NCHW2NHWC_Masks\", ([&] {\n        const scalar_t *bottom_data = masks.data_ptr<scalar_t>();\n        scalar_t *top_data = rmasks.data_ptr<scalar_t>();\n        const int dh = divideUP(mask_channels, kTileDim);\n        const int dw = divideUP(output_height * output_width, kTileDim);\n        BatchTranspose2DCUDAKernel<scalar_t>\n            <<<batch_size * dh * dw, dim3(kTileDim, kBlockRows), 0, stream>>>(\n                batch_size, mask_channels, output_height * output_width, dh, dw,\n                bottom_data, top_data);\n      }));\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"CARAFELaucherForward\", ([&] {\n        const int num_kernels =\n            batch_size * output_height * output_width * THREADS_PER_PIXEL;\n        const scalar_t *bottom_data = rfeatures.data_ptr<scalar_t>();\n        const scalar_t *bottom_masks = rmasks.data_ptr<scalar_t>();\n        scalar_t *top_data = routput.data_ptr<scalar_t>();\n\n        CARAFEForward<scalar_t>\n            <<<at::cuda::ATenCeilDiv(num_kernels, THREADS_PER_BLOCK),\n               THREADS_PER_BLOCK, 0, stream>>>(\n                num_kernels, bottom_data, bottom_masks, kernel_size, group_size,\n                scale_factor, channels, input_height, input_width,\n                output_height, output_width, mask_channels, top_data);\n      }));\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"NHWC2NCHW\", ([&] {\n        const scalar_t *bottom_data = routput.data_ptr<scalar_t>();\n        scalar_t *top_data = output.data_ptr<scalar_t>();\n        const int dh = divideUP(output_height * output_width, kTileDim);\n        const int dw = divideUP(channels, kTileDim);\n        BatchTranspose2DCUDAKernel<scalar_t>\n            <<<batch_size * dh * dw, dim3(kTileDim, kBlockRows), 0, stream>>>(\n                batch_size, output_height * output_width, channels, dh, dw,\n                bottom_data, top_data);\n      }));\n  cudaError_t err = cudaGetLastError();\n  if (cudaSuccess != err) {\n    fprintf(stderr, \"cudaCheckError() failed : %s\\n\", cudaGetErrorString(err));\n    exit(-1);\n  }\n\n  return 1;\n}\n\ntemplate <typename scalar_t>\n__global__ void CARAFEBackward_Feature(\n    const int num_kernels, const scalar_t *__restrict__ top_diff,\n    const scalar_t *__restrict__ bottom_masks, const int kernel_size,\n    const int group_size, const int scale_factor, const int channels,\n    const int down_height, const int down_width, const int height,\n    const int width, const int mask_channels,\n    scalar_t *__restrict__ bottom_diff) {\n#if MAXIMIZE_KERNEL_SIZE\n  __shared__ float shared_mask[MAX_SHARED_SCALAR_T * 2];\n#else\n  __shared__ scalar_t shared_mask[MAX_SHARED_SCALAR_T];\n#endif\n\n  int index = threadIdx.x + blockIdx.x * blockDim.x;\n  if (index > num_kernels - 1) {\n    return;\n  }\n\n  const int pixel_id = threadIdx.x / THREADS_PER_PIXEL;\n  const int split_id = threadIdx.x % THREADS_PER_PIXEL;\n  // (n, c, ph, pw) is an element in the bottom_data\n  index = index / THREADS_PER_PIXEL;\n  const int pw = index % width;\n  const int ph = (index / width) % height;\n  const int n = index / width / height;\n\n  const int start_w = pw - (kernel_size - 1) * scale_factor / 2;\n  const int end_w = pw + (kernel_size - 1) * scale_factor / 2 + 1;\n  const int start_h = ph - (kernel_size - 1) * scale_factor / 2;\n  const int end_h = ph + (kernel_size - 1) * scale_factor / 2 + 1;\n  for (int c = split_id; c < mask_channels; c += THREADS_PER_PIXEL) {\n    const int mask_w = (c % kernel_size) * scale_factor;\n    const int mask_h = (c / kernel_size % kernel_size) * scale_factor;\n    const int mask_x = start_w + mask_w;\n    const int mask_y = start_h + mask_h;\n    if (mask_y < 0 || mask_y > height - 1 || mask_x < 0 || mask_x > width - 1) {\n      shared_mask[c * WARP_SIZE + pixel_id] = 0;\n      continue;\n    }\n    const int mask_group = c / (kernel_size * kernel_size);\n    const int mask_c = (2 * mask_group + 1) * kernel_size * kernel_size - c - 1;\n    int mask_index =\n        Loc2Index(n, mask_c, mask_y, mask_x, mask_channels, height, width);\n    shared_mask[c * WARP_SIZE + pixel_id] = bottom_masks[mask_index];\n  }\n  __syncthreads();\n  const int channels_per_group = ceilf(channels / (float)group_size);\n#pragma unroll\n  for (int c = split_id; c < channels; c += THREADS_PER_PIXEL) {\n    int mask_group = c / channels_per_group;\n    int top_index = Loc2Index(n, ph, pw, c, height, width, channels);\n    scalar_t output_val = 0;\n#pragma unroll\n    for (int iy = start_h; iy < end_h; iy += scale_factor) {\n#pragma unroll\n      for (int ix = start_w; ix < end_w; ix += scale_factor) {\n        if (iy < 0 || iy > height - 1 || ix < 0 || ix > width - 1) {\n          continue;\n        }\n        int mask_iy =\n            (iy - ph + (kernel_size - 1) * scale_factor / 2) / scale_factor;\n        int mask_ix =\n            (ix - pw + (kernel_size - 1) * scale_factor / 2) / scale_factor;\n        int mask_c =\n            (mask_group * kernel_size + mask_iy) * kernel_size + mask_ix;\n        int feat_index = Loc2Index(n, iy, ix, c, height, width, channels);\n        output_val +=\n            shared_mask[mask_c * WARP_SIZE + pixel_id] * top_diff[feat_index];\n      }\n    }\n    bottom_diff[top_index] = output_val;\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void FeatureSum(const int num_kernels,\n                           const scalar_t *__restrict__ input_data,\n                           const int scale_factor, const int channels,\n                           const int height, const int width,\n                           scalar_t *__restrict__ output_data) {\n  int index = threadIdx.x + blockIdx.x * blockDim.x;\n  if (index > num_kernels - 1) {\n    return;\n  }\n  const int split_id = threadIdx.x % THREADS_PER_PIXEL;\n  index = index / THREADS_PER_PIXEL;\n  const int pw = index % width;\n  const int ph = (index / width) % height;\n  const int n = index / width / height;\n  for (int c = split_id; c < channels; c += THREADS_PER_PIXEL) {\n    scalar_t output_val = 0;\n    for (int iy = ph * scale_factor; iy < (ph + 1) * scale_factor; iy++) {\n      for (int ix = pw * scale_factor; ix < (pw + 1) * scale_factor; ix++) {\n        int input_id = Loc2Index(n, iy, ix, c, height * scale_factor,\n                                 width * scale_factor, channels);\n        output_val += input_data[input_id];\n      }\n    }\n    const int output_id = Loc2Index(n, ph, pw, c, height, width, channels);\n    output_data[output_id] = output_val;\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void CARAFEBackward_Mask(const int num_kernels,\n                                    const scalar_t *__restrict__ top_diff,\n                                    const scalar_t *__restrict__ bottom_data,\n                                    const int kernel_size, const int group_size,\n                                    const int scale_factor, const int channels,\n                                    const int down_height, const int down_width,\n                                    const int height, const int width,\n                                    const int mask_channels,\n                                    scalar_t *__restrict__ mask_diff) {\n  int index = threadIdx.x + blockIdx.x * blockDim.x;\n  if (index > num_kernels - 1) {\n    return;\n  }\n\n  const int lane_id = index % WARP_SIZE;\n  index = index / WARP_SIZE;\n  const int mask_c = index % mask_channels;\n  // (n, c, ph, pw) is an element in the bottom_data\n  index = index / mask_channels;\n  const int pw = index % width;\n  const int ph = (index / width) % height;\n  const int n = index / width / height;\n\n  const int down_pw = pw / scale_factor;\n  const int down_ph = ph / scale_factor;\n\n  const int mask_group = mask_c / (kernel_size * kernel_size);\n  const int mask_loc = mask_c % (kernel_size * kernel_size);\n\n  const int offset_x = mask_loc % kernel_size - (kernel_size - 1) / 2;\n  const int offset_y =\n      mask_loc / kernel_size % kernel_size - (kernel_size - 1) / 2;\n\n  const int down_x = down_pw + offset_x;\n  const int down_y = down_ph + offset_y;\n\n  scalar_t output_val = 0;\n\n  if (down_y >= 0 && down_y <= down_height - 1 && down_x >= 0 &&\n      down_x <= down_width - 1) {\n    const int channels_per_mask = ceilf(channels / (float)group_size);\n    const int start = channels_per_mask * mask_group;\n    const int end = min(channels_per_mask * (mask_group + 1), channels);\n    for (int c = start + lane_id; c < end; c += WARP_SIZE) {\n      int bottom_id =\n          Loc2Index(n, down_y, down_x, c, down_height, down_width, channels);\n      int top_id = Loc2Index(n, ph, pw, c, height, width, channels);\n      output_val += top_diff[top_id] * bottom_data[bottom_id];\n    }\n  }\n  __syncwarp();\n  output_val = warpReduceSum(output_val);\n  if (lane_id == 0) {\n    const int mask_id =\n        Loc2Index(n, ph, pw, mask_c, height, width, mask_channels);\n    mask_diff[mask_id] = output_val;\n  }\n}\n\nint CARAFEBackwardLaucher(const at::Tensor top_grad, const at::Tensor rfeatures,\n                          const at::Tensor masks, const int kernel_size,\n                          const int group_size, const int scale_factor,\n                          const int batch_size, const int channels,\n                          const int input_height, const int input_width,\n                          const int output_height, const int output_width,\n                          const int mask_channels, at::Tensor rtop_grad,\n                          at::Tensor rbottom_grad_hs, at::Tensor rbottom_grad,\n                          at::Tensor rmask_grad, at::Tensor bottom_grad,\n                          at::Tensor mask_grad) {\n  cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"NCHW2NHWC_Top_Grad\", ([&] {\n        const scalar_t *bottom_data = top_grad.data_ptr<scalar_t>();\n        scalar_t *top_data = rtop_grad.data_ptr<scalar_t>();\n        const int dh = divideUP(channels, kTileDim);\n        const int dw = divideUP(output_height * output_width, kTileDim);\n        BatchTranspose2DCUDAKernel<scalar_t>\n            <<<batch_size * dh * dw, dim3(kTileDim, kBlockRows), 0, stream>>>(\n                batch_size, channels, output_height * output_width, dh, dw,\n                bottom_data, top_data);\n      }));\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"CARAFELaucherBackward_Feature\", ([&] {\n        const int num_kernels =\n            batch_size * output_height * output_width * THREADS_PER_PIXEL;\n        const scalar_t *top_diff = rtop_grad.data_ptr<scalar_t>();\n        const scalar_t *bottom_masks = masks.data_ptr<scalar_t>();\n        scalar_t *bottom_diff = rbottom_grad_hs.data_ptr<scalar_t>();\n\n        CARAFEBackward_Feature<scalar_t>\n            <<<at::cuda::ATenCeilDiv(num_kernels, THREADS_PER_BLOCK),\n               THREADS_PER_BLOCK, 0, stream>>>(\n                num_kernels, top_diff, bottom_masks, kernel_size, group_size,\n                scale_factor, channels, input_height, input_width,\n                output_height, output_width, mask_channels, bottom_diff);\n      }));\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"FeatureSum\", ([&] {\n        const int num_kernels =\n            batch_size * input_height * input_width * THREADS_PER_PIXEL;\n        const scalar_t *bottom_diff_hs = rbottom_grad_hs.data_ptr<scalar_t>();\n        scalar_t *bottom_diff = rbottom_grad.data_ptr<scalar_t>();\n\n        FeatureSum<scalar_t>\n            <<<at::cuda::ATenCeilDiv(num_kernels, THREADS_PER_BLOCK),\n               THREADS_PER_BLOCK, 0, stream>>>(\n                num_kernels, bottom_diff_hs, scale_factor, channels,\n                input_height, input_width, bottom_diff);\n      }));\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"NHWC2NCHW_Bottom_Grad\", ([&] {\n        const scalar_t *bottom_data = rbottom_grad.data_ptr<scalar_t>();\n        scalar_t *top_data = bottom_grad.data_ptr<scalar_t>();\n        const int dh = divideUP(input_height * input_width, kTileDim);\n        const int dw = divideUP(channels, kTileDim);\n        BatchTranspose2DCUDAKernel<scalar_t>\n            <<<batch_size * dh * dw, dim3(kTileDim, kBlockRows), 0, stream>>>(\n                batch_size, input_height * input_width, channels, dh, dw,\n                bottom_data, top_data);\n      }));\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"CARAFELaucherBackward_Mask\", ([&] {\n        const int num_kernels = batch_size * output_height * output_width *\n                                mask_channels * WARP_SIZE;\n        const scalar_t *top_diff = rtop_grad.data_ptr<scalar_t>();\n        const scalar_t *bottom_data = rfeatures.data_ptr<scalar_t>();\n        scalar_t *mask_diff = rmask_grad.data_ptr<scalar_t>();\n\n        CARAFEBackward_Mask<scalar_t>\n            <<<at::cuda::ATenCeilDiv(num_kernels, THREADS_PER_BLOCK),\n               THREADS_PER_BLOCK, 0, stream>>>(\n                num_kernels, top_diff, bottom_data, kernel_size, group_size,\n                scale_factor, channels, input_height, input_width,\n                output_height, output_width, mask_channels, mask_diff);\n      }));\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"NHWC2NCHW_Mask_Grad\", ([&] {\n        const scalar_t *bottom_data = rmask_grad.data_ptr<scalar_t>();\n        scalar_t *top_data = mask_grad.data_ptr<scalar_t>();\n        const int dh = divideUP(output_height * output_width, kTileDim);\n        const int dw = divideUP(mask_channels, kTileDim);\n        BatchTranspose2DCUDAKernel<scalar_t>\n            <<<batch_size * dh * dw, dim3(kTileDim, kBlockRows), 0, stream>>>(\n                batch_size, output_height * output_width, mask_channels, dh, dw,\n                bottom_data, top_data);\n      }));\n  cudaError_t err = cudaGetLastError();\n  if (cudaSuccess != err) {\n    fprintf(stderr, \"cudaCheckError() failed : %s\\n\", cudaGetErrorString(err));\n    exit(-1);\n  }\n\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/carafe/src/cuda/carafe_naive_cuda.cpp",
    "content": "#include <ATen/ATen.h>\n#include <torch/torch.h>\n\n#include <cmath>\n#include <vector>\n\nint CARAFENAIVEForwardLaucher(const at::Tensor features, const at::Tensor masks,\n                              const int kernel_size, const int group_size,\n                              const int scale_factor, const int batch_size,\n                              const int channels, const int height,\n                              const int width, at::Tensor output);\n\nint CARAFENAIVEBackwardLaucher(const at::Tensor top_grad,\n                               const at::Tensor features,\n                               const at::Tensor masks, const int kernel_size,\n                               const int group_size, const int scale_factor,\n                               const int batch_size, const int channels,\n                               const int height, const int width,\n                               at::Tensor bottom_grad, at::Tensor mask_grad);\n\n#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x, \" must be a CUDAtensor \")\n#define CHECK_CONTIGUOUS(x) \\\n  TORCH_CHECK(x.is_contiguous(), #x, \" must be contiguous \")\n#define CHECK_INPUT(x) \\\n  CHECK_CUDA(x);       \\\n  CHECK_CONTIGUOUS(x)\n\nint carafe_naive_forward_cuda(at::Tensor features, at::Tensor masks,\n                              int kernel_size, int group_size, int scale_factor,\n                              at::Tensor output) {\n  CHECK_INPUT(features);\n  CHECK_INPUT(masks);\n  CHECK_INPUT(output);\n  at::DeviceGuard guard(features.device());\n\n  int batch_size = output.size(0);\n  int num_channels = output.size(1);\n  int data_height = output.size(2);\n  int data_width = output.size(3);\n\n  CARAFENAIVEForwardLaucher(features, masks, kernel_size, group_size,\n                            scale_factor, batch_size, num_channels, data_height,\n                            data_width, output);\n\n  return 1;\n}\n\nint carafe_naive_backward_cuda(at::Tensor top_grad, at::Tensor features,\n                               at::Tensor masks, int kernel_size,\n                               int group_size, int scale_factor,\n                               at::Tensor bottom_grad, at::Tensor mask_grad) {\n  CHECK_INPUT(top_grad);\n  CHECK_INPUT(features);\n  CHECK_INPUT(masks);\n  CHECK_INPUT(bottom_grad);\n  CHECK_INPUT(mask_grad);\n  at::DeviceGuard guard(top_grad.device());\n\n  int batch_size = top_grad.size(0);\n  int num_channels = top_grad.size(1);\n  int data_height = top_grad.size(2);\n  int data_width = top_grad.size(3);\n\n  CARAFENAIVEBackwardLaucher(top_grad, features, masks, kernel_size, group_size,\n                             scale_factor, batch_size, num_channels,\n                             data_height, data_width, bottom_grad, mask_grad);\n\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/carafe/src/cuda/carafe_naive_cuda_kernel.cu",
    "content": "#include <ATen/ATen.h>\n#include <THC/THCAtomics.cuh>\n\nusing namespace at;  // temporal fix for pytorch<=0.4.1 (see #9848)\n\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\n#define THREADS_PER_BLOCK 1024\n\ninline int GET_BLOCKS(const int N) {\n  int optimal_block_num = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;\n  int max_block_num = 65536;\n  return min(optimal_block_num, max_block_num);\n}\n\n__device__ inline int Loc2Index(const int n, const int c, const int h,\n                                const int w, const int channel_num,\n                                const int height, const int width) {\n  int index = w + (h + (c + n * channel_num) * height) * width;\n  return index;\n}\ntemplate <typename scalar_t>\n__global__ void CARAFENAIVEForward(const int nthreads,\n                                   const scalar_t *bottom_data,\n                                   const scalar_t *bottom_masks,\n                                   const int kernel_size, const int group_size,\n                                   const int scale_factor, const int channels,\n                                   const int height, const int width,\n                                   scalar_t *top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the bottom_data\n    int pw = index % width;\n    int ph = (index / width) % height;\n    int c = (index / width / height) % channels;\n    int n = index / width / height / channels;\n\n    int mask_channels = kernel_size * kernel_size * group_size;\n    int mask_group = c / (channels / group_size);\n\n    int down_pw = pw / scale_factor;\n    int down_ph = ph / scale_factor;\n    int down_width = width / scale_factor;\n    int down_height = height / scale_factor;\n    int start_w = down_pw - (kernel_size - 1) / 2;\n    int end_w = down_pw + (kernel_size - 1) / 2 + 1;\n    int start_h = down_ph - (kernel_size - 1) / 2;\n    int end_h = down_ph + (kernel_size - 1) / 2 + 1;\n\n    scalar_t output_val = 0;\n    for (int iy = start_h; iy < end_h; iy++) {\n      for (int ix = start_w; ix < end_w; ix++) {\n        if (iy < 0 || iy > down_height - 1 || ix < 0 || ix > down_width - 1) {\n          continue;\n        }\n        int mask_iy = iy - down_ph + (kernel_size - 1) / 2;\n        int mask_ix = ix - down_pw + (kernel_size - 1) / 2;\n        int mask_c =\n            (mask_group * kernel_size + mask_iy) * kernel_size + mask_ix;\n        int feat_index =\n            Loc2Index(n, c, iy, ix, channels, down_height, down_width);\n        int mask_index =\n            Loc2Index(n, mask_c, ph, pw, mask_channels, height, width);\n        output_val += bottom_data[feat_index] * bottom_masks[mask_index];\n      }\n    }\n    top_data[index] = output_val;\n  }\n}\n\nint CARAFENAIVEForwardLaucher(const at::Tensor features, const at::Tensor masks,\n                              const int kernel_size, const int group_size,\n                              const int scale_factor, const int batch_size,\n                              const int channels, const int height,\n                              const int width, at::Tensor output) {\n  const int output_size = batch_size * channels * height * width;\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"CARAFENAIVELaucherForward\", ([&] {\n        const scalar_t *bottom_data = features.data_ptr<scalar_t>();\n        const scalar_t *bottom_masks = masks.data_ptr<scalar_t>();\n        scalar_t *top_data = output.data_ptr<scalar_t>();\n\n        CARAFENAIVEForward<scalar_t>\n            <<<GET_BLOCKS(output_size), THREADS_PER_BLOCK>>>(\n                output_size, bottom_data, bottom_masks, kernel_size, group_size,\n                scale_factor, channels, height, width, top_data);\n      }));\n  cudaError_t err = cudaGetLastError();\n  if (cudaSuccess != err) {\n    fprintf(stderr, \"cudaCheckError() failed : %s\\n\", cudaGetErrorString(err));\n    exit(-1);\n  }\n\n  return 1;\n}\n\ntemplate <typename scalar_t>\n__global__ void CARAFENAIVEBackward(\n    const int nthreads, const scalar_t *top_diff, const scalar_t *bottom_data,\n    const scalar_t *bottom_masks, const int kernel_size, const int group_size,\n    const int scale_factor, const int channels, const int height,\n    const int width, scalar_t *bottom_diff, scalar_t *mask_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the bottom_data\n    int pw = index % width;\n    int ph = (index / width) % height;\n    int c = (index / width / height) % channels;\n    int n = index / width / height / channels;\n\n    int mask_channels = kernel_size * kernel_size * group_size;\n    int mask_group = c / (channels / group_size);\n\n    int down_pw = pw / scale_factor;\n    int down_ph = ph / scale_factor;\n    int down_width = width / scale_factor;\n    int down_height = height / scale_factor;\n    int start_w = down_pw - (kernel_size - 1) / 2;\n    int end_w = down_pw + (kernel_size - 1) / 2 + 1;\n    int start_h = down_ph - (kernel_size - 1) / 2;\n    int end_h = down_ph + (kernel_size - 1) / 2 + 1;\n\n    for (int iy = start_h; iy < end_h; iy++) {\n      for (int ix = start_w; ix < end_w; ix++) {\n        if (iy < 0 || iy > down_height - 1 || ix < 0 || ix > down_width - 1) {\n          continue;\n        }\n        int mask_iy = iy - down_ph + (kernel_size - 1) / 2;\n        int mask_ix = ix - down_pw + (kernel_size - 1) / 2;\n        int mask_c =\n            (mask_group * kernel_size + mask_iy) * kernel_size + mask_ix;\n        int feat_index =\n            Loc2Index(n, c, iy, ix, channels, down_height, down_width);\n        int mask_index =\n            Loc2Index(n, mask_c, ph, pw, mask_channels, height, width);\n        atomicAdd(bottom_diff + feat_index,\n                  bottom_masks[mask_index] * top_diff[index]);\n        atomicAdd(mask_diff + mask_index,\n                  bottom_data[feat_index] * top_diff[index]);\n      }\n    }\n  }\n}\n\nint CARAFENAIVEBackwardLaucher(const at::Tensor top_grad,\n                               const at::Tensor features,\n                               const at::Tensor masks, const int kernel_size,\n                               const int group_size, const int scale_factor,\n                               const int batch_size, const int channels,\n                               const int height, const int width,\n                               at::Tensor bottom_grad, at::Tensor mask_grad) {\n  const int output_size = batch_size * channels * height * width;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"CARAFENAIVELaucherBackward\", ([&] {\n        const scalar_t *top_diff = top_grad.data_ptr<scalar_t>();\n        const scalar_t *bottom_data = features.data_ptr<scalar_t>();\n        const scalar_t *bottom_masks = masks.data_ptr<scalar_t>();\n        scalar_t *bottom_diff = bottom_grad.data_ptr<scalar_t>();\n        scalar_t *mask_diff = mask_grad.data_ptr<scalar_t>();\n\n        CARAFENAIVEBackward<scalar_t>\n            <<<GET_BLOCKS(output_size), THREADS_PER_BLOCK>>>(\n                output_size, top_diff, bottom_data, bottom_masks, kernel_size,\n                group_size, scale_factor, channels, height, width, bottom_diff,\n                mask_diff);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (cudaSuccess != err) {\n    fprintf(stderr, \"cudaCheckError() failed : %s\\n\", cudaGetErrorString(err));\n    exit(-1);\n  }\n\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/chamfer_2d/__init__.py",
    "content": "from .dist_chamfer_2d import Chamfer2D\n\n__all__ = ['Chamfer2D']\n"
  },
  {
    "path": "code/mmdet/ops/chamfer_2d/dist_chamfer_2d.py",
    "content": "import torch\nfrom torch import nn\nfrom torch.autograd import Function\n\nfrom . import chamfer_2d\n\n\n# Chamfer's distance module @thibaultgroueix\n# GPU tensors only\nclass ChamferFunction2D(Function):\n    @staticmethod\n    def forward(ctx, xyz1, xyz2):\n        batchsize, n, _ = xyz1.size()\n        _, m, _ = xyz2.size()\n        device = xyz1.device\n\n        dist1 = torch.zeros(batchsize, n)\n        dist2 = torch.zeros(batchsize, m)\n\n        idx1 = torch.zeros(batchsize, n).type(torch.IntTensor)\n        idx2 = torch.zeros(batchsize, m).type(torch.IntTensor)\n\n        dist1 = dist1.to(device)\n        dist2 = dist2.to(device)\n        idx1 = idx1.to(device)\n        idx2 = idx2.to(device)\n        torch.cuda.set_device(device)\n\n        chamfer_2d.forward(xyz1, xyz2, dist1, dist2, idx1, idx2)\n        ctx.save_for_backward(xyz1, xyz2, idx1, idx2)\n        return dist1, dist2, idx1, idx2\n\n    @staticmethod\n    def backward(ctx, graddist1, graddist2, gradidx1, gradidx2):\n        xyz1, xyz2, idx1, idx2 = ctx.saved_tensors\n        graddist1 = graddist1.contiguous()\n        graddist2 = graddist2.contiguous()\n        device = graddist1.device\n\n        gradxyz1 = torch.zeros(xyz1.size())\n        gradxyz2 = torch.zeros(xyz2.size())\n\n        gradxyz1 = gradxyz1.to(device)\n        gradxyz2 = gradxyz2.to(device)\n        chamfer_2d.backward(\n            xyz1, xyz2, gradxyz1, gradxyz2, graddist1, graddist2, idx1, idx2\n        )\n        return gradxyz1, gradxyz2\n\n\nclass Chamfer2D(nn.Module):\n    def __init__(self):\n        super(Chamfer2D, self).__init__()\n\n    def forward(self, input1, input2):\n        input1 = input1.contiguous()\n        input2 = input2.contiguous()\n        return ChamferFunction2D.apply(input1, input2)\n"
  },
  {
    "path": "code/mmdet/ops/chamfer_2d/src/chamfer_2d.cu",
    "content": "\n#include <stdio.h>\n#include <ATen/ATen.h>\n\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#include <vector>\n\n\n\n__global__ void NmDistanceKernel(int b,int n,const float * xyz,int m,const float * xyz2,float * result,int * result_i){\n\tconst int batch=512;\n\t__shared__ float buf[batch*2];\n\tfor (int i=blockIdx.x;i<b;i+=gridDim.x){\n\t\tfor (int k2=0;k2<m;k2+=batch){\n\t\t\tint end_k=min(m,k2+batch)-k2;\n\t\t\tfor (int j=threadIdx.x;j<end_k*2;j+=blockDim.x){\n\t\t\t\tbuf[j]=xyz2[(i*m+k2)*2+j];\n\t\t\t}\n\t\t\t__syncthreads();\n\t\t\tfor (int j=threadIdx.x+blockIdx.y*blockDim.x;j<n;j+=blockDim.x*gridDim.y){\n\t\t\t\tfloat x1=xyz[(i*n+j)*2+0];\n\t\t\t\tfloat y1=xyz[(i*n+j)*2+1];\n\t\t\t\tint best_i=0;\n\t\t\t\tfloat best=0;\n\t\t\t\tint end_ka=end_k-(end_k&2);\n\t\t\t\tif (end_ka==batch){\n\t\t\t\t\tfor (int k=0;k<batch;k+=4){\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+0]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+1]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (k==0 || d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+2]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+3]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2+1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+4]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+5]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2+2;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+6]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+7]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2+3;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}else{\n\t\t\t\t\tfor (int k=0;k<end_ka;k+=4){\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+0]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+1]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (k==0 || d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+2]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+3]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2+1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+4]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+5]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2+2;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfloat x2=buf[k*2+6]-x1;\n\t\t\t\t\t\t\tfloat y2=buf[k*2+7]-y1;\n\t\t\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\t\t\tif (d<best){\n\t\t\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\t\t\tbest_i=k+k2+3;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (int k=end_ka;k<end_k;k++){\n\t\t\t\t\tfloat x2=buf[k*2+0]-x1;\n\t\t\t\t\tfloat y2=buf[k*2+1]-y1;\n\t\t\t\t\tfloat d=x2*x2+y2*y2;\n\t\t\t\t\tif (k==0 || d<best){\n\t\t\t\t\t\tbest=d;\n\t\t\t\t\t\tbest_i=k+k2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (k2==0 || result[(i*n+j)]>best){\n\t\t\t\t\tresult[(i*n+j)]=best;\n\t\t\t\t\tresult_i[(i*n+j)]=best_i;\n\t\t\t\t}\n\t\t\t}\n\t\t\t__syncthreads();\n\t\t}\n\t}\n}\n// int chamfer_cuda_forward(int b,int n,const float * xyz,int m,const float * xyz2,float * result,int * result_i,float * result2,int * result2_i, cudaStream_t stream){\nint chamfer_cuda_forward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor dist1, at::Tensor dist2, at::Tensor idx1, at::Tensor idx2){\n\n\tconst auto batch_size = xyz1.size(0);\n\tconst auto n = xyz1.size(1); //num_points point cloud A\n\tconst auto m = xyz2.size(1); //num_points point cloud B\n\n\tNmDistanceKernel<<<dim3(32,16,1),512>>>(batch_size, n, xyz1.data<float>(), m, xyz2.data<float>(), dist1.data<float>(), idx1.data<int>());\n\tNmDistanceKernel<<<dim3(32,16,1),512>>>(batch_size, m, xyz2.data<float>(), n, xyz1.data<float>(), dist2.data<float>(), idx2.data<int>());\n\n\tcudaError_t err = cudaGetLastError();\n\t  if (err != cudaSuccess) {\n\t    printf(\"error in nnd updateOutput: %s\\n\", cudaGetErrorString(err));\n\t    //THError(\"aborting\");\n\t    return 0;\n\t  }\n\t  return 1;\n\n\n}\n__global__ void NmDistanceGradKernel(int b,int n,const float * xyz1,int m,const float * xyz2,const float * grad_dist1,const int * idx1,float * grad_xyz1,float * grad_xyz2){\n\tfor (int i=blockIdx.x;i<b;i+=gridDim.x){\n\t\tfor (int j=threadIdx.x+blockIdx.y*blockDim.x;j<n;j+=blockDim.x*gridDim.y){\n\t\t\tfloat x1=xyz1[(i*n+j)*2+0];\n\t\t\tfloat y1=xyz1[(i*n+j)*2+1];\n\t\t\tint j2=idx1[i*n+j];\n\t\t\tfloat x2=xyz2[(i*m+j2)*2+0];\n\t\t\tfloat y2=xyz2[(i*m+j2)*2+1];\n\t\t\tfloat g=grad_dist1[i*n+j]*2;\n\t\t\tatomicAdd(&(grad_xyz1[(i*n+j)*2+0]),g*(x1-x2));\n\t\t\tatomicAdd(&(grad_xyz1[(i*n+j)*2+1]),g*(y1-y2));\n\t\t\tatomicAdd(&(grad_xyz2[(i*m+j2)*2+0]),-(g*(x1-x2)));\n\t\t\tatomicAdd(&(grad_xyz2[(i*m+j2)*2+1]),-(g*(y1-y2)));\n\t\t}\n\t}\n}\n// int chamfer_cuda_backward(int b,int n,const float * xyz1,int m,const float * xyz2,const float * grad_dist1,const int * idx1,const float * grad_dist2,const int * idx2,float * grad_xyz1,float * grad_xyz2, cudaStream_t stream){\nint chamfer_cuda_backward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor gradxyz1, at::Tensor gradxyz2, at::Tensor graddist1, at::Tensor graddist2, at::Tensor idx1, at::Tensor idx2){\n\t// cudaMemset(grad_xyz1,0,b*n*3*4);\n\t// cudaMemset(grad_xyz2,0,b*m*3*4);\n\t\n\tconst auto batch_size = xyz1.size(0);\n\tconst auto n = xyz1.size(1); //num_points point cloud A\n\tconst auto m = xyz2.size(1); //num_points point cloud B\n\n\tNmDistanceGradKernel<<<dim3(1,16,1),256>>>(batch_size,n,xyz1.data<float>(),m,xyz2.data<float>(),graddist1.data<float>(),idx1.data<int>(),gradxyz1.data<float>(),gradxyz2.data<float>());\n\tNmDistanceGradKernel<<<dim3(1,16,1),256>>>(batch_size,m,xyz2.data<float>(),n,xyz1.data<float>(),graddist2.data<float>(),idx2.data<int>(),gradxyz2.data<float>(),gradxyz1.data<float>());\n\t\n\tcudaError_t err = cudaGetLastError();\n\t  if (err != cudaSuccess) {\n\t    printf(\"error in nnd get grad: %s\\n\", cudaGetErrorString(err));\n\t    //THError(\"aborting\");\n\t    return 0;\n\t  }\n\t  return 1;\n\t\n}\n\n"
  },
  {
    "path": "code/mmdet/ops/chamfer_2d/src/chamfer_cuda.cpp",
    "content": "#include <torch/torch.h>\n#include <vector>\n\n///TMP\n//#include \"common.h\"\n/// NOT TMP\n\t\n\nint chamfer_cuda_forward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor dist1, at::Tensor dist2, at::Tensor idx1, at::Tensor idx2);\n\n\nint chamfer_cuda_backward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor gradxyz1, at::Tensor gradxyz2, at::Tensor graddist1, at::Tensor graddist2, at::Tensor idx1, at::Tensor idx2);\n\n\n\n\nint chamfer_forward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor dist1, at::Tensor dist2, at::Tensor idx1, at::Tensor idx2) {\n    return chamfer_cuda_forward(xyz1, xyz2, dist1, dist2, idx1, idx2);\n}\n\n\nint chamfer_backward(at::Tensor xyz1, at::Tensor xyz2, at::Tensor gradxyz1, at::Tensor gradxyz2, at::Tensor graddist1, \n\t\t\t\t\t  at::Tensor graddist2, at::Tensor idx1, at::Tensor idx2) {\n\n    return chamfer_cuda_backward(xyz1, xyz2, gradxyz1, gradxyz2, graddist1, graddist2, idx1, idx2);\n}\n\n\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"forward\", &chamfer_forward, \"chamfer forward (CUDA)\");\n  m.def(\"backward\", &chamfer_backward, \"chamfer backward (CUDA)\");\n}"
  },
  {
    "path": "code/mmdet/ops/context_block.py",
    "content": "import torch\nfrom mmcv.cnn import constant_init, kaiming_init\nfrom torch import nn\n\n\ndef last_zero_init(m):\n    if isinstance(m, nn.Sequential):\n        constant_init(m[-1], val=0)\n    else:\n        constant_init(m, val=0)\n\n\nclass ContextBlock(nn.Module):\n    \"\"\"ContextBlock module in GCNet.\n\n    See 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond'\n    (https://arxiv.org/abs/1904.11492) for details.\n\n    Args:\n        in_channels (int): Channels of the input feature map.\n        ratio (float): Ratio of channels of transform bottleneck\n        pooling_type (str): Pooling method for context modeling\n        fusion_types (list[str]|tuple[str]): Fusion method for feature fusion,\n            options: 'channels_add', 'channel_mul'\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 ratio,\n                 pooling_type='att',\n                 fusion_types=('channel_add', )):\n        super(ContextBlock, self).__init__()\n        assert pooling_type in ['avg', 'att']\n        assert isinstance(fusion_types, (list, tuple))\n        valid_fusion_types = ['channel_add', 'channel_mul']\n        assert all([f in valid_fusion_types for f in fusion_types])\n        assert len(fusion_types) > 0, 'at least one fusion should be used'\n        self.in_channels = in_channels\n        self.ratio = ratio\n        self.planes = int(in_channels * ratio)\n        self.pooling_type = pooling_type\n        self.fusion_types = fusion_types\n        if pooling_type == 'att':\n            self.conv_mask = nn.Conv2d(in_channels, 1, kernel_size=1)\n            self.softmax = nn.Softmax(dim=2)\n        else:\n            self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        if 'channel_add' in fusion_types:\n            self.channel_add_conv = nn.Sequential(\n                nn.Conv2d(self.in_channels, self.planes, kernel_size=1),\n                nn.LayerNorm([self.planes, 1, 1]),\n                nn.ReLU(inplace=True),  # yapf: disable\n                nn.Conv2d(self.planes, self.in_channels, kernel_size=1))\n        else:\n            self.channel_add_conv = None\n        if 'channel_mul' in fusion_types:\n            self.channel_mul_conv = nn.Sequential(\n                nn.Conv2d(self.in_channels, self.planes, kernel_size=1),\n                nn.LayerNorm([self.planes, 1, 1]),\n                nn.ReLU(inplace=True),  # yapf: disable\n                nn.Conv2d(self.planes, self.in_channels, kernel_size=1))\n        else:\n            self.channel_mul_conv = None\n        self.reset_parameters()\n\n    def reset_parameters(self):\n        if self.pooling_type == 'att':\n            kaiming_init(self.conv_mask, mode='fan_in')\n            self.conv_mask.inited = True\n\n        if self.channel_add_conv is not None:\n            last_zero_init(self.channel_add_conv)\n        if self.channel_mul_conv is not None:\n            last_zero_init(self.channel_mul_conv)\n\n    def spatial_pool(self, x):\n        batch, channel, height, width = x.size()\n        if self.pooling_type == 'att':\n            input_x = x\n            # [N, C, H * W]\n            input_x = input_x.view(batch, channel, height * width)\n            # [N, 1, C, H * W]\n            input_x = input_x.unsqueeze(1)\n            # [N, 1, H, W]\n            context_mask = self.conv_mask(x)\n            # [N, 1, H * W]\n            context_mask = context_mask.view(batch, 1, height * width)\n            # [N, 1, H * W]\n            context_mask = self.softmax(context_mask)\n            # [N, 1, H * W, 1]\n            context_mask = context_mask.unsqueeze(-1)\n            # [N, 1, C, 1]\n            context = torch.matmul(input_x, context_mask)\n            # [N, C, 1, 1]\n            context = context.view(batch, channel, 1, 1)\n        else:\n            # [N, C, 1, 1]\n            context = self.avg_pool(x)\n\n        return context\n\n    def forward(self, x):\n        # [N, C, 1, 1]\n        context = self.spatial_pool(x)\n\n        out = x\n        if self.channel_mul_conv is not None:\n            # [N, C, 1, 1]\n            channel_mul_term = torch.sigmoid(self.channel_mul_conv(context))\n            out = out * channel_mul_term\n        if self.channel_add_conv is not None:\n            # [N, C, 1, 1]\n            channel_add_term = self.channel_add_conv(context)\n            out = out + channel_add_term\n\n        return out\n"
  },
  {
    "path": "code/mmdet/ops/conv_ws.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import CONV_LAYERS\n\n\ndef conv_ws_2d(input,\n               weight,\n               bias=None,\n               stride=1,\n               padding=0,\n               dilation=1,\n               groups=1,\n               eps=1e-5):\n    c_in = weight.size(0)\n    weight_flat = weight.view(c_in, -1)\n    mean = weight_flat.mean(dim=1, keepdim=True).view(c_in, 1, 1, 1)\n    std = weight_flat.std(dim=1, keepdim=True).view(c_in, 1, 1, 1)\n    weight = (weight - mean) / (std + eps)\n    return F.conv2d(input, weight, bias, stride, padding, dilation, groups)\n\n\n@CONV_LAYERS.register_module(name='ConvWS')\nclass ConvWS2d(nn.Conv2d):\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 bias=True,\n                 eps=1e-5):\n        super(ConvWS2d, self).__init__(\n            in_channels,\n            out_channels,\n            kernel_size,\n            stride=stride,\n            padding=padding,\n            dilation=dilation,\n            groups=groups,\n            bias=bias)\n        self.eps = eps\n\n    def forward(self, x):\n        return conv_ws_2d(x, self.weight, self.bias, self.stride, self.padding,\n                          self.dilation, self.groups, self.eps)\n\n\n@CONV_LAYERS.register_module(name='ConvAWS')\nclass ConvAWS2d(nn.Conv2d):\n    \"\"\"AWS (Adaptive Weight Standardization)\n\n    This is a variant of Weight Standardization\n    (https://arxiv.org/pdf/1903.10520.pdf)\n    It is used in DetectoRS to avoid NaN\n    (https://arxiv.org/pdf/2006.02334.pdf)\n\n    Args:\n        in_channels (int): Number of channels in the input image\n        out_channels (int): Number of channels produced by the convolution\n        kernel_size (int or tuple): Size of the conv kernel\n        stride (int or tuple, optional): Stride of the convolution. Default: 1\n        padding (int or tuple, optional): Zero-padding added to both sides of\n            the input. Default: 0\n        dilation (int or tuple, optional): Spacing between kernel elements.\n            Default: 1\n        groups (int, optional): Number of blocked connections from input\n            channels to output channels. Default: 1\n        bias (bool, optional): If set True, adds a learnable bias to the\n            output. Default: True\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 bias=True):\n        super().__init__(\n            in_channels,\n            out_channels,\n            kernel_size,\n            stride=stride,\n            padding=padding,\n            dilation=dilation,\n            groups=groups,\n            bias=bias)\n        self.register_buffer('weight_gamma',\n                             torch.ones(self.out_channels, 1, 1, 1))\n        self.register_buffer('weight_beta',\n                             torch.zeros(self.out_channels, 1, 1, 1))\n\n    def _get_weight(self, weight):\n        weight_flat = weight.view(weight.size(0), -1)\n        mean = weight_flat.mean(dim=1).view(-1, 1, 1, 1)\n        std = torch.sqrt(weight_flat.var(dim=1) + 1e-5).view(-1, 1, 1, 1)\n        weight = (weight - mean) / std\n        weight = self.weight_gamma * weight + self.weight_beta\n        return weight\n\n    def forward(self, x):\n        weight = self._get_weight(self.weight)\n        return F.conv2d(x, weight, self.bias, self.stride, self.padding,\n                        self.dilation, self.groups)\n\n    def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,\n                              missing_keys, unexpected_keys, error_msgs):\n        \"\"\"Override default load function\n\n        AWS overrides the function _load_from_state_dict to recover\n        weight_gamma and weight_beta if they are missing. If weight_gamma and\n        weight_beta are found in the checkpoint, this function will return\n        after super()._load_from_state_dict. Otherwise, it will compute the\n        mean and std of the pretrained weights and store them in weight_beta\n        and weight_gamma.\n        \"\"\"\n\n        self.weight_gamma.data.fill_(-1)\n        local_missing_keys = []\n        super()._load_from_state_dict(state_dict, prefix, local_metadata,\n                                      strict, local_missing_keys,\n                                      unexpected_keys, error_msgs)\n        if self.weight_gamma.data.mean() > 0:\n            for k in local_missing_keys:\n                missing_keys.append(k)\n            return\n        weight = self.weight.data\n        weight_flat = weight.view(weight.size(0), -1)\n        mean = weight_flat.mean(dim=1).view(-1, 1, 1, 1)\n        std = torch.sqrt(weight_flat.var(dim=1) + 1e-5).view(-1, 1, 1, 1)\n        self.weight_beta.data.copy_(mean)\n        self.weight_gamma.data.copy_(std)\n        missing_gamma_beta = [\n            k for k in local_missing_keys\n            if k.endswith('weight_gamma') or k.endswith('weight_beta')\n        ]\n        for k in missing_gamma_beta:\n            local_missing_keys.remove(k)\n        for k in local_missing_keys:\n            missing_keys.append(k)\n"
  },
  {
    "path": "code/mmdet/ops/corner_pool/__init__.py",
    "content": "from .corner_pool import CornerPool, TLPool, BRPool\n\n__all__ = ['CornerPool', 'TLPool', 'BRPool']\n"
  },
  {
    "path": "code/mmdet/ops/corner_pool/corner_pool.py",
    "content": "import torch\nfrom torch import nn\nfrom torch.autograd import Function\n\nfrom mmcv.cnn import ConvModule\n\nfrom . import corner_pool_ext\n\n\nclass TopPoolFunction(Function):\n\n    @staticmethod\n    def forward(ctx, input):\n        output = corner_pool_ext.top_pool_forward(input)\n        ctx.save_for_backward(input)\n        return output\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        input = ctx.saved_variables[0]\n        output = corner_pool_ext.top_pool_backward(input, grad_output)\n        return output\n\n\nclass BottomPoolFunction(Function):\n\n    @staticmethod\n    def forward(ctx, input):\n        output = corner_pool_ext.bottom_pool_forward(input)\n        ctx.save_for_backward(input)\n        return output\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        input = ctx.saved_variables[0]\n        output = corner_pool_ext.bottom_pool_backward(input, grad_output)\n        return output\n\n\nclass LeftPoolFunction(Function):\n\n    @staticmethod\n    def forward(ctx, input):\n        output = corner_pool_ext.left_pool_forward(input)\n        ctx.save_for_backward(input)\n        return output\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        input = ctx.saved_variables[0]\n        output = corner_pool_ext.left_pool_backward(input, grad_output)\n        return output\n\n\nclass RightPoolFunction(Function):\n\n    @staticmethod\n    def forward(ctx, input):\n        output = corner_pool_ext.right_pool_forward(input)\n        ctx.save_for_backward(input)\n        return output\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        input = ctx.saved_variables[0]\n        output = corner_pool_ext.right_pool_backward(input, grad_output)\n        return output\n\n\nclass CornerPool(nn.Module):\n    \"\"\"Corner Pooling.\n    Corner Pooling is a new type of pooling layer that helps a\n    convolutional network better localize corners of bounding boxes.\n    Please refer to https://arxiv.org/abs/1808.01244 for more details.\n    Code is modified from https://github.com/princeton-vl/CornerNet-Lite.\n    Args:\n        mode(str): Pooling orientation for the pooling layer\n            - 'bottom': Bottom Pooling\n            - 'left': Left Pooling\n            - 'right': Right Pooling\n            - 'top': Top Pooling\n    Returns:\n        Feature map after pooling.\n    \"\"\"\n\n    pool_functions = {\n        'bottom': BottomPoolFunction,\n        'left': LeftPoolFunction,\n        'right': RightPoolFunction,\n        'top': TopPoolFunction,\n    }\n\n    cummax_dim_flip = {\n        'bottom': (2, False),\n        'left': (3, True),\n        'right': (3, False),\n        'top': (2, True),\n    }\n\n    def __init__(self, mode):\n        super(CornerPool, self).__init__()\n        assert mode in self.pool_functions\n        self.mode = mode\n        self.corner_pool = self.pool_functions[mode]\n\n    def forward(self, x):\n        if torch.__version__ >= '1.5.0':\n            dim, flip = self.cummax_dim_flip[self.mode]\n            if flip:\n                x = x.flip(dim)\n            pool_tensor, _ = torch.cummax(x, dim=dim)\n            if flip:\n                pool_tensor = pool_tensor.flip(dim)\n            return pool_tensor\n        else:\n            return self.corner_pool.apply(x)\n\n\nclass CornerPoolPack(nn.Module):\n    def __init__(self, dim, pool1, pool2, conv_cfg=None, norm_cfg=None, first_kernel_size=3, kernel_size=3, corner_dim=128):\n        super(CornerPoolPack, self).__init__()\n        self.p1_conv1 = ConvModule(\n            dim,\n            corner_dim,\n            first_kernel_size,\n            stride=1,\n            padding=(first_kernel_size - 1) // 2,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg)\n        self.p2_conv1 = ConvModule(\n            dim,\n            corner_dim,\n            first_kernel_size,\n            stride=1,\n            padding=(first_kernel_size - 1) // 2,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg)\n\n        self.p_conv1 = nn.Conv2d(corner_dim, dim, 3, padding=1, bias=False)\n        self.p_gn1   = nn.GroupNorm(num_groups=32, num_channels=dim)\n\n        self.conv1 = nn.Conv2d(dim, dim, 1, bias=False)\n        self.gn1   = nn.GroupNorm(num_groups=32, num_channels=dim)\n        self.relu1 = nn.ReLU(inplace=True)\n\n        self.conv2 = ConvModule(\n            dim,\n            dim,\n            kernel_size,\n            stride=1,\n            padding=(kernel_size - 1) // 2,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg)\n\n        self.pool1 = pool1\n        self.pool2 = pool2\n\n    def forward(self, x):\n        # pool 1\n        p1_conv1 = self.p1_conv1(x)\n        pool1    = self.pool1(p1_conv1)\n\n        # pool 2\n        p2_conv1 = self.p2_conv1(x)\n        pool2    = self.pool2(p2_conv1)\n\n        # pool 1 + pool 2\n        p_conv1 = self.p_conv1(pool1 + pool2)\n        p_gn1   = self.p_gn1(p_conv1)\n\n        conv1 = self.conv1(x)\n        gn1   = self.gn1(conv1)\n        relu1 = self.relu1(p_gn1 + gn1)\n\n        conv2 = self.conv2(relu1)\n        return conv2\n\n\nclass TLPool(CornerPoolPack):\n    def __init__(self, dim, conv_cfg=None, norm_cfg=None, first_kernel_size=3, kernel_size=3, corner_dim=128):\n        super(TLPool, self).__init__(dim, CornerPool('top'), CornerPool('left'), conv_cfg, norm_cfg, first_kernel_size, kernel_size, corner_dim)\n\n\nclass BRPool(CornerPoolPack):\n    def __init__(self, dim, conv_cfg=None, norm_cfg=None, first_kernel_size=3, kernel_size=3, corner_dim=128):\n        super(BRPool, self).__init__(dim, CornerPool('bottom'), CornerPool('right'), conv_cfg, norm_cfg, first_kernel_size, kernel_size, corner_dim)"
  },
  {
    "path": "code/mmdet/ops/corner_pool/src/corner_pool.cpp",
    "content": "// Modified from\n// https://github.com/princeton-vl/CornerNet-Lite/tree/master/core/models/py_utils/_cpools/src\n#include <torch/torch.h>\n\n#include <vector>\n\nat::Tensor bottom_pool_forward(at::Tensor input) {\n  // Initialize output\n  at::Tensor output = at::zeros_like(input);\n\n  // Get height\n  int64_t height = input.size(2);\n\n  output.copy_(input);\n\n  for (int64_t ind = 1; ind < height; ind <<= 1) {\n    at::Tensor max_temp = at::slice(output, 2, ind, height);\n    at::Tensor cur_temp = at::slice(output, 2, ind, height).clone();\n    at::Tensor next_temp = at::slice(output, 2, 0, height - ind).clone();\n    at::max_out(max_temp, cur_temp, next_temp);\n  }\n\n  return output;\n}\n\nat::Tensor bottom_pool_backward(at::Tensor input, at::Tensor grad_output) {\n  auto output = at::zeros_like(input);\n\n  int32_t batch = input.size(0);\n  int32_t channel = input.size(1);\n  int32_t height = input.size(2);\n  int32_t width = input.size(3);\n\n  auto max_val = torch::zeros({batch, channel, width},\n                              at::device(at::kCUDA).dtype(at::kFloat));\n  auto max_ind = torch::zeros({batch, channel, width},\n                              at::device(at::kCUDA).dtype(at::kLong));\n\n  auto input_temp = input.select(2, 0);\n  max_val.copy_(input_temp);\n\n  max_ind.fill_(0);\n\n  auto output_temp = output.select(2, 0);\n  auto grad_output_temp = grad_output.select(2, 0);\n  output_temp.copy_(grad_output_temp);\n\n  auto un_max_ind = max_ind.unsqueeze(2);\n  auto gt_mask = torch::zeros({batch, channel, width},\n                              at::device(at::kCUDA).dtype(at::kBool));\n  auto max_temp = torch::zeros({batch, channel, width},\n                               at::device(at::kCUDA).dtype(at::kFloat));\n  for (int32_t ind = 0; ind < height - 1; ++ind) {\n    input_temp = input.select(2, ind + 1);\n    at::gt_out(gt_mask, input_temp, max_val);\n\n    at::masked_select_out(max_temp, input_temp, gt_mask);\n    max_val.masked_scatter_(gt_mask, max_temp);\n    max_ind.masked_fill_(gt_mask, ind + 1);\n\n    grad_output_temp = grad_output.select(2, ind + 1).unsqueeze(2);\n    output.scatter_add_(2, un_max_ind, grad_output_temp);\n  }\n\n  return output;\n}\n\nat::Tensor left_pool_forward(at::Tensor input) {\n  // Initialize output\n  at::Tensor output = at::zeros_like(input);\n\n  // Get width\n  int64_t width = input.size(3);\n\n  output.copy_(input);\n\n  for (int64_t ind = 1; ind < width; ind <<= 1) {\n    at::Tensor max_temp = at::slice(output, 3, 0, width - ind);\n    at::Tensor cur_temp = at::slice(output, 3, 0, width - ind).clone();\n    at::Tensor next_temp = at::slice(output, 3, ind, width).clone();\n    at::max_out(max_temp, cur_temp, next_temp);\n  }\n\n  return output;\n}\n\nat::Tensor left_pool_backward(at::Tensor input, at::Tensor grad_output) {\n  auto output = at::zeros_like(input);\n\n  int32_t batch = input.size(0);\n  int32_t channel = input.size(1);\n  int32_t height = input.size(2);\n  int32_t width = input.size(3);\n\n  auto max_val = torch::zeros({batch, channel, height},\n                              at::device(at::kCUDA).dtype(at::kFloat));\n  auto max_ind = torch::zeros({batch, channel, height},\n                              at::device(at::kCUDA).dtype(at::kLong));\n\n  auto input_temp = input.select(3, width - 1);\n  max_val.copy_(input_temp);\n\n  max_ind.fill_(width - 1);\n\n  auto output_temp = output.select(3, width - 1);\n  auto grad_output_temp = grad_output.select(3, width - 1);\n  output_temp.copy_(grad_output_temp);\n\n  auto un_max_ind = max_ind.unsqueeze(3);\n  auto gt_mask = torch::zeros({batch, channel, height},\n                              at::device(at::kCUDA).dtype(at::kBool));\n  auto max_temp = torch::zeros({batch, channel, height},\n                               at::device(at::kCUDA).dtype(at::kFloat));\n  for (int32_t ind = 1; ind < width; ++ind) {\n    input_temp = input.select(3, width - ind - 1);\n    at::gt_out(gt_mask, input_temp, max_val);\n\n    at::masked_select_out(max_temp, input_temp, gt_mask);\n    max_val.masked_scatter_(gt_mask, max_temp);\n    max_ind.masked_fill_(gt_mask, width - ind - 1);\n\n    grad_output_temp = grad_output.select(3, width - ind - 1).unsqueeze(3);\n    output.scatter_add_(3, un_max_ind, grad_output_temp);\n  }\n\n  return output;\n}\n\nat::Tensor right_pool_forward(at::Tensor input) {\n  // Initialize output\n  at::Tensor output = at::zeros_like(input);\n\n  // Get width\n  int64_t width = input.size(3);\n\n  output.copy_(input);\n\n  for (int64_t ind = 1; ind < width; ind <<= 1) {\n    at::Tensor max_temp = at::slice(output, 3, ind, width);\n    at::Tensor cur_temp = at::slice(output, 3, ind, width).clone();\n    at::Tensor next_temp = at::slice(output, 3, 0, width - ind).clone();\n    at::max_out(max_temp, cur_temp, next_temp);\n  }\n\n  return output;\n}\n\nat::Tensor right_pool_backward(at::Tensor input, at::Tensor grad_output) {\n  at::Tensor output = at::zeros_like(input);\n\n  int32_t batch = input.size(0);\n  int32_t channel = input.size(1);\n  int32_t height = input.size(2);\n  int32_t width = input.size(3);\n\n  auto max_val = torch::zeros({batch, channel, height},\n                              at::device(at::kCUDA).dtype(at::kFloat));\n  auto max_ind = torch::zeros({batch, channel, height},\n                              at::device(at::kCUDA).dtype(at::kLong));\n\n  auto input_temp = input.select(3, 0);\n  max_val.copy_(input_temp);\n\n  max_ind.fill_(0);\n\n  auto output_temp = output.select(3, 0);\n  auto grad_output_temp = grad_output.select(3, 0);\n  output_temp.copy_(grad_output_temp);\n\n  auto un_max_ind = max_ind.unsqueeze(3);\n  auto gt_mask = torch::zeros({batch, channel, height},\n                              at::device(at::kCUDA).dtype(at::kBool));\n  auto max_temp = torch::zeros({batch, channel, height},\n                               at::device(at::kCUDA).dtype(at::kFloat));\n  for (int32_t ind = 0; ind < width - 1; ++ind) {\n    input_temp = input.select(3, ind + 1);\n    at::gt_out(gt_mask, input_temp, max_val);\n\n    at::masked_select_out(max_temp, input_temp, gt_mask);\n    max_val.masked_scatter_(gt_mask, max_temp);\n    max_ind.masked_fill_(gt_mask, ind + 1);\n\n    grad_output_temp = grad_output.select(3, ind + 1).unsqueeze(3);\n    output.scatter_add_(3, un_max_ind, grad_output_temp);\n  }\n\n  return output;\n}\n\nat::Tensor top_pool_forward(at::Tensor input) {\n  // Initialize output\n  at::Tensor output = at::zeros_like(input);\n\n  // Get height\n  int64_t height = input.size(2);\n\n  output.copy_(input);\n\n  for (int64_t ind = 1; ind < height; ind <<= 1) {\n    at::Tensor max_temp = at::slice(output, 2, 0, height - ind);\n    at::Tensor cur_temp = at::slice(output, 2, 0, height - ind).clone();\n    at::Tensor next_temp = at::slice(output, 2, ind, height).clone();\n    at::max_out(max_temp, cur_temp, next_temp);\n  }\n\n  return output;\n}\n\nat::Tensor top_pool_backward(at::Tensor input, at::Tensor grad_output) {\n  auto output = at::zeros_like(input);\n\n  int32_t batch = input.size(0);\n  int32_t channel = input.size(1);\n  int32_t height = input.size(2);\n  int32_t width = input.size(3);\n\n  auto max_val = torch::zeros({batch, channel, width},\n                              at::device(at::kCUDA).dtype(at::kFloat));\n  auto max_ind = torch::zeros({batch, channel, width},\n                              at::device(at::kCUDA).dtype(at::kLong));\n\n  auto input_temp = input.select(2, height - 1);\n  max_val.copy_(input_temp);\n\n  max_ind.fill_(height - 1);\n\n  auto output_temp = output.select(2, height - 1);\n  auto grad_output_temp = grad_output.select(2, height - 1);\n  output_temp.copy_(grad_output_temp);\n\n  auto un_max_ind = max_ind.unsqueeze(2);\n  auto gt_mask = torch::zeros({batch, channel, width},\n                              at::device(at::kCUDA).dtype(at::kBool));\n  auto max_temp = torch::zeros({batch, channel, width},\n                               at::device(at::kCUDA).dtype(at::kFloat));\n  for (int32_t ind = 1; ind < height; ++ind) {\n    input_temp = input.select(2, height - ind - 1);\n    at::gt_out(gt_mask, input_temp, max_val);\n\n    at::masked_select_out(max_temp, input_temp, gt_mask);\n    max_val.masked_scatter_(gt_mask, max_temp);\n    max_ind.masked_fill_(gt_mask, height - ind - 1);\n\n    grad_output_temp = grad_output.select(2, height - ind - 1).unsqueeze(2);\n    output.scatter_add_(2, un_max_ind, grad_output_temp);\n  }\n\n  return output;\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"bottom_pool_forward\", &bottom_pool_forward, \"Bottom Pool Forward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"bottom_pool_backward\", &bottom_pool_backward, \"Bottom Pool Backward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"left_pool_forward\", &left_pool_forward, \"Left Pool Forward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"left_pool_backward\", &left_pool_backward, \"Left Pool Backward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"right_pool_forward\", &right_pool_forward, \"Right Pool Forward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"right_pool_backward\", &right_pool_backward, \"Right Pool Backward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"top_pool_forward\", &top_pool_forward, \"Top Pool Forward\",\n        py::call_guard<py::gil_scoped_release>());\n  m.def(\"top_pool_backward\", &top_pool_backward, \"Top Pool Backward\",\n        py::call_guard<py::gil_scoped_release>());\n}\n"
  },
  {
    "path": "code/mmdet/ops/dcn/__init__.py",
    "content": "from .deform_conv import (DeformConv, DeformConvPack, ModulatedDeformConv,\n                          ModulatedDeformConvPack, deform_conv,\n                          modulated_deform_conv, PyramidDeformConv, pyramid_deform_conv)\nfrom .deform_pool import (DeformRoIPooling, DeformRoIPoolingPack,\n                          ModulatedDeformRoIPoolingPack, deform_roi_pooling)\n\n__all__ = [\n    'DeformConv', 'DeformConvPack', 'ModulatedDeformConv',\n    'ModulatedDeformConvPack', 'DeformRoIPooling', 'DeformRoIPoolingPack',\n    'ModulatedDeformRoIPoolingPack', 'deform_conv', 'modulated_deform_conv',\n    'deform_roi_pooling', 'PyramidDeformConv', 'pyramid_deform_conv'\n]\n"
  },
  {
    "path": "code/mmdet/ops/dcn/deform_conv.py",
    "content": "import math\nimport pdb\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import CONV_LAYERS\nfrom mmcv.utils import print_log\nfrom torch.autograd import Function\nfrom torch.autograd.function import once_differentiable\nfrom torch.nn.modules.utils import _pair, _single\n\nfrom . import deform_conv_ext\n\n\nclass DeformConvFunction(Function):\n\n    @staticmethod\n    def forward(ctx,\n                input,\n                offset,\n                weight,\n                stride=1,\n                padding=0,\n                dilation=1,\n                groups=1,\n                deformable_groups=1,\n                im2col_step=64):\n        if input is not None and input.dim() != 4:\n            raise ValueError(f'Expected 4D tensor as input, got {input.dim()}'\n                             'D tensor instead.')\n        ctx.stride = _pair(stride)\n        ctx.padding = _pair(padding)\n        ctx.dilation = _pair(dilation)\n        ctx.groups = groups\n        ctx.deformable_groups = deformable_groups\n        ctx.im2col_step = im2col_step\n\n        ctx.save_for_backward(input, offset, weight)\n\n        output = input.new_empty(\n            DeformConvFunction._output_size(input, weight, ctx.padding,\n                                            ctx.dilation, ctx.stride))\n\n        ctx.bufs_ = [input.new_empty(0), input.new_empty(0)]  # columns, ones\n\n        if not input.is_cuda:\n            raise NotImplementedError\n        else:\n            cur_im2col_step = min(ctx.im2col_step, input.shape[0])\n            assert (input.shape[0] %\n                    cur_im2col_step) == 0, 'im2col step must divide batchsize'\n            deform_conv_ext.deform_conv_forward(\n                input, weight, offset, output, ctx.bufs_[0], ctx.bufs_[1],\n                weight.size(3), weight.size(2), ctx.stride[1], ctx.stride[0],\n                ctx.padding[1], ctx.padding[0], ctx.dilation[1],\n                ctx.dilation[0], ctx.groups, ctx.deformable_groups,\n                cur_im2col_step)\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        input, offset, weight = ctx.saved_tensors\n\n        grad_input = grad_offset = grad_weight = None\n\n        if not grad_output.is_cuda:\n            raise NotImplementedError\n        else:\n            cur_im2col_step = min(ctx.im2col_step, input.shape[0])\n            assert (input.shape[0] %\n                    cur_im2col_step) == 0, 'im2col step must divide batchsize'\n\n            if ctx.needs_input_grad[0] or ctx.needs_input_grad[1]:\n                grad_input = torch.zeros_like(input)\n                grad_offset = torch.zeros_like(offset)\n                deform_conv_ext.deform_conv_backward_input(\n                    input, offset, grad_output, grad_input,\n                    grad_offset, weight, ctx.bufs_[0], weight.size(3),\n                    weight.size(2), ctx.stride[1], ctx.stride[0],\n                    ctx.padding[1], ctx.padding[0], ctx.dilation[1],\n                    ctx.dilation[0], ctx.groups, ctx.deformable_groups,\n                    cur_im2col_step)\n\n            if ctx.needs_input_grad[2]:\n                grad_weight = torch.zeros_like(weight)\n                deform_conv_ext.deform_conv_backward_parameters(\n                    input, offset, grad_output,\n                    grad_weight, ctx.bufs_[0], ctx.bufs_[1], weight.size(3),\n                    weight.size(2), ctx.stride[1], ctx.stride[0],\n                    ctx.padding[1], ctx.padding[0], ctx.dilation[1],\n                    ctx.dilation[0], ctx.groups, ctx.deformable_groups, 1,\n                    cur_im2col_step)\n\n        return (grad_input, grad_offset, grad_weight, None, None, None, None,\n                None)\n\n    @staticmethod\n    def _output_size(input, weight, padding, dilation, stride):\n        channels = weight.size(0)\n        output_size = (input.size(0), channels)\n        for d in range(input.dim() - 2):\n            in_size = input.size(d + 2)\n            pad = padding[d]\n            kernel = dilation[d] * (weight.size(d + 2) - 1) + 1\n            stride_ = stride[d]\n            output_size += ((in_size + (2 * pad) - kernel) // stride_ + 1, )\n        if not all(map(lambda s: s > 0, output_size)):\n            raise ValueError('convolution input is too small (output would be '\n                             f'{\"x\".join(map(str, output_size))})')\n        return output_size\n\n\nclass ModulatedDeformConvFunction(Function):\n\n    @staticmethod\n    def forward(ctx,\n                input,\n                offset,\n                mask,\n                weight,\n                bias=None,\n                stride=1,\n                padding=0,\n                dilation=1,\n                groups=1,\n                deformable_groups=1):\n        ctx.stride = stride\n        ctx.padding = padding\n        ctx.dilation = dilation\n        ctx.groups = groups\n        ctx.deformable_groups = deformable_groups\n        ctx.with_bias = bias is not None\n        if not ctx.with_bias:\n            bias = input.new_empty(1)  # fake tensor\n        if not input.is_cuda:\n            raise NotImplementedError\n        if weight.requires_grad or mask.requires_grad or offset.requires_grad \\\n                or input.requires_grad:\n            ctx.save_for_backward(input, offset, mask, weight, bias)\n        output = input.new_empty(\n            ModulatedDeformConvFunction._infer_shape(ctx, input, weight))\n        ctx._bufs = [input.new_empty(0), input.new_empty(0)]\n        deform_conv_ext.modulated_deform_conv_forward(\n            input, weight, bias, ctx._bufs[0], offset, mask, output,\n            ctx._bufs[1], weight.shape[2], weight.shape[3], ctx.stride,\n            ctx.stride, ctx.padding, ctx.padding, ctx.dilation, ctx.dilation,\n            ctx.groups, ctx.deformable_groups, ctx.with_bias)\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        if not grad_output.is_cuda:\n            raise NotImplementedError\n        input, offset, mask, weight, bias = ctx.saved_tensors\n        grad_input = torch.zeros_like(input)\n        grad_offset = torch.zeros_like(offset)\n        grad_mask = torch.zeros_like(mask)\n        grad_weight = torch.zeros_like(weight)\n        grad_bias = torch.zeros_like(bias)\n        deform_conv_ext.modulated_deform_conv_backward(\n            input, weight, bias, ctx._bufs[0], offset, mask, ctx._bufs[1],\n            grad_input, grad_weight, grad_bias, grad_offset, grad_mask,\n            grad_output, weight.shape[2], weight.shape[3], ctx.stride,\n            ctx.stride, ctx.padding, ctx.padding, ctx.dilation, ctx.dilation,\n            ctx.groups, ctx.deformable_groups, ctx.with_bias)\n        if not ctx.with_bias:\n            grad_bias = None\n\n        return (grad_input, grad_offset, grad_mask, grad_weight, grad_bias,\n                None, None, None, None, None)\n\n    @staticmethod\n    def _infer_shape(ctx, input, weight):\n        n = input.size(0)\n        channels_out = weight.size(0)\n        height, width = input.shape[2:4]\n        kernel_h, kernel_w = weight.shape[2:4]\n        # TODO: support different padding/stride/dilation in height and width\n        height_out = (height + 2 * ctx.padding -\n                      (ctx.dilation * (kernel_h - 1) + 1)) // ctx.stride + 1\n        width_out = (width + 2 * ctx.padding -\n                     (ctx.dilation * (kernel_w - 1) + 1)) // ctx.stride + 1\n        return n, channels_out, height_out, width_out\n\n\nclass PyramidDeformConvFunction(Function):\n\n    @staticmethod\n    def forward(ctx,\n                input,\n                offset,\n                weight,\n                scales=1,\n                stride=1,\n                padding=0,\n                dilation=1,\n                groups=1,\n                deformable_groups=1,\n                im2col_step=64):\n        if input is not None and input.dim() != 4:\n            raise ValueError(f'Expected 4D tensor as input, got {input.dim()}'\n                             'D tensor instead.')\n        ctx.scales = _pair(scales)\n        ctx.stride = _pair(stride)\n        ctx.padding = _pair(padding)\n        ctx.dilation = _pair(dilation)\n        ctx.groups = groups\n        ctx.deformable_groups = deformable_groups\n        ctx.im2col_step = im2col_step\n\n        ctx.save_for_backward(input, offset, weight)\n\n        output = input.new_empty(\n            PyramidDeformConvFunction._output_size(offset, weight, ctx.padding,\n                                            ctx.dilation, ctx.stride))\n\n        ctx.bufs_ = [input.new_empty(0), input.new_empty(0)]  # columns, ones\n\n        if not input.is_cuda:\n            raise NotImplementedError\n        else:\n            cur_im2col_step = min(ctx.im2col_step, input.shape[0])\n            assert (input.shape[0] %\n                    cur_im2col_step) == 0, 'im2col step must divide batchsize'\n            deform_conv_ext.pyramid_deform_conv_forward(\n                input, weight, offset, output, ctx.bufs_[0], ctx.bufs_[1],\n                weight.size(3), weight.size(2), ctx.stride[1], ctx.stride[0],\n                ctx.padding[1], ctx.padding[0], ctx.dilation[1], ctx.dilation[0], \n                ctx.scales[1], ctx.scales[0], ctx.groups, ctx.deformable_groups,\n                cur_im2col_step)\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        input, offset, weight = ctx.saved_tensors\n\n        grad_input = grad_offset = grad_weight = None\n\n        if not grad_output.is_cuda:\n            raise NotImplementedError\n        else:\n            cur_im2col_step = min(ctx.im2col_step, input.shape[0])\n            assert (input.shape[0] %\n                    cur_im2col_step) == 0, 'im2col step must divide batchsize'\n\n            if ctx.needs_input_grad[0] or ctx.needs_input_grad[1]:\n                grad_input = torch.zeros_like(input)\n                grad_offset = torch.zeros_like(offset)\n                deform_conv_ext.pyramid_deform_conv_backward_input(\n                    input, offset, grad_output, grad_input,\n                    grad_offset, weight, ctx.bufs_[0], weight.size(3),\n                    weight.size(2), ctx.stride[1], ctx.stride[0],\n                    ctx.padding[1], ctx.padding[0], ctx.dilation[1],\n                    ctx.dilation[0], ctx.scales[1], ctx.scales[0],\n                    ctx.groups, ctx.deformable_groups, cur_im2col_step)\n\n            if ctx.needs_input_grad[2]:\n                grad_weight = torch.zeros_like(weight)\n                deform_conv_ext.pyramid_deform_conv_backward_parameters(\n                    input, offset, grad_output,\n                    grad_weight, ctx.bufs_[0], ctx.bufs_[1], weight.size(3),\n                    weight.size(2), ctx.stride[1], ctx.stride[0],\n                    ctx.padding[1], ctx.padding[0], ctx.dilation[1],\n                    ctx.dilation[0], ctx.scales[1], ctx.scales[0], \n                    ctx.groups, ctx.deformable_groups, 1,\n                    cur_im2col_step)\n\n        return (grad_input, grad_offset, grad_weight, None, None, None, None,\n                None, None)\n\n    @staticmethod\n    def _output_size(input, weight, padding, dilation, stride):\n        channels = weight.size(0)\n        output_size = (input.size(0), channels)\n        for d in range(input.dim() - 2):\n            in_size = input.size(d + 2)\n            pad = padding[d]\n            kernel = dilation[d] * (weight.size(d + 2) - 1) + 1\n            stride_ = stride[d]\n            output_size += ((in_size + (2 * pad) - kernel) // stride_ + 1, )\n        if not all(map(lambda s: s > 0, output_size)):\n            raise ValueError('convolution input is too small (output would be '\n                             f'{\"x\".join(map(str, output_size))})')\n        return output_size\n\n\ndeform_conv = DeformConvFunction.apply\nmodulated_deform_conv = ModulatedDeformConvFunction.apply\npyramid_deform_conv = PyramidDeformConvFunction.apply\n\n\nclass DeformConv(nn.Module):\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 deformable_groups=1,\n                 bias=False):\n        super(DeformConv, self).__init__()\n\n        assert not bias\n        assert in_channels % groups == 0, \\\n            f'in_channels {in_channels} is not divisible by groups {groups}'\n        assert out_channels % groups == 0, \\\n            f'out_channels {out_channels} is not divisible ' \\\n            f'by groups {groups}'\n\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.kernel_size = _pair(kernel_size)\n        self.stride = _pair(stride)\n        self.padding = _pair(padding)\n        self.dilation = _pair(dilation)\n        self.groups = groups\n        self.deformable_groups = deformable_groups\n        # enable compatibility with nn.Conv2d\n        self.transposed = False\n        self.output_padding = _single(0)\n\n        self.weight = nn.Parameter(\n            torch.Tensor(out_channels, in_channels // self.groups,\n                         *self.kernel_size))\n\n        self.reset_parameters()\n\n    def reset_parameters(self):\n        n = self.in_channels\n        for k in self.kernel_size:\n            n *= k\n        stdv = 1. / math.sqrt(n)\n        self.weight.data.uniform_(-stdv, stdv)\n\n    def forward(self, x, offset):\n        # To fix an assert error in deform_conv_cuda.cpp:128\n        # input image is smaller than kernel\n        input_pad = (\n            x.size(2) < self.kernel_size[0] or x.size(3) < self.kernel_size[1])\n        if input_pad:\n            pad_h = max(self.kernel_size[0] - x.size(2), 0)\n            pad_w = max(self.kernel_size[1] - x.size(3), 0)\n            x = F.pad(x, (0, pad_w, 0, pad_h), 'constant', 0).contiguous()\n            offset = F.pad(offset, (0, pad_w, 0, pad_h), 'constant',\n                           0).contiguous()\n        out = deform_conv(x, offset, self.weight, self.stride, self.padding,\n                          self.dilation, self.groups, self.deformable_groups)\n        if input_pad:\n            out = out[:, :, :out.size(2) - pad_h, :out.size(3) -\n                      pad_w].contiguous()\n        return out\n\n\n@CONV_LAYERS.register_module(name='DCN')\nclass DeformConvPack(DeformConv):\n    \"\"\"A Deformable Conv Encapsulation that acts as normal Conv layers.\n\n    The offset tensor is like `[y0, x0, y1, x1, y2, x2, ..., y8, x8]`.\n    The spatial arrangement is like:\n    ```\n    (x0, y0) (x1, y1) (x2, y2)\n    (x3, y3) (x4, y4) (x5, y5)\n    (x6, y6) (x7, y7) (x8, y8)\n    ```\n\n    Args:\n        in_channels (int): Same as nn.Conv2d.\n        out_channels (int): Same as nn.Conv2d.\n        kernel_size (int or tuple[int]): Same as nn.Conv2d.\n        stride (int or tuple[int]): Same as nn.Conv2d.\n        padding (int or tuple[int]): Same as nn.Conv2d.\n        dilation (int or tuple[int]): Same as nn.Conv2d.\n        groups (int): Same as nn.Conv2d.\n        bias (bool or str): If specified as `auto`, it will be decided by the\n            norm_cfg. Bias will be set as True if norm_cfg is None, otherwise\n            False.\n    \"\"\"\n\n    _version = 2\n\n    def __init__(self, *args, **kwargs):\n        super(DeformConvPack, self).__init__(*args, **kwargs)\n\n        self.conv_offset = nn.Conv2d(\n            self.in_channels,\n            self.deformable_groups * 2 * self.kernel_size[0] *\n            self.kernel_size[1],\n            kernel_size=self.kernel_size,\n            stride=_pair(self.stride),\n            padding=_pair(self.padding),\n            dilation=_pair(self.dilation),\n            bias=True)\n        self.init_offset()\n\n    def init_offset(self):\n        self.conv_offset.weight.data.zero_()\n        self.conv_offset.bias.data.zero_()\n\n    def forward(self, x):\n        offset = self.conv_offset(x)\n        return deform_conv(x, offset, self.weight, self.stride, self.padding,\n                           self.dilation, self.groups, self.deformable_groups)\n\n    def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,\n                              missing_keys, unexpected_keys, error_msgs):\n        version = local_metadata.get('version', None)\n\n        if version is None or version < 2:\n            # the key is different in early versions\n            # In version < 2, DeformConvPack loads previous benchmark models.\n            if (prefix + 'conv_offset.weight' not in state_dict\n                    and prefix[:-1] + '_offset.weight' in state_dict):\n                state_dict[prefix + 'conv_offset.weight'] = state_dict.pop(\n                    prefix[:-1] + '_offset.weight')\n            if (prefix + 'conv_offset.bias' not in state_dict\n                    and prefix[:-1] + '_offset.bias' in state_dict):\n                state_dict[prefix +\n                           'conv_offset.bias'] = state_dict.pop(prefix[:-1] +\n                                                                '_offset.bias')\n\n        if version is not None and version > 1:\n            print_log(\n                f'DeformConvPack {prefix.rstrip(\".\")} is upgraded to '\n                'version 2.',\n                logger='root')\n\n        super()._load_from_state_dict(state_dict, prefix, local_metadata,\n                                      strict, missing_keys, unexpected_keys,\n                                      error_msgs)\n\n\nclass ModulatedDeformConv(nn.Module):\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 deformable_groups=1,\n                 bias=True):\n        super(ModulatedDeformConv, self).__init__()\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.kernel_size = _pair(kernel_size)\n        self.stride = stride\n        self.padding = padding\n        self.dilation = dilation\n        self.groups = groups\n        self.deformable_groups = deformable_groups\n        self.with_bias = bias\n        # enable compatibility with nn.Conv2d\n        self.transposed = False\n        self.output_padding = _single(0)\n\n        self.weight = nn.Parameter(\n            torch.Tensor(out_channels, in_channels // groups,\n                         *self.kernel_size))\n        if bias:\n            self.bias = nn.Parameter(torch.Tensor(out_channels))\n        else:\n            self.register_parameter('bias', None)\n        self.init_weights()\n\n    def init_weights(self):\n        n = self.in_channels\n        for k in self.kernel_size:\n            n *= k\n        stdv = 1. / math.sqrt(n)\n        self.weight.data.uniform_(-stdv, stdv)\n        if self.bias is not None:\n            self.bias.data.zero_()\n\n    def forward(self, x, offset, mask):\n        return modulated_deform_conv(x, offset, mask, self.weight, self.bias,\n                                     self.stride, self.padding, self.dilation,\n                                     self.groups, self.deformable_groups)\n\n\n@CONV_LAYERS.register_module(name='DCNv2')\nclass ModulatedDeformConvPack(ModulatedDeformConv):\n    \"\"\"A ModulatedDeformable Conv Encapsulation that acts as normal Conv layers.\n\n    Args:\n        in_channels (int): Same as nn.Conv2d.\n        out_channels (int): Same as nn.Conv2d.\n        kernel_size (int or tuple[int]): Same as nn.Conv2d.\n        stride (int): Same as nn.Conv2d, while tuple is not supported.\n        padding (int): Same as nn.Conv2d, while tuple is not supported.\n        dilation (int): Same as nn.Conv2d, while tuple is not supported.\n        groups (int): Same as nn.Conv2d.\n        bias (bool or str): If specified as `auto`, it will be decided by the\n            norm_cfg. Bias will be set as True if norm_cfg is None, otherwise\n            False.\n    \"\"\"\n\n    _version = 2\n\n    def __init__(self, *args, **kwargs):\n        super(ModulatedDeformConvPack, self).__init__(*args, **kwargs)\n\n        self.conv_offset = nn.Conv2d(\n            self.in_channels,\n            self.deformable_groups * 3 * self.kernel_size[0] *\n            self.kernel_size[1],\n            kernel_size=self.kernel_size,\n            stride=_pair(self.stride),\n            padding=_pair(self.padding),\n            dilation=_pair(self.dilation),\n            bias=True)\n        self.init_weights()\n\n    def init_weights(self):\n        super(ModulatedDeformConvPack, self).init_weights()\n        if hasattr(self, 'conv_offset'):\n            self.conv_offset.weight.data.zero_()\n            self.conv_offset.bias.data.zero_()\n\n    def forward(self, x):\n        out = self.conv_offset(x)\n        o1, o2, mask = torch.chunk(out, 3, dim=1)\n        offset = torch.cat((o1, o2), dim=1)\n        mask = torch.sigmoid(mask)\n        return modulated_deform_conv(x, offset, mask, self.weight, self.bias,\n                                     self.stride, self.padding, self.dilation,\n                                     self.groups, self.deformable_groups)\n\n    def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,\n                              missing_keys, unexpected_keys, error_msgs):\n        version = local_metadata.get('version', None)\n\n        if version is None or version < 2:\n            # the key is different in early versions\n            # In version < 2, ModulatedDeformConvPack\n            # loads previous benchmark models.\n            if (prefix + 'conv_offset.weight' not in state_dict\n                    and prefix[:-1] + '_offset.weight' in state_dict):\n                state_dict[prefix + 'conv_offset.weight'] = state_dict.pop(\n                    prefix[:-1] + '_offset.weight')\n            if (prefix + 'conv_offset.bias' not in state_dict\n                    and prefix[:-1] + '_offset.bias' in state_dict):\n                state_dict[prefix +\n                           'conv_offset.bias'] = state_dict.pop(prefix[:-1] +\n                                                                '_offset.bias')\n\n        if version is not None and version > 1:\n            print_log(\n                f'ModulatedDeformConvPack {prefix.rstrip(\".\")} is upgraded to '\n                'version 2.',\n                logger='root')\n\n        super()._load_from_state_dict(state_dict, prefix, local_metadata,\n                                      strict, missing_keys, unexpected_keys,\n                                      error_msgs)\n\n\nclass PyramidDeformConv(nn.Module):\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 deformable_groups=1,\n                 bias=False):\n        super(PyramidDeformConv, self).__init__()\n\n        assert not bias\n        assert in_channels % groups == 0, \\\n            f'in_channels {in_channels} is not divisible by groups {groups}'\n        assert out_channels % groups == 0, \\\n            f'out_channels {out_channels} is not divisible ' \\\n            f'by groups {groups}'\n\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.kernel_size = _pair(kernel_size)\n        self.stride = _pair(stride)\n        self.padding = _pair(padding)\n        self.dilation = _pair(dilation)\n        self.groups = groups\n        self.deformable_groups = deformable_groups\n        # enable compatibility with nn.Conv2d\n        self.transposed = False\n        self.output_padding = _single(0)\n\n        self.weight = nn.Parameter(\n            torch.Tensor(out_channels, in_channels // self.groups,\n                         *self.kernel_size))\n\n        self.reset_parameters()\n\n    def reset_parameters(self):\n        n = self.in_channels\n        for k in self.kernel_size:\n            n *= k\n        stdv = 1. / math.sqrt(n)\n        self.weight.data.uniform_(-stdv, stdv)\n\n    def forward(self, x, offset, scale_h, scale_w):\n        # To fix an assert error in deform_conv_cuda.cpp:128\n        # input image is smaller than kernel\n        input_pad = (\n            x.size(2) < self.kernel_size[0] or x.size(3) < self.kernel_size[1])\n        if input_pad:\n            pad_h = max(self.kernel_size[0] - x.size(2), 0)\n            pad_w = max(self.kernel_size[1] - x.size(3), 0)\n            x = F.pad(x, (0, pad_w, 0, pad_h), 'constant', 0).contiguous()\n            offset = F.pad(offset, (0, pad_w, 0, pad_h), 'constant',\n                           0).contiguous()\n\n        scales = _pair((scale_h, scale_w))\n        out = pyramid_deform_conv(x, offset, self.weight, scales,\n                                  self.stride, self.padding, self.dilation,\n                                  self.groups, self.deformable_groups)\n        if input_pad:\n            out = out[:, :, :out.size(2) - pad_h, :out.size(3) -\n                      pad_w].contiguous()\n        return out"
  },
  {
    "path": "code/mmdet/ops/dcn/deform_pool.py",
    "content": "import torch\nimport torch.nn as nn\nfrom torch.autograd import Function\nfrom torch.autograd.function import once_differentiable\nfrom torch.nn.modules.utils import _pair\n\nfrom . import deform_pool_ext\n\n\nclass DeformRoIPoolingFunction(Function):\n\n    @staticmethod\n    def forward(ctx,\n                data,\n                rois,\n                offset,\n                spatial_scale,\n                out_size,\n                out_channels,\n                no_trans,\n                group_size=1,\n                part_size=None,\n                sample_per_part=4,\n                trans_std=.0):\n        # TODO: support unsquare RoIs\n        out_h, out_w = _pair(out_size)\n        assert isinstance(out_h, int) and isinstance(out_w, int)\n        assert out_h == out_w\n        out_size = out_h  # out_h and out_w must be equal\n\n        ctx.spatial_scale = spatial_scale\n        ctx.out_size = out_size\n        ctx.out_channels = out_channels\n        ctx.no_trans = no_trans\n        ctx.group_size = group_size\n        ctx.part_size = out_size if part_size is None else part_size\n        ctx.sample_per_part = sample_per_part\n        ctx.trans_std = trans_std\n\n        assert 0.0 <= ctx.trans_std <= 1.0\n        if not data.is_cuda:\n            raise NotImplementedError\n\n        n = rois.shape[0]\n        output = data.new_empty(n, out_channels, out_size, out_size)\n        output_count = data.new_empty(n, out_channels, out_size, out_size)\n        deform_pool_ext.deform_psroi_pooling_forward(\n            data, rois, offset, output, output_count, ctx.no_trans,\n            ctx.spatial_scale, ctx.out_channels, ctx.group_size, ctx.out_size,\n            ctx.part_size, ctx.sample_per_part, ctx.trans_std)\n\n        if data.requires_grad or rois.requires_grad or offset.requires_grad:\n            ctx.save_for_backward(data, rois, offset)\n        ctx.output_count = output_count\n\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        if not grad_output.is_cuda:\n            raise NotImplementedError\n\n        data, rois, offset = ctx.saved_tensors\n        output_count = ctx.output_count\n        grad_input = torch.zeros_like(data)\n        grad_rois = None\n        grad_offset = torch.zeros_like(offset)\n\n        deform_pool_ext.deform_psroi_pooling_backward(\n            grad_output, data, rois, offset, output_count, grad_input,\n            grad_offset, ctx.no_trans, ctx.spatial_scale, ctx.out_channels,\n            ctx.group_size, ctx.out_size, ctx.part_size, ctx.sample_per_part,\n            ctx.trans_std)\n        return (grad_input, grad_rois, grad_offset, None, None, None, None,\n                None, None, None, None)\n\n\ndeform_roi_pooling = DeformRoIPoolingFunction.apply\n\n\nclass DeformRoIPooling(nn.Module):\n\n    def __init__(self,\n                 spatial_scale,\n                 out_size,\n                 out_channels,\n                 no_trans,\n                 group_size=1,\n                 part_size=None,\n                 sample_per_part=4,\n                 trans_std=.0):\n        super(DeformRoIPooling, self).__init__()\n        self.spatial_scale = spatial_scale\n        self.out_size = _pair(out_size)\n        self.out_channels = out_channels\n        self.no_trans = no_trans\n        self.group_size = group_size\n        self.part_size = out_size if part_size is None else part_size\n        self.sample_per_part = sample_per_part\n        self.trans_std = trans_std\n\n    def forward(self, data, rois, offset):\n        if self.no_trans:\n            offset = data.new_empty(0)\n        return deform_roi_pooling(data, rois, offset, self.spatial_scale,\n                                  self.out_size, self.out_channels,\n                                  self.no_trans, self.group_size,\n                                  self.part_size, self.sample_per_part,\n                                  self.trans_std)\n\n\nclass DeformRoIPoolingPack(DeformRoIPooling):\n\n    def __init__(self,\n                 spatial_scale,\n                 out_size,\n                 out_channels,\n                 no_trans,\n                 group_size=1,\n                 part_size=None,\n                 sample_per_part=4,\n                 trans_std=.0,\n                 num_offset_fcs=3,\n                 deform_fc_channels=1024):\n        super(DeformRoIPoolingPack,\n              self).__init__(spatial_scale, out_size, out_channels, no_trans,\n                             group_size, part_size, sample_per_part, trans_std)\n\n        self.num_offset_fcs = num_offset_fcs\n        self.deform_fc_channels = deform_fc_channels\n\n        if not no_trans:\n            seq = []\n            ic = self.out_size[0] * self.out_size[1] * self.out_channels\n            for i in range(self.num_offset_fcs):\n                if i < self.num_offset_fcs - 1:\n                    oc = self.deform_fc_channels\n                else:\n                    oc = self.out_size[0] * self.out_size[1] * 2\n                seq.append(nn.Linear(ic, oc))\n                ic = oc\n                if i < self.num_offset_fcs - 1:\n                    seq.append(nn.ReLU(inplace=True))\n            self.offset_fc = nn.Sequential(*seq)\n            self.offset_fc[-1].weight.data.zero_()\n            self.offset_fc[-1].bias.data.zero_()\n\n    def forward(self, data, rois):\n        assert data.size(1) == self.out_channels\n        n = rois.shape[0]\n        if n == 0:\n            return data.new_empty(n, self.out_channels, self.out_size[0],\n                                  self.out_size[1])\n        if self.no_trans:\n            offset = data.new_empty(0)\n            return deform_roi_pooling(data, rois, offset, self.spatial_scale,\n                                      self.out_size, self.out_channels,\n                                      self.no_trans, self.group_size,\n                                      self.part_size, self.sample_per_part,\n                                      self.trans_std)\n        else:\n            offset = data.new_empty(0)\n            x = deform_roi_pooling(data, rois, offset, self.spatial_scale,\n                                   self.out_size, self.out_channels, True,\n                                   self.group_size, self.part_size,\n                                   self.sample_per_part, self.trans_std)\n            offset = self.offset_fc(x.view(n, -1))\n            offset = offset.view(n, 2, self.out_size[0], self.out_size[1])\n            return deform_roi_pooling(data, rois, offset, self.spatial_scale,\n                                      self.out_size, self.out_channels,\n                                      self.no_trans, self.group_size,\n                                      self.part_size, self.sample_per_part,\n                                      self.trans_std)\n\n\nclass ModulatedDeformRoIPoolingPack(DeformRoIPooling):\n\n    def __init__(self,\n                 spatial_scale,\n                 out_size,\n                 out_channels,\n                 no_trans,\n                 group_size=1,\n                 part_size=None,\n                 sample_per_part=4,\n                 trans_std=.0,\n                 num_offset_fcs=3,\n                 num_mask_fcs=2,\n                 deform_fc_channels=1024):\n        super(ModulatedDeformRoIPoolingPack,\n              self).__init__(spatial_scale, out_size, out_channels, no_trans,\n                             group_size, part_size, sample_per_part, trans_std)\n\n        self.num_offset_fcs = num_offset_fcs\n        self.num_mask_fcs = num_mask_fcs\n        self.deform_fc_channels = deform_fc_channels\n\n        if not no_trans:\n            offset_fc_seq = []\n            ic = self.out_size[0] * self.out_size[1] * self.out_channels\n            for i in range(self.num_offset_fcs):\n                if i < self.num_offset_fcs - 1:\n                    oc = self.deform_fc_channels\n                else:\n                    oc = self.out_size[0] * self.out_size[1] * 2\n                offset_fc_seq.append(nn.Linear(ic, oc))\n                ic = oc\n                if i < self.num_offset_fcs - 1:\n                    offset_fc_seq.append(nn.ReLU(inplace=True))\n            self.offset_fc = nn.Sequential(*offset_fc_seq)\n            self.offset_fc[-1].weight.data.zero_()\n            self.offset_fc[-1].bias.data.zero_()\n\n            mask_fc_seq = []\n            ic = self.out_size[0] * self.out_size[1] * self.out_channels\n            for i in range(self.num_mask_fcs):\n                if i < self.num_mask_fcs - 1:\n                    oc = self.deform_fc_channels\n                else:\n                    oc = self.out_size[0] * self.out_size[1]\n                mask_fc_seq.append(nn.Linear(ic, oc))\n                ic = oc\n                if i < self.num_mask_fcs - 1:\n                    mask_fc_seq.append(nn.ReLU(inplace=True))\n                else:\n                    mask_fc_seq.append(nn.Sigmoid())\n            self.mask_fc = nn.Sequential(*mask_fc_seq)\n            self.mask_fc[-2].weight.data.zero_()\n            self.mask_fc[-2].bias.data.zero_()\n\n    def forward(self, data, rois):\n        assert data.size(1) == self.out_channels\n        n = rois.shape[0]\n        if n == 0:\n            return data.new_empty(n, self.out_channels, self.out_size[0],\n                                  self.out_size[1])\n        if self.no_trans:\n            offset = data.new_empty(0)\n            return deform_roi_pooling(data, rois, offset, self.spatial_scale,\n                                      self.out_size, self.out_channels,\n                                      self.no_trans, self.group_size,\n                                      self.part_size, self.sample_per_part,\n                                      self.trans_std)\n        else:\n            offset = data.new_empty(0)\n            x = deform_roi_pooling(data, rois, offset, self.spatial_scale,\n                                   self.out_size, self.out_channels, True,\n                                   self.group_size, self.part_size,\n                                   self.sample_per_part, self.trans_std)\n            offset = self.offset_fc(x.view(n, -1))\n            offset = offset.view(n, 2, self.out_size[0], self.out_size[1])\n            mask = self.mask_fc(x.view(n, -1))\n            mask = mask.view(n, 1, self.out_size[0], self.out_size[1])\n            return deform_roi_pooling(\n                data, rois, offset, self.spatial_scale, self.out_size,\n                self.out_channels, self.no_trans, self.group_size,\n                self.part_size, self.sample_per_part, self.trans_std) * mask\n"
  },
  {
    "path": "code/mmdet/ops/dcn/src/cuda/deform_conv_cuda.cpp",
    "content": "// modify from\n// https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/blob/mmdetection/mmdet/ops/dcn/src/deform_conv_cuda.c\n\n#include <torch/extension.h>\n#include <ATen/DeviceGuard.h>\n\n#include <cmath>\n#include <vector>\n\nvoid deformable_im2col(const at::Tensor data_im, const at::Tensor data_offset,\n                       const int channels, const int height, const int width,\n                       const int ksize_h, const int ksize_w, const int pad_h,\n                       const int pad_w, const int stride_h, const int stride_w,\n                       const int dilation_h, const int dilation_w,\n                       const int parallel_imgs, const int deformable_group,\n                       at::Tensor data_col);\n\nvoid deformable_col2im(const at::Tensor data_col, const at::Tensor data_offset,\n                       const int channels, const int height, const int width,\n                       const int ksize_h, const int ksize_w, const int pad_h,\n                       const int pad_w, const int stride_h, const int stride_w,\n                       const int dilation_h, const int dilation_w,\n                       const int parallel_imgs, const int deformable_group,\n                       at::Tensor grad_im);\n\nvoid deformable_col2im_coord(\n    const at::Tensor data_col, const at::Tensor data_im,\n    const at::Tensor data_offset, const int channels, const int height,\n    const int width, const int ksize_h, const int ksize_w, const int pad_h,\n    const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w, const int parallel_imgs,\n    const int deformable_group, at::Tensor grad_offset);\n\nvoid modulated_deformable_im2col_cuda(\n    const at::Tensor data_im, const at::Tensor data_offset,\n    const at::Tensor data_mask, const int batch_size, const int channels,\n    const int height_im, const int width_im, const int height_col,\n    const int width_col, const int kernel_h, const int kenerl_w,\n    const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w, const int deformable_group,\n    at::Tensor data_col);\n\nvoid modulated_deformable_col2im_cuda(\n    const at::Tensor data_col, const at::Tensor data_offset,\n    const at::Tensor data_mask, const int batch_size, const int channels,\n    const int height_im, const int width_im, const int height_col,\n    const int width_col, const int kernel_h, const int kenerl_w,\n    const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w, const int deformable_group,\n    at::Tensor grad_im);\n\nvoid modulated_deformable_col2im_coord_cuda(\n    const at::Tensor data_col, const at::Tensor data_im,\n    const at::Tensor data_offset, const at::Tensor data_mask,\n    const int batch_size, const int channels, const int height_im,\n    const int width_im, const int height_col, const int width_col,\n    const int kernel_h, const int kenerl_w, const int pad_h, const int pad_w,\n    const int stride_h, const int stride_w, const int dilation_h,\n    const int dilation_w, const int deformable_group, at::Tensor grad_offset,\n    at::Tensor grad_mask);\n\nvoid pyramid_deformable_im2col(const at::Tensor data_im, const at::Tensor data_offset,\n                               const int channels, const int inputheight, const int inputwidth,\n                               const int offsetheight, const int offsetwidth,\n                               const int ksize_h, const int ksize_w, const int pad_h,\n                               const int pad_w, const int stride_h, const int stride_w,\n                               const int dilation_h, const int dilation_w,\n                               const float scale_h, const float scale_w,\n                               const int parallel_imgs, const int deformable_group,\n                               at::Tensor data_col);\n\nvoid pyramid_deformable_col2im(const at::Tensor data_col, const at::Tensor data_offset,\n                               const int channels, const int inputheight, const int inputwidth,\n                               const int offsetheight, const int offsetwidth,\n                               const int ksize_h, const int ksize_w, const int pad_h,\n                               const int pad_w, const int stride_h, const int stride_w,\n                               const int dilation_h, const int dilation_w,\n                               const float scale_h, const float scale_w,\n                               const int parallel_imgs, const int deformable_group,\n                               at::Tensor grad_im);\n\nvoid pyramid_deformable_col2im_coord(\n        const at::Tensor data_col, const at::Tensor data_im,\n        const at::Tensor data_offset, const int channels, const int inputheight,\n        const int inputwidth, const int offsetheight, const int offsetwidth,\n        const int ksize_h, const int ksize_w, const int pad_h,\n        const int pad_w, const int stride_h, const int stride_w,\n        const int dilation_h, const int dilation_w, \n        const float scale_h, const float scale_w, const int parallel_imgs,\n        const int deformable_group, at::Tensor grad_offset);\n\nvoid shape_check(at::Tensor input, at::Tensor offset, at::Tensor *gradOutput,\n                 at::Tensor weight, int kH, int kW, int dH, int dW, int padH,\n                 int padW, int dilationH, int dilationW, int group,\n                 int deformable_group) {\n  TORCH_CHECK(weight.ndimension() == 4,\n           \"4D weight tensor (nOutputPlane,nInputPlane,kH,kW) expected, \"\n           \"but got: %s\",\n           weight.ndimension());\n\n  TORCH_CHECK(weight.is_contiguous(), \"weight tensor has to be contiguous\");\n\n  TORCH_CHECK(kW > 0 && kH > 0,\n           \"kernel size should be greater than zero, but got kH: %d kW: %d\", kH,\n           kW);\n\n  TORCH_CHECK((weight.size(2) == kH && weight.size(3) == kW),\n           \"kernel size should be consistent with weight, \",\n           \"but got kH: %d kW: %d weight.size(2): %d, weight.size(3): %d\", kH,\n           kW, weight.size(2), weight.size(3));\n\n  TORCH_CHECK(dW > 0 && dH > 0,\n           \"stride should be greater than zero, but got dH: %d dW: %d\", dH, dW);\n\n  TORCH_CHECK(\n      dilationW > 0 && dilationH > 0,\n      \"dilation should be greater than 0, but got dilationH: %d dilationW: %d\",\n      dilationH, dilationW);\n\n  int ndim = input.ndimension();\n  int dimf = 0;\n  int dimh = 1;\n  int dimw = 2;\n\n  if (ndim == 4) {\n    dimf++;\n    dimh++;\n    dimw++;\n  }\n\n  TORCH_CHECK(ndim == 3 || ndim == 4, \"3D or 4D input tensor expected but got: %s\",\n           ndim);\n\n  long nInputPlane = weight.size(1) * group;\n  long inputHeight = input.size(dimh);\n  long inputWidth = input.size(dimw);\n  long nOutputPlane = weight.size(0);\n  long outputHeight =\n      (inputHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n  long outputWidth =\n      (inputWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n\n  TORCH_CHECK(nInputPlane % deformable_group == 0,\n           \"input channels must divide deformable group size\");\n\n  if (outputWidth < 1 || outputHeight < 1)\n    AT_ERROR(\n        \"Given input size: (%ld x %ld x %ld). \"\n        \"Calculated output size: (%ld x %ld x %ld). Output size is too small\",\n        nInputPlane, inputHeight, inputWidth, nOutputPlane, outputHeight,\n        outputWidth);\n\n  TORCH_CHECK(input.size(1) == nInputPlane,\n           \"invalid number of input planes, expected: %d, but got: %d\",\n           nInputPlane, input.size(1));\n\n  TORCH_CHECK((inputHeight >= kH && inputWidth >= kW),\n           \"input image is smaller than kernel\");\n\n  TORCH_CHECK((offset.size(2) == outputHeight && offset.size(3) == outputWidth),\n           \"invalid spatial size of offset, expected height: %d width: %d, but \"\n           \"got height: %d width: %d\",\n           outputHeight, outputWidth, offset.size(2), offset.size(3));\n\n  TORCH_CHECK((offset.size(1) == deformable_group * 2 * kH * kW),\n           \"invalid number of channels of offset\");\n\n  if (gradOutput != NULL) {\n    TORCH_CHECK(gradOutput->size(dimf) == nOutputPlane,\n             \"invalid number of gradOutput planes, expected: %d, but got: %d\",\n             nOutputPlane, gradOutput->size(dimf));\n\n    TORCH_CHECK((gradOutput->size(dimh) == outputHeight &&\n              gradOutput->size(dimw) == outputWidth),\n             \"invalid size of gradOutput, expected height: %d width: %d , but \"\n             \"got height: %d width: %d\",\n             outputHeight, outputWidth, gradOutput->size(dimh),\n             gradOutput->size(dimw));\n  }\n}\n\nvoid pyramid_shape_check(at::Tensor input, at::Tensor offset, at::Tensor *gradOutput,\n                         at::Tensor weight, int kH, int kW, int dH, int dW, int padH,\n                         int padW, int dilationH, int dilationW, int group,\n                         int deformable_group) {\n  TORCH_CHECK(weight.ndimension() == 4,\n           \"4D weight tensor (nOutputPlane,nInputPlane,kH,kW) expected, \"\n           \"but got: %s\",\n           weight.ndimension());\n\n  TORCH_CHECK(weight.is_contiguous(), \"weight tensor has to be contiguous\");\n\n  TORCH_CHECK(kW > 0 && kH > 0,\n           \"kernel size should be greater than zero, but got kH: %d kW: %d\", kH,\n           kW);\n\n  TORCH_CHECK((weight.size(2) == kH && weight.size(3) == kW),\n           \"kernel size should be consistent with weight, \",\n           \"but got kH: %d kW: %d weight.size(2): %d, weight.size(3): %d\", kH,\n           kW, weight.size(2), weight.size(3));\n\n  TORCH_CHECK(dW > 0 && dH > 0,\n           \"stride should be greater than zero, but got dH: %d dW: %d\", dH, dW);\n\n  TORCH_CHECK(\n      dilationW > 0 && dilationH > 0,\n      \"dilation should be greater than 0, but got dilationH: %d dilationW: %d\",\n      dilationH, dilationW);\n\n  int ndim = input.ndimension();\n  int dimf = 0;\n  int dimh = 1;\n  int dimw = 2;\n\n  if (ndim == 4) {\n    dimf++;\n    dimh++;\n    dimw++;\n  }\n\n  TORCH_CHECK(ndim == 3 || ndim == 4, \"3D or 4D input tensor expected but got: %s\",\n           ndim);\n\n  long nInputPlane = weight.size(1) * group;\n  long inputHeight = input.size(dimh);\n  long inputWidth = input.size(dimw);\n  long nOutputPlane = weight.size(0);\n  long offsetHeight = offset.size(dimh);\n  long offsetWidth = offset.size(dimw);\n  long outputHeight =\n      (offsetHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n  long outputWidth =\n      (offsetWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n\n  TORCH_CHECK(nInputPlane % deformable_group == 0,\n           \"input channels must divide deformable group size\");\n\n  if (outputWidth < 1 || outputHeight < 1)\n    AT_ERROR(\n        \"Given input size: (%ld x %ld x %ld). \"\n        \"Calculated output size: (%ld x %ld x %ld). Output size is too small\",\n        nInputPlane, inputHeight, inputWidth, nOutputPlane, outputHeight,\n        outputWidth);\n\n  TORCH_CHECK(input.size(1) == nInputPlane,\n           \"invalid number of input planes, expected: %d, but got: %d\",\n           nInputPlane, input.size(1));\n\n  TORCH_CHECK((inputHeight >= kH && inputWidth >= kW),\n           \"input image is smaller than kernel\");\n\n  TORCH_CHECK((offset.size(2) == outputHeight && offset.size(3) == outputWidth),\n           \"invalid spatial size of offset, expected height: %d width: %d, but \"\n           \"got height: %d width: %d\",\n           outputHeight, outputWidth, offset.size(2), offset.size(3));\n\n  TORCH_CHECK((offset.size(1) == deformable_group * 2 * kH * kW),\n           \"invalid number of channels of offset\");\n\n  if (gradOutput != NULL) {\n    TORCH_CHECK(gradOutput->size(dimf) == nOutputPlane,\n             \"invalid number of gradOutput planes, expected: %d, but got: %d\",\n             nOutputPlane, gradOutput->size(dimf));\n\n    TORCH_CHECK((gradOutput->size(dimh) == outputHeight &&\n              gradOutput->size(dimw) == outputWidth),\n             \"invalid size of gradOutput, expected height: %d width: %d , but \"\n             \"got height: %d width: %d\",\n             outputHeight, outputWidth, gradOutput->size(dimh),\n             gradOutput->size(dimw));\n  }\n}\n\nint deform_conv_forward_cuda(at::Tensor input, at::Tensor weight,\n                             at::Tensor offset, at::Tensor output,\n                             at::Tensor columns, at::Tensor ones, int kW,\n                             int kH, int dW, int dH, int padW, int padH,\n                             int dilationW, int dilationH, int group,\n                             int deformable_group, int im2col_step) {\n  // todo: resize columns to include im2col: done\n  // todo: add im2col_step as input\n  // todo: add new output buffer and transpose it to output (or directly\n  // transpose output) todo: possibly change data indexing because of\n  // parallel_imgs\n\n  shape_check(input, offset, NULL, weight, kH, kW, dH, dW, padH, padW,\n              dilationH, dilationW, group, deformable_group);\n  at::DeviceGuard guard(input.device());\n\n  input = input.contiguous();\n  offset = offset.contiguous();\n  weight = weight.contiguous();\n\n  int batch = 1;\n  if (input.ndimension() == 3) {\n    // Force batch\n    batch = 0;\n    input.unsqueeze_(0);\n    offset.unsqueeze_(0);\n  }\n\n  // todo: assert batchsize dividable by im2col_step\n\n  long batchSize = input.size(0);\n  long nInputPlane = input.size(1);\n  long inputHeight = input.size(2);\n  long inputWidth = input.size(3);\n\n  long nOutputPlane = weight.size(0);\n\n  long outputWidth =\n      (inputWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n  long outputHeight =\n      (inputHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n\n  TORCH_CHECK((offset.size(0) == batchSize), \"invalid batch size of offset\");\n\n  output = output.view({batchSize / im2col_step, im2col_step, nOutputPlane,\n                        outputHeight, outputWidth});\n  columns = at::zeros(\n      {nInputPlane * kW * kH, im2col_step * outputHeight * outputWidth},\n      input.options());\n\n  if (ones.ndimension() != 2 ||\n      ones.size(0) * ones.size(1) < outputHeight * outputWidth) {\n    ones = at::ones({outputHeight, outputWidth}, input.options());\n  }\n\n  input = input.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                      inputHeight, inputWidth});\n  offset =\n      offset.view({batchSize / im2col_step, im2col_step,\n                   deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n\n  at::Tensor output_buffer =\n      at::zeros({batchSize / im2col_step, nOutputPlane,\n                 im2col_step * outputHeight, outputWidth},\n                output.options());\n\n  output_buffer = output_buffer.view(\n      {output_buffer.size(0), group, output_buffer.size(1) / group,\n       output_buffer.size(2), output_buffer.size(3)});\n\n  for (int elt = 0; elt < batchSize / im2col_step; elt++) {\n    deformable_im2col(input[elt], offset[elt], nInputPlane, inputHeight,\n                      inputWidth, kH, kW, padH, padW, dH, dW, dilationH,\n                      dilationW, im2col_step, deformable_group, columns);\n\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    weight = weight.view({group, weight.size(0) / group, weight.size(1),\n                          weight.size(2), weight.size(3)});\n\n    for (int g = 0; g < group; g++) {\n      output_buffer[elt][g] = output_buffer[elt][g]\n                                  .flatten(1)\n                                  .addmm_(weight[g].flatten(1), columns[g])\n                                  .view_as(output_buffer[elt][g]);\n    }\n  }\n\n  output_buffer = output_buffer.view(\n      {output_buffer.size(0), output_buffer.size(1) * output_buffer.size(2),\n       output_buffer.size(3), output_buffer.size(4)});\n\n  output_buffer = output_buffer.view({batchSize / im2col_step, nOutputPlane,\n                                      im2col_step, outputHeight, outputWidth});\n  output_buffer.transpose_(1, 2);\n  output.copy_(output_buffer);\n  output = output.view({batchSize, nOutputPlane, outputHeight, outputWidth});\n\n  input = input.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  offset = offset.view(\n      {batchSize, deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n\n  if (batch == 0) {\n    output = output.view({nOutputPlane, outputHeight, outputWidth});\n    input = input.view({nInputPlane, inputHeight, inputWidth});\n    offset = offset.view({offset.size(1), offset.size(2), offset.size(3)});\n  }\n\n  return 1;\n}\n\nint deform_conv_backward_input_cuda(at::Tensor input, at::Tensor offset,\n                                    at::Tensor gradOutput, at::Tensor gradInput,\n                                    at::Tensor gradOffset, at::Tensor weight,\n                                    at::Tensor columns, int kW, int kH, int dW,\n                                    int dH, int padW, int padH, int dilationW,\n                                    int dilationH, int group,\n                                    int deformable_group, int im2col_step) {\n  shape_check(input, offset, &gradOutput, weight, kH, kW, dH, dW, padH, padW,\n              dilationH, dilationW, group, deformable_group);\n  at::DeviceGuard guard(input.device());\n\n  input = input.contiguous();\n  offset = offset.contiguous();\n  gradOutput = gradOutput.contiguous();\n  weight = weight.contiguous();\n\n  int batch = 1;\n\n  if (input.ndimension() == 3) {\n    // Force batch\n    batch = 0;\n    input = input.view({1, input.size(0), input.size(1), input.size(2)});\n    offset = offset.view({1, offset.size(0), offset.size(1), offset.size(2)});\n    gradOutput = gradOutput.view(\n        {1, gradOutput.size(0), gradOutput.size(1), gradOutput.size(2)});\n  }\n\n  long batchSize = input.size(0);\n  long nInputPlane = input.size(1);\n  long inputHeight = input.size(2);\n  long inputWidth = input.size(3);\n\n  long nOutputPlane = weight.size(0);\n\n  long outputWidth =\n      (inputWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n  long outputHeight =\n      (inputHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n\n  TORCH_CHECK((offset.size(0) == batchSize), 3, \"invalid batch size of offset\");\n  gradInput = gradInput.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  columns = at::zeros(\n      {nInputPlane * kW * kH, im2col_step * outputHeight * outputWidth},\n      input.options());\n\n  // change order of grad output\n  gradOutput = gradOutput.view({batchSize / im2col_step, im2col_step,\n                                nOutputPlane, outputHeight, outputWidth});\n  gradOutput.transpose_(1, 2);\n\n  gradInput = gradInput.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                              inputHeight, inputWidth});\n  input = input.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                      inputHeight, inputWidth});\n  gradOffset = gradOffset.view({batchSize / im2col_step, im2col_step,\n                                deformable_group * 2 * kH * kW, outputHeight,\n                                outputWidth});\n  offset =\n      offset.view({batchSize / im2col_step, im2col_step,\n                   deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n\n  for (int elt = 0; elt < batchSize / im2col_step; elt++) {\n    // divide into groups\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    weight = weight.view({group, weight.size(0) / group, weight.size(1),\n                          weight.size(2), weight.size(3)});\n    gradOutput = gradOutput.view(\n        {gradOutput.size(0), group, gradOutput.size(1) / group,\n         gradOutput.size(2), gradOutput.size(3), gradOutput.size(4)});\n\n    for (int g = 0; g < group; g++) {\n      columns[g] = columns[g].addmm_(weight[g].flatten(1).transpose(0, 1),\n                                     gradOutput[elt][g].flatten(1), 0.0f, 1.0f);\n    }\n\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n    gradOutput = gradOutput.view(\n        {gradOutput.size(0), gradOutput.size(1) * gradOutput.size(2),\n         gradOutput.size(3), gradOutput.size(4), gradOutput.size(5)});\n\n    deformable_col2im_coord(columns, input[elt], offset[elt], nInputPlane,\n                            inputHeight, inputWidth, kH, kW, padH, padW, dH, dW,\n                            dilationH, dilationW, im2col_step, deformable_group,\n                            gradOffset[elt]);\n\n    deformable_col2im(columns, offset[elt], nInputPlane, inputHeight,\n                      inputWidth, kH, kW, padH, padW, dH, dW, dilationH,\n                      dilationW, im2col_step, deformable_group, gradInput[elt]);\n  }\n\n  gradOutput.transpose_(1, 2);\n  gradOutput =\n      gradOutput.view({batchSize, nOutputPlane, outputHeight, outputWidth});\n\n  gradInput = gradInput.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  input = input.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  gradOffset = gradOffset.view(\n      {batchSize, deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n  offset = offset.view(\n      {batchSize, deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n\n  if (batch == 0) {\n    gradOutput = gradOutput.view({nOutputPlane, outputHeight, outputWidth});\n    input = input.view({nInputPlane, inputHeight, inputWidth});\n    gradInput = gradInput.view({nInputPlane, inputHeight, inputWidth});\n    offset = offset.view({offset.size(1), offset.size(2), offset.size(3)});\n    gradOffset =\n        gradOffset.view({offset.size(1), offset.size(2), offset.size(3)});\n  }\n\n  return 1;\n}\n\nint deform_conv_backward_parameters_cuda(\n    at::Tensor input, at::Tensor offset, at::Tensor gradOutput,\n    at::Tensor gradWeight,  // at::Tensor gradBias,\n    at::Tensor columns, at::Tensor ones, int kW, int kH, int dW, int dH,\n    int padW, int padH, int dilationW, int dilationH, int group,\n    int deformable_group, float scale, int im2col_step) {\n  // todo: transpose and reshape outGrad\n  // todo: reshape columns\n  // todo: add im2col_step as input\n\n  shape_check(input, offset, &gradOutput, gradWeight, kH, kW, dH, dW, padH,\n              padW, dilationH, dilationW, group, deformable_group);\n  at::DeviceGuard guard(input.device());\n\n  input = input.contiguous();\n  offset = offset.contiguous();\n  gradOutput = gradOutput.contiguous();\n\n  int batch = 1;\n\n  if (input.ndimension() == 3) {\n    // Force batch\n    batch = 0;\n    input = input.view(\n        at::IntList({1, input.size(0), input.size(1), input.size(2)}));\n    gradOutput = gradOutput.view(\n        {1, gradOutput.size(0), gradOutput.size(1), gradOutput.size(2)});\n  }\n\n  long batchSize = input.size(0);\n  long nInputPlane = input.size(1);\n  long inputHeight = input.size(2);\n  long inputWidth = input.size(3);\n\n  long nOutputPlane = gradWeight.size(0);\n\n  long outputWidth =\n      (inputWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n  long outputHeight =\n      (inputHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n\n  TORCH_CHECK((offset.size(0) == batchSize), \"invalid batch size of offset\");\n\n  columns = at::zeros(\n      {nInputPlane * kW * kH, im2col_step * outputHeight * outputWidth},\n      input.options());\n\n  gradOutput = gradOutput.view({batchSize / im2col_step, im2col_step,\n                                nOutputPlane, outputHeight, outputWidth});\n  gradOutput.transpose_(1, 2);\n\n  at::Tensor gradOutputBuffer = at::zeros_like(gradOutput);\n  gradOutputBuffer =\n      gradOutputBuffer.view({batchSize / im2col_step, nOutputPlane, im2col_step,\n                             outputHeight, outputWidth});\n  gradOutputBuffer = gradOutputBuffer.contiguous();\n  gradOutputBuffer.copy_(gradOutput);\n  gradOutputBuffer =\n      gradOutputBuffer.view({batchSize / im2col_step, nOutputPlane,\n                             im2col_step * outputHeight, outputWidth});\n\n  gradOutput.transpose_(1, 2);\n  gradOutput =\n      gradOutput.view({batchSize, nOutputPlane, outputHeight, outputWidth});\n\n  input = input.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                      inputHeight, inputWidth});\n  offset =\n      offset.view({batchSize / im2col_step, im2col_step,\n                   deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n\n  for (int elt = 0; elt < batchSize / im2col_step; elt++) {\n    deformable_im2col(input[elt], offset[elt], nInputPlane, inputHeight,\n                      inputWidth, kH, kW, padH, padW, dH, dW, dilationH,\n                      dilationW, im2col_step, deformable_group, columns);\n\n    // divide into group\n    gradOutputBuffer = gradOutputBuffer.view(\n        {gradOutputBuffer.size(0), group, gradOutputBuffer.size(1) / group,\n         gradOutputBuffer.size(2), gradOutputBuffer.size(3)});\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    gradWeight =\n        gradWeight.view({group, gradWeight.size(0) / group, gradWeight.size(1),\n                         gradWeight.size(2), gradWeight.size(3)});\n\n    for (int g = 0; g < group; g++) {\n      gradWeight[g] = gradWeight[g]\n                          .flatten(1)\n                          .addmm_(gradOutputBuffer[elt][g].flatten(1),\n                                  columns[g].transpose(1, 0), 1.0, scale)\n                          .view_as(gradWeight[g]);\n    }\n    gradOutputBuffer = gradOutputBuffer.view(\n        {gradOutputBuffer.size(0),\n         gradOutputBuffer.size(1) * gradOutputBuffer.size(2),\n         gradOutputBuffer.size(3), gradOutputBuffer.size(4)});\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n    gradWeight = gradWeight.view({gradWeight.size(0) * gradWeight.size(1),\n                                  gradWeight.size(2), gradWeight.size(3),\n                                  gradWeight.size(4)});\n  }\n\n  input = input.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  offset = offset.view(\n      {batchSize, deformable_group * 2 * kH * kW, outputHeight, outputWidth});\n\n  if (batch == 0) {\n    gradOutput = gradOutput.view({nOutputPlane, outputHeight, outputWidth});\n    input = input.view({nInputPlane, inputHeight, inputWidth});\n  }\n\n  return 1;\n}\n\nvoid modulated_deform_conv_cuda_forward(\n    at::Tensor input, at::Tensor weight, at::Tensor bias, at::Tensor ones,\n    at::Tensor offset, at::Tensor mask, at::Tensor output, at::Tensor columns,\n    int kernel_h, int kernel_w, const int stride_h, const int stride_w,\n    const int pad_h, const int pad_w, const int dilation_h,\n    const int dilation_w, const int group, const int deformable_group,\n    const bool with_bias) {\n  TORCH_CHECK(input.is_contiguous(), \"input tensor has to be contiguous\");\n  TORCH_CHECK(weight.is_contiguous(), \"weight tensor has to be contiguous\");\n  at::DeviceGuard guard(input.device());\n\n  const int batch = input.size(0);\n  const int channels = input.size(1);\n  const int height = input.size(2);\n  const int width = input.size(3);\n\n  const int channels_out = weight.size(0);\n  const int channels_kernel = weight.size(1);\n  const int kernel_h_ = weight.size(2);\n  const int kernel_w_ = weight.size(3);\n\n  if (kernel_h_ != kernel_h || kernel_w_ != kernel_w)\n    AT_ERROR(\"Input shape and kernel shape wont match: (%d x %d vs %d x %d).\",\n             kernel_h_, kernel_w, kernel_h_, kernel_w_);\n  if (channels != channels_kernel * group)\n    AT_ERROR(\"Input shape and kernel channels wont match: (%d vs %d).\",\n             channels, channels_kernel * group);\n\n  const int height_out =\n      (height + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;\n  const int width_out =\n      (width + 2 * pad_w - (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;\n\n  if (ones.ndimension() != 2 ||\n      ones.size(0) * ones.size(1) < height_out * width_out) {\n    // Resize plane and fill with ones...\n    ones = at::ones({height_out, width_out}, input.options());\n  }\n\n  // resize output\n  output = output.view({batch, channels_out, height_out, width_out}).zero_();\n  // resize temporary columns\n  columns =\n      at::zeros({channels * kernel_h * kernel_w, 1 * height_out * width_out},\n                input.options());\n\n  output = output.view({output.size(0), group, output.size(1) / group,\n                        output.size(2), output.size(3)});\n\n  for (int b = 0; b < batch; b++) {\n    modulated_deformable_im2col_cuda(\n        input[b], offset[b], mask[b], 1, channels, height, width, height_out,\n        width_out, kernel_h, kernel_w, pad_h, pad_w, stride_h, stride_w,\n        dilation_h, dilation_w, deformable_group, columns);\n\n    // divide into group\n    weight = weight.view({group, weight.size(0) / group, weight.size(1),\n                          weight.size(2), weight.size(3)});\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n\n    for (int g = 0; g < group; g++) {\n      output[b][g] = output[b][g]\n                         .flatten(1)\n                         .addmm_(weight[g].flatten(1), columns[g])\n                         .view_as(output[b][g]);\n    }\n\n    weight = weight.view({weight.size(0) * weight.size(1), weight.size(2),\n                          weight.size(3), weight.size(4)});\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n  }\n\n  output = output.view({output.size(0), output.size(1) * output.size(2),\n                        output.size(3), output.size(4)});\n\n  if (with_bias) {\n    output += bias.view({1, bias.size(0), 1, 1});\n  }\n}\n\nvoid modulated_deform_conv_cuda_backward(\n    at::Tensor input, at::Tensor weight, at::Tensor bias, at::Tensor ones,\n    at::Tensor offset, at::Tensor mask, at::Tensor columns,\n    at::Tensor grad_input, at::Tensor grad_weight, at::Tensor grad_bias,\n    at::Tensor grad_offset, at::Tensor grad_mask, at::Tensor grad_output,\n    int kernel_h, int kernel_w, int stride_h, int stride_w, int pad_h,\n    int pad_w, int dilation_h, int dilation_w, int group, int deformable_group,\n    const bool with_bias) {\n  TORCH_CHECK(input.is_contiguous(), \"input tensor has to be contiguous\");\n  TORCH_CHECK(weight.is_contiguous(), \"weight tensor has to be contiguous\");\n  at::DeviceGuard guard(input.device());\n\n  const int batch = input.size(0);\n  const int channels = input.size(1);\n  const int height = input.size(2);\n  const int width = input.size(3);\n\n  const int channels_kernel = weight.size(1);\n  const int kernel_h_ = weight.size(2);\n  const int kernel_w_ = weight.size(3);\n  if (kernel_h_ != kernel_h || kernel_w_ != kernel_w)\n    AT_ERROR(\"Input shape and kernel shape wont match: (%d x %d vs %d x %d).\",\n             kernel_h_, kernel_w, kernel_h_, kernel_w_);\n  if (channels != channels_kernel * group)\n    AT_ERROR(\"Input shape and kernel channels wont match: (%d vs %d).\",\n             channels, channels_kernel * group);\n\n  const int height_out =\n      (height + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;\n  const int width_out =\n      (width + 2 * pad_w - (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;\n\n  if (ones.ndimension() != 2 ||\n      ones.size(0) * ones.size(1) < height_out * width_out) {\n    // Resize plane and fill with ones...\n    ones = at::ones({height_out, width_out}, input.options());\n  }\n\n  grad_input = grad_input.view({batch, channels, height, width});\n  columns = at::zeros({channels * kernel_h * kernel_w, height_out * width_out},\n                      input.options());\n\n  grad_output =\n      grad_output.view({grad_output.size(0), group, grad_output.size(1) / group,\n                        grad_output.size(2), grad_output.size(3)});\n\n  for (int b = 0; b < batch; b++) {\n    // divide int group\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    weight = weight.view({group, weight.size(0) / group, weight.size(1),\n                          weight.size(2), weight.size(3)});\n\n    for (int g = 0; g < group; g++) {\n      columns[g].addmm_(weight[g].flatten(1).transpose(0, 1),\n                        grad_output[b][g].flatten(1), 0.0f, 1.0f);\n    }\n\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n    weight = weight.view({weight.size(0) * weight.size(1), weight.size(2),\n                          weight.size(3), weight.size(4)});\n\n    // gradient w.r.t. input coordinate data\n    modulated_deformable_col2im_coord_cuda(\n        columns, input[b], offset[b], mask[b], 1, channels, height, width,\n        height_out, width_out, kernel_h, kernel_w, pad_h, pad_w, stride_h,\n        stride_w, dilation_h, dilation_w, deformable_group, grad_offset[b],\n        grad_mask[b]);\n    // gradient w.r.t. input data\n    modulated_deformable_col2im_cuda(\n        columns, offset[b], mask[b], 1, channels, height, width, height_out,\n        width_out, kernel_h, kernel_w, pad_h, pad_w, stride_h, stride_w,\n        dilation_h, dilation_w, deformable_group, grad_input[b]);\n\n    // gradient w.r.t. weight, dWeight should accumulate across the batch and\n    // group\n    modulated_deformable_im2col_cuda(\n        input[b], offset[b], mask[b], 1, channels, height, width, height_out,\n        width_out, kernel_h, kernel_w, pad_h, pad_w, stride_h, stride_w,\n        dilation_h, dilation_w, deformable_group, columns);\n\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    grad_weight = grad_weight.view({group, grad_weight.size(0) / group,\n                                    grad_weight.size(1), grad_weight.size(2),\n                                    grad_weight.size(3)});\n    if (with_bias)\n      grad_bias = grad_bias.view({group, grad_bias.size(0) / group});\n\n    for (int g = 0; g < group; g++) {\n      grad_weight[g] =\n          grad_weight[g]\n              .flatten(1)\n              .addmm_(grad_output[b][g].flatten(1), columns[g].transpose(0, 1))\n              .view_as(grad_weight[g]);\n      if (with_bias) {\n        grad_bias[g] =\n            grad_bias[g]\n                .view({-1, 1})\n                .addmm_(grad_output[b][g].flatten(1), ones.view({-1, 1}))\n                .view(-1);\n      }\n    }\n\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n    grad_weight = grad_weight.view({grad_weight.size(0) * grad_weight.size(1),\n                                    grad_weight.size(2), grad_weight.size(3),\n                                    grad_weight.size(4)});\n    if (with_bias)\n      grad_bias = grad_bias.view({grad_bias.size(0) * grad_bias.size(1)});\n  }\n  grad_output = grad_output.view({grad_output.size(0) * grad_output.size(1),\n                                  grad_output.size(2), grad_output.size(3),\n                                  grad_output.size(4)});\n}\n\n\nint pyramid_deform_conv_forward_cuda(at::Tensor input, at::Tensor weight,\n                                     at::Tensor offset, at::Tensor output,\n                                     at::Tensor columns, at::Tensor ones, int kW,\n                                     int kH, int dW, int dH, int padW, int padH,\n                                     int dilationW, int dilationH, float scaleW,\n                                     float scaleH, int group, int deformable_group, \n                                     int im2col_step) {\n\n  pyramid_shape_check(input, offset, NULL, weight, kH, kW, dH, dW, padH, padW,\n                      dilationH, dilationW, group, deformable_group);\n  at::DeviceGuard guard(input.device());\n\n  input = input.contiguous();\n  offset = offset.contiguous();\n  weight = weight.contiguous();\n\n  int batch = 1;\n  if (input.ndimension() == 3) {\n    // Force batch\n    batch = 0;\n    input.unsqueeze_(0);\n    offset.unsqueeze_(0);\n  }\n\n  // todo: assert batchsize dividable by im2col_step\n\n  long batchSize = input.size(0);\n  long nInputPlane = input.size(1);\n  long inputHeight = input.size(2);\n  long inputWidth = input.size(3);\n  long offsetHeight = offset.size(2);\n  long offsetWidth = offset.size(3);\n\n\n  long nOutputPlane = weight.size(0);\n\n  long outputWidth =\n      (offsetWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n  long outputHeight =\n      (offsetHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n\n  TORCH_CHECK((offset.size(0) == batchSize), \"invalid batch size of offset\");\n\n  output = output.view({batchSize / im2col_step, im2col_step, nOutputPlane,\n                        outputHeight, outputWidth});\n  columns = at::zeros(\n      {nInputPlane * kW * kH, im2col_step * outputHeight * outputWidth},\n      input.options());\n\n  if (ones.ndimension() != 2 ||\n      ones.size(0) * ones.size(1) < outputHeight * outputWidth) {\n    ones = at::ones({outputHeight, outputWidth}, input.options());\n  }\n\n  input = input.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                      inputHeight, inputWidth});\n  offset =\n      offset.view({batchSize / im2col_step, im2col_step,\n                   deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n\n  at::Tensor output_buffer =\n      at::zeros({batchSize / im2col_step, nOutputPlane,\n                 im2col_step * outputHeight, outputWidth},\n                output.options());\n\n  output_buffer = output_buffer.view(\n      {output_buffer.size(0), group, output_buffer.size(1) / group,\n       output_buffer.size(2), output_buffer.size(3)});\n\n  for (int elt = 0; elt < batchSize / im2col_step; elt++) {\n    pyramid_deformable_im2col(input[elt], offset[elt], nInputPlane, inputHeight, inputWidth, \n                              offsetHeight, offsetWidth, kH, kW, padH, padW, dH, dW, \n                              dilationH, dilationW, scaleH, scaleW, im2col_step, \n                              deformable_group, columns);\n\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    weight = weight.view({group, weight.size(0) / group, weight.size(1),\n                          weight.size(2), weight.size(3)});\n\n    for (int g = 0; g < group; g++) {\n      output_buffer[elt][g] = output_buffer[elt][g]\n                                  .flatten(1)\n                                  .addmm_(weight[g].flatten(1), columns[g])\n                                  .view_as(output_buffer[elt][g]);\n    }\n  }\n\n  output_buffer = output_buffer.view(\n      {output_buffer.size(0), output_buffer.size(1) * output_buffer.size(2),\n       output_buffer.size(3), output_buffer.size(4)});\n\n  output_buffer = output_buffer.view({batchSize / im2col_step, nOutputPlane,\n                                      im2col_step, outputHeight, outputWidth});\n  output_buffer.transpose_(1, 2);\n  output.copy_(output_buffer);\n  output = output.view({batchSize, nOutputPlane, outputHeight, outputWidth});\n\n  input = input.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  offset = offset.view(\n      {batchSize, deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n\n  if (batch == 0) {\n    output = output.view({nOutputPlane, outputHeight, outputWidth});\n    input = input.view({nInputPlane, inputHeight, inputWidth});\n    offset = offset.view({offset.size(1), offset.size(2), offset.size(3)});\n  }\n\n  return 1;\n}\n\nint pyramid_deform_conv_backward_input_cuda(at::Tensor input, at::Tensor offset,\n                                            at::Tensor gradOutput, at::Tensor gradInput,\n                                            at::Tensor gradOffset, at::Tensor weight,\n                                            at::Tensor columns, int kW, int kH, int dW,\n                                            int dH, int padW, int padH, int dilationW,\n                                            int dilationH, float scaleW, float scaleH,\n                                            int group, int deformable_group, int im2col_step) {\n  pyramid_shape_check(input, offset, &gradOutput, weight, kH, kW, dH, dW, padH, padW,\n                      dilationH, dilationW, group, deformable_group);\n  at::DeviceGuard guard(input.device());\n\n  input = input.contiguous();\n  offset = offset.contiguous();\n  gradOutput = gradOutput.contiguous();\n  weight = weight.contiguous();\n\n  int batch = 1;\n\n  if (input.ndimension() == 3) {\n    // Force batch\n    batch = 0;\n    input = input.view({1, input.size(0), input.size(1), input.size(2)});\n    offset = offset.view({1, offset.size(0), offset.size(1), offset.size(2)});\n    gradOutput = gradOutput.view(\n        {1, gradOutput.size(0), gradOutput.size(1), gradOutput.size(2)});\n  }\n\n  long batchSize = input.size(0);\n  long nInputPlane = input.size(1);\n  long inputHeight = input.size(2);\n  long inputWidth = input.size(3);\n  long offsetHeight = offset.size(2);\n  long offsetWidth  = offset.size(3);\n\n  long nOutputPlane = weight.size(0);\n\n  long outputWidth =\n      (offsetWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n  long outputHeight =\n      (offsetHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n\n  TORCH_CHECK((offset.size(0) == batchSize), 3, \"invalid batch size of offset\");\n  gradInput = gradInput.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  columns = at::zeros(\n      {nInputPlane * kW * kH, im2col_step * outputHeight * outputWidth},\n      input.options());\n\n  // change order of grad output\n  gradOutput = gradOutput.view({batchSize / im2col_step, im2col_step,\n                                nOutputPlane, outputHeight, outputWidth});\n  gradOutput.transpose_(1, 2);\n\n  gradInput = gradInput.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                              inputHeight, inputWidth});\n  input = input.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                      inputHeight, inputWidth});\n  gradOffset = gradOffset.view({batchSize / im2col_step, im2col_step,\n                                deformable_group * 2 * kH * kW, offsetHeight,\n                                offsetWidth});\n  offset =\n      offset.view({batchSize / im2col_step, im2col_step,\n                   deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n\n  for (int elt = 0; elt < batchSize / im2col_step; elt++) {\n    // divide into groups\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    weight = weight.view({group, weight.size(0) / group, weight.size(1),\n                          weight.size(2), weight.size(3)});\n    gradOutput = gradOutput.view(\n        {gradOutput.size(0), group, gradOutput.size(1) / group,\n         gradOutput.size(2), gradOutput.size(3), gradOutput.size(4)});\n\n    for (int g = 0; g < group; g++) {\n      columns[g] = columns[g].addmm_(weight[g].flatten(1).transpose(0, 1),\n                                     gradOutput[elt][g].flatten(1), 0.0f, 1.0f);\n    }\n\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n    gradOutput = gradOutput.view(\n        {gradOutput.size(0), gradOutput.size(1) * gradOutput.size(2),\n         gradOutput.size(3), gradOutput.size(4), gradOutput.size(5)});\n\n    pyramid_deformable_col2im_coord(columns, input[elt], offset[elt], nInputPlane,\n                                    inputHeight, inputWidth, offsetHeight, offsetWidth,\n                                    kH, kW, padH, padW, dH, dW, dilationH, dilationW, \n                                    scaleH, scaleW, im2col_step, deformable_group, gradOffset[elt]);\n\n    pyramid_deformable_col2im(columns, offset[elt], nInputPlane, inputHeight,\n                              inputWidth, offsetHeight, offsetWidth, kH, kW, \n                              padH, padW, dH, dW, dilationH, dilationW, \n                              scaleH, scaleW, im2col_step, deformable_group, gradInput[elt]);\n  }\n\n  gradOutput.transpose_(1, 2);\n  gradOutput =\n      gradOutput.view({batchSize, nOutputPlane, outputHeight, outputWidth});\n\n  gradInput = gradInput.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  input = input.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  gradOffset = gradOffset.view(\n      {batchSize, deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n  offset = offset.view(\n      {batchSize, deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n\n  if (batch == 0) {\n    gradOutput = gradOutput.view({nOutputPlane, outputHeight, outputWidth});\n    input = input.view({nInputPlane, inputHeight, inputWidth});\n    gradInput = gradInput.view({nInputPlane, inputHeight, inputWidth});\n    offset = offset.view({offset.size(1), offset.size(2), offset.size(3)});\n    gradOffset =\n        gradOffset.view({offset.size(1), offset.size(2), offset.size(3)});\n  }\n\n  return 1;\n}\n\nint pyramid_deform_conv_backward_parameters_cuda(\n    at::Tensor input, at::Tensor offset, at::Tensor gradOutput,\n    at::Tensor gradWeight,  // at::Tensor gradBias,\n    at::Tensor columns, at::Tensor ones, int kW, int kH, int dW, int dH,\n    int padW, int padH, int dilationW, int dilationH, float scaleW, float scaleH,\n    int group, int deformable_group, float scale, int im2col_step) {\n  // todo: transpose and reshape outGrad\n  // todo: reshape columns\n  // todo: add im2col_step as input\n\n  pyramid_shape_check(input, offset, &gradOutput, gradWeight, kH, kW, dH, dW, padH,\n              padW, dilationH, dilationW, group, deformable_group);\n  at::DeviceGuard guard(input.device());\n\n  input = input.contiguous();\n  offset = offset.contiguous();\n  gradOutput = gradOutput.contiguous();\n\n  int batch = 1;\n\n  if (input.ndimension() == 3) {\n    // Force batch\n    batch = 0;\n    input = input.view(\n        at::IntList({1, input.size(0), input.size(1), input.size(2)}));\n    gradOutput = gradOutput.view(\n        {1, gradOutput.size(0), gradOutput.size(1), gradOutput.size(2)});\n  }\n\n  long batchSize = input.size(0);\n  long nInputPlane = input.size(1);\n  long inputHeight = input.size(2);\n  long inputWidth = input.size(3);\n  long offsetHeight = offset.size(2);\n  long offsetWidth = offset.size(3);\n\n  long nOutputPlane = gradWeight.size(0);\n\n  long outputWidth =\n      (offsetWidth + 2 * padW - (dilationW * (kW - 1) + 1)) / dW + 1;\n  long outputHeight =\n      (offsetHeight + 2 * padH - (dilationH * (kH - 1) + 1)) / dH + 1;\n\n  TORCH_CHECK((offset.size(0) == batchSize), \"invalid batch size of offset\");\n\n  columns = at::zeros(\n      {nInputPlane * kW * kH, im2col_step * outputHeight * outputWidth},\n      input.options());\n\n  gradOutput = gradOutput.view({batchSize / im2col_step, im2col_step,\n                                nOutputPlane, outputHeight, outputWidth});\n  gradOutput.transpose_(1, 2);\n\n  at::Tensor gradOutputBuffer = at::zeros_like(gradOutput);\n  gradOutputBuffer =\n      gradOutputBuffer.view({batchSize / im2col_step, nOutputPlane, im2col_step,\n                             outputHeight, outputWidth});\n  gradOutputBuffer = gradOutputBuffer.contiguous();\n  gradOutputBuffer.copy_(gradOutput);\n  gradOutputBuffer =\n      gradOutputBuffer.view({batchSize / im2col_step, nOutputPlane,\n                             im2col_step * outputHeight, outputWidth});\n\n  gradOutput.transpose_(1, 2);\n  gradOutput =\n      gradOutput.view({batchSize, nOutputPlane, outputHeight, outputWidth});\n\n  input = input.view({batchSize / im2col_step, im2col_step, nInputPlane,\n                      inputHeight, inputWidth});\n  offset =\n      offset.view({batchSize / im2col_step, im2col_step,\n                   deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n\n  for (int elt = 0; elt < batchSize / im2col_step; elt++) {\n    pyramid_deformable_im2col(input[elt], offset[elt], nInputPlane, inputHeight, inputWidth,\n                              offsetHeight, offsetWidth, kH, kW, padH, padW, dH, dW, \n                              dilationH, dilationW, scaleH, scaleW,\n                              im2col_step, deformable_group, columns);\n\n    // divide into group\n    gradOutputBuffer = gradOutputBuffer.view(\n        {gradOutputBuffer.size(0), group, gradOutputBuffer.size(1) / group,\n         gradOutputBuffer.size(2), gradOutputBuffer.size(3)});\n    columns = columns.view({group, columns.size(0) / group, columns.size(1)});\n    gradWeight =\n        gradWeight.view({group, gradWeight.size(0) / group, gradWeight.size(1),\n                         gradWeight.size(2), gradWeight.size(3)});\n\n    for (int g = 0; g < group; g++) {\n      gradWeight[g] = gradWeight[g]\n                          .flatten(1)\n                          .addmm_(gradOutputBuffer[elt][g].flatten(1),\n                                  columns[g].transpose(1, 0), 1.0, scale)\n                          .view_as(gradWeight[g]);\n    }\n    gradOutputBuffer = gradOutputBuffer.view(\n        {gradOutputBuffer.size(0),\n         gradOutputBuffer.size(1) * gradOutputBuffer.size(2),\n         gradOutputBuffer.size(3), gradOutputBuffer.size(4)});\n    columns =\n        columns.view({columns.size(0) * columns.size(1), columns.size(2)});\n    gradWeight = gradWeight.view({gradWeight.size(0) * gradWeight.size(1),\n                                  gradWeight.size(2), gradWeight.size(3),\n                                  gradWeight.size(4)});\n  }\n\n  input = input.view({batchSize, nInputPlane, inputHeight, inputWidth});\n  offset = offset.view(\n      {batchSize, deformable_group * 2 * kH * kW, offsetHeight, offsetWidth});\n\n  if (batch == 0) {\n    gradOutput = gradOutput.view({nOutputPlane, outputHeight, outputWidth});\n    input = input.view({nInputPlane, inputHeight, inputWidth});\n  }\n\n  return 1;\n}"
  },
  {
    "path": "code/mmdet/ops/dcn/src/cuda/deform_conv_cuda_kernel.cu",
    "content": "/*!\n ******************* BEGIN Caffe Copyright Notice and Disclaimer ****************\n *\n * COPYRIGHT\n *\n * All contributions by the University of California:\n * Copyright (c) 2014-2017 The Regents of the University of California (Regents)\n * All rights reserved.\n *\n * All other contributions:\n * Copyright (c) 2014-2017, the respective contributors\n * All rights reserved.\n *\n * Caffe uses a shared copyright model: each contributor holds copyright over\n * their contributions to Caffe. The project versioning records all such\n * contribution and copyright details. If a contributor wants to further mark\n * their specific copyright on a particular contribution, they should indicate\n * their copyright solely in the commit message of the change when it is\n * committed.\n *\n * LICENSE\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n * list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * CONTRIBUTION AGREEMENT\n *\n * By contributing to the BVLC/caffe repository through pull-request, comment,\n * or otherwise, the contributor releases their content to the\n * license and copyright terms herein.\n *\n ***************** END Caffe Copyright Notice and Disclaimer ********************\n *\n * Copyright (c) 2018 Microsoft\n * Licensed under The MIT License [see LICENSE for details]\n * \\file modulated_deformable_im2col.cuh\n * \\brief Function definitions of converting an image to\n * column matrix based on kernel, padding, dilation, and offset.\n * These functions are mainly used in deformable convolution operators.\n * \\ref: https://arxiv.org/abs/1703.06211\n * \\author Yuwen Xiong, Haozhi Qi, Jifeng Dai, Xizhou Zhu, Han Hu, Dazhi Cheng\n */\n\n// modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/blob/mmdetection/mmdet/ops/dcn/src/deform_conv_cuda_kernel.cu\n\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <THC/THCAtomics.cuh>\n#include <stdio.h>\n#include <math.h>\n#include <float.h>\n\nusing namespace at;\n\n#define CUDA_KERNEL_LOOP(i, n)                                 \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < (n); \\\n       i += blockDim.x * gridDim.x)\n\nconst int CUDA_NUM_THREADS = 1024;\nconst int kMaxGridNum = 65535;\n\ninline int GET_BLOCKS(const int N)\n{\n  return std::min(kMaxGridNum, (N + CUDA_NUM_THREADS - 1) / CUDA_NUM_THREADS);\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t deformable_im2col_bilinear(const scalar_t *bottom_data, const int data_width,\n                                               const int height, const int width, scalar_t h, scalar_t w)\n{\n\n  int h_low = floor(h);\n  int w_low = floor(w);\n  int h_high = h_low + 1;\n  int w_high = w_low + 1;\n\n  scalar_t lh = h - h_low;\n  scalar_t lw = w - w_low;\n  scalar_t hh = 1 - lh, hw = 1 - lw;\n\n  scalar_t v1 = 0;\n  if (h_low >= 0 && w_low >= 0)\n    v1 = bottom_data[h_low * data_width + w_low];\n  scalar_t v2 = 0;\n  if (h_low >= 0 && w_high <= width - 1)\n    v2 = bottom_data[h_low * data_width + w_high];\n  scalar_t v3 = 0;\n  if (h_high <= height - 1 && w_low >= 0)\n    v3 = bottom_data[h_high * data_width + w_low];\n  scalar_t v4 = 0;\n  if (h_high <= height - 1 && w_high <= width - 1)\n    v4 = bottom_data[h_high * data_width + w_high];\n\n  scalar_t w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw;\n\n  scalar_t val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n  return val;\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t get_gradient_weight(scalar_t argmax_h, scalar_t argmax_w,\n                                        const int h, const int w, const int height, const int width)\n{\n\n  if (argmax_h <= -1 || argmax_h >= height || argmax_w <= -1 || argmax_w >= width)\n  {\n    //empty\n    return 0;\n  }\n\n  int argmax_h_low = floor(argmax_h);\n  int argmax_w_low = floor(argmax_w);\n  int argmax_h_high = argmax_h_low + 1;\n  int argmax_w_high = argmax_w_low + 1;\n\n  scalar_t weight = 0;\n  if (h == argmax_h_low && w == argmax_w_low)\n    weight = (h + 1 - argmax_h) * (w + 1 - argmax_w);\n  if (h == argmax_h_low && w == argmax_w_high)\n    weight = (h + 1 - argmax_h) * (argmax_w + 1 - w);\n  if (h == argmax_h_high && w == argmax_w_low)\n    weight = (argmax_h + 1 - h) * (w + 1 - argmax_w);\n  if (h == argmax_h_high && w == argmax_w_high)\n    weight = (argmax_h + 1 - h) * (argmax_w + 1 - w);\n  return weight;\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t get_coordinate_weight(scalar_t argmax_h, scalar_t argmax_w,\n                                          const int height, const int width, const scalar_t *im_data,\n                                          const int data_width, const int bp_dir)\n{\n\n  if (argmax_h <= -1 || argmax_h >= height || argmax_w <= -1 || argmax_w >= width)\n  {\n    //empty\n    return 0;\n  }\n\n  int argmax_h_low = floor(argmax_h);\n  int argmax_w_low = floor(argmax_w);\n  int argmax_h_high = argmax_h_low + 1;\n  int argmax_w_high = argmax_w_low + 1;\n\n  scalar_t weight = 0;\n\n  if (bp_dir == 0)\n  {\n    if (argmax_h_low >= 0 && argmax_w_low >= 0)\n      weight += -1 * (argmax_w_low + 1 - argmax_w) * im_data[argmax_h_low * data_width + argmax_w_low];\n    if (argmax_h_low >= 0 && argmax_w_high <= width - 1)\n      weight += -1 * (argmax_w - argmax_w_low) * im_data[argmax_h_low * data_width + argmax_w_high];\n    if (argmax_h_high <= height - 1 && argmax_w_low >= 0)\n      weight += (argmax_w_low + 1 - argmax_w) * im_data[argmax_h_high * data_width + argmax_w_low];\n    if (argmax_h_high <= height - 1 && argmax_w_high <= width - 1)\n      weight += (argmax_w - argmax_w_low) * im_data[argmax_h_high * data_width + argmax_w_high];\n  }\n  else if (bp_dir == 1)\n  {\n    if (argmax_h_low >= 0 && argmax_w_low >= 0)\n      weight += -1 * (argmax_h_low + 1 - argmax_h) * im_data[argmax_h_low * data_width + argmax_w_low];\n    if (argmax_h_low >= 0 && argmax_w_high <= width - 1)\n      weight += (argmax_h_low + 1 - argmax_h) * im_data[argmax_h_low * data_width + argmax_w_high];\n    if (argmax_h_high <= height - 1 && argmax_w_low >= 0)\n      weight += -1 * (argmax_h - argmax_h_low) * im_data[argmax_h_high * data_width + argmax_w_low];\n    if (argmax_h_high <= height - 1 && argmax_w_high <= width - 1)\n      weight += (argmax_h - argmax_h_low) * im_data[argmax_h_high * data_width + argmax_w_high];\n  }\n\n  return weight;\n}\n\ntemplate <typename scalar_t>\n__global__ void deformable_im2col_gpu_kernel(const int n, const scalar_t *data_im, const scalar_t *data_offset,\n                                             const int height, const int width, const int kernel_h, const int kernel_w,\n                                             const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n                                             const int dilation_h, const int dilation_w, const int channel_per_deformable_group,\n                                             const int batch_size, const int num_channels, const int deformable_group,\n                                             const int height_col, const int width_col,\n                                             scalar_t *data_col)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    // index index of output matrix\n    const int w_col = index % width_col;\n    const int h_col = (index / width_col) % height_col;\n    const int b_col = (index / width_col / height_col) % batch_size;\n    const int c_im = (index / width_col / height_col) / batch_size;\n    const int c_col = c_im * kernel_h * kernel_w;\n\n    // compute deformable group index\n    const int deformable_group_index = c_im / channel_per_deformable_group;\n\n    const int h_in = h_col * stride_h - pad_h;\n    const int w_in = w_col * stride_w - pad_w;\n    scalar_t *data_col_ptr = data_col + ((c_col * batch_size + b_col) * height_col + h_col) * width_col + w_col;\n    //const scalar_t* data_im_ptr = data_im + ((b_col * num_channels + c_im) * height + h_in) * width + w_in;\n    const scalar_t *data_im_ptr = data_im + (b_col * num_channels + c_im) * height * width;\n    const scalar_t *data_offset_ptr = data_offset + (b_col * deformable_group + deformable_group_index) * 2 * kernel_h * kernel_w * height_col * width_col;\n\n    for (int i = 0; i < kernel_h; ++i)\n    {\n      for (int j = 0; j < kernel_w; ++j)\n      {\n        const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_col) * width_col + w_col;\n        const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_col) * width_col + w_col;\n        const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n        const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n        scalar_t val = static_cast<scalar_t>(0);\n        const scalar_t h_im = h_in + i * dilation_h + offset_h;\n        const scalar_t w_im = w_in + j * dilation_w + offset_w;\n        if (h_im > -1 && w_im > -1 && h_im < height && w_im < width)\n        {\n          //const scalar_t map_h = i * dilation_h + offset_h;\n          //const scalar_t map_w = j * dilation_w + offset_w;\n          //const int cur_height = height - h_in;\n          //const int cur_width = width - w_in;\n          //val = deformable_im2col_bilinear(data_im_ptr, width, cur_height, cur_width, map_h, map_w);\n          val = deformable_im2col_bilinear(data_im_ptr, width, height, width, h_im, w_im);\n        }\n        *data_col_ptr = val;\n        data_col_ptr += batch_size * height_col * width_col;\n      }\n    }\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void pyramid_deformable_im2col_gpu_kernel(const int n, const scalar_t *data_im, const scalar_t *data_offset,\n                                      const int height, const int width, const int kernel_h, const int kernel_w,\n                                      const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n                                      const int dilation_h, const int dilation_w, const float scale_h, const float scale_w, \n                                      const int channel_per_deformable_group, const int batch_size, const int num_channels,\n                                      const int deformable_group, const int height_col, const int width_col, scalar_t *data_col)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    // index index of output matrix\n    const int w_col = index % width_col;\n    const int h_col = (index / width_col) % height_col;\n    const int b_col = (index / width_col / height_col) % batch_size;\n    const int c_im = (index / width_col / height_col) / batch_size;\n    const int c_col = c_im * kernel_h * kernel_w;\n\n    // compute deformable group index\n    const int deformable_group_index = c_im / channel_per_deformable_group;\n\n    const int h_in = h_col * stride_h - pad_h;\n    const int w_in = w_col * stride_w - pad_w;\n    scalar_t *data_col_ptr = data_col + ((c_col * batch_size + b_col) * height_col + h_col) * width_col + w_col;\n    //const scalar_t* data_im_ptr = data_im + ((b_col * num_channels + c_im) * height + h_in) * width + w_in;\n    const scalar_t *data_im_ptr = data_im + (b_col * num_channels + c_im) * height * width;\n    const scalar_t *data_offset_ptr = data_offset + (b_col * deformable_group + deformable_group_index) * 2 * kernel_h * kernel_w * height_col * width_col;\n\n    for (int i = 0; i < kernel_h; ++i)\n    {\n      for (int j = 0; j < kernel_w; ++j)\n      {\n        const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_col) * width_col + w_col;\n        const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_col) * width_col + w_col;\n        const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n        const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n        scalar_t val = static_cast<scalar_t>(0);\n        const scalar_t h_im = (h_in + i * dilation_h)*scale_h + offset_h;\n        const scalar_t w_im = (w_in + j * dilation_w)*scale_w + offset_w;\n        if (h_im > -1 && w_im > -1 && h_im < height && w_im < width)\n        {\n          //const scalar_t map_h = i * dilation_h + offset_h;\n          //const scalar_t map_w = j * dilation_w + offset_w;\n          //const int cur_height = height - h_in;\n          //const int cur_width = width - w_in;\n          //val = deformable_im2col_bilinear(data_im_ptr, width, cur_height, cur_width, map_h, map_w);\n          val = deformable_im2col_bilinear(data_im_ptr, width, height, width, h_im, w_im);\n        }\n        *data_col_ptr = val;\n        data_col_ptr += batch_size * height_col * width_col;\n      }\n    }\n  }\n}\n\nvoid deformable_im2col(\n    const at::Tensor data_im, const at::Tensor data_offset, const int channels,\n    const int height, const int width, const int ksize_h, const int ksize_w,\n    const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w, const int parallel_imgs,\n    const int deformable_group, at::Tensor data_col)\n{\n  // num_axes should be smaller than block size\n  // todo: check parallel_imgs is correctly passed in\n  int height_col = (height + 2 * pad_h - (dilation_h * (ksize_h - 1) + 1)) / stride_h + 1;\n  int width_col = (width + 2 * pad_w - (dilation_w * (ksize_w - 1) + 1)) / stride_w + 1;\n  int num_kernels = channels * height_col * width_col * parallel_imgs;\n  int channel_per_deformable_group = channels / deformable_group;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_im.scalar_type(), \"deformable_im2col_gpu\", ([&] {\n        const scalar_t *data_im_ = data_im.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n\n        deformable_im2col_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_im_, data_offset_, height, width, ksize_h, ksize_w,\n            pad_h, pad_w, stride_h, stride_w, dilation_h, dilation_w,\n            channel_per_deformable_group, parallel_imgs, channels, deformable_group,\n            height_col, width_col, data_col_);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in deformable_im2col: %s\\n\", cudaGetErrorString(err));\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void deformable_col2im_gpu_kernel(\n    const int n, const scalar_t *data_col, const scalar_t *data_offset,\n    const int channels, const int height, const int width,\n    const int kernel_h, const int kernel_w,\n    const int pad_h, const int pad_w,\n    const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w,\n    const int channel_per_deformable_group,\n    const int batch_size, const int deformable_group,\n    const int height_col, const int width_col,\n    scalar_t *grad_im)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    const int j = (index / width_col / height_col / batch_size) % kernel_w;\n    const int i = (index / width_col / height_col / batch_size / kernel_w) % kernel_h;\n    const int c = index / width_col / height_col / batch_size / kernel_w / kernel_h;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / channel_per_deformable_group;\n\n    int w_out = index % width_col;\n    int h_out = (index / width_col) % height_col;\n    int b = (index / width_col / height_col) % batch_size;\n    int w_in = w_out * stride_w - pad_w;\n    int h_in = h_out * stride_h - pad_h;\n\n    const scalar_t *data_offset_ptr = data_offset + (b * deformable_group + deformable_group_index) *\n                                                        2 * kernel_h * kernel_w * height_col * width_col;\n    const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out;\n    const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out;\n    const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n    const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n    const scalar_t cur_inv_h_data = h_in + i * dilation_h + offset_h;\n    const scalar_t cur_inv_w_data = w_in + j * dilation_w + offset_w;\n\n    const scalar_t cur_top_grad = data_col[index];\n    const int cur_h = (int)cur_inv_h_data;\n    const int cur_w = (int)cur_inv_w_data;\n    for (int dy = -2; dy <= 2; dy++)\n    {\n      for (int dx = -2; dx <= 2; dx++)\n      {\n        if (cur_h + dy >= 0 && cur_h + dy < height &&\n            cur_w + dx >= 0 && cur_w + dx < width &&\n            abs(cur_inv_h_data - (cur_h + dy)) < 1 &&\n            abs(cur_inv_w_data - (cur_w + dx)) < 1)\n        {\n          int cur_bottom_grad_pos = ((b * channels + c) * height + cur_h + dy) * width + cur_w + dx;\n          scalar_t weight = get_gradient_weight(cur_inv_h_data, cur_inv_w_data, cur_h + dy, cur_w + dx, height, width);\n          atomicAdd(grad_im + cur_bottom_grad_pos, weight * cur_top_grad);\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void pyramid_deformable_col2im_gpu_kernel(\n    const int n, const scalar_t *data_col, const scalar_t *data_offset,\n    const int channels, const int height, const int width,\n    const int kernel_h, const int kernel_w,\n    const int pad_h, const int pad_w,\n    const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w,\n    const float scale_h, const float scale_w,\n    const int channel_per_deformable_group,\n    const int batch_size, const int deformable_group,\n    const int height_col, const int width_col,\n    scalar_t *grad_im)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    const int j = (index / width_col / height_col / batch_size) % kernel_w;\n    const int i = (index / width_col / height_col / batch_size / kernel_w) % kernel_h;\n    const int c = index / width_col / height_col / batch_size / kernel_w / kernel_h;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / channel_per_deformable_group;\n\n    int w_out = index % width_col;\n    int h_out = (index / width_col) % height_col;\n    int b = (index / width_col / height_col) % batch_size;\n    int w_in = w_out * stride_w - pad_w;\n    int h_in = h_out * stride_h - pad_h;\n\n    const scalar_t *data_offset_ptr = data_offset + (b * deformable_group + deformable_group_index) *\n                                                        2 * kernel_h * kernel_w * height_col * width_col;\n    const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out;\n    const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out;\n    const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n    const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n    const scalar_t cur_inv_h_data = (h_in + i * dilation_h)*scale_h + offset_h;\n    const scalar_t cur_inv_w_data = (w_in + j * dilation_w)*scale_w + offset_w;\n\n    const scalar_t cur_top_grad = data_col[index];\n    const int cur_h = (int)cur_inv_h_data;\n    const int cur_w = (int)cur_inv_w_data;\n    for (int dy = -2; dy <= 2; dy++)\n    {\n      for (int dx = -2; dx <= 2; dx++)\n      {\n        if (cur_h + dy >= 0 && cur_h + dy < height &&\n            cur_w + dx >= 0 && cur_w + dx < width &&\n            abs(cur_inv_h_data - (cur_h + dy)) < 1 &&\n            abs(cur_inv_w_data - (cur_w + dx)) < 1)\n        {\n          int cur_bottom_grad_pos = ((b * channels + c) * height + cur_h + dy) * width + cur_w + dx;\n          scalar_t weight = get_gradient_weight(cur_inv_h_data, cur_inv_w_data, cur_h + dy, cur_w + dx, height, width);\n          atomicAdd(grad_im + cur_bottom_grad_pos, weight * cur_top_grad);\n        }\n      }\n    }\n  }\n}\n\nvoid deformable_col2im(\n    const at::Tensor data_col, const at::Tensor data_offset, const int channels,\n    const int height, const int width, const int ksize_h,\n    const int ksize_w, const int pad_h, const int pad_w,\n    const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w,\n    const int parallel_imgs, const int deformable_group,\n    at::Tensor grad_im)\n{\n\n  // todo: make sure parallel_imgs is passed in correctly\n  int height_col = (height + 2 * pad_h - (dilation_h * (ksize_h - 1) + 1)) / stride_h + 1;\n  int width_col = (width + 2 * pad_w - (dilation_w * (ksize_w - 1) + 1)) / stride_w + 1;\n  int num_kernels = channels * ksize_h * ksize_w * height_col * width_col * parallel_imgs;\n  int channel_per_deformable_group = channels / deformable_group;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_col.scalar_type(), \"deformable_col2im_gpu\", ([&] {\n        const scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        scalar_t *grad_im_ = grad_im.data_ptr<scalar_t>();\n\n        deformable_col2im_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_col_, data_offset_, channels, height, width, ksize_h,\n            ksize_w, pad_h, pad_w, stride_h, stride_w,\n            dilation_h, dilation_w, channel_per_deformable_group,\n            parallel_imgs, deformable_group, height_col, width_col, grad_im_);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in deformable_col2im: %s\\n\", cudaGetErrorString(err));\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void deformable_col2im_coord_gpu_kernel(const int n, const scalar_t *data_col,\n                                                   const scalar_t *data_im, const scalar_t *data_offset,\n                                                   const int channels, const int height, const int width,\n                                                   const int kernel_h, const int kernel_w,\n                                                   const int pad_h, const int pad_w,\n                                                   const int stride_h, const int stride_w,\n                                                   const int dilation_h, const int dilation_w,\n                                                   const int channel_per_deformable_group,\n                                                   const int batch_size, const int offset_channels, const int deformable_group,\n                                                   const int height_col, const int width_col, scalar_t *grad_offset)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    scalar_t val = 0;\n    int w = index % width_col;\n    int h = (index / width_col) % height_col;\n    int c = (index / width_col / height_col) % offset_channels;\n    int b = (index / width_col / height_col) / offset_channels;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / (2 * kernel_h * kernel_w);\n    const int col_step = kernel_h * kernel_w;\n    int cnt = 0;\n    const scalar_t *data_col_ptr = data_col + deformable_group_index * channel_per_deformable_group *\n                                                  batch_size * width_col * height_col;\n    const scalar_t *data_im_ptr = data_im + (b * deformable_group + deformable_group_index) *\n                                                channel_per_deformable_group / kernel_h / kernel_w * height * width;\n    const scalar_t *data_offset_ptr = data_offset + (b * deformable_group + deformable_group_index) * 2 *\n                                                        kernel_h * kernel_w * height_col * width_col;\n\n    const int offset_c = c - deformable_group_index * 2 * kernel_h * kernel_w;\n\n    for (int col_c = (offset_c / 2); col_c < channel_per_deformable_group; col_c += col_step)\n    {\n      const int col_pos = (((col_c * batch_size + b) * height_col) + h) * width_col + w;\n      const int bp_dir = offset_c % 2;\n\n      int j = (col_pos / width_col / height_col / batch_size) % kernel_w;\n      int i = (col_pos / width_col / height_col / batch_size / kernel_w) % kernel_h;\n      int w_out = col_pos % width_col;\n      int h_out = (col_pos / width_col) % height_col;\n      int w_in = w_out * stride_w - pad_w;\n      int h_in = h_out * stride_h - pad_h;\n      const int data_offset_h_ptr = (((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out);\n      const int data_offset_w_ptr = (((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out);\n      const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n      const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n      scalar_t inv_h = h_in + i * dilation_h + offset_h;\n      scalar_t inv_w = w_in + j * dilation_w + offset_w;\n      if (inv_h <= -1 || inv_w <= -1 || inv_h >= height || inv_w >= width)\n      {\n        inv_h = inv_w = -2;\n      }\n      const scalar_t weight = get_coordinate_weight(\n          inv_h, inv_w,\n          height, width, data_im_ptr + cnt * height * width, width, bp_dir);\n      val += weight * data_col_ptr[col_pos];\n      cnt += 1;\n    }\n\n    grad_offset[index] = val;\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void pyramid_deformable_col2im_coord_gpu_kernel(const int n, const scalar_t *data_col,\n                                                   const scalar_t *data_im, const scalar_t *data_offset,\n                                                   const int channels, const int height, const int width,\n                                                   const int kernel_h, const int kernel_w,\n                                                   const int pad_h, const int pad_w,\n                                                   const int stride_h, const int stride_w,\n                                                   const int dilation_h, const int dilation_w,\n                                                   const float scale_h, const float scale_w,\n                                                   const int channel_per_deformable_group,\n                                                   const int batch_size, const int offset_channels, const int deformable_group,\n                                                   const int height_col, const int width_col, scalar_t *grad_offset)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    scalar_t val = 0;\n    int w = index % width_col;\n    int h = (index / width_col) % height_col;\n    int c = (index / width_col / height_col) % offset_channels;\n    int b = (index / width_col / height_col) / offset_channels;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / (2 * kernel_h * kernel_w);\n    const int col_step = kernel_h * kernel_w;\n    int cnt = 0;\n    const scalar_t *data_col_ptr = data_col + deformable_group_index * channel_per_deformable_group *\n                                                  batch_size * width_col * height_col;\n    const scalar_t *data_im_ptr = data_im + (b * deformable_group + deformable_group_index) *\n                                                channel_per_deformable_group / kernel_h / kernel_w * height * width;\n    const scalar_t *data_offset_ptr = data_offset + (b * deformable_group + deformable_group_index) * 2 *\n                                                        kernel_h * kernel_w * height_col * width_col;\n\n    const int offset_c = c - deformable_group_index * 2 * kernel_h * kernel_w;\n\n    for (int col_c = (offset_c / 2); col_c < channel_per_deformable_group; col_c += col_step)\n    {\n      const int col_pos = (((col_c * batch_size + b) * height_col) + h) * width_col + w;\n      const int bp_dir = offset_c % 2;\n\n      int j = (col_pos / width_col / height_col / batch_size) % kernel_w;\n      int i = (col_pos / width_col / height_col / batch_size / kernel_w) % kernel_h;\n      int w_out = col_pos % width_col;\n      int h_out = (col_pos / width_col) % height_col;\n      int w_in = w_out * stride_w - pad_w;\n      int h_in = h_out * stride_h - pad_h;\n      const int data_offset_h_ptr = (((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out);\n      const int data_offset_w_ptr = (((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out);\n      const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n      const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n      scalar_t inv_h = (h_in + i * dilation_h)*scale_h + offset_h;\n      scalar_t inv_w = (w_in + j * dilation_w)*scale_w + offset_w;\n      if (inv_h <= -1 || inv_w <= -1 || inv_h >= height || inv_w >= width)\n      {\n        inv_h = inv_w = -2;\n      }\n      const scalar_t weight = get_coordinate_weight(\n          inv_h, inv_w,\n          height, width, data_im_ptr + cnt * height * width, width, bp_dir);\n      val += weight * data_col_ptr[col_pos];\n      cnt += 1;\n    }\n\n    grad_offset[index] = val;\n  }\n}\n\nvoid deformable_col2im_coord(\n    const at::Tensor data_col, const at::Tensor data_im, const at::Tensor data_offset,\n    const int channels, const int height, const int width, const int ksize_h,\n    const int ksize_w, const int pad_h, const int pad_w, const int stride_h,\n    const int stride_w, const int dilation_h, const int dilation_w,\n    const int parallel_imgs, const int deformable_group, at::Tensor grad_offset)\n{\n\n  int height_col = (height + 2 * pad_h - (dilation_h * (ksize_h - 1) + 1)) / stride_h + 1;\n  int width_col = (width + 2 * pad_w - (dilation_w * (ksize_w - 1) + 1)) / stride_w + 1;\n  int num_kernels = height_col * width_col * 2 * ksize_h * ksize_w * deformable_group * parallel_imgs;\n  int channel_per_deformable_group = channels * ksize_h * ksize_w / deformable_group;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_col.scalar_type(), \"deformable_col2im_coord_gpu\", ([&] {\n        const scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n        const scalar_t *data_im_ = data_im.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        scalar_t *grad_offset_ = grad_offset.data_ptr<scalar_t>();\n\n        deformable_col2im_coord_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_col_, data_im_, data_offset_, channels, height, width,\n            ksize_h, ksize_w, pad_h, pad_w, stride_h, stride_w,\n            dilation_h, dilation_w, channel_per_deformable_group,\n            parallel_imgs, 2 * ksize_h * ksize_w * deformable_group, deformable_group,\n            height_col, width_col, grad_offset_);\n      }));\n}\n\n\nvoid pyramid_deformable_im2col(\n  const at::Tensor data_im, const at::Tensor data_offset, const int channels,\n  const int inputHeight, const int inputWidth, const int offsetHeight, const int offsetWidth,\n  const int ksize_h, const int ksize_w, const int pad_h, const int pad_w, const int stride_h, \n  const int stride_w, const int dilation_h, const int dilation_w, const float scale_h, const float scale_w,\n  const int parallel_imgs, const int deformable_group, at::Tensor data_col)\n{\n// num_axes should be smaller than block size\n// todo: check parallel_imgs is correctly passed in\nint height_col = (offsetHeight + 2 * pad_h - (dilation_h * (ksize_h - 1) + 1)) / stride_h + 1;\nint width_col = (offsetWidth + 2 * pad_w - (dilation_w * (ksize_w - 1) + 1)) / stride_w + 1;\nint num_kernels = channels * height_col * width_col * parallel_imgs;\nint channel_per_deformable_group = channels / deformable_group;\n\nAT_DISPATCH_FLOATING_TYPES_AND_HALF(\n    data_im.scalar_type(), \"deformable_im2col_gpu\", ([&] {\n      const scalar_t *data_im_ = data_im.data_ptr<scalar_t>();\n      const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n      scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n\n      pyramid_deformable_im2col_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n          num_kernels, data_im_, data_offset_, inputHeight, inputWidth, ksize_h, ksize_w,\n          pad_h, pad_w, stride_h, stride_w, dilation_h, dilation_w, scale_h, scale_w,\n          channel_per_deformable_group, parallel_imgs, channels, deformable_group,\n          height_col, width_col, data_col_);\n    }));\n\ncudaError_t err = cudaGetLastError();\nif (err != cudaSuccess)\n{\n  printf(\"error in pyramid deformable_im2col: %s\\n\", cudaGetErrorString(err));\n}\n}\n\nvoid pyramid_deformable_col2im_coord(\n    const at::Tensor data_col, const at::Tensor data_im, const at::Tensor data_offset,\n    const int channels, const int inputHeight, const int inputWidth, const int offsetHeight,\n    const int offsetWidth, const int ksize_h, const int ksize_w, const int pad_h, const int pad_w, \n    const int stride_h, const int stride_w, const int dilation_h, const int dilation_w, const float scale_h,\n    const float scale_w, const int parallel_imgs, const int deformable_group, at::Tensor grad_offset)\n{\n\n  int height_col = (offsetHeight + 2 * pad_h - (dilation_h * (ksize_h - 1) + 1)) / stride_h + 1;\n  int width_col = (offsetWidth + 2 * pad_w - (dilation_w * (ksize_w - 1) + 1)) / stride_w + 1;\n  int num_kernels = height_col * width_col * 2 * ksize_h * ksize_w * deformable_group * parallel_imgs;\n  int channel_per_deformable_group = channels * ksize_h * ksize_w / deformable_group;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_col.scalar_type(), \"deformable_col2im_coord_gpu\", ([&] {\n        const scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n        const scalar_t *data_im_ = data_im.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        scalar_t *grad_offset_ = grad_offset.data_ptr<scalar_t>();\n\n        pyramid_deformable_col2im_coord_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_col_, data_im_, data_offset_, channels, inputHeight, inputWidth,\n            ksize_h, ksize_w, pad_h, pad_w, stride_h, stride_w, dilation_h, dilation_w, scale_h, scale_w,\n            channel_per_deformable_group, parallel_imgs, 2 * ksize_h * ksize_w * deformable_group, \n            deformable_group, height_col, width_col, grad_offset_);\n      }));\n}\n\nvoid pyramid_deformable_col2im(\n  const at::Tensor data_col, const at::Tensor data_offset, const int channels,\n  const int inputHeight, const int inputWidth, const int offsetHeight,\n  const int offsetWidth, const int ksize_h, const int ksize_w, const int pad_h, \n  const int pad_w, const int stride_h, const int stride_w, const int dilation_h,\n  const int dilation_w, const float scale_h, const float scale_w, const int parallel_imgs, \n  const int deformable_group, at::Tensor grad_im)\n{\n\n// todo: make sure parallel_imgs is passed in correctly\nint height_col = (offsetHeight + 2 * pad_h - (dilation_h * (ksize_h - 1) + 1)) / stride_h + 1;\nint width_col = (offsetWidth + 2 * pad_w - (dilation_w * (ksize_w - 1) + 1)) / stride_w + 1;\nint num_kernels = channels * ksize_h * ksize_w * height_col * width_col * parallel_imgs;\nint channel_per_deformable_group = channels / deformable_group;\n\nAT_DISPATCH_FLOATING_TYPES_AND_HALF(\n    data_col.scalar_type(), \"deformable_col2im_gpu\", ([&] {\n      const scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n      const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n      scalar_t *grad_im_ = grad_im.data_ptr<scalar_t>();\n\n      pyramid_deformable_col2im_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n          num_kernels, data_col_, data_offset_, channels, inputHeight, inputWidth,\n          ksize_h, ksize_w, pad_h, pad_w, stride_h, stride_w,\n          dilation_h, dilation_w, scale_h, scale_w, channel_per_deformable_group,\n          parallel_imgs, deformable_group, height_col, width_col, grad_im_);\n    }));\n\ncudaError_t err = cudaGetLastError();\nif (err != cudaSuccess)\n{\n  printf(\"error in pyramid deformable_col2im: %s\\n\", cudaGetErrorString(err));\n}\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t dmcn_im2col_bilinear(const scalar_t *bottom_data, const int data_width,\n                                         const int height, const int width, scalar_t h, scalar_t w)\n{\n  int h_low = floor(h);\n  int w_low = floor(w);\n  int h_high = h_low + 1;\n  int w_high = w_low + 1;\n\n  scalar_t lh = h - h_low;\n  scalar_t lw = w - w_low;\n  scalar_t hh = 1 - lh, hw = 1 - lw;\n\n  scalar_t v1 = 0;\n  if (h_low >= 0 && w_low >= 0)\n    v1 = bottom_data[h_low * data_width + w_low];\n  scalar_t v2 = 0;\n  if (h_low >= 0 && w_high <= width - 1)\n    v2 = bottom_data[h_low * data_width + w_high];\n  scalar_t v3 = 0;\n  if (h_high <= height - 1 && w_low >= 0)\n    v3 = bottom_data[h_high * data_width + w_low];\n  scalar_t v4 = 0;\n  if (h_high <= height - 1 && w_high <= width - 1)\n    v4 = bottom_data[h_high * data_width + w_high];\n\n  scalar_t w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw;\n\n  scalar_t val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n  return val;\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t dmcn_get_gradient_weight(scalar_t argmax_h, scalar_t argmax_w,\n                                             const int h, const int w, const int height, const int width)\n{\n  if (argmax_h <= -1 || argmax_h >= height || argmax_w <= -1 || argmax_w >= width)\n  {\n    //empty\n    return 0;\n  }\n\n  int argmax_h_low = floor(argmax_h);\n  int argmax_w_low = floor(argmax_w);\n  int argmax_h_high = argmax_h_low + 1;\n  int argmax_w_high = argmax_w_low + 1;\n\n  scalar_t weight = 0;\n  if (h == argmax_h_low && w == argmax_w_low)\n    weight = (h + 1 - argmax_h) * (w + 1 - argmax_w);\n  if (h == argmax_h_low && w == argmax_w_high)\n    weight = (h + 1 - argmax_h) * (argmax_w + 1 - w);\n  if (h == argmax_h_high && w == argmax_w_low)\n    weight = (argmax_h + 1 - h) * (w + 1 - argmax_w);\n  if (h == argmax_h_high && w == argmax_w_high)\n    weight = (argmax_h + 1 - h) * (argmax_w + 1 - w);\n  return weight;\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t dmcn_get_coordinate_weight(scalar_t argmax_h, scalar_t argmax_w,\n                                               const int height, const int width, const scalar_t *im_data,\n                                               const int data_width, const int bp_dir)\n{\n  if (argmax_h <= -1 || argmax_h >= height || argmax_w <= -1 || argmax_w >= width)\n  {\n    //empty\n    return 0;\n  }\n\n  int argmax_h_low = floor(argmax_h);\n  int argmax_w_low = floor(argmax_w);\n  int argmax_h_high = argmax_h_low + 1;\n  int argmax_w_high = argmax_w_low + 1;\n\n  scalar_t weight = 0;\n\n  if (bp_dir == 0)\n  {\n    if (argmax_h_low >= 0 && argmax_w_low >= 0)\n      weight += -1 * (argmax_w_low + 1 - argmax_w) * im_data[argmax_h_low * data_width + argmax_w_low];\n    if (argmax_h_low >= 0 && argmax_w_high <= width - 1)\n      weight += -1 * (argmax_w - argmax_w_low) * im_data[argmax_h_low * data_width + argmax_w_high];\n    if (argmax_h_high <= height - 1 && argmax_w_low >= 0)\n      weight += (argmax_w_low + 1 - argmax_w) * im_data[argmax_h_high * data_width + argmax_w_low];\n    if (argmax_h_high <= height - 1 && argmax_w_high <= width - 1)\n      weight += (argmax_w - argmax_w_low) * im_data[argmax_h_high * data_width + argmax_w_high];\n  }\n  else if (bp_dir == 1)\n  {\n    if (argmax_h_low >= 0 && argmax_w_low >= 0)\n      weight += -1 * (argmax_h_low + 1 - argmax_h) * im_data[argmax_h_low * data_width + argmax_w_low];\n    if (argmax_h_low >= 0 && argmax_w_high <= width - 1)\n      weight += (argmax_h_low + 1 - argmax_h) * im_data[argmax_h_low * data_width + argmax_w_high];\n    if (argmax_h_high <= height - 1 && argmax_w_low >= 0)\n      weight += -1 * (argmax_h - argmax_h_low) * im_data[argmax_h_high * data_width + argmax_w_low];\n    if (argmax_h_high <= height - 1 && argmax_w_high <= width - 1)\n      weight += (argmax_h - argmax_h_low) * im_data[argmax_h_high * data_width + argmax_w_high];\n  }\n\n  return weight;\n}\n\ntemplate <typename scalar_t>\n__global__ void modulated_deformable_im2col_gpu_kernel(const int n,\n                                                       const scalar_t *data_im, const scalar_t *data_offset, const scalar_t *data_mask,\n                                                       const int height, const int width, const int kernel_h, const int kernel_w,\n                                                       const int pad_h, const int pad_w,\n                                                       const int stride_h, const int stride_w,\n                                                       const int dilation_h, const int dilation_w,\n                                                       const int channel_per_deformable_group,\n                                                       const int batch_size, const int num_channels, const int deformable_group,\n                                                       const int height_col, const int width_col,\n                                                       scalar_t *data_col)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    // index index of output matrix\n    const int w_col = index % width_col;\n    const int h_col = (index / width_col) % height_col;\n    const int b_col = (index / width_col / height_col) % batch_size;\n    const int c_im = (index / width_col / height_col) / batch_size;\n    const int c_col = c_im * kernel_h * kernel_w;\n\n    // compute deformable group index\n    const int deformable_group_index = c_im / channel_per_deformable_group;\n\n    const int h_in = h_col * stride_h - pad_h;\n    const int w_in = w_col * stride_w - pad_w;\n\n    scalar_t *data_col_ptr = data_col + ((c_col * batch_size + b_col) * height_col + h_col) * width_col + w_col;\n    //const float* data_im_ptr = data_im + ((b_col * num_channels + c_im) * height + h_in) * width + w_in;\n    const scalar_t *data_im_ptr = data_im + (b_col * num_channels + c_im) * height * width;\n    const scalar_t *data_offset_ptr = data_offset + (b_col * deformable_group + deformable_group_index) * 2 * kernel_h * kernel_w * height_col * width_col;\n\n    const scalar_t *data_mask_ptr = data_mask + (b_col * deformable_group + deformable_group_index) * kernel_h * kernel_w * height_col * width_col;\n\n    for (int i = 0; i < kernel_h; ++i)\n    {\n      for (int j = 0; j < kernel_w; ++j)\n      {\n        const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_col) * width_col + w_col;\n        const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_col) * width_col + w_col;\n        const int data_mask_hw_ptr = ((i * kernel_w + j) * height_col + h_col) * width_col + w_col;\n        const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n        const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n        const scalar_t mask = data_mask_ptr[data_mask_hw_ptr];\n        scalar_t val = static_cast<scalar_t>(0);\n        const scalar_t h_im = h_in + i * dilation_h + offset_h;\n        const scalar_t w_im = w_in + j * dilation_w + offset_w;\n        //if (h_im >= 0 && w_im >= 0 && h_im < height && w_im < width) {\n        if (h_im > -1 && w_im > -1 && h_im < height && w_im < width)\n        {\n          //const float map_h = i * dilation_h + offset_h;\n          //const float map_w = j * dilation_w + offset_w;\n          //const int cur_height = height - h_in;\n          //const int cur_width = width - w_in;\n          //val = dmcn_im2col_bilinear(data_im_ptr, width, cur_height, cur_width, map_h, map_w);\n          val = dmcn_im2col_bilinear(data_im_ptr, width, height, width, h_im, w_im);\n        }\n        *data_col_ptr = val * mask;\n        data_col_ptr += batch_size * height_col * width_col;\n        //data_col_ptr += height_col * width_col;\n      }\n    }\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void modulated_deformable_col2im_gpu_kernel(const int n,\n                                                       const scalar_t *data_col, const scalar_t *data_offset, const scalar_t *data_mask,\n                                                       const int channels, const int height, const int width,\n                                                       const int kernel_h, const int kernel_w,\n                                                       const int pad_h, const int pad_w,\n                                                       const int stride_h, const int stride_w,\n                                                       const int dilation_h, const int dilation_w,\n                                                       const int channel_per_deformable_group,\n                                                       const int batch_size, const int deformable_group,\n                                                       const int height_col, const int width_col,\n                                                       scalar_t *grad_im)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    const int j = (index / width_col / height_col / batch_size) % kernel_w;\n    const int i = (index / width_col / height_col / batch_size / kernel_w) % kernel_h;\n    const int c = index / width_col / height_col / batch_size / kernel_w / kernel_h;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / channel_per_deformable_group;\n\n    int w_out = index % width_col;\n    int h_out = (index / width_col) % height_col;\n    int b = (index / width_col / height_col) % batch_size;\n    int w_in = w_out * stride_w - pad_w;\n    int h_in = h_out * stride_h - pad_h;\n\n    const scalar_t *data_offset_ptr = data_offset + (b * deformable_group + deformable_group_index) * 2 * kernel_h * kernel_w * height_col * width_col;\n    const scalar_t *data_mask_ptr = data_mask + (b * deformable_group + deformable_group_index) * kernel_h * kernel_w * height_col * width_col;\n    const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out;\n    const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out;\n    const int data_mask_hw_ptr = ((i * kernel_w + j) * height_col + h_out) * width_col + w_out;\n    const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n    const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n    const scalar_t mask = data_mask_ptr[data_mask_hw_ptr];\n    const scalar_t cur_inv_h_data = h_in + i * dilation_h + offset_h;\n    const scalar_t cur_inv_w_data = w_in + j * dilation_w + offset_w;\n\n    const scalar_t cur_top_grad = data_col[index] * mask;\n    const int cur_h = (int)cur_inv_h_data;\n    const int cur_w = (int)cur_inv_w_data;\n    for (int dy = -2; dy <= 2; dy++)\n    {\n      for (int dx = -2; dx <= 2; dx++)\n      {\n        if (cur_h + dy >= 0 && cur_h + dy < height &&\n            cur_w + dx >= 0 && cur_w + dx < width &&\n            abs(cur_inv_h_data - (cur_h + dy)) < 1 &&\n            abs(cur_inv_w_data - (cur_w + dx)) < 1)\n        {\n          int cur_bottom_grad_pos = ((b * channels + c) * height + cur_h + dy) * width + cur_w + dx;\n          scalar_t weight = dmcn_get_gradient_weight(cur_inv_h_data, cur_inv_w_data, cur_h + dy, cur_w + dx, height, width);\n          atomicAdd(grad_im + cur_bottom_grad_pos, weight * cur_top_grad);\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void modulated_deformable_col2im_coord_gpu_kernel(const int n,\n                                                             const scalar_t *data_col, const scalar_t *data_im,\n                                                             const scalar_t *data_offset, const scalar_t *data_mask,\n                                                             const int channels, const int height, const int width,\n                                                             const int kernel_h, const int kernel_w,\n                                                             const int pad_h, const int pad_w,\n                                                             const int stride_h, const int stride_w,\n                                                             const int dilation_h, const int dilation_w,\n                                                             const int channel_per_deformable_group,\n                                                             const int batch_size, const int offset_channels, const int deformable_group,\n                                                             const int height_col, const int width_col,\n                                                             scalar_t *grad_offset, scalar_t *grad_mask)\n{\n  CUDA_KERNEL_LOOP(index, n)\n  {\n    scalar_t val = 0, mval = 0;\n    int w = index % width_col;\n    int h = (index / width_col) % height_col;\n    int c = (index / width_col / height_col) % offset_channels;\n    int b = (index / width_col / height_col) / offset_channels;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / (2 * kernel_h * kernel_w);\n    const int col_step = kernel_h * kernel_w;\n    int cnt = 0;\n    const scalar_t *data_col_ptr = data_col + deformable_group_index * channel_per_deformable_group * batch_size * width_col * height_col;\n    const scalar_t *data_im_ptr = data_im + (b * deformable_group + deformable_group_index) * channel_per_deformable_group / kernel_h / kernel_w * height * width;\n    const scalar_t *data_offset_ptr = data_offset + (b * deformable_group + deformable_group_index) * 2 * kernel_h * kernel_w * height_col * width_col;\n    const scalar_t *data_mask_ptr = data_mask + (b * deformable_group + deformable_group_index) * kernel_h * kernel_w * height_col * width_col;\n\n    const int offset_c = c - deformable_group_index * 2 * kernel_h * kernel_w;\n\n    for (int col_c = (offset_c / 2); col_c < channel_per_deformable_group; col_c += col_step)\n    {\n      const int col_pos = (((col_c * batch_size + b) * height_col) + h) * width_col + w;\n      const int bp_dir = offset_c % 2;\n\n      int j = (col_pos / width_col / height_col / batch_size) % kernel_w;\n      int i = (col_pos / width_col / height_col / batch_size / kernel_w) % kernel_h;\n      int w_out = col_pos % width_col;\n      int h_out = (col_pos / width_col) % height_col;\n      int w_in = w_out * stride_w - pad_w;\n      int h_in = h_out * stride_h - pad_h;\n      const int data_offset_h_ptr = (((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out);\n      const int data_offset_w_ptr = (((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out);\n      const int data_mask_hw_ptr = (((i * kernel_w + j) * height_col + h_out) * width_col + w_out);\n      const scalar_t offset_h = data_offset_ptr[data_offset_h_ptr];\n      const scalar_t offset_w = data_offset_ptr[data_offset_w_ptr];\n      const scalar_t mask = data_mask_ptr[data_mask_hw_ptr];\n      scalar_t inv_h = h_in + i * dilation_h + offset_h;\n      scalar_t inv_w = w_in + j * dilation_w + offset_w;\n      if (inv_h <= -1 || inv_w <= -1 || inv_h >= height || inv_w >= width)\n      {\n        inv_h = inv_w = -2;\n      }\n      else\n      {\n        mval += data_col_ptr[col_pos] * dmcn_im2col_bilinear(data_im_ptr + cnt * height * width, width, height, width, inv_h, inv_w);\n      }\n      const scalar_t weight = dmcn_get_coordinate_weight(\n          inv_h, inv_w,\n          height, width, data_im_ptr + cnt * height * width, width, bp_dir);\n      val += weight * data_col_ptr[col_pos] * mask;\n      cnt += 1;\n    }\n    // KERNEL_ASSIGN(grad_offset[index], offset_req, val);\n    grad_offset[index] = val;\n    if (offset_c % 2 == 0)\n      // KERNEL_ASSIGN(grad_mask[(((b * deformable_group + deformable_group_index) * kernel_h * kernel_w + offset_c / 2) * height_col + h) * width_col + w], mask_req, mval);\n      grad_mask[(((b * deformable_group + deformable_group_index) * kernel_h * kernel_w + offset_c / 2) * height_col + h) * width_col + w] = mval;\n  }\n}\n\nvoid modulated_deformable_im2col_cuda(\n    const at::Tensor data_im, const at::Tensor data_offset, const at::Tensor data_mask,\n    const int batch_size, const int channels, const int height_im, const int width_im,\n    const int height_col, const int width_col, const int kernel_h, const int kenerl_w,\n    const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w,\n    const int deformable_group, at::Tensor data_col)\n{\n  // num_axes should be smaller than block size\n  const int channel_per_deformable_group = channels / deformable_group;\n  const int num_kernels = channels * batch_size * height_col * width_col;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_im.scalar_type(), \"modulated_deformable_im2col_gpu\", ([&] {\n        const scalar_t *data_im_ = data_im.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        const scalar_t *data_mask_ = data_mask.data_ptr<scalar_t>();\n        scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n\n        modulated_deformable_im2col_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_im_, data_offset_, data_mask_, height_im, width_im, kernel_h, kenerl_w,\n            pad_h, pad_w, stride_h, stride_w, dilation_h, dilation_w, channel_per_deformable_group,\n            batch_size, channels, deformable_group, height_col, width_col, data_col_);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in modulated_deformable_im2col_cuda: %s\\n\", cudaGetErrorString(err));\n  }\n}\n\nvoid modulated_deformable_col2im_cuda(\n    const at::Tensor data_col, const at::Tensor data_offset, const at::Tensor data_mask,\n    const int batch_size, const int channels, const int height_im, const int width_im,\n    const int height_col, const int width_col, const int kernel_h, const int kernel_w,\n    const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w,\n    const int deformable_group, at::Tensor grad_im)\n{\n\n  const int channel_per_deformable_group = channels / deformable_group;\n  const int num_kernels = channels * kernel_h * kernel_w * batch_size * height_col * width_col;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_col.scalar_type(), \"modulated_deformable_col2im_gpu\", ([&] {\n        const scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        const scalar_t *data_mask_ = data_mask.data_ptr<scalar_t>();\n        scalar_t *grad_im_ = grad_im.data_ptr<scalar_t>();\n\n        modulated_deformable_col2im_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_col_, data_offset_, data_mask_, channels, height_im, width_im,\n            kernel_h, kernel_w, pad_h, pad_w, stride_h, stride_w,\n            dilation_h, dilation_w, channel_per_deformable_group,\n            batch_size, deformable_group, height_col, width_col, grad_im_);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in modulated_deformable_col2im_cuda: %s\\n\", cudaGetErrorString(err));\n  }\n}\n\nvoid modulated_deformable_col2im_coord_cuda(\n    const at::Tensor data_col, const at::Tensor data_im, const at::Tensor data_offset, const at::Tensor data_mask,\n    const int batch_size, const int channels, const int height_im, const int width_im,\n    const int height_col, const int width_col, const int kernel_h, const int kernel_w,\n    const int pad_h, const int pad_w, const int stride_h, const int stride_w,\n    const int dilation_h, const int dilation_w,\n    const int deformable_group,\n    at::Tensor grad_offset, at::Tensor grad_mask)\n{\n  const int num_kernels = batch_size * height_col * width_col * 2 * kernel_h * kernel_w * deformable_group;\n  const int channel_per_deformable_group = channels * kernel_h * kernel_w / deformable_group;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data_col.scalar_type(), \"modulated_deformable_col2im_coord_gpu\", ([&] {\n        const scalar_t *data_col_ = data_col.data_ptr<scalar_t>();\n        const scalar_t *data_im_ = data_im.data_ptr<scalar_t>();\n        const scalar_t *data_offset_ = data_offset.data_ptr<scalar_t>();\n        const scalar_t *data_mask_ = data_mask.data_ptr<scalar_t>();\n        scalar_t *grad_offset_ = grad_offset.data_ptr<scalar_t>();\n        scalar_t *grad_mask_ = grad_mask.data_ptr<scalar_t>();\n\n        modulated_deformable_col2im_coord_gpu_kernel<<<GET_BLOCKS(num_kernels), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            num_kernels, data_col_, data_im_, data_offset_, data_mask_, channels, height_im, width_im,\n            kernel_h, kernel_w, pad_h, pad_w, stride_h, stride_w,\n            dilation_h, dilation_w, channel_per_deformable_group,\n            batch_size, 2 * kernel_h * kernel_w * deformable_group, deformable_group, height_col, width_col,\n            grad_offset_, grad_mask_);\n      }));\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in modulated_deformable_col2im_coord_cuda: %s\\n\", cudaGetErrorString(err));\n  }\n}\n"
  },
  {
    "path": "code/mmdet/ops/dcn/src/cuda/deform_pool_cuda.cpp",
    "content": "// modify from\n// https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/blob/mmdetection/mmdet/ops/dcn/src/modulated_dcn_cuda.c\n\n// based on\n// author: Charles Shang\n// https://github.com/torch/cunn/blob/master/lib/THCUNN/generic/SpatialConvolutionMM.cu\n\n#include <torch/extension.h>\n#include <ATen/DeviceGuard.h>\n\n#include <cmath>\n#include <vector>\n\nvoid DeformablePSROIPoolForward(\n    const at::Tensor data, const at::Tensor bbox, const at::Tensor trans,\n    at::Tensor out, at::Tensor top_count, const int batch, const int channels,\n    const int height, const int width, const int num_bbox,\n    const int channels_trans, const int no_trans, const float spatial_scale,\n    const int output_dim, const int group_size, const int pooled_size,\n    const int part_size, const int sample_per_part, const float trans_std);\n\nvoid DeformablePSROIPoolBackwardAcc(\n    const at::Tensor out_grad, const at::Tensor data, const at::Tensor bbox,\n    const at::Tensor trans, const at::Tensor top_count, at::Tensor in_grad,\n    at::Tensor trans_grad, const int batch, const int channels,\n    const int height, const int width, const int num_bbox,\n    const int channels_trans, const int no_trans, const float spatial_scale,\n    const int output_dim, const int group_size, const int pooled_size,\n    const int part_size, const int sample_per_part, const float trans_std);\n\nvoid deform_psroi_pooling_cuda_forward(\n    at::Tensor input, at::Tensor bbox, at::Tensor trans, at::Tensor out,\n    at::Tensor top_count, const int no_trans, const float spatial_scale,\n    const int output_dim, const int group_size, const int pooled_size,\n    const int part_size, const int sample_per_part, const float trans_std) {\n  TORCH_CHECK(input.is_contiguous(), \"input tensor has to be contiguous\");\n  at::DeviceGuard guard(input.device());\n\n  const int batch = input.size(0);\n  const int channels = input.size(1);\n  const int height = input.size(2);\n  const int width = input.size(3);\n  const int channels_trans = no_trans ? 2 : trans.size(1);\n\n  const int num_bbox = bbox.size(0);\n  if (num_bbox != out.size(0))\n    AT_ERROR(\"Output shape and bbox number wont match: (%d vs %d).\",\n             out.size(0), num_bbox);\n\n  DeformablePSROIPoolForward(\n      input, bbox, trans, out, top_count, batch, channels, height, width,\n      num_bbox, channels_trans, no_trans, spatial_scale, output_dim, group_size,\n      pooled_size, part_size, sample_per_part, trans_std);\n}\n\nvoid deform_psroi_pooling_cuda_backward(\n    at::Tensor out_grad, at::Tensor input, at::Tensor bbox, at::Tensor trans,\n    at::Tensor top_count, at::Tensor input_grad, at::Tensor trans_grad,\n    const int no_trans, const float spatial_scale, const int output_dim,\n    const int group_size, const int pooled_size, const int part_size,\n    const int sample_per_part, const float trans_std) {\n  TORCH_CHECK(out_grad.is_contiguous(), \"out_grad tensor has to be contiguous\");\n  TORCH_CHECK(input.is_contiguous(), \"input tensor has to be contiguous\");\n  at::DeviceGuard guard(input.device());\n\n  const int batch = input.size(0);\n  const int channels = input.size(1);\n  const int height = input.size(2);\n  const int width = input.size(3);\n  const int channels_trans = no_trans ? 2 : trans.size(1);\n\n  const int num_bbox = bbox.size(0);\n  if (num_bbox != out_grad.size(0))\n    AT_ERROR(\"Output shape and bbox number wont match: (%d vs %d).\",\n             out_grad.size(0), num_bbox);\n\n  DeformablePSROIPoolBackwardAcc(\n      out_grad, input, bbox, trans, top_count, input_grad, trans_grad, batch,\n      channels, height, width, num_bbox, channels_trans, no_trans,\n      spatial_scale, output_dim, group_size, pooled_size, part_size,\n      sample_per_part, trans_std);\n}\n"
  },
  {
    "path": "code/mmdet/ops/dcn/src/cuda/deform_pool_cuda_kernel.cu",
    "content": "/*!\n * Copyright (c) 2017 Microsoft\n * Licensed under The MIT License [see LICENSE for details]\n * \\file deformable_psroi_pooling.cu\n * \\brief\n * \\author Yi Li, Guodong Zhang, Jifeng Dai\n*/\n/***************** Adapted by Charles Shang *********************/\n// modify from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/blob/mmdetection/mmdet/ops/dcn/src/cuda/deform_psroi_pooling_cuda.cu\n\n#include <ATen/ATen.h>\n#include <THC/THCAtomics.cuh>\n#include <stdio.h>\n#include <math.h>\n#include <algorithm>\n\nusing namespace at;\n\n#define CUDA_KERNEL_LOOP(i, n)                        \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; \\\n       i < (n);                                       \\\n       i += blockDim.x * gridDim.x)\n\nconst int CUDA_NUM_THREADS = 1024;\ninline int GET_BLOCKS(const int N)\n{\n  return (N + CUDA_NUM_THREADS - 1) / CUDA_NUM_THREADS;\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t bilinear_interp(\n    const scalar_t *data,\n    const scalar_t x,\n    const scalar_t y,\n    const int width,\n    const int height)\n{\n  int x1 = floor(x);\n  int x2 = ceil(x);\n  int y1 = floor(y);\n  int y2 = ceil(y);\n  scalar_t dist_x = (scalar_t)(x - x1);\n  scalar_t dist_y = (scalar_t)(y - y1);\n  scalar_t value11 = data[y1 * width + x1];\n  scalar_t value12 = data[y2 * width + x1];\n  scalar_t value21 = data[y1 * width + x2];\n  scalar_t value22 = data[y2 * width + x2];\n  scalar_t value = (1 - dist_x) * (1 - dist_y) * value11 + (1 - dist_x) * dist_y * value12 + dist_x * (1 - dist_y) * value21 + dist_x * dist_y * value22;\n  return value;\n}\n\ntemplate <typename scalar_t>\n__global__ void DeformablePSROIPoolForwardKernel(\n    const int count,\n    const scalar_t *bottom_data,\n    const scalar_t spatial_scale,\n    const int channels,\n    const int height, const int width,\n    const int pooled_height, const int pooled_width,\n    const scalar_t *bottom_rois, const scalar_t *bottom_trans,\n    const int no_trans,\n    const scalar_t trans_std,\n    const int sample_per_part,\n    const int output_dim,\n    const int group_size,\n    const int part_size,\n    const int num_classes,\n    const int channels_each_class,\n    scalar_t *top_data,\n    scalar_t *top_count)\n{\n  CUDA_KERNEL_LOOP(index, count)\n  {\n    // The output is in order (n, ctop, ph, pw)\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int ctop = (index / pooled_width / pooled_height) % output_dim;\n    int n = index / pooled_width / pooled_height / output_dim;\n\n    // [start, end) interval for spatial sampling\n    const scalar_t *offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    scalar_t roi_start_w = (scalar_t)(round(offset_bottom_rois[1])) * spatial_scale - 0.5;\n    scalar_t roi_start_h = (scalar_t)(round(offset_bottom_rois[2])) * spatial_scale - 0.5;\n    scalar_t roi_end_w = (scalar_t)(round(offset_bottom_rois[3]) + 1.) * spatial_scale - 0.5;\n    scalar_t roi_end_h = (scalar_t)(round(offset_bottom_rois[4]) + 1.) * spatial_scale - 0.5;\n\n    // Force too small ROIs to be 1x1\n    scalar_t roi_width = max(roi_end_w - roi_start_w, 0.1); //avoid 0\n    scalar_t roi_height = max(roi_end_h - roi_start_h, 0.1);\n\n    // Compute w and h at bottom\n    scalar_t bin_size_h = roi_height / (scalar_t)(pooled_height);\n    scalar_t bin_size_w = roi_width / (scalar_t)(pooled_width);\n\n    scalar_t sub_bin_size_h = bin_size_h / (scalar_t)(sample_per_part);\n    scalar_t sub_bin_size_w = bin_size_w / (scalar_t)(sample_per_part);\n\n    int part_h = floor((scalar_t)(ph) / pooled_height * part_size);\n    int part_w = floor((scalar_t)(pw) / pooled_width * part_size);\n    int class_id = ctop / channels_each_class;\n    scalar_t trans_x = no_trans ? (scalar_t)(0) : bottom_trans[(((n * num_classes + class_id) * 2) * part_size + part_h) * part_size + part_w] * (scalar_t)trans_std;\n    scalar_t trans_y = no_trans ? (scalar_t)(0) : bottom_trans[(((n * num_classes + class_id) * 2 + 1) * part_size + part_h) * part_size + part_w] * (scalar_t)trans_std;\n\n    scalar_t wstart = (scalar_t)(pw)*bin_size_w + roi_start_w;\n    wstart += trans_x * roi_width;\n    scalar_t hstart = (scalar_t)(ph)*bin_size_h + roi_start_h;\n    hstart += trans_y * roi_height;\n\n    scalar_t sum = 0;\n    int count = 0;\n    int gw = floor((scalar_t)(pw)*group_size / pooled_width);\n    int gh = floor((scalar_t)(ph)*group_size / pooled_height);\n    gw = min(max(gw, 0), group_size - 1);\n    gh = min(max(gh, 0), group_size - 1);\n\n    const scalar_t *offset_bottom_data = bottom_data + (roi_batch_ind * channels) * height * width;\n    for (int ih = 0; ih < sample_per_part; ih++)\n    {\n      for (int iw = 0; iw < sample_per_part; iw++)\n      {\n        scalar_t w = wstart + iw * sub_bin_size_w;\n        scalar_t h = hstart + ih * sub_bin_size_h;\n        // bilinear interpolation\n        if (w < -0.5 || w > width - 0.5 || h < -0.5 || h > height - 0.5)\n        {\n          continue;\n        }\n        w = min(max(w, 0.), width - 1.);\n        h = min(max(h, 0.), height - 1.);\n        int c = (ctop * group_size + gh) * group_size + gw;\n        scalar_t val = bilinear_interp(offset_bottom_data + c * height * width, w, h, width, height);\n        sum += val;\n        count++;\n      }\n    }\n    top_data[index] = count == 0 ? (scalar_t)(0) : sum / count;\n    top_count[index] = count;\n  }\n}\n\ntemplate <typename scalar_t>\n__global__ void DeformablePSROIPoolBackwardAccKernel(\n    const int count,\n    const scalar_t *top_diff,\n    const scalar_t *top_count,\n    const int num_rois,\n    const scalar_t spatial_scale,\n    const int channels,\n    const int height, const int width,\n    const int pooled_height, const int pooled_width,\n    const int output_dim,\n    scalar_t *bottom_data_diff, scalar_t *bottom_trans_diff,\n    const scalar_t *bottom_data,\n    const scalar_t *bottom_rois,\n    const scalar_t *bottom_trans,\n    const int no_trans,\n    const scalar_t trans_std,\n    const int sample_per_part,\n    const int group_size,\n    const int part_size,\n    const int num_classes,\n    const int channels_each_class)\n{\n  CUDA_KERNEL_LOOP(index, count)\n  {\n    // The output is in order (n, ctop, ph, pw)\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int ctop = (index / pooled_width / pooled_height) % output_dim;\n    int n = index / pooled_width / pooled_height / output_dim;\n\n    // [start, end) interval for spatial sampling\n    const scalar_t *offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    scalar_t roi_start_w = (scalar_t)(round(offset_bottom_rois[1])) * spatial_scale - 0.5;\n    scalar_t roi_start_h = (scalar_t)(round(offset_bottom_rois[2])) * spatial_scale - 0.5;\n    scalar_t roi_end_w = (scalar_t)(round(offset_bottom_rois[3]) + 1.) * spatial_scale - 0.5;\n    scalar_t roi_end_h = (scalar_t)(round(offset_bottom_rois[4]) + 1.) * spatial_scale - 0.5;\n\n    // Force too small ROIs to be 1x1\n    scalar_t roi_width = max(roi_end_w - roi_start_w, 0.1); //avoid 0\n    scalar_t roi_height = max(roi_end_h - roi_start_h, 0.1);\n\n    // Compute w and h at bottom\n    scalar_t bin_size_h = roi_height / (scalar_t)(pooled_height);\n    scalar_t bin_size_w = roi_width / (scalar_t)(pooled_width);\n\n    scalar_t sub_bin_size_h = bin_size_h / (scalar_t)(sample_per_part);\n    scalar_t sub_bin_size_w = bin_size_w / (scalar_t)(sample_per_part);\n\n    int part_h = floor((scalar_t)(ph) / pooled_height * part_size);\n    int part_w = floor((scalar_t)(pw) / pooled_width * part_size);\n    int class_id = ctop / channels_each_class;\n    scalar_t trans_x = no_trans ? (scalar_t)(0) : bottom_trans[(((n * num_classes + class_id) * 2) * part_size + part_h) * part_size + part_w] * (scalar_t)trans_std;\n    scalar_t trans_y = no_trans ? (scalar_t)(0) : bottom_trans[(((n * num_classes + class_id) * 2 + 1) * part_size + part_h) * part_size + part_w] * (scalar_t)trans_std;\n\n    scalar_t wstart = (scalar_t)(pw)*bin_size_w + roi_start_w;\n    wstart += trans_x * roi_width;\n    scalar_t hstart = (scalar_t)(ph)*bin_size_h + roi_start_h;\n    hstart += trans_y * roi_height;\n\n    if (top_count[index] <= 0)\n    {\n      continue;\n    }\n    scalar_t diff_val = top_diff[index] / top_count[index];\n    const scalar_t *offset_bottom_data = bottom_data + roi_batch_ind * channels * height * width;\n    scalar_t *offset_bottom_data_diff = bottom_data_diff + roi_batch_ind * channels * height * width;\n    int gw = floor((scalar_t)(pw)*group_size / pooled_width);\n    int gh = floor((scalar_t)(ph)*group_size / pooled_height);\n    gw = min(max(gw, 0), group_size - 1);\n    gh = min(max(gh, 0), group_size - 1);\n\n    for (int ih = 0; ih < sample_per_part; ih++)\n    {\n      for (int iw = 0; iw < sample_per_part; iw++)\n      {\n        scalar_t w = wstart + iw * sub_bin_size_w;\n        scalar_t h = hstart + ih * sub_bin_size_h;\n        // bilinear interpolation\n        if (w < -0.5 || w > width - 0.5 || h < -0.5 || h > height - 0.5)\n        {\n          continue;\n        }\n        w = min(max(w, 0.), width - 1.);\n        h = min(max(h, 0.), height - 1.);\n        int c = (ctop * group_size + gh) * group_size + gw;\n        // backward on feature\n        int x0 = floor(w);\n        int x1 = ceil(w);\n        int y0 = floor(h);\n        int y1 = ceil(h);\n        scalar_t dist_x = w - x0, dist_y = h - y0;\n        scalar_t q00 = (1 - dist_x) * (1 - dist_y);\n        scalar_t q01 = (1 - dist_x) * dist_y;\n        scalar_t q10 = dist_x * (1 - dist_y);\n        scalar_t q11 = dist_x * dist_y;\n        int bottom_index_base = c * height * width;\n        atomicAdd(offset_bottom_data_diff + bottom_index_base + y0 * width + x0, q00 * diff_val);\n        atomicAdd(offset_bottom_data_diff + bottom_index_base + y1 * width + x0, q01 * diff_val);\n        atomicAdd(offset_bottom_data_diff + bottom_index_base + y0 * width + x1, q10 * diff_val);\n        atomicAdd(offset_bottom_data_diff + bottom_index_base + y1 * width + x1, q11 * diff_val);\n\n        if (no_trans)\n        {\n          continue;\n        }\n        scalar_t U00 = offset_bottom_data[bottom_index_base + y0 * width + x0];\n        scalar_t U01 = offset_bottom_data[bottom_index_base + y1 * width + x0];\n        scalar_t U10 = offset_bottom_data[bottom_index_base + y0 * width + x1];\n        scalar_t U11 = offset_bottom_data[bottom_index_base + y1 * width + x1];\n        scalar_t diff_x = (U11 * dist_y + U10 * (1 - dist_y) - U01 * dist_y - U00 * (1 - dist_y)) * trans_std * diff_val;\n        diff_x *= roi_width;\n        scalar_t diff_y = (U11 * dist_x + U01 * (1 - dist_x) - U10 * dist_x - U00 * (1 - dist_x)) * trans_std * diff_val;\n        diff_y *= roi_height;\n\n        atomicAdd(bottom_trans_diff + (((n * num_classes + class_id) * 2) * part_size + part_h) * part_size + part_w, diff_x);\n        atomicAdd(bottom_trans_diff + (((n * num_classes + class_id) * 2 + 1) * part_size + part_h) * part_size + part_w, diff_y);\n      }\n    }\n  }\n}\n\nvoid DeformablePSROIPoolForward(const at::Tensor data,\n                                const at::Tensor bbox,\n                                const at::Tensor trans,\n                                at::Tensor out,\n                                at::Tensor top_count,\n                                const int batch,\n                                const int channels,\n                                const int height,\n                                const int width,\n                                const int num_bbox,\n                                const int channels_trans,\n                                const int no_trans,\n                                const float spatial_scale,\n                                const int output_dim,\n                                const int group_size,\n                                const int pooled_size,\n                                const int part_size,\n                                const int sample_per_part,\n                                const float trans_std)\n{\n  const int pooled_height = pooled_size;\n  const int pooled_width = pooled_size;\n  const int count = num_bbox * output_dim * pooled_height * pooled_width;\n  const int num_classes = no_trans ? 1 : channels_trans / 2;\n  const int channels_each_class = no_trans ? output_dim : output_dim / num_classes;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      data.scalar_type(), \"deformable_psroi_pool_forward\", ([&] {\n        const scalar_t *bottom_data = data.data_ptr<scalar_t>();\n        const scalar_t *bottom_rois = bbox.data_ptr<scalar_t>();\n        const scalar_t *bottom_trans = no_trans ? NULL : trans.data_ptr<scalar_t>();\n        scalar_t *top_data = out.data_ptr<scalar_t>();\n        scalar_t *top_count_data = top_count.data_ptr<scalar_t>();\n\n        DeformablePSROIPoolForwardKernel<<<GET_BLOCKS(count), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            count, bottom_data, (scalar_t)spatial_scale, channels, height, width, pooled_height, pooled_width,\n            bottom_rois, bottom_trans, no_trans, (scalar_t)trans_std, sample_per_part, output_dim,\n            group_size, part_size, num_classes, channels_each_class, top_data, top_count_data);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in DeformablePSROIPoolForward: %s\\n\", cudaGetErrorString(err));\n  }\n}\n\nvoid DeformablePSROIPoolBackwardAcc(const at::Tensor out_grad,\n                                    const at::Tensor data,\n                                    const at::Tensor bbox,\n                                    const at::Tensor trans,\n                                    const at::Tensor top_count,\n                                    at::Tensor in_grad,\n                                    at::Tensor trans_grad,\n                                    const int batch,\n                                    const int channels,\n                                    const int height,\n                                    const int width,\n                                    const int num_bbox,\n                                    const int channels_trans,\n                                    const int no_trans,\n                                    const float spatial_scale,\n                                    const int output_dim,\n                                    const int group_size,\n                                    const int pooled_size,\n                                    const int part_size,\n                                    const int sample_per_part,\n                                    const float trans_std)\n{\n  // LOG(INFO) << \"DeformablePSROIPoolBackward\";\n  const int num_rois = num_bbox;\n  const int pooled_height = pooled_size;\n  const int pooled_width = pooled_size;\n  const int count = num_bbox * output_dim * pooled_height * pooled_width;\n  const int num_classes = no_trans ? 1 : channels_trans / 2;\n  const int channels_each_class = no_trans ? output_dim : output_dim / num_classes;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      out_grad.scalar_type(), \"deformable_psroi_pool_backward_acc\", ([&] {\n        const scalar_t *top_diff = out_grad.data_ptr<scalar_t>();\n        const scalar_t *bottom_data = data.data_ptr<scalar_t>();\n        const scalar_t *bottom_rois = bbox.data_ptr<scalar_t>();\n        const scalar_t *bottom_trans = no_trans ? NULL : trans.data_ptr<scalar_t>();\n        scalar_t *bottom_data_diff = in_grad.data_ptr<scalar_t>();\n        scalar_t *bottom_trans_diff = no_trans ? NULL : trans_grad.data_ptr<scalar_t>();\n        const scalar_t *top_count_data = top_count.data_ptr<scalar_t>();\n\n        DeformablePSROIPoolBackwardAccKernel<<<GET_BLOCKS(count), CUDA_NUM_THREADS, 0, at::cuda::getCurrentCUDAStream()>>>(\n            count, top_diff, top_count_data, num_rois, (scalar_t)spatial_scale, channels, height, width,\n            pooled_height, pooled_width, output_dim, bottom_data_diff, bottom_trans_diff,\n            bottom_data, bottom_rois, bottom_trans, no_trans, (scalar_t)trans_std, sample_per_part,\n            group_size, part_size, num_classes, channels_each_class);\n      }));\n\n  cudaError_t err = cudaGetLastError();\n  if (err != cudaSuccess)\n  {\n    printf(\"error in DeformablePSROIPoolForward: %s\\n\", cudaGetErrorString(err));\n  }\n}\n"
  },
  {
    "path": "code/mmdet/ops/dcn/src/deform_conv_ext.cpp",
    "content": "// modify from\n// https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/blob/mmdetection/mmdet/ops/dcn/src/deform_conv_cuda.c\n\n#include <torch/extension.h>\n#include <ATen/DeviceGuard.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nint deform_conv_forward_cuda(at::Tensor input, at::Tensor weight,\n                             at::Tensor offset, at::Tensor output,\n                             at::Tensor columns, at::Tensor ones, int kW,\n                             int kH, int dW, int dH, int padW, int padH,\n                             int dilationW, int dilationH, int group,\n                             int deformable_group, int im2col_step);\n\nint deform_conv_backward_input_cuda(at::Tensor input, at::Tensor offset,\n                                    at::Tensor gradOutput, at::Tensor gradInput,\n                                    at::Tensor gradOffset, at::Tensor weight,\n                                    at::Tensor columns, int kW, int kH, int dW,\n                                    int dH, int padW, int padH, int dilationW,\n                                    int dilationH, int group,\n                                    int deformable_group, int im2col_step);\n\nint deform_conv_backward_parameters_cuda(\n    at::Tensor input, at::Tensor offset, at::Tensor gradOutput,\n    at::Tensor gradWeight,  // at::Tensor gradBias,\n    at::Tensor columns, at::Tensor ones, int kW, int kH, int dW, int dH,\n    int padW, int padH, int dilationW, int dilationH, int group,\n    int deformable_group, float scale, int im2col_step);\n\nvoid modulated_deform_conv_cuda_forward(\n    at::Tensor input, at::Tensor weight, at::Tensor bias, at::Tensor ones,\n    at::Tensor offset, at::Tensor mask, at::Tensor output, at::Tensor columns,\n    int kernel_h, int kernel_w, const int stride_h, const int stride_w,\n    const int pad_h, const int pad_w, const int dilation_h,\n    const int dilation_w, const int group, const int deformable_group,\n    const bool with_bias);\n\nvoid modulated_deform_conv_cuda_backward(\n    at::Tensor input, at::Tensor weight, at::Tensor bias, at::Tensor ones,\n    at::Tensor offset, at::Tensor mask, at::Tensor columns,\n    at::Tensor grad_input, at::Tensor grad_weight, at::Tensor grad_bias,\n    at::Tensor grad_offset, at::Tensor grad_mask, at::Tensor grad_output,\n    int kernel_h, int kernel_w, int stride_h, int stride_w, int pad_h,\n    int pad_w, int dilation_h, int dilation_w, int group, int deformable_group,\n    const bool with_bias);\n\nint pyramid_deform_conv_forward_cuda(at::Tensor input, at::Tensor weight,\n                                     at::Tensor offset, at::Tensor output,\n                                     at::Tensor columns, at::Tensor ones, int kW,\n                                     int kH, int dW, int dH, int padW, int padH,\n                                     int dilationW, int dilationH, float scaleW,\n                                     float scaleH, int group, int deformable_group, \n                                     int im2col_step);\n\nint pyramid_deform_conv_backward_input_cuda(at::Tensor input, at::Tensor offset,\n                                            at::Tensor gradOutput, at::Tensor gradInput,\n                                            at::Tensor gradOffset, at::Tensor weight,\n                                            at::Tensor columns, int kW, int kH, int dW,\n                                            int dH, int padW, int padH, int dilationW,\n                                            int dilationH, float scaleW, float scaleH, int group,\n                                            int deformable_group, int im2col_step);\n\nint pyramid_deform_conv_backward_parameters_cuda(\n                at::Tensor input, at::Tensor offset, at::Tensor gradOutput,\n                at::Tensor gradWeight,  // at::Tensor gradBias,\n                at::Tensor columns, at::Tensor ones, int kW, int kH, int dW, int dH,\n                int padW, int padH, int dilationW, int dilationH, float scaleW, float scaleH, \n                int group, int deformable_group, float scale, int im2col_step);\n#endif\n\nint deform_conv_forward(at::Tensor input, at::Tensor weight,\n                             at::Tensor offset, at::Tensor output,\n                             at::Tensor columns, at::Tensor ones, int kW,\n                             int kH, int dW, int dH, int padW, int padH,\n                             int dilationW, int dilationH, int group,\n                             int deformable_group, int im2col_step) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return deform_conv_forward_cuda(input, weight, offset, output, columns,\n        ones, kW, kH, dW, dH, padW, padH, dilationW, dilationH, group,\n        deformable_group, im2col_step);\n#else\n    AT_ERROR(\"deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"deform conv is not implemented on CPU\");\n}\n\nint deform_conv_backward_input(at::Tensor input, at::Tensor offset,\n                                    at::Tensor gradOutput, at::Tensor gradInput,\n                                    at::Tensor gradOffset, at::Tensor weight,\n                                    at::Tensor columns, int kW, int kH, int dW,\n                                    int dH, int padW, int padH, int dilationW,\n                                    int dilationH, int group,\n                                    int deformable_group, int im2col_step) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return deform_conv_backward_input_cuda(input, offset, gradOutput,\n        gradInput, gradOffset, weight, columns, kW, kH, dW, dH, padW, padH,\n        dilationW, dilationH, group, deformable_group, im2col_step);\n#else\n    AT_ERROR(\"deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"deform conv is not implemented on CPU\");\n}\n\nint deform_conv_backward_parameters(\n    at::Tensor input, at::Tensor offset, at::Tensor gradOutput,\n    at::Tensor gradWeight,  // at::Tensor gradBias,\n    at::Tensor columns, at::Tensor ones, int kW, int kH, int dW, int dH,\n    int padW, int padH, int dilationW, int dilationH, int group,\n    int deformable_group, float scale, int im2col_step) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return deform_conv_backward_parameters_cuda(input, offset, gradOutput,\n        gradWeight, columns, ones, kW, kH, dW, dH, padW, padH, dilationW,\n        dilationH, group, deformable_group, scale, im2col_step);\n#else\n    AT_ERROR(\"deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"deform conv is not implemented on CPU\");\n}\n\nvoid modulated_deform_conv_forward(\n    at::Tensor input, at::Tensor weight, at::Tensor bias, at::Tensor ones,\n    at::Tensor offset, at::Tensor mask, at::Tensor output, at::Tensor columns,\n    int kernel_h, int kernel_w, const int stride_h, const int stride_w,\n    const int pad_h, const int pad_w, const int dilation_h,\n    const int dilation_w, const int group, const int deformable_group,\n    const bool with_bias) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return modulated_deform_conv_cuda_forward(input, weight, bias, ones,\n        offset, mask, output, columns, kernel_h, kernel_w, stride_h,\n        stride_w, pad_h, pad_w, dilation_h, dilation_w, group,\n        deformable_group, with_bias);\n#else\n    AT_ERROR(\"modulated deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"modulated deform conv is not implemented on CPU\");\n}\n\nvoid modulated_deform_conv_backward(\n    at::Tensor input, at::Tensor weight, at::Tensor bias, at::Tensor ones,\n    at::Tensor offset, at::Tensor mask, at::Tensor columns,\n    at::Tensor grad_input, at::Tensor grad_weight, at::Tensor grad_bias,\n    at::Tensor grad_offset, at::Tensor grad_mask, at::Tensor grad_output,\n    int kernel_h, int kernel_w, int stride_h, int stride_w, int pad_h,\n    int pad_w, int dilation_h, int dilation_w, int group, int deformable_group,\n    const bool with_bias) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return modulated_deform_conv_cuda_backward(input, weight, bias, ones,\n        offset, mask, columns, grad_input, grad_weight, grad_bias, grad_offset,\n        grad_mask, grad_output, kernel_h, kernel_w, stride_h, stride_w,\n        pad_h, pad_w, dilation_h, dilation_w, group, deformable_group,\n        with_bias);\n#else\n    AT_ERROR(\"modulated deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"modulated deform conv is not implemented on CPU\");\n}\n\nint pyramid_deform_conv_forward(at::Tensor input, at::Tensor weight,\n                                at::Tensor offset, at::Tensor output,\n                                at::Tensor columns, at::Tensor ones, int kW,\n                                int kH, int dW, int dH, int padW, int padH,\n                                int dilationW, int dilationH, float scaleW, \n                                float scaleH, int group, int deformable_group, \n                                int im2col_step) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return pyramid_deform_conv_forward_cuda(input, weight, offset, output, columns,\n            ones, kW, kH, dW, dH, padW, padH, dilationW, dilationH, scaleW, scaleH,\n            group, deformable_group, im2col_step);\n#else\n    AT_ERROR(\"pyramid deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"pyramid deform conv is not implemented on CPU\");\n}\n\nint pyramid_deform_conv_backward_input(at::Tensor input, at::Tensor offset,\n                                       at::Tensor gradOutput, at::Tensor gradInput,\n                                       at::Tensor gradOffset, at::Tensor weight,\n                                       at::Tensor columns, int kW, int kH, int dW,\n                                       int dH, int padW, int padH, int dilationW,\n                                       int dilationH, float scaleW, float scaleH,\n                                       int group, int deformable_group, int im2col_step) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return pyramid_deform_conv_backward_input_cuda(input, offset, gradOutput,\n        gradInput, gradOffset, weight, columns, kW, kH, dW, dH, padW, padH,\n        dilationW, dilationH, scaleW, scaleH, group, deformable_group, im2col_step);\n#else\n    AT_ERROR(\"pyramid deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"pyramid deform conv is not implemented on CPU\");\n}\n\nint pyramid_deform_conv_backward_parameters(\n    at::Tensor input, at::Tensor offset, at::Tensor gradOutput,\n    at::Tensor gradWeight,  // at::Tensor gradBias,\n    at::Tensor columns, at::Tensor ones, int kW, int kH, int dW, int dH,\n    int padW, int padH, int dilationW, int dilationH, float scaleW, float scaleH, \n    int group, int deformable_group, float scale, int im2col_step) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return pyramid_deform_conv_backward_parameters_cuda(input, offset, gradOutput,\n        gradWeight, columns, ones, kW, kH, dW, dH, padW, padH, dilationW,\n        dilationH, scaleW, scaleH, group, deformable_group, scale, im2col_step);\n#else\n    AT_ERROR(\"pyramid deform conv is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"pyramid deform conv is not implemented on CPU\");\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"deform_conv_forward\", &deform_conv_forward,\n        \"deform forward\");\n  m.def(\"deform_conv_backward_input\", &deform_conv_backward_input,\n        \"deform_conv_backward_input\");\n  m.def(\"deform_conv_backward_parameters\",\n        &deform_conv_backward_parameters,\n        \"deform_conv_backward_parameters\");\n  m.def(\"modulated_deform_conv_forward\",\n        &modulated_deform_conv_forward,\n        \"modulated deform conv forward\");\n  m.def(\"modulated_deform_conv_backward\",\n        &modulated_deform_conv_backward,\n        \"modulated deform conv backward\");\n  m.def(\"pyramid_deform_conv_forward\", \n         &pyramid_deform_conv_forward,\n        \"pyramid deform forward\");\n  m.def(\"pyramid_deform_conv_backward_input\", \n         &pyramid_deform_conv_backward_input,\n        \"pyramid deform_conv_backward_input\");\n  m.def(\"pyramid_deform_conv_backward_parameters\",\n        &pyramid_deform_conv_backward_parameters,\n        \"pyramid deform_conv_backward_parameters\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/dcn/src/deform_pool_ext.cpp",
    "content": "// modify from\n// https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/blob/mmdetection/mmdet/ops/dcn/src/modulated_dcn_cuda.c\n\n// based on\n// author: Charles Shang\n// https://github.com/torch/cunn/blob/master/lib/THCUNN/generic/SpatialConvolutionMM.cu\n\n#include <torch/extension.h>\n#include <ATen/DeviceGuard.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nvoid deform_psroi_pooling_cuda_forward(\n    at::Tensor input, at::Tensor bbox, at::Tensor trans, at::Tensor out,\n    at::Tensor top_count, const int no_trans, const float spatial_scale,\n    const int output_dim, const int group_size, const int pooled_size,\n    const int part_size, const int sample_per_part, const float trans_std);\n\nvoid deform_psroi_pooling_cuda_backward(\n    at::Tensor out_grad, at::Tensor input, at::Tensor bbox, at::Tensor trans,\n    at::Tensor top_count, at::Tensor input_grad, at::Tensor trans_grad,\n    const int no_trans, const float spatial_scale, const int output_dim,\n    const int group_size, const int pooled_size, const int part_size,\n    const int sample_per_part, const float trans_std);\n#endif\n\nvoid deform_psroi_pooling_forward(\n    at::Tensor input, at::Tensor bbox, at::Tensor trans, at::Tensor out,\n    at::Tensor top_count, const int no_trans, const float spatial_scale,\n    const int output_dim, const int group_size, const int pooled_size,\n    const int part_size, const int sample_per_part, const float trans_std) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return deform_psroi_pooling_cuda_forward(input, bbox, trans, out, top_count,\n        no_trans, spatial_scale, output_dim, group_size, pooled_size,\n        part_size, sample_per_part, trans_std);\n#else\n    AT_ERROR(\"deform psroi pooling is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"deform psroi pooling is not implemented on CPU\");\n}\n\nvoid deform_psroi_pooling_backward(\n    at::Tensor out_grad, at::Tensor input, at::Tensor bbox, at::Tensor trans,\n    at::Tensor top_count, at::Tensor input_grad, at::Tensor trans_grad,\n    const int no_trans, const float spatial_scale, const int output_dim,\n    const int group_size, const int pooled_size, const int part_size,\n    const int sample_per_part, const float trans_std) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return deform_psroi_pooling_cuda_backward(out_grad, input, bbox, trans,\n        top_count, input_grad, trans_grad, no_trans, spatial_scale,\n        output_dim, group_size, pooled_size, part_size, sample_per_part,\n        trans_std);\n#else\n    AT_ERROR(\"deform psroi pooling is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"deform psroi pooling is not implemented on CPU\");\n}\n\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"deform_psroi_pooling_forward\", &deform_psroi_pooling_forward,\n        \"deform psroi pooling forward\");\n  m.def(\"deform_psroi_pooling_backward\", &deform_psroi_pooling_backward,\n        \"deform psroi pooling backward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/generalized_attention.py",
    "content": "import math\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import kaiming_init\n\n\nclass GeneralizedAttention(nn.Module):\n    \"\"\"GeneralizedAttention module.\n\n    See 'An Empirical Study of Spatial Attention Mechanisms in Deep Networks'\n    (https://arxiv.org/abs/1711.07971) for details.\n\n    Args:\n        in_channels (int): Channels of the input feature map.\n        spatial_range (int): The spatial range.\n            -1 indicates no spatial range constraint.\n        num_heads (int): The head number of empirical_attention module.\n        position_embedding_dim (int): The position embedding dimension.\n        position_magnitude (int): A multiplier acting on coord difference.\n        kv_stride (int): The feature stride acting on key/value feature map.\n        q_stride (int): The feature stride acting on query feature map.\n        attention_type (str): A binary indicator string for indicating which\n            items in generalized empirical_attention module are used.\n            '1000' indicates 'query and key content' (appr - appr) item,\n            '0100' indicates 'query content and relative position'\n              (appr - position) item,\n            '0010' indicates 'key content only' (bias - appr) item,\n            '0001' indicates 'relative position only' (bias - position) item.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 spatial_range=-1,\n                 num_heads=9,\n                 position_embedding_dim=-1,\n                 position_magnitude=1,\n                 kv_stride=2,\n                 q_stride=1,\n                 attention_type='1111'):\n\n        super(GeneralizedAttention, self).__init__()\n\n        # hard range means local range for non-local operation\n        self.position_embedding_dim = (\n            position_embedding_dim\n            if position_embedding_dim > 0 else in_channels)\n\n        self.position_magnitude = position_magnitude\n        self.num_heads = num_heads\n        self.in_channels = in_channels\n        self.spatial_range = spatial_range\n        self.kv_stride = kv_stride\n        self.q_stride = q_stride\n        self.attention_type = [bool(int(_)) for _ in attention_type]\n        self.qk_embed_dim = in_channels // num_heads\n        out_c = self.qk_embed_dim * num_heads\n\n        if self.attention_type[0] or self.attention_type[1]:\n            self.query_conv = nn.Conv2d(\n                in_channels=in_channels,\n                out_channels=out_c,\n                kernel_size=1,\n                bias=False)\n            self.query_conv.kaiming_init = True\n\n        if self.attention_type[0] or self.attention_type[2]:\n            self.key_conv = nn.Conv2d(\n                in_channels=in_channels,\n                out_channels=out_c,\n                kernel_size=1,\n                bias=False)\n            self.key_conv.kaiming_init = True\n\n        self.v_dim = in_channels // num_heads\n        self.value_conv = nn.Conv2d(\n            in_channels=in_channels,\n            out_channels=self.v_dim * num_heads,\n            kernel_size=1,\n            bias=False)\n        self.value_conv.kaiming_init = True\n\n        if self.attention_type[1] or self.attention_type[3]:\n            self.appr_geom_fc_x = nn.Linear(\n                self.position_embedding_dim // 2, out_c, bias=False)\n            self.appr_geom_fc_x.kaiming_init = True\n\n            self.appr_geom_fc_y = nn.Linear(\n                self.position_embedding_dim // 2, out_c, bias=False)\n            self.appr_geom_fc_y.kaiming_init = True\n\n        if self.attention_type[2]:\n            stdv = 1.0 / math.sqrt(self.qk_embed_dim * 2)\n            appr_bias_value = -2 * stdv * torch.rand(out_c) + stdv\n            self.appr_bias = nn.Parameter(appr_bias_value)\n\n        if self.attention_type[3]:\n            stdv = 1.0 / math.sqrt(self.qk_embed_dim * 2)\n            geom_bias_value = -2 * stdv * torch.rand(out_c) + stdv\n            self.geom_bias = nn.Parameter(geom_bias_value)\n\n        self.proj_conv = nn.Conv2d(\n            in_channels=self.v_dim * num_heads,\n            out_channels=in_channels,\n            kernel_size=1,\n            bias=True)\n        self.proj_conv.kaiming_init = True\n        self.gamma = nn.Parameter(torch.zeros(1))\n\n        if self.spatial_range >= 0:\n            # only works when non local is after 3*3 conv\n            if in_channels == 256:\n                max_len = 84\n            elif in_channels == 512:\n                max_len = 42\n\n            max_len_kv = int((max_len - 1.0) / self.kv_stride + 1)\n            local_constraint_map = np.ones(\n                (max_len, max_len, max_len_kv, max_len_kv), dtype=np.int)\n            for iy in range(max_len):\n                for ix in range(max_len):\n                    local_constraint_map[\n                        iy, ix,\n                        max((iy - self.spatial_range) //\n                            self.kv_stride, 0):min((iy + self.spatial_range +\n                                                    1) // self.kv_stride +\n                                                   1, max_len),\n                        max((ix - self.spatial_range) //\n                            self.kv_stride, 0):min((ix + self.spatial_range +\n                                                    1) // self.kv_stride +\n                                                   1, max_len)] = 0\n\n            self.local_constraint_map = nn.Parameter(\n                torch.from_numpy(local_constraint_map).byte(),\n                requires_grad=False)\n\n        if self.q_stride > 1:\n            self.q_downsample = nn.AvgPool2d(\n                kernel_size=1, stride=self.q_stride)\n        else:\n            self.q_downsample = None\n\n        if self.kv_stride > 1:\n            self.kv_downsample = nn.AvgPool2d(\n                kernel_size=1, stride=self.kv_stride)\n        else:\n            self.kv_downsample = None\n\n        self.init_weights()\n\n    def get_position_embedding(self,\n                               h,\n                               w,\n                               h_kv,\n                               w_kv,\n                               q_stride,\n                               kv_stride,\n                               device,\n                               feat_dim,\n                               wave_length=1000):\n        h_idxs = torch.linspace(0, h - 1, h).cuda(device)\n        h_idxs = h_idxs.view((h, 1)) * q_stride\n\n        w_idxs = torch.linspace(0, w - 1, w).cuda(device)\n        w_idxs = w_idxs.view((w, 1)) * q_stride\n\n        h_kv_idxs = torch.linspace(0, h_kv - 1, h_kv).cuda(device)\n        h_kv_idxs = h_kv_idxs.view((h_kv, 1)) * kv_stride\n\n        w_kv_idxs = torch.linspace(0, w_kv - 1, w_kv).cuda(device)\n        w_kv_idxs = w_kv_idxs.view((w_kv, 1)) * kv_stride\n\n        # (h, h_kv, 1)\n        h_diff = h_idxs.unsqueeze(1) - h_kv_idxs.unsqueeze(0)\n        h_diff *= self.position_magnitude\n\n        # (w, w_kv, 1)\n        w_diff = w_idxs.unsqueeze(1) - w_kv_idxs.unsqueeze(0)\n        w_diff *= self.position_magnitude\n\n        feat_range = torch.arange(0, feat_dim / 4).cuda(device)\n\n        dim_mat = torch.Tensor([wave_length]).cuda(device)\n        dim_mat = dim_mat**((4. / feat_dim) * feat_range)\n        dim_mat = dim_mat.view((1, 1, -1))\n\n        embedding_x = torch.cat(\n            ((w_diff / dim_mat).sin(), (w_diff / dim_mat).cos()), dim=2)\n\n        embedding_y = torch.cat(\n            ((h_diff / dim_mat).sin(), (h_diff / dim_mat).cos()), dim=2)\n\n        return embedding_x, embedding_y\n\n    def forward(self, x_input):\n        num_heads = self.num_heads\n\n        # use empirical_attention\n        if self.q_downsample is not None:\n            x_q = self.q_downsample(x_input)\n        else:\n            x_q = x_input\n        n, _, h, w = x_q.shape\n\n        if self.kv_downsample is not None:\n            x_kv = self.kv_downsample(x_input)\n        else:\n            x_kv = x_input\n        _, _, h_kv, w_kv = x_kv.shape\n\n        if self.attention_type[0] or self.attention_type[1]:\n            proj_query = self.query_conv(x_q).view(\n                (n, num_heads, self.qk_embed_dim, h * w))\n            proj_query = proj_query.permute(0, 1, 3, 2)\n\n        if self.attention_type[0] or self.attention_type[2]:\n            proj_key = self.key_conv(x_kv).view(\n                (n, num_heads, self.qk_embed_dim, h_kv * w_kv))\n\n        if self.attention_type[1] or self.attention_type[3]:\n            position_embed_x, position_embed_y = self.get_position_embedding(\n                h, w, h_kv, w_kv, self.q_stride, self.kv_stride,\n                x_input.device, self.position_embedding_dim)\n            # (n, num_heads, w, w_kv, dim)\n            position_feat_x = self.appr_geom_fc_x(position_embed_x).\\\n                view(1, w, w_kv, num_heads, self.qk_embed_dim).\\\n                permute(0, 3, 1, 2, 4).\\\n                repeat(n, 1, 1, 1, 1)\n\n            # (n, num_heads, h, h_kv, dim)\n            position_feat_y = self.appr_geom_fc_y(position_embed_y).\\\n                view(1, h, h_kv, num_heads, self.qk_embed_dim).\\\n                permute(0, 3, 1, 2, 4).\\\n                repeat(n, 1, 1, 1, 1)\n\n            position_feat_x /= math.sqrt(2)\n            position_feat_y /= math.sqrt(2)\n\n        # accelerate for saliency only\n        if (np.sum(self.attention_type) == 1) and self.attention_type[2]:\n            appr_bias = self.appr_bias.\\\n                view(1, num_heads, 1, self.qk_embed_dim).\\\n                repeat(n, 1, 1, 1)\n\n            energy = torch.matmul(appr_bias, proj_key).\\\n                view(n, num_heads, 1, h_kv * w_kv)\n\n            h = 1\n            w = 1\n        else:\n            # (n, num_heads, h*w, h_kv*w_kv), query before key, 540mb for\n            if not self.attention_type[0]:\n                energy = torch.zeros(\n                    n,\n                    num_heads,\n                    h,\n                    w,\n                    h_kv,\n                    w_kv,\n                    dtype=x_input.dtype,\n                    device=x_input.device)\n\n            # attention_type[0]: appr - appr\n            # attention_type[1]: appr - position\n            # attention_type[2]: bias - appr\n            # attention_type[3]: bias - position\n            if self.attention_type[0] or self.attention_type[2]:\n                if self.attention_type[0] and self.attention_type[2]:\n                    appr_bias = self.appr_bias.\\\n                        view(1, num_heads, 1, self.qk_embed_dim)\n                    energy = torch.matmul(proj_query + appr_bias, proj_key).\\\n                        view(n, num_heads, h, w, h_kv, w_kv)\n\n                elif self.attention_type[0]:\n                    energy = torch.matmul(proj_query, proj_key).\\\n                        view(n, num_heads, h, w, h_kv, w_kv)\n\n                elif self.attention_type[2]:\n                    appr_bias = self.appr_bias.\\\n                        view(1, num_heads, 1, self.qk_embed_dim).\\\n                        repeat(n, 1, 1, 1)\n\n                    energy += torch.matmul(appr_bias, proj_key).\\\n                        view(n, num_heads, 1, 1, h_kv, w_kv)\n\n            if self.attention_type[1] or self.attention_type[3]:\n                if self.attention_type[1] and self.attention_type[3]:\n                    geom_bias = self.geom_bias.\\\n                        view(1, num_heads, 1, self.qk_embed_dim)\n\n                    proj_query_reshape = (proj_query + geom_bias).\\\n                        view(n, num_heads, h, w, self.qk_embed_dim)\n\n                    energy_x = torch.matmul(\n                        proj_query_reshape.permute(0, 1, 3, 2, 4),\n                        position_feat_x.permute(0, 1, 2, 4, 3))\n                    energy_x = energy_x.\\\n                        permute(0, 1, 3, 2, 4).unsqueeze(4)\n\n                    energy_y = torch.matmul(\n                        proj_query_reshape,\n                        position_feat_y.permute(0, 1, 2, 4, 3))\n                    energy_y = energy_y.unsqueeze(5)\n\n                    energy += energy_x + energy_y\n\n                elif self.attention_type[1]:\n                    proj_query_reshape = proj_query.\\\n                        view(n, num_heads, h, w, self.qk_embed_dim)\n                    proj_query_reshape = proj_query_reshape.\\\n                        permute(0, 1, 3, 2, 4)\n                    position_feat_x_reshape = position_feat_x.\\\n                        permute(0, 1, 2, 4, 3)\n                    position_feat_y_reshape = position_feat_y.\\\n                        permute(0, 1, 2, 4, 3)\n\n                    energy_x = torch.matmul(proj_query_reshape,\n                                            position_feat_x_reshape)\n                    energy_x = energy_x.permute(0, 1, 3, 2, 4).unsqueeze(4)\n\n                    energy_y = torch.matmul(proj_query_reshape,\n                                            position_feat_y_reshape)\n                    energy_y = energy_y.unsqueeze(5)\n\n                    energy += energy_x + energy_y\n\n                elif self.attention_type[3]:\n                    geom_bias = self.geom_bias.\\\n                        view(1, num_heads, self.qk_embed_dim, 1).\\\n                        repeat(n, 1, 1, 1)\n\n                    position_feat_x_reshape = position_feat_x.\\\n                        view(n, num_heads, w*w_kv, self.qk_embed_dim)\n\n                    position_feat_y_reshape = position_feat_y.\\\n                        view(n, num_heads, h * h_kv, self.qk_embed_dim)\n\n                    energy_x = torch.matmul(position_feat_x_reshape, geom_bias)\n                    energy_x = energy_x.view(n, num_heads, 1, w, 1, w_kv)\n\n                    energy_y = torch.matmul(position_feat_y_reshape, geom_bias)\n                    energy_y = energy_y.view(n, num_heads, h, 1, h_kv, 1)\n\n                    energy += energy_x + energy_y\n\n            energy = energy.view(n, num_heads, h * w, h_kv * w_kv)\n\n        if self.spatial_range >= 0:\n            cur_local_constraint_map = \\\n                self.local_constraint_map[:h, :w, :h_kv, :w_kv].\\\n                contiguous().\\\n                view(1, 1, h*w, h_kv*w_kv)\n\n            energy = energy.masked_fill_(cur_local_constraint_map,\n                                         float('-inf'))\n\n        attention = F.softmax(energy, 3)\n\n        proj_value = self.value_conv(x_kv)\n        proj_value_reshape = proj_value.\\\n            view((n, num_heads, self.v_dim, h_kv * w_kv)).\\\n            permute(0, 1, 3, 2)\n\n        out = torch.matmul(attention, proj_value_reshape).\\\n            permute(0, 1, 3, 2).\\\n            contiguous().\\\n            view(n, self.v_dim * self.num_heads, h, w)\n\n        out = self.proj_conv(out)\n        out = self.gamma * out + x_input\n        return out\n\n    def init_weights(self):\n        for m in self.modules():\n            if hasattr(m, 'kaiming_init') and m.kaiming_init:\n                kaiming_init(\n                    m,\n                    mode='fan_in',\n                    nonlinearity='leaky_relu',\n                    bias=0,\n                    distribution='uniform',\n                    a=1)\n"
  },
  {
    "path": "code/mmdet/ops/masked_conv/__init__.py",
    "content": "from .masked_conv import MaskedConv2d, masked_conv2d\n\n__all__ = ['masked_conv2d', 'MaskedConv2d']\n"
  },
  {
    "path": "code/mmdet/ops/masked_conv/masked_conv.py",
    "content": "import math\n\nimport torch\nimport torch.nn as nn\nfrom torch.autograd import Function\nfrom torch.autograd.function import once_differentiable\nfrom torch.nn.modules.utils import _pair\n\nfrom . import masked_conv2d_ext\n\n\nclass MaskedConv2dFunction(Function):\n\n    @staticmethod\n    def forward(ctx, features, mask, weight, bias, padding=0, stride=1):\n        assert mask.dim() == 3 and mask.size(0) == 1\n        assert features.dim() == 4 and features.size(0) == 1\n        assert features.size()[2:] == mask.size()[1:]\n        pad_h, pad_w = _pair(padding)\n        stride_h, stride_w = _pair(stride)\n        if stride_h != 1 or stride_w != 1:\n            raise ValueError(\n                'Stride could not only be 1 in masked_conv2d currently.')\n        if not features.is_cuda:\n            raise NotImplementedError\n\n        out_channel, in_channel, kernel_h, kernel_w = weight.size()\n\n        batch_size = features.size(0)\n        out_h = int(\n            math.floor((features.size(2) + 2 * pad_h -\n                        (kernel_h - 1) - 1) / stride_h + 1))\n        out_w = int(\n            math.floor((features.size(3) + 2 * pad_w -\n                        (kernel_h - 1) - 1) / stride_w + 1))\n        mask_inds = torch.nonzero(mask[0] > 0, as_tuple=False)\n        output = features.new_zeros(batch_size, out_channel, out_h, out_w)\n        if mask_inds.numel() > 0:\n            mask_h_idx = mask_inds[:, 0].contiguous()\n            mask_w_idx = mask_inds[:, 1].contiguous()\n            data_col = features.new_zeros(in_channel * kernel_h * kernel_w,\n                                          mask_inds.size(0))\n            masked_conv2d_ext.masked_im2col_forward(features, mask_h_idx,\n                                                    mask_w_idx, kernel_h,\n                                                    kernel_w, pad_h, pad_w,\n                                                    data_col)\n\n            masked_output = torch.addmm(1, bias[:, None], 1,\n                                        weight.view(out_channel, -1), data_col)\n            masked_conv2d_ext.masked_col2im_forward(masked_output, mask_h_idx,\n                                                    mask_w_idx, out_h, out_w,\n                                                    out_channel, output)\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        return (None, ) * 5\n\n\nmasked_conv2d = MaskedConv2dFunction.apply\n\n\nclass MaskedConv2d(nn.Conv2d):\n    \"\"\"A MaskedConv2d which inherits the official Conv2d.\n\n    The masked forward doesn't implement the backward function and only\n    supports the stride parameter to be 1 currently.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 bias=True):\n        super(MaskedConv2d,\n              self).__init__(in_channels, out_channels, kernel_size, stride,\n                             padding, dilation, groups, bias)\n\n    def forward(self, input, mask=None):\n        if mask is None:  # fallback to the normal Conv2d\n            return super(MaskedConv2d, self).forward(input)\n        else:\n            return masked_conv2d(input, mask, self.weight, self.bias,\n                                 self.padding)\n"
  },
  {
    "path": "code/mmdet/ops/masked_conv/src/cuda/masked_conv2d_cuda.cpp",
    "content": "#include <torch/extension.h>\n\n#include <cmath>\n#include <vector>\n\nint MaskedIm2colForwardLaucher(const at::Tensor im, const int height,\n                               const int width, const int channels,\n                               const int kernel_h, const int kernel_w,\n                               const int pad_h, const int pad_w,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int mask_cnt,\n                               at::Tensor col);\n\nint MaskedCol2imForwardLaucher(const at::Tensor col, const int height,\n                               const int width, const int channels,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int mask_cnt,\n                               at::Tensor im);\n\n#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x, \" must be a CUDAtensor \")\n#define CHECK_CONTIGUOUS(x) \\\n  TORCH_CHECK(x.is_contiguous(), #x, \" must be contiguous \")\n#define CHECK_INPUT(x) \\\n  CHECK_CUDA(x);       \\\n  CHECK_CONTIGUOUS(x)\n\nint masked_im2col_forward_cuda(const at::Tensor im, const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int kernel_h,\n                               const int kernel_w, const int pad_h,\n                               const int pad_w, at::Tensor col) {\n  CHECK_INPUT(im);\n  CHECK_INPUT(mask_h_idx);\n  CHECK_INPUT(mask_w_idx);\n  CHECK_INPUT(col);\n  // im: (n, ic, h, w), kernel size (kh, kw)\n  // kernel: (oc, ic * kh * kw), col: (kh * kw * ic, ow * oh)\n  at::DeviceGuard guard(im.device());\n\n  int channels = im.size(1);\n  int height = im.size(2);\n  int width = im.size(3);\n  int mask_cnt = mask_h_idx.size(0);\n\n  MaskedIm2colForwardLaucher(im, height, width, channels, kernel_h, kernel_w,\n                             pad_h, pad_w, mask_h_idx, mask_w_idx, mask_cnt,\n                             col);\n\n  return 1;\n}\n\nint masked_col2im_forward_cuda(const at::Tensor col,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, int height,\n                               int width, int channels, at::Tensor im) {\n  CHECK_INPUT(col);\n  CHECK_INPUT(mask_h_idx);\n  CHECK_INPUT(mask_w_idx);\n  CHECK_INPUT(im);\n  // im: (n, ic, h, w), kernel size (kh, kw)\n  // kernel: (oc, ic * kh * kh), col: (kh * kw * ic, ow * oh)\n  at::DeviceGuard guard(col.device());\n\n  int mask_cnt = mask_h_idx.size(0);\n\n  MaskedCol2imForwardLaucher(col, height, width, channels, mask_h_idx,\n                             mask_w_idx, mask_cnt, im);\n\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/masked_conv/src/cuda/masked_conv2d_kernel.cu",
    "content": "#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <THC/THCAtomics.cuh>\n\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\n#define THREADS_PER_BLOCK 1024\n\ninline int GET_BLOCKS(const int N) {\n  int optimal_block_num = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;\n  int max_block_num = 65000;\n  return min(optimal_block_num, max_block_num);\n}\n\ntemplate <typename scalar_t>\n__global__ void MaskedIm2colForward(const int n, const scalar_t *data_im,\n                                    const int height, const int width,\n                                    const int kernel_h, const int kernel_w,\n                                    const int pad_h, const int pad_w,\n                                    const int64_t *mask_h_idx,\n                                    const int64_t *mask_w_idx,\n                                    const int mask_cnt, scalar_t *data_col) {\n  // mask_cnt * channels\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    const int m_index = index % mask_cnt;\n    const int h_col = mask_h_idx[m_index];\n    const int w_col = mask_w_idx[m_index];\n    const int c_im = index / mask_cnt;\n    const int c_col = c_im * kernel_h * kernel_w;\n    const int h_offset = h_col - pad_h;\n    const int w_offset = w_col - pad_w;\n    scalar_t *data_col_ptr = data_col + c_col * mask_cnt + m_index;\n    for (int i = 0; i < kernel_h; ++i) {\n      int h_im = h_offset + i;\n      for (int j = 0; j < kernel_w; ++j) {\n        int w_im = w_offset + j;\n        if (h_im >= 0 && w_im >= 0 && h_im < height && w_im < width) {\n          *data_col_ptr =\n              (scalar_t)data_im[(c_im * height + h_im) * width + w_im];\n        } else {\n          *data_col_ptr = 0.0;\n        }\n        data_col_ptr += mask_cnt;\n      }\n    }\n  }\n}\n\nint MaskedIm2colForwardLaucher(const at::Tensor bottom_data, const int height,\n                               const int width, const int channels,\n                               const int kernel_h, const int kernel_w,\n                               const int pad_h, const int pad_w,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int mask_cnt,\n                               at::Tensor top_data) {\n  const int output_size = mask_cnt * channels;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      bottom_data.scalar_type(), \"MaskedIm2colLaucherForward\", ([&] {\n        const scalar_t *bottom_data_ = bottom_data.data_ptr<scalar_t>();\n        const int64_t *mask_h_idx_ = mask_h_idx.data_ptr<int64_t>();\n        const int64_t *mask_w_idx_ = mask_w_idx.data_ptr<int64_t>();\n        scalar_t *top_data_ = top_data.data_ptr<scalar_t>();\n        MaskedIm2colForward<scalar_t>\n            <<<GET_BLOCKS(output_size), THREADS_PER_BLOCK, 0, at::cuda::getCurrentCUDAStream()\n>>>(\n                output_size, bottom_data_, height, width, kernel_h, kernel_w,\n                pad_h, pad_w, mask_h_idx_, mask_w_idx_, mask_cnt, top_data_);\n      }));\n  THCudaCheck(cudaGetLastError());\n  return 1;\n}\n\ntemplate <typename scalar_t>\n__global__ void MaskedCol2imForward(const int n, const scalar_t *data_col,\n                                    const int height, const int width,\n                                    const int channels,\n                                    const int64_t *mask_h_idx,\n                                    const int64_t *mask_w_idx,\n                                    const int mask_cnt, scalar_t *data_im) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    const int m_index = index % mask_cnt;\n    const int h_im = mask_h_idx[m_index];\n    const int w_im = mask_w_idx[m_index];\n    const int c_im = index / mask_cnt;\n    // compute the start and end of the output\n    data_im[(c_im * height + h_im) * width + w_im] = data_col[index];\n  }\n}\n\nint MaskedCol2imForwardLaucher(const at::Tensor bottom_data, const int height,\n                               const int width, const int channels,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int mask_cnt,\n                               at::Tensor top_data) {\n  const int output_size = mask_cnt * channels;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      bottom_data.scalar_type(), \"MaskedCol2imLaucherForward\", ([&] {\n        const scalar_t *bottom_data_ = bottom_data.data_ptr<scalar_t>();\n        const int64_t *mask_h_idx_ = mask_h_idx.data_ptr<int64_t>();\n        const int64_t *mask_w_idx_ = mask_w_idx.data_ptr<int64_t>();\n        scalar_t *top_data_ = top_data.data_ptr<scalar_t>();\n\n        MaskedCol2imForward<scalar_t>\n            <<<GET_BLOCKS(output_size), THREADS_PER_BLOCK, 0, at::cuda::getCurrentCUDAStream()>>>(\n                output_size, bottom_data_, height, width, channels, mask_h_idx_,\n                mask_w_idx_, mask_cnt, top_data_);\n      }));\n  THCudaCheck(cudaGetLastError());\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/masked_conv/src/masked_conv2d_ext.cpp",
    "content": "#include <torch/extension.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nint masked_im2col_forward_cuda(const at::Tensor im, const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int kernel_h,\n                               const int kernel_w, const int pad_h,\n                               const int pad_w, at::Tensor col);\n\nint masked_col2im_forward_cuda(const at::Tensor col,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, int height,\n                               int width, int channels, at::Tensor im);\n#endif\n\nint masked_im2col_forward(const at::Tensor im, const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, const int kernel_h,\n                               const int kernel_w, const int pad_h,\n                               const int pad_w, at::Tensor col) {\n  if (im.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return masked_im2col_forward_cuda(im, mask_h_idx, mask_w_idx, kernel_h,\n      kernel_w, pad_h, pad_w, col);\n#else\n    AT_ERROR(\"masked_im2col is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"masked_im2col is not implemented on CPU\");\n}\n\nint masked_col2im_forward(const at::Tensor col,\n                               const at::Tensor mask_h_idx,\n                               const at::Tensor mask_w_idx, int height,\n                               int width, int channels, at::Tensor im) {\n  if (col.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return masked_col2im_forward_cuda(col, mask_h_idx, mask_w_idx, height,\n      width, channels, im);\n#else\n    AT_ERROR(\"masked_col2im is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"masked_col2im is not implemented on CPU\");\n}\n\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"masked_im2col_forward\", &masked_im2col_forward,\n        \"masked_im2col forward\");\n  m.def(\"masked_col2im_forward\", &masked_col2im_forward,\n        \"masked_col2im forward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/merge_cells.py",
    "content": "from abc import abstractmethod\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import ConvModule\n\n\nclass BaseMergeCell(nn.Module):\n    \"\"\"The basic class for cells used in NAS-FPN and NAS-FCOS.\n\n    BaseMergeCell takes 2 inputs. After applying concolution\n    on them, they are resized to the target size. Then,\n    they go through binary_op, which depends on the type of cell.\n    If with_out_conv is True, the result of output will go through\n    another convolution layer.\n\n    Args:\n        in_channels (int): number of input channels in out_conv layer.\n        out_channels (int): number of output channels in out_conv layer.\n        with_out_conv (bool): Whether to use out_conv layer\n        out_conv_cfg (dict): Config dict for convolution layer, which should\n            contain \"groups\", \"kernel_size\", \"padding\", \"bias\" to build\n            out_conv layer.\n        out_norm_cfg (dict): Config dict for normalization layer in out_conv.\n        out_conv_order (tuple): The order of conv/norm/activation layers in\n            out_conv.\n        with_input1_conv (bool): Whether to use convolution on input1.\n        with_input2_conv (bool): Whether to use convolution on input2.\n        input_conv_cfg (dict): Config dict for building input1_conv layer and\n            input2_conv layer, which is expected to contain the type of\n            convolution.\n            Default: None, which means using conv2d.\n        input_norm_cfg (dict): Config dict for normalization layer in\n            input1_conv and input2_conv layer. Default: None.\n        upsample_mode (str): Interpolation method used to resize the output\n            of input1_conv and input2_conv to target size. Currently, we\n            support ['nearest', 'bilinear']. Default: 'nearest'.\n\n    \"\"\"\n\n    def __init__(self,\n                 fused_channels=256,\n                 out_channels=256,\n                 with_out_conv=True,\n                 out_conv_cfg=dict(\n                     groups=1, kernel_size=3, padding=1, bias=True),\n                 out_norm_cfg=None,\n                 out_conv_order=('act', 'conv', 'norm'),\n                 with_input1_conv=False,\n                 with_input2_conv=False,\n                 input_conv_cfg=None,\n                 input_norm_cfg=None,\n                 upsample_mode='nearest'):\n        super(BaseMergeCell, self).__init__()\n        assert upsample_mode in ['nearest', 'bilinear']\n        self.with_out_conv = with_out_conv\n        self.with_input1_conv = with_input1_conv\n        self.with_input2_conv = with_input2_conv\n        self.upsample_mode = upsample_mode\n\n        if self.with_out_conv:\n            self.out_conv = ConvModule(\n                fused_channels,\n                out_channels,\n                **out_conv_cfg,\n                norm_cfg=out_norm_cfg,\n                order=out_conv_order)\n\n        self.input1_conv = self._build_input_conv(\n            out_channels, input_conv_cfg,\n            input_norm_cfg) if with_input1_conv else nn.Sequential()\n        self.input2_conv = self._build_input_conv(\n            out_channels, input_conv_cfg,\n            input_norm_cfg) if with_input2_conv else nn.Sequential()\n\n    def _build_input_conv(self, channel, conv_cfg, norm_cfg):\n        return ConvModule(\n            channel,\n            channel,\n            3,\n            padding=1,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg,\n            bias=True)\n\n    @abstractmethod\n    def _binary_op(self, x1, x2):\n        pass\n\n    def _resize(self, x, size):\n        if x.shape[-2:] == size:\n            return x\n        elif x.shape[-2:] < size:\n            return F.interpolate(x, size=size, mode=self.upsample_mode)\n        else:\n            assert x.shape[-2] % size[-2] == 0 and x.shape[-1] % size[-1] == 0\n            kernel_size = x.shape[-1] // size[-1]\n            x = F.max_pool2d(x, kernel_size=kernel_size, stride=kernel_size)\n            return x\n\n    def forward(self, x1, x2, out_size=None):\n        assert x1.shape[:2] == x2.shape[:2]\n        assert out_size is None or len(out_size) == 2\n        if out_size is None:  # resize to larger one\n            out_size = max(x1.size()[2:], x2.size()[2:])\n\n        x1 = self.input1_conv(x1)\n        x2 = self.input2_conv(x2)\n\n        x1 = self._resize(x1, out_size)\n        x2 = self._resize(x2, out_size)\n\n        x = self._binary_op(x1, x2)\n        if self.with_out_conv:\n            x = self.out_conv(x)\n        return x\n\n\nclass SumCell(BaseMergeCell):\n\n    def __init__(self, in_channels, out_channels, **kwargs):\n        super(SumCell, self).__init__(in_channels, out_channels, **kwargs)\n\n    def _binary_op(self, x1, x2):\n        return x1 + x2\n\n\nclass ConcatCell(BaseMergeCell):\n\n    def __init__(self, in_channels, out_channels, **kwargs):\n        super(ConcatCell, self).__init__(in_channels * 2, out_channels,\n                                         **kwargs)\n\n    def _binary_op(self, x1, x2):\n        ret = torch.cat([x1, x2], dim=1)\n        return ret\n\n\nclass GlobalPoolingCell(BaseMergeCell):\n\n    def __init__(self, in_channels=None, out_channels=None, **kwargs):\n        super().__init__(in_channels, out_channels, **kwargs)\n        self.global_pool = nn.AdaptiveAvgPool2d((1, 1))\n\n    def _binary_op(self, x1, x2):\n        x2_att = self.global_pool(x2).sigmoid()\n        return x2 + x2_att * x1\n"
  },
  {
    "path": "code/mmdet/ops/nms/__init__.py",
    "content": "from .nms_wrapper import batched_nms, nms, nms_match, soft_nms\n\n__all__ = ['nms', 'soft_nms', 'batched_nms', 'nms_match']\n"
  },
  {
    "path": "code/mmdet/ops/nms/nms_wrapper.py",
    "content": "import numpy as np\nimport torch\n\nfrom . import nms_ext\n\n\ndef nms(dets, iou_thr, device_id=None):\n    \"\"\"Dispatch to either CPU or GPU NMS implementations.\n\n    The input can be either a torch tensor or numpy array. GPU NMS will be used\n    if the input is a gpu tensor or device_id is specified, otherwise CPU NMS\n    will be used. The returned type will always be the same as inputs.\n\n    Arguments:\n        dets (torch.Tensor or np.ndarray): bboxes with scores.\n        iou_thr (float): IoU threshold for NMS.\n        device_id (int, optional): when `dets` is a numpy array, if `device_id`\n            is None, then cpu nms is used, otherwise gpu_nms will be used.\n\n    Returns:\n        tuple: kept bboxes and indice, which is always the same data type as\n            the input.\n\n    Example:\n        >>> dets = np.array([[49.1, 32.4, 51.0, 35.9, 0.9],\n        >>>                  [49.3, 32.9, 51.0, 35.3, 0.9],\n        >>>                  [49.2, 31.8, 51.0, 35.4, 0.5],\n        >>>                  [35.1, 11.5, 39.1, 15.7, 0.5],\n        >>>                  [35.6, 11.8, 39.3, 14.2, 0.5],\n        >>>                  [35.3, 11.5, 39.9, 14.5, 0.4],\n        >>>                  [35.2, 11.7, 39.7, 15.7, 0.3]], dtype=np.float32)\n        >>> iou_thr = 0.6\n        >>> suppressed, inds = nms(dets, iou_thr)\n        >>> assert len(inds) == len(suppressed) == 3\n    \"\"\"\n    # convert dets (tensor or numpy array) to tensor\n    if isinstance(dets, torch.Tensor):\n        is_numpy = False\n        dets_th = dets\n    elif isinstance(dets, np.ndarray):\n        is_numpy = True\n        device = 'cpu' if device_id is None else f'cuda:{device_id}'\n        dets_th = torch.from_numpy(dets).to(device)\n    else:\n        raise TypeError('dets must be either a Tensor or numpy array, '\n                        f'but got {type(dets)}')\n\n    # execute cpu or cuda nms\n    if dets_th.shape[0] == 0:\n        inds = dets_th.new_zeros(0, dtype=torch.long)\n    else:\n        if dets_th.is_cuda:\n            inds = nms_ext.nms(dets_th, iou_thr)\n        else:\n            inds = nms_ext.nms(dets_th, iou_thr)\n\n    if is_numpy:\n        inds = inds.cpu().numpy()\n    return dets[inds, :], inds\n\n\ndef soft_nms(dets, iou_thr, method='linear', sigma=0.5, min_score=1e-3):\n    \"\"\"Dispatch to only CPU Soft NMS implementations.\n\n    The input can be either a torch tensor or numpy array.\n    The returned type will always be the same as inputs.\n\n    Arguments:\n        dets (torch.Tensor or np.ndarray): bboxes with scores.\n        iou_thr (float): IoU threshold for Soft NMS.\n        method (str): either 'linear' or 'gaussian'\n        sigma (float): hyperparameter for gaussian method\n        min_score (float): score filter threshold\n\n    Returns:\n        tuple: new det bboxes and indice, which is always the same\n        data type as the input.\n\n    Example:\n        >>> dets = np.array([[4., 3., 5., 3., 0.9],\n        >>>                  [4., 3., 5., 4., 0.9],\n        >>>                  [3., 1., 3., 1., 0.5],\n        >>>                  [3., 1., 3., 1., 0.5],\n        >>>                  [3., 1., 3., 1., 0.4],\n        >>>                  [3., 1., 3., 1., 0.0]], dtype=np.float32)\n        >>> iou_thr = 0.6\n        >>> new_dets, inds = soft_nms(dets, iou_thr, sigma=0.5)\n        >>> assert len(inds) == len(new_dets) == 5\n    \"\"\"\n    # convert dets (tensor or numpy array) to tensor\n    if isinstance(dets, torch.Tensor):\n        is_tensor = True\n        dets_t = dets.detach().cpu()\n    elif isinstance(dets, np.ndarray):\n        is_tensor = False\n        dets_t = torch.from_numpy(dets)\n    else:\n        raise TypeError('dets must be either a Tensor or numpy array, '\n                        f'but got {type(dets)}')\n\n    method_codes = {'linear': 1, 'gaussian': 2}\n    if method not in method_codes:\n        raise ValueError(f'Invalid method for SoftNMS: {method}')\n    results = nms_ext.soft_nms(dets_t, iou_thr, method_codes[method], sigma,\n                               min_score)\n\n    new_dets = results[:, :5]\n    inds = results[:, 5]\n\n    if is_tensor:\n        return new_dets.to(\n            device=dets.device, dtype=dets.dtype), inds.to(\n                device=dets.device, dtype=torch.long)\n    else:\n        return new_dets.numpy().astype(dets.dtype), inds.numpy().astype(\n            np.int64)\n\n\ndef batched_nms(bboxes, scores, inds, nms_cfg, class_agnostic=False):\n    \"\"\"Performs non-maximum suppression in a batched fashion.\n\n    Modified from https://github.com/pytorch/vision/blob\n    /505cd6957711af790211896d32b40291bea1bc21/torchvision/ops/boxes.py#L39.\n    In order to perform NMS independently per class, we add an offset to all\n    the boxes. The offset is dependent only on the class idx, and is large\n    enough so that boxes from different classes do not overlap.\n\n    Arguments:\n        bboxes (torch.Tensor): bboxes in shape (N, 4).\n        scores (torch.Tensor): scores in shape (N, ).\n        inds (torch.Tensor): each index value correspond to a bbox cluster,\n            and NMS will not be applied between elements of different inds,\n            shape (N, ).\n        nms_cfg (dict): specify nms type and class_agnostic as well as other\n            parameters like iou_thr.\n        class_agnostic (bool): if true, nms is class agnostic,\n            i.e. IoU thresholding happens over all bboxes,\n            regardless of the predicted class\n\n    Returns:\n        tuple: kept bboxes and indice.\n    \"\"\"\n    nms_cfg_ = nms_cfg.copy()\n    class_agnostic = nms_cfg_.pop('class_agnostic', class_agnostic)\n    if class_agnostic:\n        bboxes_for_nms = bboxes\n    else:\n        max_coordinate = bboxes.max()\n        offsets = inds.to(bboxes) * (max_coordinate + 1)\n        bboxes_for_nms = bboxes + offsets[:, None]\n    nms_type = nms_cfg_.pop('type', 'nms')\n    nms_op = eval(nms_type)\n    dets, keep = nms_op(\n        torch.cat([bboxes_for_nms, scores[:, None]], -1), **nms_cfg_)\n    bboxes = bboxes[keep]\n    scores = dets[:, -1]\n    return torch.cat([bboxes, scores[:, None]], -1), keep\n\n\ndef nms_match(dets, thresh):\n    \"\"\"Matched dets into different groups by NMS.\n\n    NMS match is Similar to NMS but when a bbox is suppressed, nms match will\n    record the indice of supporessed bbox and form a group with the indice of\n    kept bbox. In each group, indice is sorted as score order.\n\n    Arguments:\n        dets (torch.Tensor | np.ndarray): Det bboxes with scores, shape (N, 5).\n        iou_thr (float): IoU thresh for NMS.\n\n    Returns:\n        List[Tensor | ndarray]: The outer list corresponds different matched\n            group, the inner Tensor corresponds the indices for a group in\n            score order.\n    \"\"\"\n    if dets.shape[0] == 0:\n        matched = []\n    else:\n        assert dets.shape[-1] == 5, 'inputs dets.shape should be (N, 5), ' \\\n                                    f'but get {dets.shape}'\n        if isinstance(dets, torch.Tensor):\n            dets_t = dets.detach().cpu()\n        else:\n            dets_t = torch.from_numpy(dets)\n        matched = nms_ext.nms_match(dets_t, thresh)\n\n    if isinstance(dets, torch.Tensor):\n        return [dets.new_tensor(m, dtype=torch.long) for m in matched]\n    else:\n        return [np.array(m, dtype=np.int) for m in matched]\n"
  },
  {
    "path": "code/mmdet/ops/nms/src/cpu/nms_cpu.cpp",
    "content": "// Soft-NMS is added by MMDetection.\n// Modified from\n// https://github.com/bharatsingh430/soft-nms/blob/master/lib/nms/cpu_nms.pyx.\n// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n#include <torch/extension.h>\n\ntemplate <typename scalar_t>\nat::Tensor nms_cpu_kernel(const at::Tensor& dets, const float threshold) {\n  AT_ASSERTM(!dets.device().is_cuda(), \"dets must be a CPU tensor\");\n\n  if (dets.numel() == 0) {\n    return at::empty({0}, dets.options().dtype(at::kLong).device(at::kCPU));\n  }\n\n  auto x1_t = dets.select(1, 0).contiguous();\n  auto y1_t = dets.select(1, 1).contiguous();\n  auto x2_t = dets.select(1, 2).contiguous();\n  auto y2_t = dets.select(1, 3).contiguous();\n  auto scores = dets.select(1, 4).contiguous();\n\n  at::Tensor areas_t = (x2_t - x1_t) * (y2_t - y1_t);\n\n  auto order_t = std::get<1>(scores.sort(0, /* descending=*/true));\n\n  auto ndets = dets.size(0);\n  at::Tensor suppressed_t = at::zeros({ndets}, dets.options().dtype(at::kByte));\n  at::Tensor keep_t = at::zeros({ndets}, dets.options().dtype(at::kLong));\n\n  auto suppressed = suppressed_t.data_ptr<uint8_t>();\n  auto keep = keep_t.data_ptr<int64_t>();\n  auto order = order_t.data_ptr<int64_t>();\n  auto x1 = x1_t.data_ptr<scalar_t>();\n  auto y1 = y1_t.data_ptr<scalar_t>();\n  auto x2 = x2_t.data_ptr<scalar_t>();\n  auto y2 = y2_t.data_ptr<scalar_t>();\n  auto areas = areas_t.data_ptr<scalar_t>();\n\n  int64_t num_to_keep = 0;\n\n  for (int64_t _i = 0; _i < ndets; _i++) {\n    auto i = order[_i];\n    if (suppressed[i] == 1) continue;\n    keep[num_to_keep++] = i;\n    auto ix1 = x1[i];\n    auto iy1 = y1[i];\n    auto ix2 = x2[i];\n    auto iy2 = y2[i];\n    auto iarea = areas[i];\n\n    for (int64_t _j = _i + 1; _j < ndets; _j++) {\n      auto j = order[_j];\n      if (suppressed[j] == 1) continue;\n      auto xx1 = std::max(ix1, x1[j]);\n      auto yy1 = std::max(iy1, y1[j]);\n      auto xx2 = std::min(ix2, x2[j]);\n      auto yy2 = std::min(iy2, y2[j]);\n\n      auto w = std::max(static_cast<scalar_t>(0), xx2 - xx1);\n      auto h = std::max(static_cast<scalar_t>(0), yy2 - yy1);\n      auto inter = w * h;\n      auto ovr = inter / (iarea + areas[j] - inter);\n      if (ovr > threshold) suppressed[j] = 1;\n    }\n  }\n  return keep_t.narrow(/*dim=*/0, /*start=*/0, /*length=*/num_to_keep);\n}\n\nat::Tensor nms_cpu(const at::Tensor& dets, const float threshold) {\n  at::Tensor result;\n  AT_DISPATCH_FLOATING_TYPES(dets.scalar_type(), \"nms\", [&] {\n    result = nms_cpu_kernel<scalar_t>(dets, threshold);\n  });\n  return result;\n}\n\ntemplate <typename scalar_t>\nat::Tensor soft_nms_cpu_kernel(const at::Tensor& dets, const float threshold,\n                               const unsigned char method, const float sigma,\n                               const float min_score) {\n  AT_ASSERTM(!dets.device().is_cuda(), \"dets must be a CPU tensor\");\n\n  if (dets.numel() == 0) {\n    return at::empty({0}, dets.options().dtype(at::kLong).device(at::kCPU));\n  }\n\n  auto x1_t = dets.select(1, 0).contiguous();\n  auto y1_t = dets.select(1, 1).contiguous();\n  auto x2_t = dets.select(1, 2).contiguous();\n  auto y2_t = dets.select(1, 3).contiguous();\n  auto scores_t = dets.select(1, 4).contiguous();\n\n  at::Tensor areas_t = (x2_t - x1_t) * (y2_t - y1_t);\n\n  auto ndets = dets.size(0);\n  auto x1 = x1_t.data_ptr<scalar_t>();\n  auto y1 = y1_t.data_ptr<scalar_t>();\n  auto x2 = x2_t.data_ptr<scalar_t>();\n  auto y2 = y2_t.data_ptr<scalar_t>();\n  auto scores = scores_t.data_ptr<scalar_t>();\n  auto areas = areas_t.data_ptr<scalar_t>();\n\n  int64_t pos = 0;\n  at::Tensor inds_t = at::arange(ndets, dets.options());\n  auto inds = inds_t.data_ptr<scalar_t>();\n\n  for (int64_t i = 0; i < ndets; i++) {\n    auto max_score = scores[i];\n    auto max_pos = i;\n\n    auto ix1 = x1[i];\n    auto iy1 = y1[i];\n    auto ix2 = x2[i];\n    auto iy2 = y2[i];\n    auto iscore = scores[i];\n    auto iarea = areas[i];\n    auto iind = inds[i];\n\n    pos = i + 1;\n    // get max box\n    while (pos < ndets) {\n      if (max_score < scores[pos]) {\n        max_score = scores[pos];\n        max_pos = pos;\n      }\n      pos = pos + 1;\n    }\n    // add max box as a detection\n    x1[i] = x1[max_pos];\n    y1[i] = y1[max_pos];\n    x2[i] = x2[max_pos];\n    y2[i] = y2[max_pos];\n    scores[i] = scores[max_pos];\n    areas[i] = areas[max_pos];\n    inds[i] = inds[max_pos];\n\n    // swap ith box with position of max box\n    x1[max_pos] = ix1;\n    y1[max_pos] = iy1;\n    x2[max_pos] = ix2;\n    y2[max_pos] = iy2;\n    scores[max_pos] = iscore;\n    areas[max_pos] = iarea;\n    inds[max_pos] = iind;\n\n    ix1 = x1[i];\n    iy1 = y1[i];\n    ix2 = x2[i];\n    iy2 = y2[i];\n    iscore = scores[i];\n    iarea = areas[i];\n\n    pos = i + 1;\n    // NMS iterations, note that N changes if detection boxes fall below\n    // threshold\n    while (pos < ndets) {\n      auto xx1 = std::max(ix1, x1[pos]);\n      auto yy1 = std::max(iy1, y1[pos]);\n      auto xx2 = std::min(ix2, x2[pos]);\n      auto yy2 = std::min(iy2, y2[pos]);\n\n      auto w = std::max(static_cast<scalar_t>(0), xx2 - xx1);\n      auto h = std::max(static_cast<scalar_t>(0), yy2 - yy1);\n      auto inter = w * h;\n      auto ovr = inter / (iarea + areas[pos] - inter);\n\n      scalar_t weight = 1.;\n      if (method == 1) {\n        if (ovr > threshold) weight = 1 - ovr;\n      } else if (method == 2) {\n        weight = std::exp(-(ovr * ovr) / sigma);\n      } else {\n        // original NMS\n        if (ovr > threshold) {\n          weight = 0;\n        } else {\n          weight = 1;\n        }\n      }\n      scores[pos] = weight * scores[pos];\n      // if box score falls below threshold, discard the box by\n      // swapping with last box update N\n      if (scores[pos] < min_score) {\n        x1[pos] = x1[ndets - 1];\n        y1[pos] = y1[ndets - 1];\n        x2[pos] = x2[ndets - 1];\n        y2[pos] = y2[ndets - 1];\n        scores[pos] = scores[ndets - 1];\n        areas[pos] = areas[ndets - 1];\n        inds[pos] = inds[ndets - 1];\n        ndets = ndets - 1;\n        pos = pos - 1;\n      }\n      pos = pos + 1;\n    }\n  }\n  at::Tensor result = at::zeros({6, ndets}, dets.options());\n  result[0] = x1_t.slice(0, 0, ndets);\n  result[1] = y1_t.slice(0, 0, ndets);\n  result[2] = x2_t.slice(0, 0, ndets);\n  result[3] = y2_t.slice(0, 0, ndets);\n  result[4] = scores_t.slice(0, 0, ndets);\n  result[5] = inds_t.slice(0, 0, ndets);\n\n  result = result.t().contiguous();\n  return result;\n}\n\nat::Tensor soft_nms_cpu(const at::Tensor& dets, const float threshold,\n                        const unsigned char method, const float sigma,\n                        const float min_score) {\n  at::Tensor result;\n  AT_DISPATCH_FLOATING_TYPES(dets.scalar_type(), \"soft_nms\", [&] {\n    result = soft_nms_cpu_kernel<scalar_t>(dets, threshold, method, sigma,\n                                           min_score);\n  });\n  return result;\n}\n\n\ntemplate <typename scalar_t>\nstd::vector<std::vector<int> > nms_match_cpu_kernel(const at::Tensor& dets,\n                                                    const float threshold) {\n  AT_ASSERTM(!dets.type().is_cuda(), \"dets must be a CPU tensor\");\n\n  auto x1_t = dets.select(1, 0).contiguous();\n  auto y1_t = dets.select(1, 1).contiguous();\n  auto x2_t = dets.select(1, 2).contiguous();\n  auto y2_t = dets.select(1, 3).contiguous();\n  auto scores = dets.select(1, 4).contiguous();\n\n  at::Tensor areas_t = (x2_t - x1_t) * (y2_t - y1_t);\n\n  auto order_t = std::get<1>(scores.sort(0, /* descending=*/true));\n\n  auto ndets = dets.size(0);\n  at::Tensor suppressed_t =\n      at::zeros({ndets}, dets.options().dtype(at::kByte).device(at::kCPU));\n\n  auto suppressed = suppressed_t.data_ptr<uint8_t>();\n  auto order = order_t.data_ptr<int64_t>();\n  auto x1 = x1_t.data_ptr<scalar_t>();\n  auto y1 = y1_t.data_ptr<scalar_t>();\n  auto x2 = x2_t.data_ptr<scalar_t>();\n  auto y2 = y2_t.data_ptr<scalar_t>();\n  auto areas = areas_t.data_ptr<scalar_t>();\n\n  std::vector<int> keep;\n  std::vector<std::vector<int> > matched;\n\n  for (int64_t _i = 0; _i < ndets; _i++) {\n    auto i = order[_i];\n    if (suppressed[i] == 1) continue;\n    keep.push_back(i);\n    std::vector<int> v_i;\n    auto ix1 = x1[i];\n    auto iy1 = y1[i];\n    auto ix2 = x2[i];\n    auto iy2 = y2[i];\n    auto iarea = areas[i];\n\n    for (int64_t _j = _i + 1; _j < ndets; _j++) {\n      auto j = order[_j];\n      if (suppressed[j] == 1) continue;\n      auto xx1 = std::max(ix1, x1[j]);\n      auto yy1 = std::max(iy1, y1[j]);\n      auto xx2 = std::min(ix2, x2[j]);\n      auto yy2 = std::min(iy2, y2[j]);\n\n      auto w = std::max(static_cast<scalar_t>(0), xx2 - xx1);\n      auto h = std::max(static_cast<scalar_t>(0), yy2 - yy1);\n      auto inter = w * h;\n      auto ovr = inter / (iarea + areas[j] - inter);\n      if (ovr >= threshold) {\n        suppressed[j] = 1;\n        v_i.push_back(j);\n      }\n    }\n    matched.push_back(v_i);\n  }\n  for (size_t i = 0; i < keep.size(); i++)\n    matched[i].insert(matched[i].begin(), keep[i]);\n  return matched;\n}\n\nstd::vector<std::vector<int> > nms_match_cpu(const at::Tensor& dets,\n                                             const float threshold) {\n  std::vector<std::vector<int> > result;\n  // result = nms_match_cpu_kernel<scalar_t>(dets, threshold);\n  AT_DISPATCH_FLOATING_TYPES(dets.scalar_type(), \"nms_match\", [&] {\n    result = nms_match_cpu_kernel<scalar_t>(dets, threshold);\n  });\n  return result;\n}\n"
  },
  {
    "path": "code/mmdet/ops/nms/src/cuda/nms_cuda.cpp",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n#include <torch/extension.h>\n\n#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x, \" must be a CUDAtensor \")\n\nat::Tensor nms_cuda_forward(const at::Tensor boxes, float nms_overlap_thresh);\n\nat::Tensor nms_cuda(const at::Tensor& dets, const float threshold) {\n  CHECK_CUDA(dets);\n  if (dets.numel() == 0)\n    return at::empty({0}, dets.options().dtype(at::kLong).device(at::kCPU));\n  return nms_cuda_forward(dets, threshold);\n}\n"
  },
  {
    "path": "code/mmdet/ops/nms/src/cuda/nms_kernel.cu",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <ATen/DeviceGuard.h>\n\n#include <THC/THC.h>\n#include <THC/THCDeviceUtils.cuh>\n\n#include <vector>\n#include <iostream>\n\nint const threadsPerBlock = sizeof(unsigned long long) * 8;\n\n__device__ inline float devIoU(float const * const a, float const * const b) {\n  float left = max(a[0], b[0]), right = min(a[2], b[2]);\n  float top = max(a[1], b[1]), bottom = min(a[3], b[3]);\n  float width = max(right - left, 0.f), height = max(bottom - top, 0.f);\n  float interS = width * height;\n  float Sa = (a[2] - a[0]) * (a[3] - a[1]);\n  float Sb = (b[2] - b[0]) * (b[3] - b[1]);\n  return interS / (Sa + Sb - interS);\n}\n\n__global__ void nms_kernel(const int n_boxes, const float nms_overlap_thresh,\n                           const float *dev_boxes, unsigned long long *dev_mask) {\n  const int row_start = blockIdx.y;\n  const int col_start = blockIdx.x;\n\n  // if (row_start > col_start) return;\n\n  const int row_size =\n        min(n_boxes - row_start * threadsPerBlock, threadsPerBlock);\n  const int col_size =\n        min(n_boxes - col_start * threadsPerBlock, threadsPerBlock);\n\n  __shared__ float block_boxes[threadsPerBlock * 5];\n  if (threadIdx.x < col_size) {\n    block_boxes[threadIdx.x * 5 + 0] =\n        dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 0];\n    block_boxes[threadIdx.x * 5 + 1] =\n        dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 1];\n    block_boxes[threadIdx.x * 5 + 2] =\n        dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 2];\n    block_boxes[threadIdx.x * 5 + 3] =\n        dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 3];\n    block_boxes[threadIdx.x * 5 + 4] =\n        dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 4];\n  }\n  __syncthreads();\n\n  if (threadIdx.x < row_size) {\n    const int cur_box_idx = threadsPerBlock * row_start + threadIdx.x;\n    const float *cur_box = dev_boxes + cur_box_idx * 5;\n    int i = 0;\n    unsigned long long t = 0;\n    int start = 0;\n    if (row_start == col_start) {\n      start = threadIdx.x + 1;\n    }\n    for (i = start; i < col_size; i++) {\n      if (devIoU(cur_box, block_boxes + i * 5) > nms_overlap_thresh) {\n        t |= 1ULL << i;\n      }\n    }\n    const int col_blocks = THCCeilDiv(n_boxes, threadsPerBlock);\n    dev_mask[cur_box_idx * col_blocks + col_start] = t;\n  }\n}\n\n// boxes is a N x 5 tensor\nat::Tensor nms_cuda_forward(const at::Tensor boxes, float nms_overlap_thresh) {\n\n  // Ensure CUDA uses the input tensor device.\n  at::DeviceGuard guard(boxes.device());\n\n  using scalar_t = float;\n  AT_ASSERTM(boxes.device().is_cuda(), \"boxes must be a CUDA tensor\");\n  auto scores = boxes.select(1, 4);\n  auto order_t = std::get<1>(scores.sort(0, /* descending=*/true));\n  auto boxes_sorted = boxes.index_select(0, order_t);\n\n  int boxes_num = boxes.size(0);\n\n  const int col_blocks = THCCeilDiv(boxes_num, threadsPerBlock);\n\n  scalar_t* boxes_dev = boxes_sorted.data_ptr<scalar_t>();\n\n  THCState *state = at::globalContext().lazyInitCUDA(); // TODO replace with getTHCState\n\n  unsigned long long* mask_dev = NULL;\n  //THCudaCheck(THCudaMalloc(state, (void**) &mask_dev,\n  //                      boxes_num * col_blocks * sizeof(unsigned long long)));\n\n  mask_dev = (unsigned long long*) THCudaMalloc(state, boxes_num * col_blocks * sizeof(unsigned long long));\n\n  dim3 blocks(THCCeilDiv(boxes_num, threadsPerBlock),\n              THCCeilDiv(boxes_num, threadsPerBlock));\n  dim3 threads(threadsPerBlock);\n  nms_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(boxes_num,\n                                  nms_overlap_thresh,\n                                  boxes_dev,\n                                  mask_dev);\n\n  std::vector<unsigned long long> mask_host(boxes_num * col_blocks);\n  THCudaCheck(cudaMemcpyAsync(\n\t\t\t  &mask_host[0],\n\t\t\t  mask_dev,\n\t\t\t  sizeof(unsigned long long) * boxes_num * col_blocks,\n\t\t\t  cudaMemcpyDeviceToHost,\n\t\t\t  at::cuda::getCurrentCUDAStream()\n\t\t\t  ));\n\n  std::vector<unsigned long long> remv(col_blocks);\n  memset(&remv[0], 0, sizeof(unsigned long long) * col_blocks);\n\n  at::Tensor keep = at::empty({boxes_num}, boxes.options().dtype(at::kLong).device(at::kCPU));\n  int64_t* keep_out = keep.data_ptr<int64_t>();\n\n  int num_to_keep = 0;\n  for (int i = 0; i < boxes_num; i++) {\n    int nblock = i / threadsPerBlock;\n    int inblock = i % threadsPerBlock;\n\n    if (!(remv[nblock] & (1ULL << inblock))) {\n      keep_out[num_to_keep++] = i;\n      unsigned long long *p = &mask_host[0] + i * col_blocks;\n      for (int j = nblock; j < col_blocks; j++) {\n        remv[j] |= p[j];\n      }\n    }\n  }\n\n  THCudaFree(state, mask_dev);\n  // TODO improve this part\n  return order_t.index({\n      keep.narrow(/*dim=*/0, /*start=*/0, /*length=*/num_to_keep).to(\n          order_t.device(), keep.scalar_type())});\n}\n"
  },
  {
    "path": "code/mmdet/ops/nms/src/nms_ext.cpp",
    "content": "// Modified from https://github.com/bharatsingh430/soft-nms/blob/master/lib/nms/cpu_nms.pyx, Soft-NMS is added\n// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n#include <torch/extension.h>\n\nat::Tensor nms_cpu(const at::Tensor& dets, const float threshold);\n\nat::Tensor soft_nms_cpu(const at::Tensor& dets, const float threshold,\n                    const unsigned char method, const float sigma, const\n                    float min_score);\n\nstd::vector<std::vector<int> > nms_match_cpu(const at::Tensor& dets, const float threshold);\n\n\n#ifdef WITH_CUDA\nat::Tensor nms_cuda(const at::Tensor& dets, const float threshold);\n#endif\n\nat::Tensor nms(const at::Tensor& dets, const float threshold){\n  if (dets.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return nms_cuda(dets, threshold);\n#else\n    AT_ERROR(\"nms is not compiled with GPU support\");\n#endif\n  }\n  return nms_cpu(dets, threshold);\n}\n\nat::Tensor soft_nms(const at::Tensor& dets, const float threshold,\n                        const unsigned char method, const float sigma, const\n                        float min_score) {\n  if (dets.device().is_cuda()) {\n    AT_ERROR(\"soft_nms is not implemented on GPU\");\n  }\n  return soft_nms_cpu(dets, threshold, method, sigma, min_score);\n}\n\nstd::vector<std::vector<int> > nms_match(const at::Tensor& dets, const float threshold) {\n  if (dets.type().is_cuda()) {\n    AT_ERROR(\"nms_match is not implemented on GPU\");\n  }\n  return nms_match_cpu(dets, threshold);\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"nms\", &nms, \"non-maximum suppression\");\n  m.def(\"soft_nms\", &soft_nms, \"soft non-maximum suppression\");\n  m.def(\"nms_match\", &nms_match, \"non-maximum suppression match\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/non_local.py",
    "content": "import torch\nimport torch.nn as nn\nfrom mmcv.cnn import ConvModule, constant_init, normal_init\n\n\nclass NonLocal2D(nn.Module):\n    \"\"\"Non-local module.\n\n    See https://arxiv.org/abs/1711.07971 for details.\n\n    Args:\n        in_channels (int): Channels of the input feature map.\n        reduction (int): Channel reduction ratio.\n        use_scale (bool): Whether to scale pairwise_weight by 1/inter_channels.\n        conv_cfg (dict): The config dict for convolution layers.\n            (only applicable to conv_out)\n        norm_cfg (dict): The config dict for normalization layers.\n            (only applicable to conv_out)\n        mode (str): Options are `embedded_gaussian` and `dot_product`.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 reduction=2,\n                 use_scale=True,\n                 conv_cfg=None,\n                 norm_cfg=None,\n                 mode='embedded_gaussian'):\n        super(NonLocal2D, self).__init__()\n        self.in_channels = in_channels\n        self.reduction = reduction\n        self.use_scale = use_scale\n        self.inter_channels = in_channels // reduction\n        self.mode = mode\n        assert mode in ['embedded_gaussian', 'dot_product']\n\n        # g, theta, phi are actually `nn.Conv2d`. Here we use ConvModule for\n        # potential usage.\n        self.g = ConvModule(\n            self.in_channels, self.inter_channels, kernel_size=1, act_cfg=None)\n        self.theta = ConvModule(\n            self.in_channels, self.inter_channels, kernel_size=1, act_cfg=None)\n        self.phi = ConvModule(\n            self.in_channels, self.inter_channels, kernel_size=1, act_cfg=None)\n        self.conv_out = ConvModule(\n            self.inter_channels,\n            self.in_channels,\n            kernel_size=1,\n            conv_cfg=conv_cfg,\n            norm_cfg=norm_cfg,\n            act_cfg=None)\n\n        self.init_weights()\n\n    def init_weights(self, std=0.01, zeros_init=True):\n        for m in [self.g, self.theta, self.phi]:\n            normal_init(m.conv, std=std)\n        if zeros_init:\n            constant_init(self.conv_out.conv, 0)\n        else:\n            normal_init(self.conv_out.conv, std=std)\n\n    def embedded_gaussian(self, theta_x, phi_x):\n        # pairwise_weight: [N, HxW, HxW]\n        pairwise_weight = torch.matmul(theta_x, phi_x)\n        if self.use_scale:\n            # theta_x.shape[-1] is `self.inter_channels`\n            pairwise_weight /= theta_x.shape[-1]**0.5\n        pairwise_weight = pairwise_weight.softmax(dim=-1)\n        return pairwise_weight\n\n    def dot_product(self, theta_x, phi_x):\n        # pairwise_weight: [N, HxW, HxW]\n        pairwise_weight = torch.matmul(theta_x, phi_x)\n        pairwise_weight /= pairwise_weight.shape[-1]\n        return pairwise_weight\n\n    def forward(self, x):\n        n, _, h, w = x.shape\n\n        # g_x: [N, HxW, C]\n        g_x = self.g(x).view(n, self.inter_channels, -1)\n        g_x = g_x.permute(0, 2, 1)\n\n        # theta_x: [N, HxW, C]\n        theta_x = self.theta(x).view(n, self.inter_channels, -1)\n        theta_x = theta_x.permute(0, 2, 1)\n\n        # phi_x: [N, C, HxW]\n        phi_x = self.phi(x).view(n, self.inter_channels, -1)\n\n        pairwise_func = getattr(self, self.mode)\n        # pairwise_weight: [N, HxW, HxW]\n        pairwise_weight = pairwise_func(theta_x, phi_x)\n\n        # y: [N, HxW, C]\n        y = torch.matmul(pairwise_weight, g_x)\n        # y: [N, C, H, W]\n        y = y.permute(0, 2, 1).reshape(n, self.inter_channels, h, w)\n\n        output = x + self.conv_out(y)\n\n        return output\n"
  },
  {
    "path": "code/mmdet/ops/plugin.py",
    "content": "from mmcv.cnn import ConvModule\n\nfrom .context_block import ContextBlock\nfrom .generalized_attention import GeneralizedAttention\nfrom .non_local import NonLocal2D\n\nplugin_cfg = {\n    # format: layer_type: (abbreviation, module)\n    'ContextBlock': ('context_block', ContextBlock),\n    'GeneralizedAttention': ('gen_attention_block', GeneralizedAttention),\n    'NonLocal2D': ('nonlocal_block', NonLocal2D),\n    'ConvModule': ('conv_block', ConvModule),\n}\n\n\ndef build_plugin_layer(cfg, postfix='', **kwargs):\n    \"\"\" Build plugin layer\n\n    Args:\n        cfg (None or dict): cfg should contain:\n            type (str): identify plugin layer type.\n            layer args: args needed to instantiate a plugin layer.\n        postfix (int, str): appended into norm abbreviation to\n            create named layer.\n\n    Returns:\n        name (str): abbreviation + postfix\n        layer (nn.Module): created plugin layer\n    \"\"\"\n    assert isinstance(cfg, dict) and 'type' in cfg\n    cfg_ = cfg.copy()\n\n    layer_type = cfg_.pop('type')\n    if layer_type not in plugin_cfg:\n        raise KeyError(f'Unrecognized plugin type {layer_type}')\n    else:\n        abbr, plugin_layer = plugin_cfg[layer_type]\n\n    assert isinstance(postfix, (int, str))\n    name = abbr + str(postfix)\n\n    layer = plugin_layer(**kwargs, **cfg_)\n\n    return name, layer\n"
  },
  {
    "path": "code/mmdet/ops/point_sample.py",
    "content": "# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend  # noqa\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.nn.modules.utils import _pair\n\n\ndef normalize(grid):\n    \"\"\"Normalize input grid from [-1, 1] to [0, 1]\n\n    Args:\n        grid (Tensor): The grid to be normalize, range [-1, 1].\n\n    Returns:\n        Tensor: Normalized grid, range [0, 1].\n    \"\"\"\n\n    return (grid + 1.0) / 2.0\n\n\ndef denormalize(grid):\n    \"\"\"Denormalize input grid from range [0, 1] to [-1, 1]\n    Args:\n        grid (Tensor): The grid to be denormalize, range [0, 1].\n\n    Returns:\n        Tensor: Denormalized grid, range [-1, 1].\n    \"\"\"\n\n    return grid * 2.0 - 1.0\n\n\ndef generate_grid(num_grid, size, device):\n    \"\"\"Generate regular square grid of points in [0, 1] x [0, 1] coordinate\n    space.\n\n    Args:\n        num_grid (int): The number of grids to sample, one for each region.\n        size (tuple(int, int)): The side size of the regular grid.\n        device (torch.device): Desired device of returned tensor.\n\n    Returns:\n        (torch.Tensor): A tensor of shape (num_grid, size[0]*size[1], 2) that\n            contains coordinates for the regular grids.\n    \"\"\"\n\n    affine_trans = torch.tensor([[[1., 0., 0.], [0., 1., 0.]]], device=device)\n    grid = F.affine_grid(\n        affine_trans, torch.Size((1, 1, *size)), align_corners=False)\n    grid = normalize(grid)\n    return grid.view(1, -1, 2).expand(num_grid, -1, -1)\n\n\ndef rel_roi_point_to_abs_img_point(rois, rel_roi_points):\n    \"\"\"Convert roi based relative point coordinates to image based absolute\n    point coordinates.\n\n    Args:\n        rois (Tensor): RoIs or BBoxes, shape (N, 4) or (N, 5)\n        rel_roi_points (Tensor): Point coordinates inside RoI, relative to\n            RoI, location, range (0, 1), shape (N, P, 2)\n\n    Returns:\n        Tensor: Image based absolute point coordinates, shape (N, P, 2)\n    \"\"\"\n\n    with torch.no_grad():\n        assert rel_roi_points.size(0) == rois.size(0)\n        assert rois.dim() == 2\n        assert rel_roi_points.dim() == 3\n        assert rel_roi_points.size(2) == 2\n        # remove batch idx\n        if rois.size(1) == 5:\n            rois = rois[:, 1:]\n        abs_img_points = rel_roi_points.clone()\n        abs_img_points[:, :, 0] = abs_img_points[:, :, 0] * (\n            rois[:, None, 2] - rois[:, None, 0])\n        abs_img_points[:, :, 1] = abs_img_points[:, :, 1] * (\n            rois[:, None, 3] - rois[:, None, 1])\n        abs_img_points[:, :, 0] += rois[:, None, 0]\n        abs_img_points[:, :, 1] += rois[:, None, 1]\n    return abs_img_points\n\n\ndef abs_img_point_to_rel_img_point(abs_img_points,\n                                   img_shape,\n                                   spatial_scale=1.):\n    \"\"\"Convert image based absolute point coordinates to image based relative\n    coordinates for sampling.\n\n    Args:\n        abs_img_points (Tensor): Image based absolute point coordinates,\n            shape (N, P, 2)\n        img_shape (tuple): (height, width) of image or feature map.\n        spatial_scale (float): Scale points by this factor. Default: 1.\n\n    Returns:\n        Tensor: Image based relative point coordinates for sampling,\n            shape (N, P, 2)\n    \"\"\"\n\n    assert isinstance(img_shape, tuple) and len(img_shape) == 2\n    h, w = img_shape\n    scale = torch.tensor([w, h],\n                         dtype=torch.float,\n                         device=abs_img_points.device)\n    scale = scale.view(1, 1, 2)\n    rel_img_points = abs_img_points / scale * spatial_scale\n\n    return rel_img_points\n\n\ndef rel_roi_point_to_rel_img_point(rois,\n                                   rel_roi_points,\n                                   img_shape,\n                                   spatial_scale=1.):\n    \"\"\"Convert roi based relative point coordinates to image based absolute\n    point coordinates.\n\n    Args:\n        rois (Tensor): RoIs or BBoxes, shape (N, 4) or (N, 5)\n        rel_roi_points (Tensor): Point coordinates inside RoI, relative to\n            RoI, location, range (0, 1), shape (N, P, 2)\n        img_shape (tuple): (height, width) of image or feature map.\n        spatial_scale (float): Scale points by this factor. Default: 1.\n\n    Returns:\n        Tensor: Image based relative point coordinates for sampling,\n            shape (N, P, 2)\n    \"\"\"\n\n    abs_img_point = rel_roi_point_to_abs_img_point(rois, rel_roi_points)\n    rel_img_point = abs_img_point_to_rel_img_point(abs_img_point, img_shape,\n                                                   spatial_scale)\n\n    return rel_img_point\n\n\ndef point_sample(input, points, align_corners=False, **kwargs):\n    \"\"\"A wrapper around :function:`grid_sample` to support 3D point_coords\n    tensors Unlike :function:`torch.nn.functional.grid_sample` it assumes\n    point_coords to lie inside [0, 1] x [0, 1] square.\n\n    Args:\n        input (Tensor): Feature map, shape (N, C, H, W).\n        points (Tensor): Image based absolute point coordinates (normalized),\n            range [0, 1] x [0, 1], shape (N, P, 2) or (N, Hgrid, Wgrid, 2).\n        align_corners (bool): Whether align_corners. Default: False\n\n    Returns:\n        Tensor: Features of `point` on `input`, shape (N, C, P) or\n            (N, C, Hgrid, Wgrid).\n    \"\"\"\n\n    add_dim = False\n    if points.dim() == 3:\n        add_dim = True\n        points = points.unsqueeze(2)\n    output = F.grid_sample(\n        input, denormalize(points), align_corners=align_corners, **kwargs)\n    if add_dim:\n        output = output.squeeze(3)\n    return output\n\n\nclass SimpleRoIAlign(nn.Module):\n\n    def __init__(self, out_size, spatial_scale, aligned=True):\n        \"\"\"Simple RoI align in PointRend, faster than standard RoIAlign.\n\n        Args:\n            out_size (tuple[int]): h, w\n            spatial_scale (float): scale the input boxes by this number\n            aligned (bool): if False, use the legacy implementation in\n                MMDetection, align_corners=True will be used in F.grid_sample.\n                If True, align the results more perfectly.\n        \"\"\"\n\n        super(SimpleRoIAlign, self).__init__()\n        self.out_size = _pair(out_size)\n        self.spatial_scale = float(spatial_scale)\n        # to be consistent with other RoI ops\n        self.use_torchvision = False\n        self.aligned = aligned\n\n    def forward(self, features, rois):\n\n        num_imgs = features.size(0)\n        num_rois = rois.size(0)\n        rel_roi_points = generate_grid(\n            num_rois, self.out_size, device=rois.device)\n\n        point_feats = []\n        for batch_ind in range(num_imgs):\n            # unravel batch dim\n            feat = features[batch_ind].unsqueeze(0)\n            inds = (rois[:, 0].long() == batch_ind)\n            if inds.any():\n                rel_img_points = rel_roi_point_to_rel_img_point(\n                    rois[inds], rel_roi_points[inds], feat.shape[2:],\n                    self.spatial_scale).unsqueeze(0)\n                point_feat = point_sample(\n                    feat, rel_img_points, align_corners=not self.aligned)\n                point_feat = point_feat.squeeze(0).transpose(0, 1)\n                point_feats.append(point_feat)\n\n        channels = features.size(1)\n        roi_feats = torch.cat(point_feats, dim=0)\n        roi_feats = roi_feats.reshape(num_rois, channels, *self.out_size)\n\n        return roi_feats\n\n    def __repr__(self):\n        format_str = self.__class__.__name__\n        format_str += '(out_size={}, spatial_scale={}'.format(\n            self.out_size, self.spatial_scale)\n        return format_str\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/__init__.py",
    "content": "from .roi_align import RoIAlign, roi_align\n\n__all__ = ['roi_align', 'RoIAlign']\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/gradcheck.py",
    "content": "import os.path as osp\nimport sys\n\nimport numpy as np\nimport torch\nfrom torch.autograd import gradcheck\n\nsys.path.append(osp.abspath(osp.join(__file__, '../../')))\nfrom roi_align import RoIAlign  # noqa: E402, isort:skip\n\nfeat_size = 15\nspatial_scale = 1.0 / 8\nimg_size = feat_size / spatial_scale\nnum_imgs = 2\nnum_rois = 20\n\nbatch_ind = np.random.randint(num_imgs, size=(num_rois, 1))\nrois = np.random.rand(num_rois, 4) * img_size * 0.5\nrois[:, 2:] += img_size * 0.5\nrois = np.hstack((batch_ind, rois))\n\nfeat = torch.randn(\n    num_imgs, 16, feat_size, feat_size, requires_grad=True, device='cuda:0')\nrois = torch.from_numpy(rois).float().cuda()\ninputs = (feat, rois)\nprint('Gradcheck for roi align...')\ntest = gradcheck(RoIAlign(3, spatial_scale), inputs, atol=1e-3, eps=1e-3)\nprint(test)\ntest = gradcheck(RoIAlign(3, spatial_scale, 2), inputs, atol=1e-3, eps=1e-3)\nprint(test)\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/roi_align.py",
    "content": "from torch import nn\nfrom torch.autograd import Function\nfrom torch.autograd.function import once_differentiable\nfrom torch.nn.modules.utils import _pair\n\nfrom . import roi_align_ext\n\n\nclass RoIAlignFunction(Function):\n\n    @staticmethod\n    def forward(ctx,\n                features,\n                rois,\n                out_size,\n                spatial_scale,\n                sample_num=0,\n                aligned=True):\n        out_h, out_w = _pair(out_size)\n        assert isinstance(out_h, int) and isinstance(out_w, int)\n        ctx.spatial_scale = spatial_scale\n        ctx.sample_num = sample_num\n        ctx.save_for_backward(rois)\n        ctx.feature_size = features.size()\n        ctx.aligned = aligned\n\n        if aligned:\n            output = roi_align_ext.forward_v2(features, rois, spatial_scale,\n                                              out_h, out_w, sample_num,\n                                              aligned)\n        elif features.is_cuda:\n            (batch_size, num_channels, data_height,\n             data_width) = features.size()\n            num_rois = rois.size(0)\n\n            output = features.new_zeros(num_rois, num_channels, out_h, out_w)\n            roi_align_ext.forward_v1(features, rois, out_h, out_w,\n                                     spatial_scale, sample_num, output)\n        else:\n            raise NotImplementedError\n\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        feature_size = ctx.feature_size\n        spatial_scale = ctx.spatial_scale\n        sample_num = ctx.sample_num\n        rois = ctx.saved_tensors[0]\n        aligned = ctx.aligned\n        assert feature_size is not None\n\n        batch_size, num_channels, data_height, data_width = feature_size\n        out_w = grad_output.size(3)\n        out_h = grad_output.size(2)\n\n        grad_input = grad_rois = None\n        if not aligned:\n            if ctx.needs_input_grad[0]:\n                grad_input = rois.new_zeros(batch_size, num_channels,\n                                            data_height, data_width)\n                roi_align_ext.backward_v1(grad_output.contiguous(), rois,\n                                          out_h, out_w, spatial_scale,\n                                          sample_num, grad_input)\n        else:\n            grad_input = roi_align_ext.backward_v2(grad_output, rois,\n                                                   spatial_scale, out_h, out_w,\n                                                   batch_size, num_channels,\n                                                   data_height, data_width,\n                                                   sample_num, aligned)\n\n        return grad_input, grad_rois, None, None, None, None\n\n\nroi_align = RoIAlignFunction.apply\n\n\nclass RoIAlign(nn.Module):\n\n    def __init__(self,\n                 out_size,\n                 spatial_scale,\n                 sample_num=0,\n                 use_torchvision=False,\n                 aligned=True):\n        \"\"\"\n        Args:\n            out_size (tuple): h, w\n            spatial_scale (float): scale the input boxes by this number\n            sample_num (int): number of inputs samples to take for each\n                output sample. 2 to take samples densely for current models.\n            use_torchvision (bool): whether to use roi_align from torchvision\n            aligned (bool): if False, use the legacy implementation in\n                MMDetection. If True, align the results more perfectly.\n\n        Note:\n            The implementation of RoIAlign when aligned=True is modified from\n            https://github.com/facebookresearch/detectron2/\n\n            The meaning of aligned=True:\n\n            Given a continuous coordinate c, its two neighboring pixel\n            indices (in our pixel model) are computed by floor(c - 0.5) and\n            ceil(c - 0.5). For example, c=1.3 has pixel neighbors with discrete\n            indices [0] and [1] (which are sampled from the underlying signal\n            at continuous coordinates 0.5 and 1.5). But the original roi_align\n            (aligned=False) does not subtract the 0.5 when computing\n            neighboring pixel indices and therefore it uses pixels with a\n            slightly incorrect alignment (relative to our pixel model) when\n            performing bilinear interpolation.\n\n            With `aligned=True`,\n            we first appropriately scale the ROI and then shift it by -0.5\n            prior to calling roi_align. This produces the correct neighbors;\n\n            The difference does not make a difference to the model's\n            performance if ROIAlign is used together with conv layers.\n        \"\"\"\n        super(RoIAlign, self).__init__()\n        self.out_size = _pair(out_size)\n        self.spatial_scale = float(spatial_scale)\n        self.aligned = aligned\n        self.sample_num = int(sample_num)\n        self.use_torchvision = use_torchvision\n        assert not (use_torchvision and\n                    aligned), 'Torchvision does not support aligned RoIAlgin'\n\n    def forward(self, features, rois):\n        \"\"\"\n        Args:\n            features: NCHW images\n            rois: Bx5 boxes. First column is the index into N. The other 4\n            columns are xyxy.\n        \"\"\"\n        assert rois.dim() == 2 and rois.size(1) == 5\n\n        if self.use_torchvision:\n            from torchvision.ops import roi_align as tv_roi_align\n            return tv_roi_align(features, rois, self.out_size,\n                                self.spatial_scale, self.sample_num)\n        else:\n            return roi_align(features, rois, self.out_size, self.spatial_scale,\n                             self.sample_num, self.aligned)\n\n    def __repr__(self):\n        indent_str = '\\n    '\n        format_str = self.__class__.__name__\n        format_str += f'({indent_str}out_size={self.out_size},'\n        format_str += f'{indent_str}spatial_scale={self.spatial_scale},'\n        format_str += f'{indent_str}sample_num={self.sample_num},'\n        format_str += f'{indent_str}use_torchvision={self.use_torchvision},'\n        format_str += f'{indent_str}aligned={self.aligned})'\n        return format_str\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/src/cpu/roi_align_v2.cpp",
    "content": "// Modified from\n// https://github.com/facebookresearch/detectron2/tree/master/detectron2/layers/csrc/ROIAlign\n// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n#include <ATen/ATen.h>\n#include <ATen/TensorUtils.h>\n\n// implementation taken from Caffe2\ntemplate <typename T>\nstruct PreCalc {\n  int pos1;\n  int pos2;\n  int pos3;\n  int pos4;\n  T w1;\n  T w2;\n  T w3;\n  T w4;\n};\n\ntemplate <typename T>\nvoid pre_calc_for_bilinear_interpolate(\n    const int height, const int width, const int pooled_height,\n    const int pooled_width, const int iy_upper, const int ix_upper,\n    T roi_start_h, T roi_start_w, T bin_size_h, T bin_size_w,\n    int roi_bin_grid_h, int roi_bin_grid_w, std::vector<PreCalc<T>>& pre_calc) {\n  int pre_calc_index = 0;\n  for (int ph = 0; ph < pooled_height; ph++) {\n    for (int pw = 0; pw < pooled_width; pw++) {\n      for (int iy = 0; iy < iy_upper; iy++) {\n        const T yy = roi_start_h + ph * bin_size_h +\n                     static_cast<T>(iy + .5f) * bin_size_h /\n                         static_cast<T>(roi_bin_grid_h);  // e.g., 0.5, 1.5\n        for (int ix = 0; ix < ix_upper; ix++) {\n          const T xx = roi_start_w + pw * bin_size_w +\n                       static_cast<T>(ix + .5f) * bin_size_w /\n                           static_cast<T>(roi_bin_grid_w);\n\n          T x = xx;\n          T y = yy;\n          // deal with: inverse elements are out of feature map boundary\n          if (y < -1.0 || y > height || x < -1.0 || x > width) {\n            // empty\n            PreCalc<T> pc;\n            pc.pos1 = 0;\n            pc.pos2 = 0;\n            pc.pos3 = 0;\n            pc.pos4 = 0;\n            pc.w1 = 0;\n            pc.w2 = 0;\n            pc.w3 = 0;\n            pc.w4 = 0;\n            pre_calc[pre_calc_index] = pc;\n            pre_calc_index += 1;\n            continue;\n          }\n\n          if (y <= 0) {\n            y = 0;\n          }\n          if (x <= 0) {\n            x = 0;\n          }\n\n          int y_low = (int)y;\n          int x_low = (int)x;\n          int y_high;\n          int x_high;\n\n          if (y_low >= height - 1) {\n            y_high = y_low = height - 1;\n            y = (T)y_low;\n          } else {\n            y_high = y_low + 1;\n          }\n\n          if (x_low >= width - 1) {\n            x_high = x_low = width - 1;\n            x = (T)x_low;\n          } else {\n            x_high = x_low + 1;\n          }\n\n          T ly = y - y_low;\n          T lx = x - x_low;\n          T hy = 1. - ly, hx = 1. - lx;\n          T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n          // save weights and indices\n          PreCalc<T> pc;\n          pc.pos1 = y_low * width + x_low;\n          pc.pos2 = y_low * width + x_high;\n          pc.pos3 = y_high * width + x_low;\n          pc.pos4 = y_high * width + x_high;\n          pc.w1 = w1;\n          pc.w2 = w2;\n          pc.w3 = w3;\n          pc.w4 = w4;\n          pre_calc[pre_calc_index] = pc;\n\n          pre_calc_index += 1;\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename T>\nvoid ROIAlignForward(const int nthreads, const T* input, const T& spatial_scale,\n                     const int channels, const int height, const int width,\n                     const int pooled_height, const int pooled_width,\n                     const int sampling_ratio, const T* rois, T* output,\n                     bool aligned) {\n  int n_rois = nthreads / channels / pooled_width / pooled_height;\n  // (n, c, ph, pw) is an element in the pooled output\n  // can be parallelized using omp\n  // #pragma omp parallel for num_threads(32)\n  for (int n = 0; n < n_rois; n++) {\n    int index_n = n * channels * pooled_width * pooled_height;\n\n    const T* offset_rois = rois + n * 5;\n    int roi_batch_ind = offset_rois[0];\n\n    // Do not use rounding; this implementation detail is critical\n    T offset = aligned ? (T)0.5 : (T)0.0;\n    T roi_start_w = offset_rois[1] * spatial_scale - offset;\n    T roi_start_h = offset_rois[2] * spatial_scale - offset;\n    T roi_end_w = offset_rois[3] * spatial_scale - offset;\n    T roi_end_h = offset_rois[4] * spatial_scale - offset;\n\n    T roi_width = roi_end_w - roi_start_w;\n    T roi_height = roi_end_h - roi_start_h;\n    if (aligned) {\n      AT_ASSERTM(roi_width >= 0 && roi_height >= 0,\n                 \"ROIs in ROIAlign cannot have non-negative size!\");\n    } else {  // for backward-compatibility only\n      roi_width = std::max(roi_width, (T)1.);\n      roi_height = std::max(roi_height, (T)1.);\n    }\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n                             ? sampling_ratio\n                             : ceil(roi_height / pooled_height);  // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    // When the grid is empty, output zeros == 0/1, instead of NaN.\n    const T count = std::max(roi_bin_grid_h * roi_bin_grid_w, 1);  // e.g. = 4\n\n    // we want to precalculate indices and weights shared by all channels,\n    // this is the key point of optimization\n    std::vector<PreCalc<T>> pre_calc(roi_bin_grid_h * roi_bin_grid_w *\n                                     pooled_width * pooled_height);\n    pre_calc_for_bilinear_interpolate(\n        height, width, pooled_height, pooled_width, roi_bin_grid_h,\n        roi_bin_grid_w, roi_start_h, roi_start_w, bin_size_h, bin_size_w,\n        roi_bin_grid_h, roi_bin_grid_w, pre_calc);\n\n    for (int c = 0; c < channels; c++) {\n      int index_n_c = index_n + c * pooled_width * pooled_height;\n      const T* offset_input =\n          input + (roi_batch_ind * channels + c) * height * width;\n      int pre_calc_index = 0;\n\n      for (int ph = 0; ph < pooled_height; ph++) {\n        for (int pw = 0; pw < pooled_width; pw++) {\n          int index = index_n_c + ph * pooled_width + pw;\n\n          T output_val = 0.;\n          for (int iy = 0; iy < roi_bin_grid_h; iy++) {\n            for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n              PreCalc<T> pc = pre_calc[pre_calc_index];\n              output_val += pc.w1 * offset_input[pc.pos1] +\n                            pc.w2 * offset_input[pc.pos2] +\n                            pc.w3 * offset_input[pc.pos3] +\n                            pc.w4 * offset_input[pc.pos4];\n\n              pre_calc_index += 1;\n            }\n          }\n          output_val /= count;\n\n          output[index] = output_val;\n        }  // for pw\n      }    // for ph\n    }      // for c\n  }        // for n\n}\n\ntemplate <typename T>\nvoid bilinear_interpolate_gradient(const int height, const int width, T y, T x,\n                                   T& w1, T& w2, T& w3, T& w4, int& x_low,\n                                   int& x_high, int& y_low, int& y_high,\n                                   const int index /* index for debug only*/) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    // empty\n    w1 = w2 = w3 = w4 = 0.;\n    x_low = x_high = y_low = y_high = -1;\n    return;\n  }\n\n  if (y <= 0) y = 0;\n  if (x <= 0) x = 0;\n\n  y_low = (int)y;\n  x_low = (int)x;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (T)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (T)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  T ly = y - y_low;\n  T lx = x - x_low;\n  T hy = 1. - ly, hx = 1. - lx;\n\n  // reference in forward\n  // T v1 = input[y_low * width + x_low];\n  // T v2 = input[y_low * width + x_high];\n  // T v3 = input[y_high * width + x_low];\n  // T v4 = input[y_high * width + x_high];\n  // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n\n  w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  return;\n}\n\ntemplate <class T>\ninline void add(T* address, const T& val) {\n  *address += val;\n}\n\ntemplate <typename T>\nvoid ROIAlignBackward(const int nthreads, const T* grad_output,\n                      const T& spatial_scale, const int channels,\n                      const int height, const int width,\n                      const int pooled_height, const int pooled_width,\n                      const int sampling_ratio, T* grad_input, const T* rois,\n                      const int n_stride, const int c_stride,\n                      const int h_stride, const int w_stride, bool aligned) {\n  for (int index = 0; index < nthreads; index++) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_rois = rois + n * 5;\n    int roi_batch_ind = offset_rois[0];\n\n    // Do not use rounding; this implementation detail is critical\n    T offset = aligned ? (T)0.5 : (T)0.0;\n    T roi_start_w = offset_rois[1] * spatial_scale - offset;\n    T roi_start_h = offset_rois[2] * spatial_scale - offset;\n    T roi_end_w = offset_rois[3] * spatial_scale - offset;\n    T roi_end_h = offset_rois[4] * spatial_scale - offset;\n\n    T roi_width = roi_end_w - roi_start_w;\n    T roi_height = roi_end_h - roi_start_h;\n    if (aligned) {\n      AT_ASSERTM(roi_width >= 0 && roi_height >= 0,\n                 \"ROIs in ROIAlign do not have non-negative size!\");\n    } else {  // for backward-compatibility only\n      roi_width = std::max(roi_width, (T)1.);\n      roi_height = std::max(roi_height, (T)1.);\n    }\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    T* offset_grad_input =\n        grad_input + ((roi_batch_ind * channels + c) * height * width);\n\n    int output_offset = n * n_stride + c * c_stride;\n    const T* offset_grad_output = grad_output + output_offset;\n    const T grad_output_this_bin =\n        offset_grad_output[ph * h_stride + pw * w_stride];\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n                             ? sampling_ratio\n                             : ceil(roi_height / pooled_height);  // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    const T count = roi_bin_grid_h * roi_bin_grid_w;  // e.g. = 4\n\n    for (int iy = 0; iy < roi_bin_grid_h; iy++) {\n      const T y = roi_start_h + ph * bin_size_h +\n                  static_cast<T>(iy + .5f) * bin_size_h /\n                      static_cast<T>(roi_bin_grid_h);  // e.g., 0.5, 1.5\n      for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n        const T x = roi_start_w + pw * bin_size_w +\n                    static_cast<T>(ix + .5f) * bin_size_w /\n                        static_cast<T>(roi_bin_grid_w);\n\n        T w1, w2, w3, w4;\n        int x_low, x_high, y_low, y_high;\n\n        bilinear_interpolate_gradient(height, width, y, x, w1, w2, w3, w4,\n                                      x_low, x_high, y_low, y_high, index);\n\n        T g1 = grad_output_this_bin * w1 / count;\n        T g2 = grad_output_this_bin * w2 / count;\n        T g3 = grad_output_this_bin * w3 / count;\n        T g4 = grad_output_this_bin * w4 / count;\n\n        if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {\n          // atomic add is not needed for now since it is single threaded\n          add(offset_grad_input + y_low * width + x_low, static_cast<T>(g1));\n          add(offset_grad_input + y_low * width + x_high, static_cast<T>(g2));\n          add(offset_grad_input + y_high * width + x_low, static_cast<T>(g3));\n          add(offset_grad_input + y_high * width + x_high, static_cast<T>(g4));\n        }  // if\n      }    // ix\n    }      // iy\n  }        // for\n}  // ROIAlignBackward\n\nat::Tensor ROIAlignForwardV2CPULaucher(const at::Tensor& input,\n                                       const at::Tensor& rois,\n                                       const float spatial_scale,\n                                       const int pooled_height,\n                                       const int pooled_width,\n                                       const int sampling_ratio, bool aligned) {\n  AT_ASSERTM(input.device().is_cpu(), \"input must be a CPU tensor\");\n  AT_ASSERTM(rois.device().is_cpu(), \"rois must be a CPU tensor\");\n\n  at::TensorArg input_t{input, \"input\", 1}, rois_t{rois, \"rois\", 2};\n\n  at::CheckedFrom c = \"ROIAlignForwardV2CPULaucher\";\n  at::checkAllSameType(c, {input_t, rois_t});\n\n  auto num_rois = rois.size(0);\n  auto channels = input.size(1);\n  auto height = input.size(2);\n  auto width = input.size(3);\n\n  at::Tensor output = at::zeros(\n      {num_rois, channels, pooled_height, pooled_width}, input.options());\n\n  auto output_size = num_rois * pooled_height * pooled_width * channels;\n\n  if (output.numel() == 0) return output;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(input.scalar_type(), \"ROIAlign_forward\", [&] {\n    ROIAlignForward<scalar_t>(\n        output_size, input.contiguous().data_ptr<scalar_t>(), spatial_scale,\n        channels, height, width, pooled_height, pooled_width, sampling_ratio,\n        rois.contiguous().data_ptr<scalar_t>(), output.data_ptr<scalar_t>(), aligned);\n  });\n  return output;\n}\n\nat::Tensor ROIAlignBackwardV2CPULaucher(\n    const at::Tensor& grad, const at::Tensor& rois, const float spatial_scale,\n    const int pooled_height, const int pooled_width, const int batch_size,\n    const int channels, const int height, const int width,\n    const int sampling_ratio, bool aligned) {\n  AT_ASSERTM(grad.device().is_cpu(), \"grad must be a CPU tensor\");\n  AT_ASSERTM(rois.device().is_cpu(), \"rois must be a CPU tensor\");\n\n  at::TensorArg grad_t{grad, \"grad\", 1}, rois_t{rois, \"rois\", 2};\n\n  at::CheckedFrom c = \"ROIAlignBackwardV2CPULaucher\";\n  at::checkAllSameType(c, {grad_t, rois_t});\n\n  at::Tensor grad_input =\n      at::zeros({batch_size, channels, height, width}, grad.options());\n\n  // handle possibly empty gradients\n  if (grad.numel() == 0) {\n    return grad_input;\n  }\n\n  // get stride values to ensure indexing into gradients is correct.\n  int n_stride = grad.stride(0);\n  int c_stride = grad.stride(1);\n  int h_stride = grad.stride(2);\n  int w_stride = grad.stride(3);\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(grad.scalar_type(), \"ROIAlign_backward\", [&] {\n    ROIAlignBackward<scalar_t>(\n        grad.numel(), grad.contiguous().data_ptr<scalar_t>(), spatial_scale,\n        channels, height, width, pooled_height, pooled_width, sampling_ratio,\n        grad_input.data_ptr<scalar_t>(), rois.contiguous().data_ptr<scalar_t>(),\n        n_stride, c_stride, h_stride, w_stride, aligned);\n  });\n  return grad_input;\n}\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/src/cuda/roi_align_kernel.cu",
    "content": "#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <THC/THCAtomics.cuh>\n\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\n#define THREADS_PER_BLOCK 1024\n\ninline int GET_BLOCKS(const int N) {\n  int optimal_block_num = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;\n  int max_block_num = 65000;\n  return min(optimal_block_num, max_block_num);\n}\n\ntemplate <typename scalar_t>\n__device__ scalar_t bilinear_interpolate(const scalar_t *bottom_data,\n                                         const int height, const int width,\n                                         scalar_t y, scalar_t x) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    return 0;\n  }\n\n  if (y <= 0) y = 0;\n  if (x <= 0) x = 0;\n\n  int y_low = (int)y;\n  int x_low = (int)x;\n  int y_high;\n  int x_high;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (scalar_t)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (scalar_t)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  scalar_t ly = y - y_low;\n  scalar_t lx = x - x_low;\n  scalar_t hy = 1. - ly;\n  scalar_t hx = 1. - lx;\n  // do bilinear interpolation\n  scalar_t lt = bottom_data[y_low * width + x_low];\n  scalar_t rt = bottom_data[y_low * width + x_high];\n  scalar_t lb = bottom_data[y_high * width + x_low];\n  scalar_t rb = bottom_data[y_high * width + x_high];\n  scalar_t w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  scalar_t val = (w1 * lt + w2 * rt + w3 * lb + w4 * rb);\n\n  return val;\n}\n\ntemplate <typename scalar_t>\n__global__ void ROIAlignForwardV1(\n    const int nthreads, const scalar_t *bottom_data,\n    const scalar_t *bottom_rois, const scalar_t spatial_scale,\n    const int sample_num, const int channels, const int height, const int width,\n    const int pooled_height, const int pooled_width, scalar_t *top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the aligned output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const scalar_t *offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    scalar_t roi_start_w = offset_bottom_rois[1] * spatial_scale;\n    scalar_t roi_start_h = offset_bottom_rois[2] * spatial_scale;\n    scalar_t roi_end_w = (offset_bottom_rois[3] + 1) * spatial_scale;\n    scalar_t roi_end_h = (offset_bottom_rois[4] + 1) * spatial_scale;\n\n    // Force malformed ROIs to be 1x1\n    scalar_t roi_width = fmaxf((scalar_t)roi_end_w - roi_start_w, 0.);\n    scalar_t roi_height = fmaxf((scalar_t)roi_end_h - roi_start_h, 0.);\n\n    scalar_t bin_size_h = roi_height / pooled_height;\n    scalar_t bin_size_w = roi_width / pooled_width;\n\n    const scalar_t *offset_bottom_data =\n        bottom_data + (roi_batch_ind * channels + c) * height * width;\n\n    int sample_num_h = (sample_num > 0)\n                           ? sample_num\n                           : ceil(roi_height / pooled_height);  // e.g., = 2\n    int sample_num_w =\n        (sample_num > 0) ? sample_num : ceil(roi_width / pooled_width);\n\n    scalar_t output_val = 0;\n    for (int iy = 0; iy < sample_num_h; iy++) {\n      const scalar_t y = roi_start_h + ph * bin_size_h +\n                         (scalar_t)(iy + scalar_t(.5f)) * bin_size_h /\n                             (scalar_t)(sample_num_h);\n      for (int ix = 0; ix < sample_num_w; ix++) {\n        const scalar_t x = roi_start_w + pw * bin_size_w +\n                           (scalar_t)(ix + scalar_t(.5f)) * bin_size_w /\n                               (scalar_t)(sample_num_w);\n        scalar_t val = bilinear_interpolate<scalar_t>(offset_bottom_data,\n                                                      height, width, y, x);\n        output_val += val;\n      }\n    }\n    output_val /= (sample_num_h * sample_num_w);\n    top_data[index] = output_val;\n  }\n}\n\nint ROIAlignForwardLaucher(const at::Tensor features, const at::Tensor rois,\n                           const float spatial_scale, const int sample_num,\n                           const int channels, const int height,\n                           const int width, const int num_rois,\n                           const int pooled_height, const int pooled_width,\n                           at::Tensor output) {\n  const int output_size = num_rois * pooled_height * pooled_width * channels;\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"ROIAlignLaucherForward\", ([&] {\n        const scalar_t *bottom_data = features.data_ptr<scalar_t>();\n        const scalar_t *rois_data = rois.data_ptr<scalar_t>();\n        scalar_t *top_data = output.data_ptr<scalar_t>();\n\n        ROIAlignForwardV1<scalar_t>\n            <<<GET_BLOCKS(output_size), THREADS_PER_BLOCK, 0,\n               at::cuda::getCurrentCUDAStream()>>>(\n                output_size, bottom_data, rois_data, scalar_t(spatial_scale),\n                sample_num, channels, height, width, pooled_height,\n                pooled_width, top_data);\n      }));\n  THCudaCheck(cudaGetLastError());\n  return 1;\n}\n\ntemplate <typename scalar_t>\n__device__ void bilinear_interpolate_gradient(const int height, const int width,\n                                              scalar_t y, scalar_t x,\n                                              scalar_t &w1, scalar_t &w2,\n                                              scalar_t &w3, scalar_t &w4,\n                                              int &x_low, int &x_high,\n                                              int &y_low, int &y_high) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    w1 = w2 = w3 = w4 = 0.;\n    x_low = x_high = y_low = y_high = -1;\n    return;\n  }\n\n  if (y <= 0) y = 0;\n  if (x <= 0) x = 0;\n\n  y_low = (int)y;\n  x_low = (int)x;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (scalar_t)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (scalar_t)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  scalar_t ly = y - y_low;\n  scalar_t lx = x - x_low;\n  scalar_t hy = 1. - ly;\n  scalar_t hx = 1. - lx;\n\n  w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  return;\n}\n\ntemplate <typename scalar_t>\n__global__ void ROIAlignBackwardV1(\n    const int nthreads, const scalar_t *top_diff, const scalar_t *bottom_rois,\n    const scalar_t spatial_scale, const int sample_num, const int channels,\n    const int height, const int width, const int pooled_height,\n    const int pooled_width, scalar_t *bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the aligned output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const scalar_t *offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    scalar_t roi_start_w = offset_bottom_rois[1] * spatial_scale;\n    scalar_t roi_start_h = offset_bottom_rois[2] * spatial_scale;\n    scalar_t roi_end_w = (offset_bottom_rois[3] + 1) * spatial_scale;\n    scalar_t roi_end_h = (offset_bottom_rois[4] + 1) * spatial_scale;\n\n    // Force malformed ROIs to be 1x1\n    scalar_t roi_width = fmaxf((scalar_t)roi_end_w - roi_start_w, 0.);\n    scalar_t roi_height = fmaxf((scalar_t)roi_end_h - roi_start_h, 0.);\n\n    scalar_t bin_size_h = roi_height / pooled_height;\n    scalar_t bin_size_w = roi_width / pooled_width;\n\n    scalar_t *offset_bottom_diff =\n        bottom_diff + (roi_batch_ind * channels + c) * height * width;\n    int offset_top = (n * channels + c) * pooled_height * pooled_width +\n                     ph * pooled_width + pw;\n    scalar_t offset_top_diff = top_diff[offset_top];\n\n    int sample_num_h = (sample_num > 0)\n                           ? sample_num\n                           : ceil(roi_height / pooled_height);  // e.g., = 2\n    int sample_num_w =\n        (sample_num > 0) ? sample_num : ceil(roi_width / pooled_width);\n\n    const scalar_t count = (scalar_t)(sample_num_h * sample_num_w);\n\n    for (int iy = 0; iy < sample_num_h; iy++) {\n      const scalar_t y =\n          roi_start_h + ph * bin_size_h +\n          (scalar_t)(iy + .5f) * bin_size_h / (scalar_t)(sample_num_h);\n      for (int ix = 0; ix < sample_num_w; ix++) {\n        const scalar_t x =\n            roi_start_w + pw * bin_size_w +\n            (scalar_t)(ix + .5f) * bin_size_w / (scalar_t)(sample_num_w);\n        scalar_t w1, w2, w3, w4;\n        int x_low, x_high, y_low, y_high;\n\n        bilinear_interpolate_gradient<scalar_t>(\n            height, width, y, x, w1, w2, w3, w4, x_low, x_high, y_low, y_high);\n        scalar_t g1 = offset_top_diff * w1 / count;\n        scalar_t g2 = offset_top_diff * w2 / count;\n        scalar_t g3 = offset_top_diff * w3 / count;\n        scalar_t g4 = offset_top_diff * w4 / count;\n        if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {\n          atomicAdd(offset_bottom_diff + y_low * width + x_low, g1);\n          atomicAdd(offset_bottom_diff + y_low * width + x_high, g2);\n          atomicAdd(offset_bottom_diff + y_high * width + x_low, g3);\n          atomicAdd(offset_bottom_diff + y_high * width + x_high, g4);\n        }\n      }\n    }\n  }\n}\n\nint ROIAlignBackwardLaucher(const at::Tensor top_grad, const at::Tensor rois,\n                            const float spatial_scale, const int sample_num,\n                            const int channels, const int height,\n                            const int width, const int num_rois,\n                            const int pooled_height, const int pooled_width,\n                            at::Tensor bottom_grad) {\n  const int output_size = num_rois * pooled_height * pooled_width * channels;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"ROIAlignLaucherBackward\", ([&] {\n        const scalar_t *top_diff = top_grad.data_ptr<scalar_t>();\n        const scalar_t *rois_data = rois.data_ptr<scalar_t>();\n        scalar_t *bottom_diff = bottom_grad.data_ptr<scalar_t>();\n        if (sizeof(scalar_t) == sizeof(double)) {\n          fprintf(stderr, \"double is not supported\\n\");\n          exit(-1);\n        }\n\n        ROIAlignBackwardV1<scalar_t>\n            <<<GET_BLOCKS(output_size), THREADS_PER_BLOCK, 0,\n               at::cuda::getCurrentCUDAStream()>>>(\n                output_size, top_diff, rois_data, spatial_scale, sample_num,\n                channels, height, width, pooled_height, pooled_width,\n                bottom_diff);\n      }));\n  THCudaCheck(cudaGetLastError());\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/src/cuda/roi_align_kernel_v2.cu",
    "content": "// Modified from\n// https://github.com/facebookresearch/detectron2/tree/master/detectron2/layers/csrc/ROIAlign\n// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAGuard.h>\n#include <ATen/cuda/CUDAApplyUtils.cuh>\n\n// TODO make it in a common file\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\ntemplate <typename T>\n__device__ T bilinear_interpolate(const T* bottom_data, const int height,\n                                  const int width, T y, T x,\n                                  const int index /* index for debug only*/) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    // empty\n    return 0;\n  }\n\n  if (y <= 0) y = 0;\n  if (x <= 0) x = 0;\n\n  int y_low = (int)y;\n  int x_low = (int)x;\n  int y_high;\n  int x_high;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (T)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (T)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  T ly = y - y_low;\n  T lx = x - x_low;\n  T hy = 1. - ly, hx = 1. - lx;\n  // do bilinear interpolation\n  T v1 = bottom_data[y_low * width + x_low];\n  T v2 = bottom_data[y_low * width + x_high];\n  T v3 = bottom_data[y_high * width + x_low];\n  T v4 = bottom_data[y_high * width + x_high];\n  T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n\n  return val;\n}\n\ntemplate <typename T>\n__global__ void RoIAlignForwardV2(\n    const int nthreads, const T* bottom_data, const T spatial_scale,\n    const int channels, const int height, const int width,\n    const int pooled_height, const int pooled_width, const int sampling_ratio,\n    const T* bottom_rois, T* top_data, bool aligned) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n\n    // Do not use rounding; this implementation detail is critical\n    T offset = aligned ? (T)0.5 : (T)0.0;\n    T roi_start_w = offset_bottom_rois[1] * spatial_scale - offset;\n    T roi_start_h = offset_bottom_rois[2] * spatial_scale - offset;\n    T roi_end_w = offset_bottom_rois[3] * spatial_scale - offset;\n    T roi_end_h = offset_bottom_rois[4] * spatial_scale - offset;\n\n    T roi_width = roi_end_w - roi_start_w;\n    T roi_height = roi_end_h - roi_start_h;\n    if (!aligned) {  // for backward-compatibility only\n      roi_width = max(roi_width, (T)1.);\n      roi_height = max(roi_height, (T)1.);\n    }\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    const T* offset_bottom_data =\n        bottom_data + (roi_batch_ind * channels + c) * height * width;\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n                             ? sampling_ratio\n                             : ceil(roi_height / pooled_height);  // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    // When the grid is empty, output zeros.\n    const T count = max(roi_bin_grid_h * roi_bin_grid_w, 1);  // e.g. = 4\n\n    T output_val = 0.;\n    for (int iy = 0; iy < roi_bin_grid_h; iy++)  // e.g., iy = 0, 1\n    {\n      const T y = roi_start_h + ph * bin_size_h +\n                  static_cast<T>(iy + .5f) * bin_size_h /\n                      static_cast<T>(roi_bin_grid_h);  // e.g., 0.5, 1.5\n      for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n        const T x = roi_start_w + pw * bin_size_w +\n                    static_cast<T>(ix + .5f) * bin_size_w /\n                        static_cast<T>(roi_bin_grid_w);\n\n        T val = bilinear_interpolate(offset_bottom_data, height, width, y, x,\n                                     index);\n        output_val += val;\n      }\n    }\n    output_val /= count;\n\n    top_data[index] = output_val;\n  }\n}\n\ntemplate <typename T>\n__device__ void bilinear_interpolate_gradient(\n    const int height, const int width, T y, T x, T& w1, T& w2, T& w3, T& w4,\n    int& x_low, int& x_high, int& y_low, int& y_high,\n    const int index /* index for debug only*/) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    // empty\n    w1 = w2 = w3 = w4 = 0.;\n    x_low = x_high = y_low = y_high = -1;\n    return;\n  }\n\n  if (y <= 0) y = 0;\n  if (x <= 0) x = 0;\n\n  y_low = (int)y;\n  x_low = (int)x;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (T)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (T)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  T ly = y - y_low;\n  T lx = x - x_low;\n  T hy = 1. - ly, hx = 1. - lx;\n\n  // reference in forward\n  // T v1 = bottom_data[y_low * width + x_low];\n  // T v2 = bottom_data[y_low * width + x_high];\n  // T v3 = bottom_data[y_high * width + x_low];\n  // T v4 = bottom_data[y_high * width + x_high];\n  // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n\n  w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  return;\n}\n\ntemplate <typename T>\n__global__ void RoIAlignBackwardFeatureV2(\n    const int nthreads, const T* top_diff, const int num_rois,\n    const T spatial_scale, const int channels, const int height,\n    const int width, const int pooled_height, const int pooled_width,\n    const int sampling_ratio, T* bottom_diff, const T* bottom_rois,\n    bool aligned) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n\n    // Do not use rounding; this implementation detail is critical\n    T offset = aligned ? (T)0.5 : (T)0.0;\n    T roi_start_w = offset_bottom_rois[1] * spatial_scale - offset;\n    T roi_start_h = offset_bottom_rois[2] * spatial_scale - offset;\n    T roi_end_w = offset_bottom_rois[3] * spatial_scale - offset;\n    T roi_end_h = offset_bottom_rois[4] * spatial_scale - offset;\n\n    T roi_width = roi_end_w - roi_start_w;\n    T roi_height = roi_end_h - roi_start_h;\n    if (!aligned) {  // for backward-compatibility only\n      roi_width = max(roi_width, (T)1.);\n      roi_height = max(roi_height, (T)1.);\n    }\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    T* offset_bottom_diff =\n        bottom_diff + (roi_batch_ind * channels + c) * height * width;\n\n    int top_offset = (n * channels + c) * pooled_height * pooled_width;\n    const T* offset_top_diff = top_diff + top_offset;\n    const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n                             ? sampling_ratio\n                             : ceil(roi_height / pooled_height);  // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    const T count = roi_bin_grid_h * roi_bin_grid_w;  // e.g. = 4\n\n    for (int iy = 0; iy < roi_bin_grid_h; iy++)  // e.g., iy = 0, 1\n    {\n      const T y = roi_start_h + ph * bin_size_h +\n                  static_cast<T>(iy + .5f) * bin_size_h /\n                      static_cast<T>(roi_bin_grid_h);  // e.g., 0.5, 1.5\n      for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n        const T x = roi_start_w + pw * bin_size_w +\n                    static_cast<T>(ix + .5f) * bin_size_w /\n                        static_cast<T>(roi_bin_grid_w);\n\n        T w1, w2, w3, w4;\n        int x_low, x_high, y_low, y_high;\n\n        bilinear_interpolate_gradient(height, width, y, x, w1, w2, w3, w4,\n                                      x_low, x_high, y_low, y_high, index);\n\n        T g1 = top_diff_this_bin * w1 / count;\n        T g2 = top_diff_this_bin * w2 / count;\n        T g3 = top_diff_this_bin * w3 / count;\n        T g4 = top_diff_this_bin * w4 / count;\n\n        if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {\n          atomicAdd(offset_bottom_diff + y_low * width + x_low,\n                    static_cast<T>(g1));\n          atomicAdd(offset_bottom_diff + y_low * width + x_high,\n                    static_cast<T>(g2));\n          atomicAdd(offset_bottom_diff + y_high * width + x_low,\n                    static_cast<T>(g3));\n          atomicAdd(offset_bottom_diff + y_high * width + x_high,\n                    static_cast<T>(g4));\n        }  // if\n      }    // ix\n    }      // iy\n  }        // CUDA_1D_KERNEL_LOOP\n}  // RoIAlignBackward\n\nat::Tensor ROIAlignForwardV2Laucher(const at::Tensor& input,\n                                    const at::Tensor& rois,\n                                    const float spatial_scale,\n                                    const int pooled_height,\n                                    const int pooled_width,\n                                    const int sampling_ratio, bool aligned) {\n  AT_ASSERTM(input.device().is_cuda(), \"input must be a CUDA tensor\");\n  AT_ASSERTM(rois.device().is_cuda(), \"rois must be a CUDA tensor\");\n  at::TensorArg input_t{input, \"input\", 1}, rois_t{rois, \"rois\", 2};\n\n  at::CheckedFrom c = \"ROIAlign_forward_cuda\";\n  at::checkAllSameGPU(c, {input_t, rois_t});\n  at::checkAllSameType(c, {input_t, rois_t});\n  at::cuda::CUDAGuard device_guard(input.device());\n\n  auto num_rois = rois.size(0);\n  auto channels = input.size(1);\n  auto height = input.size(2);\n  auto width = input.size(3);\n\n  auto output = at::empty({num_rois, channels, pooled_height, pooled_width},\n                          input.options());\n  auto output_size = num_rois * pooled_height * pooled_width * channels;\n  cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n  dim3 grid(std::min(at::cuda::ATenCeilDiv(static_cast<int64_t>(output_size), static_cast<int64_t>(512)), static_cast<int64_t>(4096)));\n  dim3 block(512);\n\n  if (output.numel() == 0) {\n    AT_CUDA_CHECK(cudaGetLastError());\n    return output;\n  }\n\n  AT_DISPATCH_FLOATING_TYPES(input.scalar_type(), \"ROIAlign_forward\", [&] {\n    RoIAlignForwardV2<scalar_t><<<grid, block, 0, stream>>>(\n        output_size, input.contiguous().data_ptr<scalar_t>(), spatial_scale,\n        channels, height, width, pooled_height, pooled_width, sampling_ratio,\n        rois.contiguous().data_ptr<scalar_t>(), output.data_ptr<scalar_t>(), aligned);\n  });\n  cudaDeviceSynchronize();\n  AT_CUDA_CHECK(cudaGetLastError());\n  return output;\n}\n\n// TODO remove the dependency on input and use instead its sizes -> save memory\nat::Tensor ROIAlignBackwardV2Laucher(\n    const at::Tensor& grad, const at::Tensor& rois, const float spatial_scale,\n    const int pooled_height, const int pooled_width, const int batch_size,\n    const int channels, const int height, const int width,\n    const int sampling_ratio, bool aligned) {\n  AT_ASSERTM(grad.device().is_cuda(), \"grad must be a CUDA tensor\");\n  AT_ASSERTM(rois.device().is_cuda(), \"rois must be a CUDA tensor\");\n\n  at::TensorArg grad_t{grad, \"grad\", 1}, rois_t{rois, \"rois\", 2};\n  at::CheckedFrom c = \"ROIAlign_backward_cuda\";\n  at::checkAllSameGPU(c, {grad_t, rois_t});\n  at::checkAllSameType(c, {grad_t, rois_t});\n  at::cuda::CUDAGuard device_guard(grad.device());\n\n  auto num_rois = rois.size(0);\n  auto grad_input =\n      at::zeros({batch_size, channels, height, width}, grad.options());\n\n  cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n  dim3 grid(std::min(at::cuda::ATenCeilDiv(static_cast<int64_t>(grad.numel()), static_cast<int64_t>(512)), static_cast<int64_t>(4096)));\n  dim3 block(512);\n\n  // handle possibly empty gradients\n  if (grad.numel() == 0) {\n    AT_CUDA_CHECK(cudaGetLastError());\n    return grad_input;\n  }\n\n  AT_DISPATCH_FLOATING_TYPES(grad.scalar_type(), \"ROIAlign_backward\", [&] {\n    RoIAlignBackwardFeatureV2<scalar_t><<<grid, block, 0, stream>>>(\n        grad.numel(), grad.contiguous().data_ptr<scalar_t>(), num_rois,\n        spatial_scale, channels, height, width, pooled_height, pooled_width,\n        sampling_ratio, grad_input.data_ptr<scalar_t>(),\n        rois.contiguous().data_ptr<scalar_t>(), aligned);\n  });\n  AT_CUDA_CHECK(cudaGetLastError());\n  return grad_input;\n}\n"
  },
  {
    "path": "code/mmdet/ops/roi_align/src/roi_align_ext.cpp",
    "content": "#include <ATen/ATen.h>\n#include <torch/extension.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nint ROIAlignForwardLaucher(const at::Tensor features, const at::Tensor rois,\n                           const float spatial_scale, const int sample_num,\n                           const int channels, const int height,\n                           const int width, const int num_rois,\n                           const int pooled_height, const int pooled_width,\n                           at::Tensor output);\n\nint ROIAlignBackwardLaucher(const at::Tensor top_grad, const at::Tensor rois,\n                            const float spatial_scale, const int sample_num,\n                            const int channels, const int height,\n                            const int width, const int num_rois,\n                            const int pooled_height, const int pooled_width,\n                            at::Tensor bottom_grad);\n\nat::Tensor ROIAlignForwardV2Laucher(const at::Tensor& input,\n                                    const at::Tensor& rois,\n                                    const float spatial_scale,\n                                    const int pooled_height,\n                                    const int pooled_width,\n                                    const int sampling_ratio, bool aligned);\n\nat::Tensor ROIAlignBackwardV2Laucher(\n    const at::Tensor& grad, const at::Tensor& rois, const float spatial_scale,\n    const int pooled_height, const int pooled_width, const int batch_size,\n    const int channels, const int height, const int width,\n    const int sampling_ratio, bool aligned);\n#endif\n\nat::Tensor ROIAlignForwardV2CPULaucher(const at::Tensor& input,\n                                       const at::Tensor& rois,\n                                       const float spatial_scale,\n                                       const int pooled_height,\n                                       const int pooled_width,\n                                       const int sampling_ratio, bool aligned);\n\nat::Tensor ROIAlignBackwardV2CPULaucher(\n    const at::Tensor& grad, const at::Tensor& rois, const float spatial_scale,\n    const int pooled_height, const int pooled_width, const int batch_size,\n    const int channels, const int height, const int width,\n    const int sampling_ratio, bool aligned);\n\n#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x, \" must be a CUDAtensor \")\n#define CHECK_CONTIGUOUS(x) \\\n  TORCH_CHECK(x.is_contiguous(), #x, \" must be contiguous \")\n#define CHECK_INPUT(x) \\\n  CHECK_CUDA(x);       \\\n  CHECK_CONTIGUOUS(x)\n\nint ROIAlign_forwardV1(at::Tensor features, at::Tensor rois, int pooled_height,\n                       int pooled_width, float spatial_scale, int sample_num,\n                       at::Tensor output) {\n  if (features.device().is_cuda()) {\n#ifdef WITH_CUDA\n    CHECK_INPUT(features);\n    CHECK_INPUT(rois);\n    CHECK_INPUT(output);\n    at::DeviceGuard guard(features.device());\n\n    // Number of ROIs\n    int num_rois = rois.size(0);\n    int size_rois = rois.size(1);\n\n    if (size_rois != 5) {\n      printf(\"wrong roi size\\n\");\n      return 0;\n    }\n\n    int num_channels = features.size(1);\n    int data_height = features.size(2);\n    int data_width = features.size(3);\n\n    ROIAlignForwardLaucher(features, rois, spatial_scale, sample_num,\n                           num_channels, data_height, data_width, num_rois,\n                           pooled_height, pooled_width, output);\n\n    return 1;\n#else\n    AT_ERROR(\"ROIAlign is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"ROIAlign is not implemented on CPU\");\n}\n\nint ROIAlign_backwardV1(at::Tensor top_grad, at::Tensor rois, int pooled_height,\n                        int pooled_width, float spatial_scale, int sample_num,\n                        at::Tensor bottom_grad) {\n  if (top_grad.device().is_cuda()) {\n#ifdef WITH_CUDA\n    CHECK_INPUT(top_grad);\n    CHECK_INPUT(rois);\n    CHECK_INPUT(bottom_grad);\n    at::DeviceGuard guard(top_grad.device());\n\n    // Number of ROIs\n    int num_rois = rois.size(0);\n    int size_rois = rois.size(1);\n    if (size_rois != 5) {\n      printf(\"wrong roi size\\n\");\n      return 0;\n    }\n\n    int num_channels = bottom_grad.size(1);\n    int data_height = bottom_grad.size(2);\n    int data_width = bottom_grad.size(3);\n\n    ROIAlignBackwardLaucher(top_grad, rois, spatial_scale, sample_num,\n                            num_channels, data_height, data_width, num_rois,\n                            pooled_height, pooled_width, bottom_grad);\n\n    return 1;\n#else\n    AT_ERROR(\"ROIAlign is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"ROIAlign is not implemented on CPU\");\n}\n\n// Interface for Python\ninline at::Tensor ROIAlign_forwardV2(const at::Tensor& input,\n                                     const at::Tensor& rois,\n                                     const float spatial_scale,\n                                     const int pooled_height,\n                                     const int pooled_width,\n                                     const int sampling_ratio, bool aligned) {\n  if (input.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return ROIAlignForwardV2Laucher(input, rois, spatial_scale, pooled_height,\n                                    pooled_width, sampling_ratio, aligned);\n#else\n    AT_ERROR(\"ROIAlignV2 is not compiled with GPU support\");\n#endif\n  }\n  return ROIAlignForwardV2CPULaucher(input, rois, spatial_scale, pooled_height,\n                                     pooled_width, sampling_ratio, aligned);\n}\n\ninline at::Tensor ROIAlign_backwardV2(\n    const at::Tensor& grad, const at::Tensor& rois, const float spatial_scale,\n    const int pooled_height, const int pooled_width, const int batch_size,\n    const int channels, const int height, const int width,\n    const int sampling_ratio, bool aligned) {\n  if (grad.device().is_cuda()) {\n#ifdef WITH_CUDA\n    return ROIAlignBackwardV2Laucher(grad, rois, spatial_scale, pooled_height,\n                                     pooled_width, batch_size, channels, height,\n                                     width, sampling_ratio, aligned);\n#else\n    AT_ERROR(\"ROIAlignV2 is not compiled with GPU support\");\n#endif\n  }\n  return ROIAlignBackwardV2CPULaucher(grad, rois, spatial_scale, pooled_height,\n                                      pooled_width, batch_size, channels,\n                                      height, width, sampling_ratio, aligned);\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"forward_v1\", &ROIAlign_forwardV1, \"Roi_Align V1 forward\");\n  m.def(\"backward_v1\", &ROIAlign_backwardV1, \"Roi_Align V1 backward\");\n  m.def(\"forward_v2\", &ROIAlign_forwardV2, \"Roi_Align V2 forward\");\n  m.def(\"backward_v2\", &ROIAlign_backwardV2, \"Roi_Align V2 backward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/roi_pool/__init__.py",
    "content": "from .roi_pool import RoIPool, roi_pool\n\n__all__ = ['roi_pool', 'RoIPool']\n"
  },
  {
    "path": "code/mmdet/ops/roi_pool/gradcheck.py",
    "content": "import os.path as osp\nimport sys\n\nimport torch\nfrom torch.autograd import gradcheck\n\nsys.path.append(osp.abspath(osp.join(__file__, '../../')))\nfrom roi_pool import RoIPool  # noqa: E402, isort:skip\n\nfeat = torch.randn(4, 16, 15, 15, requires_grad=True).cuda()\nrois = torch.Tensor([[0, 0, 0, 50, 50], [0, 10, 30, 43, 55],\n                     [1, 67, 40, 110, 120]]).cuda()\ninputs = (feat, rois)\nprint('Gradcheck for roi pooling...')\ntest = gradcheck(RoIPool(4, 1.0 / 8), inputs, eps=1e-5, atol=1e-3)\nprint(test)\n"
  },
  {
    "path": "code/mmdet/ops/roi_pool/roi_pool.py",
    "content": "import torch\nimport torch.nn as nn\nfrom torch.autograd import Function\nfrom torch.autograd.function import once_differentiable\nfrom torch.nn.modules.utils import _pair\n\nfrom . import roi_pool_ext\n\n\nclass RoIPoolFunction(Function):\n\n    @staticmethod\n    def forward(ctx, features, rois, out_size, spatial_scale):\n        assert features.is_cuda\n        out_h, out_w = _pair(out_size)\n        assert isinstance(out_h, int) and isinstance(out_w, int)\n        ctx.save_for_backward(rois)\n        num_channels = features.size(1)\n        num_rois = rois.size(0)\n        out_size = (num_rois, num_channels, out_h, out_w)\n        output = features.new_zeros(out_size)\n        argmax = features.new_zeros(out_size, dtype=torch.int)\n        roi_pool_ext.forward(features, rois, out_h, out_w, spatial_scale,\n                             output, argmax)\n        ctx.spatial_scale = spatial_scale\n        ctx.feature_size = features.size()\n        ctx.argmax = argmax\n\n        return output\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, grad_output):\n        assert grad_output.is_cuda\n        spatial_scale = ctx.spatial_scale\n        feature_size = ctx.feature_size\n        argmax = ctx.argmax\n        rois = ctx.saved_tensors[0]\n        assert feature_size is not None\n\n        grad_input = grad_rois = None\n        if ctx.needs_input_grad[0]:\n            grad_input = grad_output.new_zeros(feature_size)\n            roi_pool_ext.backward(grad_output.contiguous(), rois, argmax,\n                                  spatial_scale, grad_input)\n\n        return grad_input, grad_rois, None, None\n\n\nroi_pool = RoIPoolFunction.apply\n\n\nclass RoIPool(nn.Module):\n\n    def __init__(self, out_size, spatial_scale, use_torchvision=False):\n        super(RoIPool, self).__init__()\n\n        self.out_size = _pair(out_size)\n        self.spatial_scale = float(spatial_scale)\n        self.use_torchvision = use_torchvision\n\n    def forward(self, features, rois):\n        if self.use_torchvision:\n            from torchvision.ops import roi_pool as tv_roi_pool\n            return tv_roi_pool(features, rois, self.out_size,\n                               self.spatial_scale)\n        else:\n            return roi_pool(features, rois, self.out_size, self.spatial_scale)\n\n    def __repr__(self):\n        format_str = self.__class__.__name__\n        format_str += f'(out_size={self.out_size}, '\n        format_str += f'spatial_scale={self.spatial_scale}, '\n        format_str += f'use_torchvision={self.use_torchvision})'\n        return format_str\n"
  },
  {
    "path": "code/mmdet/ops/roi_pool/src/cuda/roi_pool_kernel.cu",
    "content": "#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <THC/THCAtomics.cuh>\n\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\n#define THREADS_PER_BLOCK 1024\n\ninline int GET_BLOCKS(const int N) {\n  int optimal_block_num = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;\n  int max_block_num = 65000;\n  return min(optimal_block_num, max_block_num);\n}\n\ntemplate <typename scalar_t>\n__global__ void ROIPoolForward(const int nthreads, const scalar_t *bottom_data,\n                               const scalar_t *rois,\n                               const scalar_t spatial_scale, const int channels,\n                               const int height, const int width,\n                               const int pooled_h, const int pooled_w,\n                               scalar_t *top_data, int *argmax_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_w;\n    int ph = (index / pooled_w) % pooled_h;\n    int c = (index / pooled_w / pooled_h) % channels;\n    int n = index / pooled_w / pooled_h / channels;\n\n    const scalar_t *offset_rois = rois + n * 5;\n    int roi_batch_ind = offset_rois[0];\n    // calculate the roi region on feature maps\n    scalar_t roi_x1 = offset_rois[1] * spatial_scale;\n    scalar_t roi_y1 = offset_rois[2] * spatial_scale;\n    scalar_t roi_x2 = (offset_rois[3] + 1) * spatial_scale;\n    scalar_t roi_y2 = (offset_rois[4] + 1) * spatial_scale;\n\n    // force malformed rois to be 1x1\n    scalar_t roi_w = roi_x2 - roi_x1;\n    scalar_t roi_h = roi_y2 - roi_y1;\n    if (roi_w <= 0 || roi_h <= 0) continue;\n\n    scalar_t bin_size_w = roi_w / static_cast<scalar_t>(pooled_w);\n    scalar_t bin_size_h = roi_h / static_cast<scalar_t>(pooled_h);\n\n    // the corresponding bin region\n    int bin_x1 = floor(static_cast<scalar_t>(pw) * bin_size_w + roi_x1);\n    int bin_y1 = floor(static_cast<scalar_t>(ph) * bin_size_h + roi_y1);\n    int bin_x2 = ceil(static_cast<scalar_t>(pw + 1) * bin_size_w + roi_x1);\n    int bin_y2 = ceil(static_cast<scalar_t>(ph + 1) * bin_size_h + roi_y1);\n\n    // add roi offsets and clip to input boundaries\n    bin_x1 = min(max(bin_x1, 0), width);\n    bin_y1 = min(max(bin_y1, 0), height);\n    bin_x2 = min(max(bin_x2, 0), width);\n    bin_y2 = min(max(bin_y2, 0), height);\n    bool is_empty = (bin_y2 <= bin_y1) || (bin_x2 <= bin_x1);\n\n    // If nothing is pooled, argmax = -1 causes nothing to be backprop'd\n    int max_idx = -1;\n    bottom_data += (roi_batch_ind * channels + c) * height * width;\n\n    // Define an empty pooling region to be zero\n    scalar_t max_val = is_empty ? static_cast<scalar_t>(0)\n                                : bottom_data[bin_y1 * width + bin_x1] - 1;\n\n    for (int h = bin_y1; h < bin_y2; ++h) {\n      for (int w = bin_x1; w < bin_x2; ++w) {\n        int offset = h * width + w;\n        if (bottom_data[offset] > max_val) {\n          max_val = bottom_data[offset];\n          max_idx = offset;\n        }\n      }\n    }\n    top_data[index] = max_val;\n    if (argmax_data != NULL) argmax_data[index] = max_idx;\n  }\n}\n\nint ROIPoolForwardLaucher(const at::Tensor features, const at::Tensor rois,\n                          const float spatial_scale, const int channels,\n                          const int height, const int width, const int num_rois,\n                          const int pooled_h, const int pooled_w,\n                          at::Tensor output, at::Tensor argmax) {\n  const int output_size = num_rois * channels * pooled_h * pooled_w;\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      features.scalar_type(), \"ROIPoolLaucherForward\", ([&] {\n        const scalar_t *bottom_data = features.data_ptr<scalar_t>();\n        const scalar_t *rois_data = rois.data_ptr<scalar_t>();\n        scalar_t *top_data = output.data_ptr<scalar_t>();\n        int *argmax_data = argmax.data_ptr<int>();\n\n        ROIPoolForward<scalar_t><<<GET_BLOCKS(output_size), THREADS_PER_BLOCK,\n                                   0, at::cuda::getCurrentCUDAStream()>>>(\n            output_size, bottom_data, rois_data, scalar_t(spatial_scale),\n            channels, height, width, pooled_h, pooled_w, top_data, argmax_data);\n      }));\n  THCudaCheck(cudaGetLastError());\n  return 1;\n}\ntemplate <typename scalar_t>\n__global__ void ROIPoolBackward(const int nthreads, const scalar_t *top_diff,\n                                const scalar_t *rois, const int *argmax_data,\n                                const scalar_t spatial_scale,\n                                const int channels, const int height,\n                                const int width, const int pooled_h,\n                                const int pooled_w, scalar_t *bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int pw = index % pooled_w;\n    int ph = (index / pooled_w) % pooled_h;\n    int c = (index / pooled_w / pooled_h) % channels;\n    int n = index / pooled_w / pooled_h / channels;\n    int roi_batch_ind = rois[n * 5];\n    int bottom_index = argmax_data[(n * channels + c) * pooled_h * pooled_w +\n                                   ph * pooled_w + pw];\n    if (bottom_index != -1) {\n      atomicAdd(bottom_diff + (roi_batch_ind * channels + c) * height * width +\n                    bottom_index,\n                top_diff[index]);\n    }\n  }\n}\nint ROIPoolBackwardLaucher(const at::Tensor top_grad, const at::Tensor rois,\n                           const at::Tensor argmax, const float spatial_scale,\n                           const int batch_size, const int channels,\n                           const int height, const int width,\n                           const int num_rois, const int pooled_h,\n                           const int pooled_w, at::Tensor bottom_grad) {\n  const int output_size = num_rois * pooled_h * pooled_w * channels;\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      top_grad.scalar_type(), \"ROIPoolLaucherBackward\", ([&] {\n        const scalar_t *top_diff = top_grad.data_ptr<scalar_t>();\n        const scalar_t *rois_data = rois.data_ptr<scalar_t>();\n        const int *argmax_data = argmax.data_ptr<int>();\n        scalar_t *bottom_diff = bottom_grad.data_ptr<scalar_t>();\n        if (sizeof(scalar_t) == sizeof(double)) {\n          fprintf(stderr, \"double is not supported\\n\");\n          exit(-1);\n        }\n        ROIPoolBackward<scalar_t><<<GET_BLOCKS(output_size), THREADS_PER_BLOCK,\n                                    0, at::cuda::getCurrentCUDAStream()>>>(\n            output_size, top_diff, rois_data, argmax_data,\n            scalar_t(spatial_scale), channels, height, width, pooled_h,\n            pooled_w, bottom_diff);\n      }));\n  THCudaCheck(cudaGetLastError());\n  return 1;\n}\n"
  },
  {
    "path": "code/mmdet/ops/roi_pool/src/roi_pool_ext.cpp",
    "content": "#include <torch/extension.h>\n\n#include <cmath>\n#include <vector>\n\n#ifdef WITH_CUDA\nint ROIPoolForwardLaucher(const at::Tensor features, const at::Tensor rois,\n                          const float spatial_scale, const int channels,\n                          const int height, const int width, const int num_rois,\n                          const int pooled_h, const int pooled_w,\n                          at::Tensor output, at::Tensor argmax);\n\nint ROIPoolBackwardLaucher(const at::Tensor top_grad, const at::Tensor rois,\n                           const at::Tensor argmax, const float spatial_scale,\n                           const int batch_size, const int channels,\n                           const int height, const int width,\n                           const int num_rois, const int pooled_h,\n                           const int pooled_w, at::Tensor bottom_grad);\n#endif\n\n#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x, \" must be a CUDAtensor \")\n#define CHECK_CONTIGUOUS(x) \\\n  TORCH_CHECK(x.is_contiguous(), #x, \" must be contiguous \")\n#define CHECK_INPUT(x) \\\n  CHECK_CUDA(x);       \\\n  CHECK_CONTIGUOUS(x)\n\nint roi_pooling_forward(at::Tensor features, at::Tensor rois,\n                             int pooled_height, int pooled_width,\n                             float spatial_scale, at::Tensor output,\n                             at::Tensor argmax) {\n  if (features.device().is_cuda()) {\n#ifdef WITH_CUDA\n    CHECK_INPUT(features);\n    CHECK_INPUT(rois);\n    CHECK_INPUT(output);\n    CHECK_INPUT(argmax);\n    at::DeviceGuard guard(features.device());\n\n    // Number of ROIs\n    int num_rois = rois.size(0);\n    int size_rois = rois.size(1);\n\n    if (size_rois != 5) {\n      printf(\"wrong roi size\\n\");\n      return 0;\n    }\n\n    int channels = features.size(1);\n    int height = features.size(2);\n    int width = features.size(3);\n\n    ROIPoolForwardLaucher(features, rois, spatial_scale, channels, height, width,\n                          num_rois, pooled_height, pooled_width, output, argmax);\n\n    return 1;\n#else\n    AT_ERROR(\"roi_pool is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"roi_pool is not implemented on CPU\");\n}\n\nint roi_pooling_backward(at::Tensor top_grad, at::Tensor rois,\n                              at::Tensor argmax, float spatial_scale,\n                              at::Tensor bottom_grad) {\n  if (top_grad.device().is_cuda()) {\n#ifdef WITH_CUDA\n    CHECK_INPUT(top_grad);\n    CHECK_INPUT(rois);\n    CHECK_INPUT(argmax);\n    CHECK_INPUT(bottom_grad);\n    at::DeviceGuard guard(top_grad.device());\n\n    int pooled_height = top_grad.size(2);\n    int pooled_width = top_grad.size(3);\n    int num_rois = rois.size(0);\n    int size_rois = rois.size(1);\n\n    if (size_rois != 5) {\n      printf(\"wrong roi size\\n\");\n      return 0;\n    }\n    int batch_size = bottom_grad.size(0);\n    int channels = bottom_grad.size(1);\n    int height = bottom_grad.size(2);\n    int width = bottom_grad.size(3);\n\n    ROIPoolBackwardLaucher(top_grad, rois, argmax, spatial_scale, batch_size,\n                           channels, height, width, num_rois, pooled_height,\n                           pooled_width, bottom_grad);\n\n  return 1;\n#else\n    AT_ERROR(\"roi_pool is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"roi_pool is not implemented on CPU\");\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"forward\", &roi_pooling_forward, \"Roi_Pooling forward\");\n  m.def(\"backward\", &roi_pooling_backward, \"Roi_Pooling backward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/saconv.py",
    "content": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom mmcv.cnn import CONV_LAYERS, constant_init\n\nfrom .conv_ws import ConvAWS2d\nfrom .dcn import deform_conv\n\n\n@CONV_LAYERS.register_module(name='SAC')\nclass SAConv2d(ConvAWS2d):\n    \"\"\"SAC (Switchable Atrous Convolution)\n\n    This is an implementation of SAC in DetectoRS\n    (https://arxiv.org/pdf/2006.02334.pdf).\n\n    Args:\n        in_channels (int): Number of channels in the input image\n        out_channels (int): Number of channels produced by the convolution\n        kernel_size (int or tuple): Size of the convolving kernel\n        stride (int or tuple, optional): Stride of the convolution. Default: 1\n        padding (int or tuple, optional): Zero-padding added to both sides of\n            the input. Default: 0\n        padding_mode (string, optional): ``'zeros'``, ``'reflect'``,\n            ``'replicate'`` or ``'circular'``. Default: ``'zeros'``\n        dilation (int or tuple, optional): Spacing between kernel elements.\n            Default: 1\n        groups (int, optional): Number of blocked connections from input\n            channels to output channels. Default: 1\n        bias (bool, optional): If ``True``, adds a learnable bias to the\n            output. Default: ``True``\n        use_deform: If ``True``, replace convolution with deformable\n            convolution. Default: ``False``.\n    \"\"\"\n\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 kernel_size,\n                 stride=1,\n                 padding=0,\n                 dilation=1,\n                 groups=1,\n                 bias=True,\n                 use_deform=False):\n        super().__init__(\n            in_channels,\n            out_channels,\n            kernel_size,\n            stride=stride,\n            padding=padding,\n            dilation=dilation,\n            groups=groups,\n            bias=bias)\n        self.use_deform = use_deform\n        self.switch = nn.Conv2d(\n            self.in_channels, 1, kernel_size=1, stride=stride, bias=True)\n        self.weight_diff = nn.Parameter(torch.Tensor(self.weight.size()))\n        self.pre_context = nn.Conv2d(\n            self.in_channels, self.in_channels, kernel_size=1, bias=True)\n        self.post_context = nn.Conv2d(\n            self.out_channels, self.out_channels, kernel_size=1, bias=True)\n        if self.use_deform:\n            self.offset_s = nn.Conv2d(\n                self.in_channels,\n                18,\n                kernel_size=3,\n                padding=1,\n                stride=stride,\n                bias=True)\n            self.offset_l = nn.Conv2d(\n                self.in_channels,\n                18,\n                kernel_size=3,\n                padding=1,\n                stride=stride,\n                bias=True)\n        self.init_weights()\n\n    def init_weights(self):\n        constant_init(self.switch, 0, bias=1)\n        self.weight_diff.data.zero_()\n        constant_init(self.pre_context, 0)\n        constant_init(self.post_context, 0)\n        if self.use_deform:\n            constant_init(self.offset_s, 0)\n            constant_init(self.offset_l, 0)\n\n    def forward(self, x):\n        # pre-context\n        avg_x = F.adaptive_avg_pool2d(x, output_size=1)\n        avg_x = self.pre_context(avg_x)\n        avg_x = avg_x.expand_as(x)\n        x = x + avg_x\n        # switch\n        avg_x = F.pad(x, pad=(2, 2, 2, 2), mode='reflect')\n        avg_x = F.avg_pool2d(avg_x, kernel_size=5, stride=1, padding=0)\n        switch = self.switch(avg_x)\n        # sac\n        weight = self._get_weight(self.weight)\n        if self.use_deform:\n            offset = self.offset_s(avg_x)\n            out_s = deform_conv(x, offset, weight, self.stride, self.padding,\n                                self.dilation, self.groups, 1)\n        else:\n            out_s = super().conv2d_forward(x, weight)\n        ori_p = self.padding\n        ori_d = self.dilation\n        self.padding = tuple(3 * p for p in self.padding)\n        self.dilation = tuple(3 * d for d in self.dilation)\n        weight = weight + self.weight_diff\n        if self.use_deform:\n            offset = self.offset_l(avg_x)\n            out_l = deform_conv(x, offset, weight, self.stride, self.padding,\n                                self.dilation, self.groups, 1)\n        else:\n            out_l = super().conv2d_forward(x, weight)\n        out = switch * out_s + (1 - switch) * out_l\n        self.padding = ori_p\n        self.dilation = ori_d\n        # post-context\n        avg_x = F.adaptive_avg_pool2d(out, output_size=1)\n        avg_x = self.post_context(avg_x)\n        avg_x = avg_x.expand_as(out)\n        out = out + avg_x\n        return out\n"
  },
  {
    "path": "code/mmdet/ops/sigmoid_focal_loss/__init__.py",
    "content": "from .sigmoid_focal_loss import SigmoidFocalLoss, sigmoid_focal_loss\n\n__all__ = ['SigmoidFocalLoss', 'sigmoid_focal_loss']\n"
  },
  {
    "path": "code/mmdet/ops/sigmoid_focal_loss/sigmoid_focal_loss.py",
    "content": "import torch.nn as nn\nfrom torch.autograd import Function\nfrom torch.autograd.function import once_differentiable\n\nfrom . import sigmoid_focal_loss_ext\n\n\nclass SigmoidFocalLossFunction(Function):\n\n    @staticmethod\n    def forward(ctx, input, target, gamma=2.0, alpha=0.25):\n        ctx.save_for_backward(input, target)\n        num_classes = input.shape[1]\n        ctx.num_classes = num_classes\n        ctx.gamma = gamma\n        ctx.alpha = alpha\n\n        loss = sigmoid_focal_loss_ext.forward(input, target, num_classes,\n                                              gamma, alpha)\n        return loss\n\n    @staticmethod\n    @once_differentiable\n    def backward(ctx, d_loss):\n        input, target = ctx.saved_tensors\n        num_classes = ctx.num_classes\n        gamma = ctx.gamma\n        alpha = ctx.alpha\n        d_loss = d_loss.contiguous()\n        d_input = sigmoid_focal_loss_ext.backward(input, target, d_loss,\n                                                  num_classes, gamma, alpha)\n        return d_input, None, None, None, None\n\n\nsigmoid_focal_loss = SigmoidFocalLossFunction.apply\n\n\n# TODO: remove this module\nclass SigmoidFocalLoss(nn.Module):\n\n    def __init__(self, gamma, alpha):\n        super(SigmoidFocalLoss, self).__init__()\n        self.gamma = gamma\n        self.alpha = alpha\n\n    def forward(self, logits, targets):\n        assert logits.is_cuda\n        loss = sigmoid_focal_loss(logits, targets, self.gamma, self.alpha)\n        return loss.sum()\n\n    def __repr__(self):\n        tmpstr = self.__class__.__name__\n        tmpstr += f'(gamma={self.gamma}, alpha={self.alpha})'\n        return tmpstr\n"
  },
  {
    "path": "code/mmdet/ops/sigmoid_focal_loss/src/cuda/sigmoid_focal_loss_cuda.cu",
    "content": "// modified from\n// https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/csrc/cuda/SigmoidFocalLoss_cuda.cu\n\n// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n// This file is modified from\n// https://github.com/pytorch/pytorch/blob/master/modules/detectron/sigmoid_focal_loss_op.cu\n// Cheng-Yang Fu\n// cyfu@cs.unc.edu\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n\n#include <THC/THC.h>\n#include <THC/THCAtomics.cuh>\n#include <THC/THCDeviceUtils.cuh>\n\n#include <cfloat>\n\n// TODO make it in a common file\n#define CUDA_1D_KERNEL_LOOP(i, n)                            \\\n  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \\\n       i += blockDim.x * gridDim.x)\n\ntemplate <typename scalar_t>\n__global__ void SigmoidFocalLossForward(const int nthreads,\n                                        const scalar_t *logits,\n                                        const int64_t *targets,\n                                        const int num_classes,\n                                        const float gamma, const float alpha,\n                                        const int num, scalar_t *losses) {\n  CUDA_1D_KERNEL_LOOP(i, nthreads) {\n    int n = i / num_classes;\n    int d = i % num_classes;  // current class[0~79];\n    int t = targets[n];       // target class [0~79];\n\n    // Decide it is positive or negative case.\n    scalar_t c1 = (t == d);\n    scalar_t c2 = (t >= 0 & t != d);\n\n    scalar_t zn = (1.0 - alpha);\n    scalar_t zp = (alpha);\n\n    // p = 1. / 1. + expf(-x); p = sigmoid(x)\n    scalar_t p = 1. / (1. + expf(-logits[i]));\n\n    // (1-p)**gamma * log(p) where\n    scalar_t term1 = powf((1. - p), gamma) * logf(max(p, FLT_MIN));\n\n    // p**gamma * log(1-p)\n    scalar_t term2 =\n        powf(p, gamma) *\n        (-1. * logits[i] * (logits[i] >= 0) -\n         logf(1. + expf(logits[i] - 2. * logits[i] * (logits[i] >= 0))));\n\n    losses[i] = 0.0;\n    losses[i] += -c1 * term1 * zp;\n    losses[i] += -c2 * term2 * zn;\n\n  }  // CUDA_1D_KERNEL_LOOP\n}  // SigmoidFocalLossForward\n\ntemplate <typename scalar_t>\n__global__ void SigmoidFocalLossBackward(\n    const int nthreads, const scalar_t *logits, const int64_t *targets,\n    const scalar_t *d_losses, const int num_classes, const float gamma,\n    const float alpha, const int num, scalar_t *d_logits) {\n  CUDA_1D_KERNEL_LOOP(i, nthreads) {\n    int n = i / num_classes;\n    int d = i % num_classes;  // current class[0~79];\n    int t = targets[n];       // target class [0~79], 80 is background;\n\n    // Decide it is positive or negative case.\n    scalar_t c1 = (t == d);\n    scalar_t c2 = (t >= 0 & t != d);\n\n    scalar_t zn = (1.0 - alpha);\n    scalar_t zp = (alpha);\n    // p = 1. / 1. + expf(-x); p = sigmoid(x)\n    scalar_t p = 1. / (1. + expf(-logits[i]));\n\n    // (1-p)**g * (1 - p - g*p*log(p)\n    scalar_t term1 =\n        powf((1. - p), gamma) * (1. - p - (p * gamma * logf(max(p, FLT_MIN))));\n\n    // (p**g) * (g*(1-p)*log(1-p) - p)\n    scalar_t term2 =\n        powf(p, gamma) *\n        ((-1. * logits[i] * (logits[i] >= 0) -\n          logf(1. + expf(logits[i] - 2. * logits[i] * (logits[i] >= 0)))) *\n             (1. - p) * gamma -\n         p);\n    d_logits[i] = 0.0;\n    d_logits[i] += -c1 * term1 * zp;\n    d_logits[i] += -c2 * term2 * zn;\n    d_logits[i] = d_logits[i] * d_losses[i];\n\n  }  // CUDA_1D_KERNEL_LOOP\n}  // SigmoidFocalLossBackward\n\nat::Tensor SigmoidFocalLoss_forward_cuda(const at::Tensor &logits,\n                                         const at::Tensor &targets,\n                                         const int num_classes,\n                                         const float gamma, const float alpha) {\n  AT_ASSERTM(logits.device().is_cuda(), \"logits must be a CUDA tensor\");\n  AT_ASSERTM(targets.device().is_cuda(), \"targets must be a CUDA tensor\");\n  AT_ASSERTM(logits.dim() == 2, \"logits should be NxClass\");\n  AT_ASSERTM(targets.max().item<long>() <= (long)num_classes,\n             \"target label should smaller or equal than num classes\");\n\n  const int num_samples = logits.size(0);\n\n  auto losses = at::empty({num_samples, logits.size(1)}, logits.options());\n  auto losses_size = num_samples * logits.size(1);\n\n  dim3 grid(\n      std::min(THCCeilDiv((int64_t)losses_size, (int64_t)512), (int64_t)4096));\n  dim3 block(512);\n\n  if (losses.numel() == 0) {\n    THCudaCheck(cudaGetLastError());\n    return losses;\n  }\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      logits.scalar_type(), \"SigmoidFocalLoss_forward\", [&] {\n        SigmoidFocalLossForward<scalar_t>\n            <<<grid, block, 0, at::cuda::getCurrentCUDAStream()>>>(\n                losses_size, logits.contiguous().data_ptr<scalar_t>(),\n                targets.contiguous().data_ptr<int64_t>(), num_classes, gamma,\n                alpha, num_samples, losses.data_ptr<scalar_t>());\n      });\n  THCudaCheck(cudaGetLastError());\n  return losses;\n}\n\nat::Tensor SigmoidFocalLoss_backward_cuda(const at::Tensor &logits,\n                                          const at::Tensor &targets,\n                                          const at::Tensor &d_losses,\n                                          const int num_classes,\n                                          const float gamma,\n                                          const float alpha) {\n  AT_ASSERTM(logits.device().is_cuda(), \"logits must be a CUDA tensor\");\n  AT_ASSERTM(targets.device().is_cuda(), \"targets must be a CUDA tensor\");\n  AT_ASSERTM(d_losses.device().is_cuda(), \"d_losses must be a CUDA tensor\");\n\n  AT_ASSERTM(logits.dim() == 2, \"logits should be NxClass\");\n\n  const int num_samples = logits.size(0);\n  AT_ASSERTM(logits.size(1) == num_classes,\n             \"logits.size(1) should be num_classes\");\n\n  auto d_logits = at::zeros({num_samples, num_classes}, logits.options());\n  auto d_logits_size = num_samples * logits.size(1);\n\n  dim3 grid(std::min(THCCeilDiv((int64_t)d_logits_size, (int64_t)512),\n                     (int64_t)4096));\n  dim3 block(512);\n\n  if (d_logits.numel() == 0) {\n    THCudaCheck(cudaGetLastError());\n    return d_logits;\n  }\n\n  AT_DISPATCH_FLOATING_TYPES_AND_HALF(\n      logits.scalar_type(), \"SigmoidFocalLoss_backward\", [&] {\n        SigmoidFocalLossBackward<scalar_t>\n            <<<grid, block, 0, at::cuda::getCurrentCUDAStream()>>>(\n                d_logits_size, logits.contiguous().data_ptr<scalar_t>(),\n                targets.contiguous().data_ptr<int64_t>(),\n                d_losses.contiguous().data_ptr<scalar_t>(), num_classes, gamma,\n                alpha, num_samples, d_logits.data_ptr<scalar_t>());\n      });\n\n  THCudaCheck(cudaGetLastError());\n  return d_logits;\n}\n"
  },
  {
    "path": "code/mmdet/ops/sigmoid_focal_loss/src/sigmoid_focal_loss_ext.cpp",
    "content": "// modify from\n// https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/csrc/SigmoidFocalLoss.h\n#include <torch/extension.h>\n\n#ifdef WITH_CUDA\nat::Tensor SigmoidFocalLoss_forward_cuda(const at::Tensor &logits,\n                                         const at::Tensor &targets,\n                                         const int num_classes,\n                                         const float gamma, const float alpha);\n\nat::Tensor SigmoidFocalLoss_backward_cuda(const at::Tensor &logits,\n                                          const at::Tensor &targets,\n                                          const at::Tensor &d_losses,\n                                          const int num_classes,\n                                          const float gamma, const float alpha);\n#endif\n\n// Interface for Python\nat::Tensor SigmoidFocalLoss_forward(const at::Tensor &logits,\n                                    const at::Tensor &targets,\n                                    const int num_classes, const float gamma,\n                                    const float alpha) {\n  if (logits.device().is_cuda()) {\n#ifdef WITH_CUDA\n    at::DeviceGuard guard(logits.device());\n    return SigmoidFocalLoss_forward_cuda(logits, targets, num_classes, gamma,\n                                         alpha);\n#else\n      AT_ERROR(\"SigmoidFocalLoss is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"SigmoidFocalLoss is not implemented on the CPU\");\n}\n\nat::Tensor SigmoidFocalLoss_backward(const at::Tensor &logits,\n                                     const at::Tensor &targets,\n                                     const at::Tensor &d_losses,\n                                     const int num_classes, const float gamma,\n                                     const float alpha) {\n  if (logits.device().is_cuda()) {\n#ifdef WITH_CUDA\n    at::DeviceGuard guard(logits.device());\n    return SigmoidFocalLoss_backward_cuda(logits, targets, d_losses,\n                                          num_classes, gamma, alpha);\n#else\n      AT_ERROR(\"SigmoidFocalLoss is not compiled with GPU support\");\n#endif\n  }\n  AT_ERROR(\"SigmoidFocalLoss is not implemented on the CPU\");\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"forward\", &SigmoidFocalLoss_forward,\n        \"SigmoidFocalLoss forward\");\n  m.def(\"backward\", &SigmoidFocalLoss_backward,\n        \"SigmoidFocalLoss backward\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/utils/__init__.py",
    "content": "# from . import compiling_info\nfrom .compiling_info import get_compiler_version, get_compiling_cuda_version\n\n# get_compiler_version = compiling_info.get_compiler_version\n# get_compiling_cuda_version = compiling_info.get_compiling_cuda_version\n\n__all__ = ['get_compiler_version', 'get_compiling_cuda_version']\n"
  },
  {
    "path": "code/mmdet/ops/utils/src/compiling_info.cpp",
    "content": "// modified from\n// https://github.com/facebookresearch/detectron2/blob/master/detectron2/layers/csrc/vision.cpp\n#include <torch/extension.h>\n\n#ifdef WITH_CUDA\n#include <cuda_runtime_api.h>\nint get_cudart_version() { return CUDART_VERSION; }\n#endif\n\nstd::string get_compiling_cuda_version() {\n#ifdef WITH_CUDA\n  std::ostringstream oss;\n\n  // copied from\n  // https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/cuda/detail/CUDAHooks.cpp#L231\n  auto printCudaStyleVersion = [&](int v) {\n    oss << (v / 1000) << \".\" << (v / 10 % 100);\n    if (v % 10 != 0) {\n      oss << \".\" << (v % 10);\n    }\n  };\n  printCudaStyleVersion(get_cudart_version());\n  return oss.str();\n#else\n  return std::string(\"not available\");\n#endif\n}\n\n// similar to\n// https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/Version.cpp\nstd::string get_compiler_version() {\n  std::ostringstream ss;\n#if defined(__GNUC__)\n#ifndef __clang__\n  { ss << \"GCC \" << __GNUC__ << \".\" << __GNUC_MINOR__; }\n#endif\n#endif\n\n#if defined(__clang_major__)\n  {\n    ss << \"clang \" << __clang_major__ << \".\" << __clang_minor__ << \".\"\n       << __clang_patchlevel__;\n  }\n#endif\n\n#if defined(_MSC_VER)\n  { ss << \"MSVC \" << _MSC_FULL_VER; }\n#endif\n  return ss.str();\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"get_compiler_version\", &get_compiler_version, \"get_compiler_version\");\n  m.def(\"get_compiling_cuda_version\", &get_compiling_cuda_version,\n        \"get_compiling_cuda_version\");\n}\n"
  },
  {
    "path": "code/mmdet/ops/wrappers.py",
    "content": "\"\"\"\nModified from https://github.com/facebookresearch/detectron2/blob/master\n/detectron2/layers/wrappers.py\nWrap some nn modules to support empty tensor input.\nCurrently, these wrappers are mainly used in mask heads like fcn_mask_head\nand maskiou_heads since mask heads are trained on only positive RoIs.\n\"\"\"\nimport math\n\nimport torch\nimport torch.nn as nn\nfrom mmcv.cnn import CONV_LAYERS\nfrom torch.nn.modules.utils import _pair\n\n\nclass NewEmptyTensorOp(torch.autograd.Function):\n\n    @staticmethod\n    def forward(ctx, x, new_shape):\n        ctx.shape = x.shape\n        return x.new_empty(new_shape)\n\n    @staticmethod\n    def backward(ctx, grad):\n        shape = ctx.shape\n        return NewEmptyTensorOp.apply(grad, shape), None\n\n\n@CONV_LAYERS.register_module(name='Conv', force=True)\nclass Conv2d(nn.Conv2d):\n\n    def forward(self, x):\n        if x.numel() == 0 and torch.__version__ <= '1.4':\n            out_shape = [x.shape[0], self.out_channels]\n            for i, k, p, s, d in zip(x.shape[-2:], self.kernel_size,\n                                     self.padding, self.stride, self.dilation):\n                o = (i + 2 * p - (d * (k - 1) + 1)) // s + 1\n                out_shape.append(o)\n            empty = NewEmptyTensorOp.apply(x, out_shape)\n            if self.training:\n                # produce dummy gradient to avoid DDP warning.\n                dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0\n                return empty + dummy\n            else:\n                return empty\n\n        return super().forward(x)\n\n\nclass ConvTranspose2d(nn.ConvTranspose2d):\n\n    def forward(self, x):\n        if x.numel() == 0 and torch.__version__ <= '1.4.0':\n            out_shape = [x.shape[0], self.out_channels]\n            for i, k, p, s, d, op in zip(x.shape[-2:], self.kernel_size,\n                                         self.padding, self.stride,\n                                         self.dilation, self.output_padding):\n                out_shape.append((i - 1) * s - 2 * p + (d * (k - 1) + 1) + op)\n            empty = NewEmptyTensorOp.apply(x, out_shape)\n            if self.training:\n                # produce dummy gradient to avoid DDP warning.\n                dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0\n                return empty + dummy\n            else:\n                return empty\n\n        return super(ConvTranspose2d, self).forward(x)\n\n\nclass MaxPool2d(nn.MaxPool2d):\n\n    def forward(self, x):\n        if x.numel() == 0 and torch.__version__ <= '1.4':\n            out_shape = list(x.shape[:2])\n            for i, k, p, s, d in zip(x.shape[-2:], _pair(self.kernel_size),\n                                     _pair(self.padding), _pair(self.stride),\n                                     _pair(self.dilation)):\n                o = (i + 2 * p - (d * (k - 1) + 1)) / s + 1\n                o = math.ceil(o) if self.ceil_mode else math.floor(o)\n                out_shape.append(o)\n            empty = NewEmptyTensorOp.apply(x, out_shape)\n            return empty\n\n        return super().forward(x)\n\n\nclass Linear(torch.nn.Linear):\n\n    def forward(self, x):\n        if x.numel() == 0:\n            out_shape = [x.shape[0], self.out_features]\n            empty = NewEmptyTensorOp.apply(x, out_shape)\n            if self.training:\n                # produce dummy gradient to avoid DDP warning.\n                dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0\n                return empty + dummy\n            else:\n                return empty\n\n        return super().forward(x)\n"
  },
  {
    "path": "code/mmdet/utils/__init__.py",
    "content": "from .collect_env import collect_env\nfrom .logger import get_root_logger\n\n__all__ = ['get_root_logger', 'collect_env']\n"
  },
  {
    "path": "code/mmdet/utils/collect_env.py",
    "content": "import os.path as osp\nimport subprocess\nimport sys\nfrom collections import defaultdict\n\nimport cv2\nimport mmcv\nimport torch\nimport torchvision\n\nimport mmdet\n\n\ndef collect_env():\n    \"\"\"Collect the information of the running environments.\"\"\"\n    env_info = {}\n    env_info['sys.platform'] = sys.platform\n    env_info['Python'] = sys.version.replace('\\n', '')\n\n    cuda_available = torch.cuda.is_available()\n    env_info['CUDA available'] = cuda_available\n\n    if cuda_available:\n        from torch.utils.cpp_extension import CUDA_HOME\n        env_info['CUDA_HOME'] = CUDA_HOME\n\n        if CUDA_HOME is not None and osp.isdir(CUDA_HOME):\n            try:\n                nvcc = osp.join(CUDA_HOME, 'bin/nvcc')\n                nvcc = subprocess.check_output(\n                    f'\"{nvcc}\" -V | tail -n1', shell=True)\n                nvcc = nvcc.decode('utf-8').strip()\n            except subprocess.SubprocessError:\n                nvcc = 'Not Available'\n            env_info['NVCC'] = nvcc\n\n        devices = defaultdict(list)\n        for k in range(torch.cuda.device_count()):\n            devices[torch.cuda.get_device_name(k)].append(str(k))\n        for name, devids in devices.items():\n            env_info['GPU ' + ','.join(devids)] = name\n\n    gcc = subprocess.check_output('gcc --version | head -n1', shell=True)\n    gcc = gcc.decode('utf-8').strip()\n    env_info['GCC'] = gcc\n\n    env_info['PyTorch'] = torch.__version__\n    env_info['PyTorch compiling details'] = torch.__config__.show()\n\n    env_info['TorchVision'] = torchvision.__version__\n\n    env_info['OpenCV'] = cv2.__version__\n\n    env_info['MMCV'] = mmcv.__version__\n    env_info['MMDetection'] = mmdet.__version__\n    from mmdet.ops import get_compiler_version, get_compiling_cuda_version\n    env_info['MMDetection Compiler'] = get_compiler_version()\n    env_info['MMDetection CUDA Compiler'] = get_compiling_cuda_version()\n    return env_info\n\n\nif __name__ == '__main__':\n    for name, val in collect_env().items():\n        print(f'{name}: {val}')\n"
  },
  {
    "path": "code/mmdet/utils/contextmanagers.py",
    "content": "import asyncio\nimport contextlib\nimport logging\nimport os\nimport time\nfrom typing import List\n\nimport torch\n\nlogger = logging.getLogger(__name__)\n\nDEBUG_COMPLETED_TIME = bool(os.environ.get('DEBUG_COMPLETED_TIME', False))\n\n\n@contextlib.asynccontextmanager\nasync def completed(trace_name='',\n                    name='',\n                    sleep_interval=0.05,\n                    streams: List[torch.cuda.Stream] = None):\n    \"\"\"\n    Async context manager that waits for work to complete on\n    given CUDA streams.\n\n    \"\"\"\n    if not torch.cuda.is_available():\n        yield\n        return\n\n    stream_before_context_switch = torch.cuda.current_stream()\n    if not streams:\n        streams = [stream_before_context_switch]\n    else:\n        streams = [s if s else stream_before_context_switch for s in streams]\n\n    end_events = [\n        torch.cuda.Event(enable_timing=DEBUG_COMPLETED_TIME) for _ in streams\n    ]\n\n    if DEBUG_COMPLETED_TIME:\n        start = torch.cuda.Event(enable_timing=True)\n        stream_before_context_switch.record_event(start)\n\n        cpu_start = time.monotonic()\n    logger.debug('%s %s starting, streams: %s', trace_name, name, streams)\n    grad_enabled_before = torch.is_grad_enabled()\n    try:\n        yield\n    finally:\n        current_stream = torch.cuda.current_stream()\n        assert current_stream == stream_before_context_switch\n\n        if DEBUG_COMPLETED_TIME:\n            cpu_end = time.monotonic()\n        for i, stream in enumerate(streams):\n            event = end_events[i]\n            stream.record_event(event)\n\n        grad_enabled_after = torch.is_grad_enabled()\n\n        # observed change of torch.is_grad_enabled() during concurrent run of\n        # async_test_bboxes code\n        assert (grad_enabled_before == grad_enabled_after\n                ), 'Unexpected is_grad_enabled() value change'\n\n        are_done = [e.query() for e in end_events]\n        logger.debug('%s %s completed: %s streams: %s', trace_name, name,\n                     are_done, streams)\n        with torch.cuda.stream(stream_before_context_switch):\n            while not all(are_done):\n                await asyncio.sleep(sleep_interval)\n                are_done = [e.query() for e in end_events]\n                logger.debug(\n                    '%s %s completed: %s streams: %s',\n                    trace_name,\n                    name,\n                    are_done,\n                    streams,\n                )\n\n        current_stream = torch.cuda.current_stream()\n        assert current_stream == stream_before_context_switch\n\n        if DEBUG_COMPLETED_TIME:\n            cpu_time = (cpu_end - cpu_start) * 1000\n            stream_times_ms = ''\n            for i, stream in enumerate(streams):\n                elapsed_time = start.elapsed_time(end_events[i])\n                stream_times_ms += f' {stream} {elapsed_time:.2f} ms'\n            logger.info('%s %s %.2f ms %s', trace_name, name, cpu_time,\n                        stream_times_ms)\n\n\n@contextlib.asynccontextmanager\nasync def concurrent(streamqueue: asyncio.Queue,\n                     trace_name='concurrent',\n                     name='stream'):\n    \"\"\"Run code concurrently in different streams.\n\n    :param streamqueue: asyncio.Queue instance.\n\n    Queue tasks define the pool of streams used for concurrent execution.\n\n    \"\"\"\n    if not torch.cuda.is_available():\n        yield\n        return\n\n    initial_stream = torch.cuda.current_stream()\n\n    with torch.cuda.stream(initial_stream):\n        stream = await streamqueue.get()\n        assert isinstance(stream, torch.cuda.Stream)\n\n        try:\n            with torch.cuda.stream(stream):\n                logger.debug('%s %s is starting, stream: %s', trace_name, name,\n                             stream)\n                yield\n                current = torch.cuda.current_stream()\n                assert current == stream\n                logger.debug('%s %s has finished, stream: %s', trace_name,\n                             name, stream)\n        finally:\n            streamqueue.task_done()\n            streamqueue.put_nowait(stream)\n"
  },
  {
    "path": "code/mmdet/utils/logger.py",
    "content": "import logging\n\nfrom mmcv.utils import get_logger\n\n\ndef get_root_logger(log_file=None, log_level=logging.INFO):\n    \"\"\"Get root logger\n\n    Args:\n        log_file (str, optional): File path of log. Defaults to None.\n        log_level (int, optional): The level of logger.\n            Defaults to logging.INFO.\n\n    Returns:\n        :obj:`logging.Logger`: The obtained logger\n    \"\"\"\n    logger = get_logger(name='mmdet', log_file=log_file, log_level=log_level)\n\n    return logger\n"
  },
  {
    "path": "code/mmdet/utils/profiling.py",
    "content": "import contextlib\nimport sys\nimport time\n\nimport torch\n\nif sys.version_info >= (3, 7):\n\n    @contextlib.contextmanager\n    def profile_time(trace_name,\n                     name,\n                     enabled=True,\n                     stream=None,\n                     end_stream=None):\n        \"\"\"Print time spent by CPU and GPU.\n\n        Useful as a temporary context manager to find sweet spots of\n        code suitable for async implementation.\n\n        \"\"\"\n        if (not enabled) or not torch.cuda.is_available():\n            yield\n            return\n        stream = stream if stream else torch.cuda.current_stream()\n        end_stream = end_stream if end_stream else stream\n        start = torch.cuda.Event(enable_timing=True)\n        end = torch.cuda.Event(enable_timing=True)\n        stream.record_event(start)\n        try:\n            cpu_start = time.monotonic()\n            yield\n        finally:\n            cpu_end = time.monotonic()\n            end_stream.record_event(end)\n            end.synchronize()\n            cpu_time = (cpu_end - cpu_start) * 1000\n            gpu_time = start.elapsed_time(end)\n            msg = f'{trace_name} {name} cpu_time {cpu_time:.2f} ms '\n            msg += f'gpu_time {gpu_time:.2f} ms stream {stream}'\n            print(msg, end_stream)\n"
  },
  {
    "path": "code/mmdet/utils/util_mixins.py",
    "content": "\"\"\"\nThis module defines the :class:`NiceRepr` mixin class, which defines a\n``__repr__`` and ``__str__`` method that only depend on a custom ``__nice__``\nmethod, which you must define. This means you only have to overload one\nfunction instead of two.  Furthermore, if the object defines a ``__len__``\nmethod, then the ``__nice__`` method defaults to something sensible, otherwise\nit is treated as abstract and raises ``NotImplementedError``.\n\nTo use simply have your object inherit from :class:`NiceRepr`\n(multi-inheritance should be ok).\n\nThis code was copied from the ubelt library: https://github.com/Erotemic/ubelt\n\nExample:\n    >>> # Objects that define __nice__ have a default __str__ and __repr__\n    >>> class Student(NiceRepr):\n    ...    def __init__(self, name):\n    ...        self.name = name\n    ...    def __nice__(self):\n    ...        return self.name\n    >>> s1 = Student('Alice')\n    >>> s2 = Student('Bob')\n    >>> print(f's1 = {s1}')\n    >>> print(f's2 = {s2}')\n    s1 = <Student(Alice)>\n    s2 = <Student(Bob)>\n\nExample:\n    >>> # Objects that define __len__ have a default __nice__\n    >>> class Group(NiceRepr):\n    ...    def __init__(self, data):\n    ...        self.data = data\n    ...    def __len__(self):\n    ...        return len(self.data)\n    >>> g = Group([1, 2, 3])\n    >>> print(f'g = {g}')\n    g = <Group(3)>\n\n\"\"\"\nimport warnings\n\n\nclass NiceRepr(object):\n    \"\"\"\n    Inherit from this class and define ``__nice__`` to \"nicely\" print your\n    objects.\n\n    Defines ``__str__`` and ``__repr__`` in terms of ``__nice__`` function\n    Classes that inherit from :class:`NiceRepr` should redefine ``__nice__``.\n    If the inheriting class has a ``__len__``, method then the default\n    ``__nice__`` method will return its length.\n\n    Example:\n        >>> class Foo(NiceRepr):\n        ...    def __nice__(self):\n        ...        return 'info'\n        >>> foo = Foo()\n        >>> assert str(foo) == '<Foo(info)>'\n        >>> assert repr(foo).startswith('<Foo(info) at ')\n\n    Example:\n        >>> class Bar(NiceRepr):\n        ...    pass\n        >>> bar = Bar()\n        >>> import pytest\n        >>> with pytest.warns(None) as record:\n        >>>     assert 'object at' in str(bar)\n        >>>     assert 'object at' in repr(bar)\n\n    Example:\n        >>> class Baz(NiceRepr):\n        ...    def __len__(self):\n        ...        return 5\n        >>> baz = Baz()\n        >>> assert str(baz) == '<Baz(5)>'\n    \"\"\"\n\n    def __nice__(self):\n        \"\"\"str: a \"nice\" summary string describing this module\"\"\"\n        if hasattr(self, '__len__'):\n            # It is a common pattern for objects to use __len__ in __nice__\n            # As a convenience we define a default __nice__ for these objects\n            return str(len(self))\n        else:\n            # In all other cases force the subclass to overload __nice__\n            raise NotImplementedError(\n                f'Define the __nice__ method for {self.__class__!r}')\n\n    def __repr__(self):\n        \"\"\"str: the string of the module\"\"\"\n        try:\n            nice = self.__nice__()\n            classname = self.__class__.__name__\n            return f'<{classname}({nice}) at {hex(id(self))}>'\n        except NotImplementedError as ex:\n            warnings.warn(str(ex), category=RuntimeWarning)\n            return object.__repr__(self)\n\n    def __str__(self):\n        \"\"\"str: the string of the module\"\"\"\n        try:\n            classname = self.__class__.__name__\n            nice = self.__nice__()\n            return f'<{classname}({nice})>'\n        except NotImplementedError as ex:\n            warnings.warn(str(ex), category=RuntimeWarning)\n            return object.__repr__(self)\n"
  },
  {
    "path": "code/pytest.ini",
    "content": "[pytest]\naddopts = --xdoctest --xdoctest-style=auto\nnorecursedirs = .git ignore build __pycache__ data docker docs .eggs\n\nfilterwarnings= default\n                ignore:.*No cfgstr given in Cacher constructor or call.*:Warning\n                ignore:.*Define the __nice__ method for.*:Warning\n"
  },
  {
    "path": "code/requirements/build.txt",
    "content": "# These must be installed before building mmdetection\nnumpy\ntorch>=1.3\n"
  },
  {
    "path": "code/requirements/docs.txt",
    "content": "recommonmark\nsphinx\nsphinx_markdown_tables\nsphinx_rtd_theme\n"
  },
  {
    "path": "code/requirements/optional.txt",
    "content": "albumentations>=0.3.2\ncityscapesscripts\nimagecorruptions\n"
  },
  {
    "path": "code/requirements/readthedocs.txt",
    "content": "mmcv\ntorch\ntorchvision\n"
  },
  {
    "path": "code/requirements/runtime.txt",
    "content": "matplotlib\nmmcv==0.6.2\nnumpy\nscipy\n# need older pillow until torchvision is fixed\nPillow<=6.2.2\nsix\nterminaltables\ntorch>=1.3\ntorchvision\n"
  },
  {
    "path": "code/requirements/tests.txt",
    "content": "asynctest\ncodecov\nflake8\ninterrogate\nisort\n# Note: used for kwarray.group_items, this may be ported to mmcv in the future.\nkwarray\npytest\npytest-cov\npytest-runner\nubelt\nxdoctest >= 0.10.0\nyapf\n"
  },
  {
    "path": "code/requirements.txt",
    "content": "-r requirements/build.txt\n-r requirements/optional.txt\n-r requirements/runtime.txt\n-r requirements/tests.txt\n"
  },
  {
    "path": "code/setup.py",
    "content": "#!/usr/bin/env python\nimport os\nimport subprocess\nimport time\nfrom setuptools import find_packages, setup\n\nimport torch\nfrom torch.utils.cpp_extension import (BuildExtension, CppExtension,\n                                       CUDAExtension)\n\n\ndef readme():\n    with open('../README.md', encoding='utf-8') as f:\n        content = f.read()\n    return content\n\n\nversion_file = 'mmdet/version.py'\n\n\ndef get_git_hash():\n\n    def _minimal_ext_cmd(cmd):\n        # construct minimal environment\n        env = {}\n        for k in ['SYSTEMROOT', 'PATH', 'HOME']:\n            v = os.environ.get(k)\n            if v is not None:\n                env[k] = v\n        # LANGUAGE is used on win32\n        env['LANGUAGE'] = 'C'\n        env['LANG'] = 'C'\n        env['LC_ALL'] = 'C'\n        out = subprocess.Popen(\n            cmd, stdout=subprocess.PIPE, env=env).communicate()[0]\n        return out\n\n    try:\n        out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])\n        sha = out.strip().decode('ascii')\n    except OSError:\n        sha = 'unknown'\n\n    return sha\n\n\ndef get_hash():\n    if os.path.exists('.git'):\n        sha = get_git_hash()[:7]\n    elif os.path.exists(version_file):\n        try:\n            from mmdet.version import __version__\n            sha = __version__.split('+')[-1]\n        except ImportError:\n            raise ImportError('Unable to get git version')\n    else:\n        sha = 'unknown'\n\n    return sha\n\n\ndef write_version_py():\n    content = \"\"\"# GENERATED VERSION FILE\n# TIME: {}\n\n__version__ = '{}'\nshort_version = '{}'\nversion_info = ({})\n\"\"\"\n    sha = get_hash()\n    with open('mmdet/VERSION', 'r') as f:\n        SHORT_VERSION = f.read().strip()\n    VERSION_INFO = ', '.join(SHORT_VERSION.split('.'))\n    VERSION = SHORT_VERSION + '+' + sha\n\n    version_file_str = content.format(time.asctime(), VERSION, SHORT_VERSION,\n                                      VERSION_INFO)\n    with open(version_file, 'w') as f:\n        f.write(version_file_str)\n\n\ndef get_version():\n    with open(version_file, 'r') as f:\n        exec(compile(f.read(), version_file, 'exec'))\n    return locals()['__version__']\n\n\ndef make_cuda_ext(name, module, sources, sources_cuda=[]):\n\n    define_macros = []\n    extra_compile_args = {'cxx': []}\n\n    if torch.cuda.is_available() or os.getenv('FORCE_CUDA', '0') == '1':\n        define_macros += [('WITH_CUDA', None)]\n        extension = CUDAExtension\n        extra_compile_args['nvcc'] = [\n            '-D__CUDA_NO_HALF_OPERATORS__',\n            '-D__CUDA_NO_HALF_CONVERSIONS__',\n            '-D__CUDA_NO_HALF2_OPERATORS__',\n        ]\n        sources += sources_cuda\n    else:\n        print(f'Compiling {name} without CUDA')\n        extension = CppExtension\n        # raise EnvironmentError('CUDA is required to compile MMDetection!')\n\n    return extension(\n        name=f'{module}.{name}',\n        sources=[os.path.join(*module.split('.'), p) for p in sources],\n        define_macros=define_macros,\n        extra_compile_args=extra_compile_args)\n\n\ndef parse_requirements(fname='requirements.txt', with_version=True):\n    \"\"\"\n    Parse the package dependencies listed in a requirements file but strips\n    specific versioning information.\n\n    Args:\n        fname (str): path to requirements file\n        with_version (bool, default=False): if True include version specs\n\n    Returns:\n        List[str]: list of requirements items\n\n    CommandLine:\n        python -c \"import setup; print(setup.parse_requirements())\"\n    \"\"\"\n    import sys\n    from os.path import exists\n    import re\n    require_fpath = fname\n\n    def parse_line(line):\n        \"\"\"\n        Parse information from a line in a requirements text file\n        \"\"\"\n        if line.startswith('-r '):\n            # Allow specifying requirements in other files\n            target = line.split(' ')[1]\n            for info in parse_require_file(target):\n                yield info\n        else:\n            info = {'line': line}\n            if line.startswith('-e '):\n                info['package'] = line.split('#egg=')[1]\n            else:\n                # Remove versioning from the package\n                pat = '(' + '|'.join(['>=', '==', '>']) + ')'\n                parts = re.split(pat, line, maxsplit=1)\n                parts = [p.strip() for p in parts]\n\n                info['package'] = parts[0]\n                if len(parts) > 1:\n                    op, rest = parts[1:]\n                    if ';' in rest:\n                        # Handle platform specific dependencies\n                        # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies\n                        version, platform_deps = map(str.strip,\n                                                     rest.split(';'))\n                        info['platform_deps'] = platform_deps\n                    else:\n                        version = rest  # NOQA\n                    info['version'] = (op, version)\n            yield info\n\n    def parse_require_file(fpath):\n        with open(fpath, 'r') as f:\n            for line in f.readlines():\n                line = line.strip()\n                if line and not line.startswith('#'):\n                    for info in parse_line(line):\n                        yield info\n\n    def gen_packages_items():\n        if exists(require_fpath):\n            for info in parse_require_file(require_fpath):\n                parts = [info['package']]\n                if with_version and 'version' in info:\n                    parts.extend(info['version'])\n                if not sys.version.startswith('3.4'):\n                    # apparently package_deps are broken in 3.4\n                    platform_deps = info.get('platform_deps')\n                    if platform_deps is not None:\n                        parts.append(';' + platform_deps)\n                item = ''.join(parts)\n                yield item\n\n    packages = list(gen_packages_items())\n    return packages\n\n\nif __name__ == '__main__':\n    write_version_py()\n    setup(\n        name='mmdet',\n        version=get_version(),\n        description='Open MMLab Detection Toolbox and Benchmark',\n        long_description=readme(),\n        author='OpenMMLab',\n        author_email='chenkaidev@gmail.com',\n        keywords='computer vision, object detection',\n        url='https://github.com/open-mmlab/mmdetection',\n        packages=find_packages(exclude=('configs', 'tools', 'demo')),\n        package_data={'mmdet.ops': ['*/*.so']},\n        classifiers=[\n            'Development Status :: 4 - Beta',\n            'License :: OSI Approved :: Apache Software License',\n            'Operating System :: OS Independent',\n            'Programming Language :: Python :: 3',\n            'Programming Language :: Python :: 3.5',\n            'Programming Language :: Python :: 3.6',\n            'Programming Language :: Python :: 3.7',\n        ],\n        license='Apache License 2.0',\n        setup_requires=parse_requirements('requirements/build.txt'),\n        tests_require=parse_requirements('requirements/tests.txt'),\n        install_requires=parse_requirements('requirements/runtime.txt'),\n        extras_require={\n            'all': parse_requirements('requirements.txt'),\n            'tests': parse_requirements('requirements/tests.txt'),\n            'build': parse_requirements('requirements/build.txt'),\n            'optional': parse_requirements('requirements/optional.txt'),\n        },\n        ext_modules=[\n            make_cuda_ext(\n                name='compiling_info',\n                module='mmdet.ops.utils',\n                sources=['src/compiling_info.cpp']),\n            make_cuda_ext(\n                name='nms_ext',\n                module='mmdet.ops.nms',\n                sources=['src/nms_ext.cpp', 'src/cpu/nms_cpu.cpp'],\n                sources_cuda=[\n                    'src/cuda/nms_cuda.cpp', 'src/cuda/nms_kernel.cu'\n                ]),\n            make_cuda_ext(\n                name='roi_align_ext',\n                module='mmdet.ops.roi_align',\n                sources=[\n                    'src/roi_align_ext.cpp',\n                    'src/cpu/roi_align_v2.cpp',\n                ],\n                sources_cuda=[\n                    'src/cuda/roi_align_kernel.cu',\n                    'src/cuda/roi_align_kernel_v2.cu'\n                ]),\n            make_cuda_ext(\n                name='roi_pool_ext',\n                module='mmdet.ops.roi_pool',\n                sources=['src/roi_pool_ext.cpp'],\n                sources_cuda=['src/cuda/roi_pool_kernel.cu']),\n            make_cuda_ext(\n                name='deform_conv_ext',\n                module='mmdet.ops.dcn',\n                sources=['src/deform_conv_ext.cpp'],\n                sources_cuda=[\n                    'src/cuda/deform_conv_cuda.cpp',\n                    'src/cuda/deform_conv_cuda_kernel.cu'\n                ]),\n            make_cuda_ext(\n                name='deform_pool_ext',\n                module='mmdet.ops.dcn',\n                sources=['src/deform_pool_ext.cpp'],\n                sources_cuda=[\n                    'src/cuda/deform_pool_cuda.cpp',\n                    'src/cuda/deform_pool_cuda_kernel.cu'\n                ]),\n            make_cuda_ext(\n                name='sigmoid_focal_loss_ext',\n                module='mmdet.ops.sigmoid_focal_loss',\n                sources=['src/sigmoid_focal_loss_ext.cpp'],\n                sources_cuda=['src/cuda/sigmoid_focal_loss_cuda.cu']),\n            make_cuda_ext(\n                name='masked_conv2d_ext',\n                module='mmdet.ops.masked_conv',\n                sources=['src/masked_conv2d_ext.cpp'],\n                sources_cuda=[\n                    'src/cuda/masked_conv2d_cuda.cpp',\n                    'src/cuda/masked_conv2d_kernel.cu'\n                ]),\n            make_cuda_ext(\n                name='carafe_ext',\n                module='mmdet.ops.carafe',\n                sources=['src/carafe_ext.cpp'],\n                sources_cuda=[\n                    'src/cuda/carafe_cuda.cpp',\n                    'src/cuda/carafe_cuda_kernel.cu'\n                ]),\n            make_cuda_ext(\n                name='carafe_naive_ext',\n                module='mmdet.ops.carafe',\n                sources=['src/carafe_naive_ext.cpp'],\n                sources_cuda=[\n                    'src/cuda/carafe_naive_cuda.cpp',\n                    'src/cuda/carafe_naive_cuda_kernel.cu'\n                ]),\n            make_cuda_ext(\n                name='corner_pool_ext',\n                module='mmdet.ops.corner_pool',\n                sources=['src/corner_pool.cpp']),\n            make_cuda_ext(\n                name='chamfer_2d',\n                module='mmdet.ops.chamfer_2d',\n                sources=['src/chamfer_cuda.cpp', 'src/chamfer_2d.cu']),\n        ],\n        cmdclass={'build_ext': BuildExtension},\n        zip_safe=False)\n"
  },
  {
    "path": "code/tests/async_benchmark.py",
    "content": "import asyncio\nimport os\nimport shutil\nimport urllib\n\nimport mmcv\nimport torch\n\nfrom mmdet.apis import (async_inference_detector, inference_detector,\n                        init_detector, show_result)\nfrom mmdet.utils.contextmanagers import concurrent\nfrom mmdet.utils.profiling import profile_time\n\n\nasync def main():\n    \"\"\"\n\n    Benchmark between async and synchronous inference interfaces.\n\n    Sample runs for 20 demo images on K80 GPU, model - mask_rcnn_r50_fpn_1x:\n\n    async\tsync\n\n    7981.79 ms\t9660.82 ms\n    8074.52 ms\t9660.94 ms\n    7976.44 ms\t9406.83 ms\n\n    Async variant takes about 0.83-0.85 of the time of the synchronous\n    interface.\n\n    \"\"\"\n    project_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))\n\n    config_file = os.path.join(project_dir,\n                               'configs/mask_rcnn_r50_fpn_1x_coco.py')\n    checkpoint_file = os.path.join(\n        project_dir, 'checkpoints/mask_rcnn_r50_fpn_1x_20181010-069fa190.pth')\n\n    if not os.path.exists(checkpoint_file):\n        url = ('https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection'\n               '/models/mask_rcnn_r50_fpn_1x_20181010-069fa190.pth')\n        print(f'Downloading {url} ...')\n        local_filename, _ = urllib.request.urlretrieve(url)\n        os.makedirs(os.path.dirname(checkpoint_file), exist_ok=True)\n        shutil.move(local_filename, checkpoint_file)\n        print(f'Saved as {checkpoint_file}')\n    else:\n        print(f'Using existing checkpoint {checkpoint_file}')\n\n    device = 'cuda:0'\n    model = init_detector(\n        config_file, checkpoint=checkpoint_file, device=device)\n\n    # queue is used for concurrent inference of multiple images\n    streamqueue = asyncio.Queue()\n    # queue size defines concurrency level\n    streamqueue_size = 4\n\n    for _ in range(streamqueue_size):\n        streamqueue.put_nowait(torch.cuda.Stream(device=device))\n\n    # test a single image and show the results\n    img = mmcv.imread(os.path.join(project_dir, 'demo/demo.jpg'))\n\n    # warmup\n    await async_inference_detector(model, img)\n\n    async def detect(img):\n        async with concurrent(streamqueue):\n            return await async_inference_detector(model, img)\n\n    num_of_images = 20\n    with profile_time('benchmark', 'async'):\n        tasks = [\n            asyncio.create_task(detect(img)) for _ in range(num_of_images)\n        ]\n        async_results = await asyncio.gather(*tasks)\n\n    with torch.cuda.stream(torch.cuda.default_stream()):\n        with profile_time('benchmark', 'sync'):\n            sync_results = [\n                inference_detector(model, img) for _ in range(num_of_images)\n            ]\n\n    result_dir = os.path.join(project_dir, 'demo')\n    show_result(\n        img,\n        async_results[0],\n        model.CLASSES,\n        score_thr=0.5,\n        show=False,\n        out_file=os.path.join(result_dir, 'result_async.jpg'))\n    show_result(\n        img,\n        sync_results[0],\n        model.CLASSES,\n        score_thr=0.5,\n        show=False,\n        out_file=os.path.join(result_dir, 'result_sync.jpg'))\n\n\nif __name__ == '__main__':\n    asyncio.run(main())\n"
  },
  {
    "path": "code/tests/test_anchor.py",
    "content": "\"\"\"\nCommandLine:\n    pytest tests/test_anchor.py\n    xdoctest tests/test_anchor.py zero\n\n\"\"\"\nimport torch\n\n\ndef test_standard_anchor_generator():\n    from mmdet.core.anchor import build_anchor_generator\n    anchor_generator_cfg = dict(\n        type='AnchorGenerator',\n        scales=[8],\n        ratios=[0.5, 1.0, 2.0],\n        strides=[4, 8])\n\n    anchor_generator = build_anchor_generator(anchor_generator_cfg)\n    assert anchor_generator is not None\n\n\ndef test_strides():\n    from mmdet.core import AnchorGenerator\n    # Square strides\n    self = AnchorGenerator([10], [1.], [1.], [10])\n    anchors = self.grid_anchors([(2, 2)], device='cpu')\n\n    expected_anchors = torch.tensor([[-5., -5., 5., 5.], [5., -5., 15., 5.],\n                                     [-5., 5., 5., 15.], [5., 5., 15., 15.]])\n\n    assert torch.equal(anchors[0], expected_anchors)\n\n    # Different strides in x and y direction\n    self = AnchorGenerator([(10, 20)], [1.], [1.], [10])\n    anchors = self.grid_anchors([(2, 2)], device='cpu')\n\n    expected_anchors = torch.tensor([[-5., -5., 5., 5.], [5., -5., 15., 5.],\n                                     [-5., 15., 5., 25.], [5., 15., 15., 25.]])\n\n    assert torch.equal(anchors[0], expected_anchors)\n\n\ndef test_ssd_anchor_generator():\n    from mmdet.core.anchor import build_anchor_generator\n    if torch.cuda.is_available():\n        device = 'cuda'\n    else:\n        device = 'cpu'\n\n    anchor_generator_cfg = dict(\n        type='SSDAnchorGenerator',\n        scale_major=False,\n        input_size=300,\n        basesize_ratio_range=(0.15, 0.9),\n        strides=[8, 16, 32, 64, 100, 300],\n        ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]])\n\n    featmap_sizes = [(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)]\n    anchor_generator = build_anchor_generator(anchor_generator_cfg)\n\n    # check base anchors\n    expected_base_anchors = [\n        torch.Tensor([[-6.5000, -6.5000, 14.5000, 14.5000],\n                      [-11.3704, -11.3704, 19.3704, 19.3704],\n                      [-10.8492, -3.4246, 18.8492, 11.4246],\n                      [-3.4246, -10.8492, 11.4246, 18.8492]]),\n        torch.Tensor([[-14.5000, -14.5000, 30.5000, 30.5000],\n                      [-25.3729, -25.3729, 41.3729, 41.3729],\n                      [-23.8198, -7.9099, 39.8198, 23.9099],\n                      [-7.9099, -23.8198, 23.9099, 39.8198],\n                      [-30.9711, -4.9904, 46.9711, 20.9904],\n                      [-4.9904, -30.9711, 20.9904, 46.9711]]),\n        torch.Tensor([[-33.5000, -33.5000, 65.5000, 65.5000],\n                      [-45.5366, -45.5366, 77.5366, 77.5366],\n                      [-54.0036, -19.0018, 86.0036, 51.0018],\n                      [-19.0018, -54.0036, 51.0018, 86.0036],\n                      [-69.7365, -12.5788, 101.7365, 44.5788],\n                      [-12.5788, -69.7365, 44.5788, 101.7365]]),\n        torch.Tensor([[-44.5000, -44.5000, 108.5000, 108.5000],\n                      [-56.9817, -56.9817, 120.9817, 120.9817],\n                      [-76.1873, -22.0937, 140.1873, 86.0937],\n                      [-22.0937, -76.1873, 86.0937, 140.1873],\n                      [-100.5019, -12.1673, 164.5019, 76.1673],\n                      [-12.1673, -100.5019, 76.1673, 164.5019]]),\n        torch.Tensor([[-53.5000, -53.5000, 153.5000, 153.5000],\n                      [-66.2185, -66.2185, 166.2185, 166.2185],\n                      [-96.3711, -23.1855, 196.3711, 123.1855],\n                      [-23.1855, -96.3711, 123.1855, 196.3711]]),\n        torch.Tensor([[19.5000, 19.5000, 280.5000, 280.5000],\n                      [6.6342, 6.6342, 293.3658, 293.3658],\n                      [-34.5549, 57.7226, 334.5549, 242.2774],\n                      [57.7226, -34.5549, 242.2774, 334.5549]]),\n    ]\n    base_anchors = anchor_generator.base_anchors\n    for i, base_anchor in enumerate(base_anchors):\n        assert base_anchor.allclose(expected_base_anchors[i])\n\n    # check valid flags\n    expected_valid_pixels = [5776, 2166, 600, 150, 36, 4]\n    multi_level_valid_flags = anchor_generator.valid_flags(\n        featmap_sizes, (300, 300), device)\n    for i, single_level_valid_flag in enumerate(multi_level_valid_flags):\n        assert single_level_valid_flag.sum() == expected_valid_pixels[i]\n\n    # check number of base anchors for each level\n    assert anchor_generator.num_base_anchors == [4, 6, 6, 6, 4, 4]\n\n    # check anchor generation\n    anchors = anchor_generator.grid_anchors(featmap_sizes, device)\n    assert len(anchors) == 6\n\n\ndef test_anchor_generator_with_tuples():\n    from mmdet.core.anchor import build_anchor_generator\n    if torch.cuda.is_available():\n        device = 'cuda'\n    else:\n        device = 'cpu'\n\n    anchor_generator_cfg = dict(\n        type='SSDAnchorGenerator',\n        scale_major=False,\n        input_size=300,\n        basesize_ratio_range=(0.15, 0.9),\n        strides=[8, 16, 32, 64, 100, 300],\n        ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]])\n\n    featmap_sizes = [(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)]\n    anchor_generator = build_anchor_generator(anchor_generator_cfg)\n    anchors = anchor_generator.grid_anchors(featmap_sizes, device)\n\n    anchor_generator_cfg_tuples = dict(\n        type='SSDAnchorGenerator',\n        scale_major=False,\n        input_size=300,\n        basesize_ratio_range=(0.15, 0.9),\n        strides=[(8, 8), (16, 16), (32, 32), (64, 64), (100, 100), (300, 300)],\n        ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]])\n\n    anchor_generator_tuples = build_anchor_generator(\n        anchor_generator_cfg_tuples)\n    anchors_tuples = anchor_generator_tuples.grid_anchors(\n        featmap_sizes, device)\n    for anchor, anchor_tuples in zip(anchors, anchors_tuples):\n        assert torch.equal(anchor, anchor_tuples)\n\n\ndef test_retina_anchor():\n    from mmdet.models import build_head\n    if torch.cuda.is_available():\n        device = 'cuda'\n    else:\n        device = 'cpu'\n\n    # head configs modified from\n    # configs/nas_fpn/retinanet_r50_fpn_crop640_50e.py\n    bbox_head = dict(\n        type='RetinaSepBNHead',\n        num_classes=4,\n        num_ins=5,\n        in_channels=4,\n        stacked_convs=1,\n        feat_channels=4,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            octave_base_scale=4,\n            scales_per_octave=3,\n            ratios=[0.5, 1.0, 2.0],\n            strides=[8, 16, 32, 64, 128]),\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[.0, .0, .0, .0],\n            target_stds=[1.0, 1.0, 1.0, 1.0]))\n\n    retina_head = build_head(bbox_head)\n    assert retina_head.anchor_generator is not None\n\n    # use the featmap sizes in NASFPN setting to test retina head\n    featmap_sizes = [(80, 80), (40, 40), (20, 20), (10, 10), (5, 5)]\n    # check base anchors\n    expected_base_anchors = [\n        torch.Tensor([[-22.6274, -11.3137, 22.6274, 11.3137],\n                      [-28.5088, -14.2544, 28.5088, 14.2544],\n                      [-35.9188, -17.9594, 35.9188, 17.9594],\n                      [-16.0000, -16.0000, 16.0000, 16.0000],\n                      [-20.1587, -20.1587, 20.1587, 20.1587],\n                      [-25.3984, -25.3984, 25.3984, 25.3984],\n                      [-11.3137, -22.6274, 11.3137, 22.6274],\n                      [-14.2544, -28.5088, 14.2544, 28.5088],\n                      [-17.9594, -35.9188, 17.9594, 35.9188]]),\n        torch.Tensor([[-45.2548, -22.6274, 45.2548, 22.6274],\n                      [-57.0175, -28.5088, 57.0175, 28.5088],\n                      [-71.8376, -35.9188, 71.8376, 35.9188],\n                      [-32.0000, -32.0000, 32.0000, 32.0000],\n                      [-40.3175, -40.3175, 40.3175, 40.3175],\n                      [-50.7968, -50.7968, 50.7968, 50.7968],\n                      [-22.6274, -45.2548, 22.6274, 45.2548],\n                      [-28.5088, -57.0175, 28.5088, 57.0175],\n                      [-35.9188, -71.8376, 35.9188, 71.8376]]),\n        torch.Tensor([[-90.5097, -45.2548, 90.5097, 45.2548],\n                      [-114.0350, -57.0175, 114.0350, 57.0175],\n                      [-143.6751, -71.8376, 143.6751, 71.8376],\n                      [-64.0000, -64.0000, 64.0000, 64.0000],\n                      [-80.6349, -80.6349, 80.6349, 80.6349],\n                      [-101.5937, -101.5937, 101.5937, 101.5937],\n                      [-45.2548, -90.5097, 45.2548, 90.5097],\n                      [-57.0175, -114.0350, 57.0175, 114.0350],\n                      [-71.8376, -143.6751, 71.8376, 143.6751]]),\n        torch.Tensor([[-181.0193, -90.5097, 181.0193, 90.5097],\n                      [-228.0701, -114.0350, 228.0701, 114.0350],\n                      [-287.3503, -143.6751, 287.3503, 143.6751],\n                      [-128.0000, -128.0000, 128.0000, 128.0000],\n                      [-161.2699, -161.2699, 161.2699, 161.2699],\n                      [-203.1873, -203.1873, 203.1873, 203.1873],\n                      [-90.5097, -181.0193, 90.5097, 181.0193],\n                      [-114.0350, -228.0701, 114.0350, 228.0701],\n                      [-143.6751, -287.3503, 143.6751, 287.3503]]),\n        torch.Tensor([[-362.0387, -181.0193, 362.0387, 181.0193],\n                      [-456.1401, -228.0701, 456.1401, 228.0701],\n                      [-574.7006, -287.3503, 574.7006, 287.3503],\n                      [-256.0000, -256.0000, 256.0000, 256.0000],\n                      [-322.5398, -322.5398, 322.5398, 322.5398],\n                      [-406.3747, -406.3747, 406.3747, 406.3747],\n                      [-181.0193, -362.0387, 181.0193, 362.0387],\n                      [-228.0701, -456.1401, 228.0701, 456.1401],\n                      [-287.3503, -574.7006, 287.3503, 574.7006]])\n    ]\n    base_anchors = retina_head.anchor_generator.base_anchors\n    for i, base_anchor in enumerate(base_anchors):\n        assert base_anchor.allclose(expected_base_anchors[i])\n\n    # check valid flags\n    expected_valid_pixels = [57600, 14400, 3600, 900, 225]\n    multi_level_valid_flags = retina_head.anchor_generator.valid_flags(\n        featmap_sizes, (640, 640), device)\n    for i, single_level_valid_flag in enumerate(multi_level_valid_flags):\n        assert single_level_valid_flag.sum() == expected_valid_pixels[i]\n\n    # check number of base anchors for each level\n    assert retina_head.anchor_generator.num_base_anchors == [9, 9, 9, 9, 9]\n\n    # check anchor generation\n    anchors = retina_head.anchor_generator.grid_anchors(featmap_sizes, device)\n    assert len(anchors) == 5\n\n\ndef test_guided_anchor():\n    from mmdet.models import build_head\n    if torch.cuda.is_available():\n        device = 'cuda'\n    else:\n        device = 'cpu'\n    # head configs modified from\n    # configs/guided_anchoring/ga_retinanet_r50_fpn_1x_coco.py\n    bbox_head = dict(\n        type='GARetinaHead',\n        num_classes=8,\n        in_channels=4,\n        stacked_convs=1,\n        feat_channels=4,\n        approx_anchor_generator=dict(\n            type='AnchorGenerator',\n            octave_base_scale=4,\n            scales_per_octave=3,\n            ratios=[0.5, 1.0, 2.0],\n            strides=[8, 16, 32, 64, 128]),\n        square_anchor_generator=dict(\n            type='AnchorGenerator',\n            ratios=[1.0],\n            scales=[4],\n            strides=[8, 16, 32, 64, 128]))\n\n    ga_retina_head = build_head(bbox_head)\n    assert ga_retina_head.approx_anchor_generator is not None\n\n    # use the featmap sizes in NASFPN setting to test ga_retina_head\n    featmap_sizes = [(100, 152), (50, 76), (25, 38), (13, 19), (7, 10)]\n    # check base anchors\n    expected_approxs = [\n        torch.Tensor([[-22.6274, -11.3137, 22.6274, 11.3137],\n                      [-28.5088, -14.2544, 28.5088, 14.2544],\n                      [-35.9188, -17.9594, 35.9188, 17.9594],\n                      [-16.0000, -16.0000, 16.0000, 16.0000],\n                      [-20.1587, -20.1587, 20.1587, 20.1587],\n                      [-25.3984, -25.3984, 25.3984, 25.3984],\n                      [-11.3137, -22.6274, 11.3137, 22.6274],\n                      [-14.2544, -28.5088, 14.2544, 28.5088],\n                      [-17.9594, -35.9188, 17.9594, 35.9188]]),\n        torch.Tensor([[-45.2548, -22.6274, 45.2548, 22.6274],\n                      [-57.0175, -28.5088, 57.0175, 28.5088],\n                      [-71.8376, -35.9188, 71.8376, 35.9188],\n                      [-32.0000, -32.0000, 32.0000, 32.0000],\n                      [-40.3175, -40.3175, 40.3175, 40.3175],\n                      [-50.7968, -50.7968, 50.7968, 50.7968],\n                      [-22.6274, -45.2548, 22.6274, 45.2548],\n                      [-28.5088, -57.0175, 28.5088, 57.0175],\n                      [-35.9188, -71.8376, 35.9188, 71.8376]]),\n        torch.Tensor([[-90.5097, -45.2548, 90.5097, 45.2548],\n                      [-114.0350, -57.0175, 114.0350, 57.0175],\n                      [-143.6751, -71.8376, 143.6751, 71.8376],\n                      [-64.0000, -64.0000, 64.0000, 64.0000],\n                      [-80.6349, -80.6349, 80.6349, 80.6349],\n                      [-101.5937, -101.5937, 101.5937, 101.5937],\n                      [-45.2548, -90.5097, 45.2548, 90.5097],\n                      [-57.0175, -114.0350, 57.0175, 114.0350],\n                      [-71.8376, -143.6751, 71.8376, 143.6751]]),\n        torch.Tensor([[-181.0193, -90.5097, 181.0193, 90.5097],\n                      [-228.0701, -114.0350, 228.0701, 114.0350],\n                      [-287.3503, -143.6751, 287.3503, 143.6751],\n                      [-128.0000, -128.0000, 128.0000, 128.0000],\n                      [-161.2699, -161.2699, 161.2699, 161.2699],\n                      [-203.1873, -203.1873, 203.1873, 203.1873],\n                      [-90.5097, -181.0193, 90.5097, 181.0193],\n                      [-114.0350, -228.0701, 114.0350, 228.0701],\n                      [-143.6751, -287.3503, 143.6751, 287.3503]]),\n        torch.Tensor([[-362.0387, -181.0193, 362.0387, 181.0193],\n                      [-456.1401, -228.0701, 456.1401, 228.0701],\n                      [-574.7006, -287.3503, 574.7006, 287.3503],\n                      [-256.0000, -256.0000, 256.0000, 256.0000],\n                      [-322.5398, -322.5398, 322.5398, 322.5398],\n                      [-406.3747, -406.3747, 406.3747, 406.3747],\n                      [-181.0193, -362.0387, 181.0193, 362.0387],\n                      [-228.0701, -456.1401, 228.0701, 456.1401],\n                      [-287.3503, -574.7006, 287.3503, 574.7006]])\n    ]\n    approxs = ga_retina_head.approx_anchor_generator.base_anchors\n    for i, base_anchor in enumerate(approxs):\n        assert base_anchor.allclose(expected_approxs[i])\n\n    # check valid flags\n    expected_valid_pixels = [136800, 34200, 8550, 2223, 630]\n    multi_level_valid_flags = ga_retina_head.approx_anchor_generator \\\n        .valid_flags(featmap_sizes, (800, 1216), device)\n    for i, single_level_valid_flag in enumerate(multi_level_valid_flags):\n        assert single_level_valid_flag.sum() == expected_valid_pixels[i]\n\n    # check number of base anchors for each level\n    assert ga_retina_head.approx_anchor_generator.num_base_anchors == [\n        9, 9, 9, 9, 9\n    ]\n\n    # check approx generation\n    squares = ga_retina_head.square_anchor_generator.grid_anchors(\n        featmap_sizes, device)\n    assert len(squares) == 5\n\n    expected_squares = [\n        torch.Tensor([[-16., -16., 16., 16.]]),\n        torch.Tensor([[-32., -32., 32., 32]]),\n        torch.Tensor([[-64., -64., 64., 64.]]),\n        torch.Tensor([[-128., -128., 128., 128.]]),\n        torch.Tensor([[-256., -256., 256., 256.]])\n    ]\n    squares = ga_retina_head.square_anchor_generator.base_anchors\n    for i, base_anchor in enumerate(squares):\n        assert base_anchor.allclose(expected_squares[i])\n\n    # square_anchor_generator does not check valid flags\n    # check number of base anchors for each level\n    assert (ga_retina_head.square_anchor_generator.num_base_anchors == [\n        1, 1, 1, 1, 1\n    ])\n\n    # check square generation\n    anchors = ga_retina_head.square_anchor_generator.grid_anchors(\n        featmap_sizes, device)\n    assert len(anchors) == 5\n"
  },
  {
    "path": "code/tests/test_assigner.py",
    "content": "\"\"\"\nTests the Assigner objects.\n\nCommandLine:\n    pytest tests/test_assigner.py\n    xdoctest tests/test_assigner.py zero\n\n\n\n\"\"\"\nimport torch\n\nfrom mmdet.core.bbox.assigners import (ApproxMaxIoUAssigner,\n                                       CenterRegionAssigner, MaxIoUAssigner,\n                                       PointAssigner)\n\n\ndef test_max_iou_assigner():\n    self = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_labels = torch.LongTensor([2, 3])\n    assign_result = self.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n    assert len(assign_result.gt_inds) == 4\n    assert len(assign_result.labels) == 4\n\n    expected_gt_inds = torch.LongTensor([1, 0, 2, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_max_iou_assigner_with_ignore():\n    self = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [30, 32, 40, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_bboxes_ignore = torch.Tensor([\n        [30, 30, 40, 40],\n    ])\n    assign_result = self.assign(\n        bboxes, gt_bboxes, gt_bboxes_ignore=gt_bboxes_ignore)\n\n    expected_gt_inds = torch.LongTensor([1, 0, 2, -1])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_max_iou_assigner_with_empty_gt():\n    \"\"\"\n    Test corner case where an image might have no true detections\n    \"\"\"\n    self = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([])\n    assign_result = self.assign(bboxes, gt_bboxes)\n\n    expected_gt_inds = torch.LongTensor([0, 0, 0, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_max_iou_assigner_with_empty_boxes():\n    \"\"\"\n    Test corner case where an network might predict no boxes\n    \"\"\"\n    self = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.empty((0, 4))\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_labels = torch.LongTensor([2, 3])\n\n    # Test with gt_labels\n    assign_result = self.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n    assert len(assign_result.gt_inds) == 0\n    assert tuple(assign_result.labels.shape) == (0, )\n\n    # Test without gt_labels\n    assign_result = self.assign(bboxes, gt_bboxes, gt_labels=None)\n    assert len(assign_result.gt_inds) == 0\n    assert assign_result.labels is None\n\n\ndef test_max_iou_assigner_with_empty_boxes_and_ignore():\n    \"\"\"\n    Test corner case where an network might predict no boxes and ignore_iof_thr\n    is on\n    \"\"\"\n    self = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n    )\n    bboxes = torch.empty((0, 4))\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_bboxes_ignore = torch.Tensor([\n        [30, 30, 40, 40],\n    ])\n    gt_labels = torch.LongTensor([2, 3])\n\n    # Test with gt_labels\n    assign_result = self.assign(\n        bboxes,\n        gt_bboxes,\n        gt_labels=gt_labels,\n        gt_bboxes_ignore=gt_bboxes_ignore)\n    assert len(assign_result.gt_inds) == 0\n    assert tuple(assign_result.labels.shape) == (0, )\n\n    # Test without gt_labels\n    assign_result = self.assign(\n        bboxes, gt_bboxes, gt_labels=None, gt_bboxes_ignore=gt_bboxes_ignore)\n    assert len(assign_result.gt_inds) == 0\n    assert assign_result.labels is None\n\n\ndef test_max_iou_assigner_with_empty_boxes_and_gt():\n    \"\"\"\n    Test corner case where an network might predict no boxes and no gt\n    \"\"\"\n    self = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.empty((0, 4))\n    gt_bboxes = torch.empty((0, 4))\n    assign_result = self.assign(bboxes, gt_bboxes)\n    assert len(assign_result.gt_inds) == 0\n\n\ndef test_point_assigner():\n    self = PointAssigner()\n    points = torch.FloatTensor([  # [x, y, stride]\n        [0, 0, 1],\n        [10, 10, 1],\n        [5, 5, 1],\n        [32, 32, 1],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    assign_result = self.assign(points, gt_bboxes)\n    expected_gt_inds = torch.LongTensor([1, 2, 1, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_point_assigner_with_empty_gt():\n    \"\"\"\n    Test corner case where an image might have no true detections\n    \"\"\"\n    self = PointAssigner()\n    points = torch.FloatTensor([  # [x, y, stride]\n        [0, 0, 1],\n        [10, 10, 1],\n        [5, 5, 1],\n        [32, 32, 1],\n    ])\n    gt_bboxes = torch.FloatTensor([])\n    assign_result = self.assign(points, gt_bboxes)\n\n    expected_gt_inds = torch.LongTensor([0, 0, 0, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_point_assigner_with_empty_boxes_and_gt():\n    \"\"\"\n    Test corner case where an image might predict no points and no gt\n    \"\"\"\n    self = PointAssigner()\n    points = torch.FloatTensor([])\n    gt_bboxes = torch.FloatTensor([])\n    assign_result = self.assign(points, gt_bboxes)\n    assert len(assign_result.gt_inds) == 0\n\n\ndef test_approx_iou_assigner():\n    self = ApproxMaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    approxs_per_octave = 1\n    approxs = bboxes\n    squares = bboxes\n    assign_result = self.assign(approxs, squares, approxs_per_octave,\n                                gt_bboxes)\n\n    expected_gt_inds = torch.LongTensor([1, 0, 2, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_approx_iou_assigner_with_empty_gt():\n    \"\"\"\n    Test corner case where an image might have no true detections\n    \"\"\"\n    self = ApproxMaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([])\n    approxs_per_octave = 1\n    approxs = bboxes\n    squares = bboxes\n    assign_result = self.assign(approxs, squares, approxs_per_octave,\n                                gt_bboxes)\n\n    expected_gt_inds = torch.LongTensor([0, 0, 0, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_approx_iou_assigner_with_empty_boxes():\n    \"\"\"\n    Test corner case where an network might predict no boxes\n    \"\"\"\n    self = ApproxMaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.empty((0, 4))\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    approxs_per_octave = 1\n    approxs = bboxes\n    squares = bboxes\n    assign_result = self.assign(approxs, squares, approxs_per_octave,\n                                gt_bboxes)\n    assert len(assign_result.gt_inds) == 0\n\n\ndef test_approx_iou_assigner_with_empty_boxes_and_gt():\n    \"\"\"\n    Test corner case where an network might predict no boxes and no gt\n    \"\"\"\n    self = ApproxMaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n    )\n    bboxes = torch.empty((0, 4))\n    gt_bboxes = torch.empty((0, 4))\n    approxs_per_octave = 1\n    approxs = bboxes\n    squares = bboxes\n    assign_result = self.assign(approxs, squares, approxs_per_octave,\n                                gt_bboxes)\n    assert len(assign_result.gt_inds) == 0\n\n\ndef test_random_assign_result():\n    \"\"\"\n    Test random instantiation of assign result to catch corner cases\n    \"\"\"\n    from mmdet.core.bbox.assigners.assign_result import AssignResult\n    AssignResult.random()\n\n    AssignResult.random(num_gts=0, num_preds=0)\n    AssignResult.random(num_gts=0, num_preds=3)\n    AssignResult.random(num_gts=3, num_preds=3)\n    AssignResult.random(num_gts=0, num_preds=3)\n    AssignResult.random(num_gts=7, num_preds=7)\n    AssignResult.random(num_gts=7, num_preds=64)\n    AssignResult.random(num_gts=24, num_preds=3)\n\n\ndef test_center_region_assigner():\n    self = CenterRegionAssigner(pos_scale=0.3, neg_scale=1)\n    bboxes = torch.FloatTensor([[0, 0, 10, 10], [10, 10, 20, 20], [8, 8, 9,\n                                                                   9]])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 11, 11],  # match bboxes[0]\n        [10, 10, 20, 20],  # match bboxes[1]\n        [4.5, 4.5, 5.5, 5.5],  # match bboxes[0] but area is too small\n        [0, 0, 10, 10],  # match bboxes[1] and has a smaller area than gt[0]\n    ])\n    gt_labels = torch.LongTensor([2, 3, 4, 5])\n    assign_result = self.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n    assert len(assign_result.gt_inds) == 3\n    assert len(assign_result.labels) == 3\n    expected_gt_inds = torch.LongTensor([4, 2, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n    shadowed_labels = assign_result.get_extra_property('shadowed_labels')\n    # [8, 8, 9, 9] in the shadowed region of [0, 0, 11, 11] (label: 2)\n    assert torch.any(shadowed_labels == torch.LongTensor([[2, 2]]))\n    # [8, 8, 9, 9] in the shadowed region of [0, 0, 10, 10] (label: 5)\n    assert torch.any(shadowed_labels == torch.LongTensor([[2, 5]]))\n    # [0, 0, 10, 10] is already assigned to [4.5, 4.5, 5.5, 5.5].\n    #   Therefore, [0, 0, 11, 11] (label: 2) is shadowed\n    assert torch.any(shadowed_labels == torch.LongTensor([[0, 2]]))\n\n\ndef test_center_region_assigner_with_ignore():\n    self = CenterRegionAssigner(\n        pos_scale=0.5,\n        neg_scale=1,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],  # match bboxes[0]\n        [10, 10, 20, 20],  # match bboxes[1]\n    ])\n    gt_bboxes_ignore = torch.FloatTensor([\n        [0, 0, 10, 10],  # match bboxes[0]\n    ])\n    gt_labels = torch.LongTensor([1, 2])\n    assign_result = self.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n    assert len(assign_result.gt_inds) == 2\n    assert len(assign_result.labels) == 2\n\n    expected_gt_inds = torch.LongTensor([-1, 2])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n\n\ndef test_center_region_assigner_with_empty_bboxes():\n    self = CenterRegionAssigner(\n        pos_scale=0.5,\n        neg_scale=1,\n    )\n    bboxes = torch.empty((0, 4)).float()\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],  # match bboxes[0]\n        [10, 10, 20, 20],  # match bboxes[1]\n    ])\n    gt_labels = torch.LongTensor([1, 2])\n    assign_result = self.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n    assert assign_result.gt_inds is None or assign_result.gt_inds.numel() == 0\n    assert assign_result.labels is None or assign_result.labels.numel() == 0\n\n\ndef test_center_region_assigner_with_empty_gts():\n    self = CenterRegionAssigner(\n        pos_scale=0.5,\n        neg_scale=1,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n    ])\n    gt_bboxes = torch.empty((0, 4)).float()\n    gt_labels = torch.empty((0, )).long()\n    assign_result = self.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n    assert len(assign_result.gt_inds) == 2\n    expected_gt_inds = torch.LongTensor([0, 0])\n    assert torch.all(assign_result.gt_inds == expected_gt_inds)\n"
  },
  {
    "path": "code/tests/test_async.py",
    "content": "\"\"\"Tests for async interface.\"\"\"\n\nimport asyncio\nimport os\nimport sys\n\nimport asynctest\nimport mmcv\nimport torch\n\nfrom mmdet.apis import async_inference_detector, init_detector\n\nif sys.version_info >= (3, 7):\n    from mmdet.utils.contextmanagers import concurrent\n\n\nclass AsyncTestCase(asynctest.TestCase):\n    use_default_loop = False\n    forbid_get_event_loop = True\n\n    TEST_TIMEOUT = int(os.getenv('ASYNCIO_TEST_TIMEOUT', '30'))\n\n    def _run_test_method(self, method):\n        result = method()\n        if asyncio.iscoroutine(result):\n            self.loop.run_until_complete(\n                asyncio.wait_for(result, timeout=self.TEST_TIMEOUT))\n\n\nclass MaskRCNNDetector:\n\n    def __init__(self,\n                 model_config,\n                 checkpoint=None,\n                 streamqueue_size=3,\n                 device='cuda:0'):\n\n        self.streamqueue_size = streamqueue_size\n        self.device = device\n        # build the model and load checkpoint\n        self.model = init_detector(\n            model_config, checkpoint=None, device=self.device)\n        self.streamqueue = None\n\n    async def init(self):\n        self.streamqueue = asyncio.Queue()\n        for _ in range(self.streamqueue_size):\n            stream = torch.cuda.Stream(device=self.device)\n            self.streamqueue.put_nowait(stream)\n\n    if sys.version_info >= (3, 7):\n\n        async def apredict(self, img):\n            if isinstance(img, str):\n                img = mmcv.imread(img)\n            async with concurrent(self.streamqueue):\n                result = await async_inference_detector(self.model, img)\n            return result\n\n\nclass AsyncInferenceTestCase(AsyncTestCase):\n\n    if sys.version_info >= (3, 7):\n\n        async def test_simple_inference(self):\n            if not torch.cuda.is_available():\n                import pytest\n\n                pytest.skip('test requires GPU and torch+cuda')\n\n            ori_grad_enabled = torch.is_grad_enabled()\n            root_dir = os.path.dirname(os.path.dirname(__name__))\n            model_config = os.path.join(\n                root_dir, 'configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py')\n            detector = MaskRCNNDetector(model_config)\n            await detector.init()\n            img_path = os.path.join(root_dir, 'demo/demo.jpg')\n            bboxes, _ = await detector.apredict(img_path)\n            self.assertTrue(bboxes)\n            # asy inference detector will hack grad_enabled,\n            # so restore here to avoid it to influence other tests\n            torch.set_grad_enabled(ori_grad_enabled)\n"
  },
  {
    "path": "code/tests/test_backbone.py",
    "content": "import pytest\nimport torch\nfrom torch.nn.modules import AvgPool2d, GroupNorm\nfrom torch.nn.modules.batchnorm import _BatchNorm\n\nfrom mmdet.models.backbones import RegNet, Res2Net, ResNet, ResNetV1d, ResNeXt\nfrom mmdet.models.backbones.hourglass import HourglassNet\nfrom mmdet.models.backbones.res2net import Bottle2neck\nfrom mmdet.models.backbones.resnet import BasicBlock, Bottleneck\nfrom mmdet.models.backbones.resnext import Bottleneck as BottleneckX\nfrom mmdet.models.utils import ResLayer\nfrom mmdet.ops import DeformConvPack\n\n\ndef is_block(modules):\n    \"\"\"Check if is ResNet building block.\"\"\"\n    if isinstance(modules, (BasicBlock, Bottleneck, BottleneckX, Bottle2neck)):\n        return True\n    return False\n\n\ndef is_norm(modules):\n    \"\"\"Check if is one of the norms.\"\"\"\n    if isinstance(modules, (GroupNorm, _BatchNorm)):\n        return True\n    return False\n\n\ndef all_zeros(modules):\n    \"\"\"Check if the weight(and bias) is all zero.\"\"\"\n    weight_zero = torch.allclose(modules.weight.data,\n                                 torch.zeros_like(modules.weight.data))\n    if hasattr(modules, 'bias'):\n        bias_zero = torch.allclose(modules.bias.data,\n                                   torch.zeros_like(modules.bias.data))\n    else:\n        bias_zero = True\n\n    return weight_zero and bias_zero\n\n\ndef check_norm_state(modules, train_state):\n    \"\"\"Check if norm layer is in correct train state.\"\"\"\n    for mod in modules:\n        if isinstance(mod, _BatchNorm):\n            if mod.training != train_state:\n                return False\n    return True\n\n\ndef test_resnet_basic_block():\n\n    with pytest.raises(AssertionError):\n        # Not implemented yet.\n        dcn = dict(type='DCN', deformable_groups=1, fallback_on_stride=False)\n        BasicBlock(64, 64, dcn=dcn)\n\n    with pytest.raises(AssertionError):\n        # Not implemented yet.\n        plugins = [\n            dict(\n                cfg=dict(type='ContextBlock', ratio=1. / 16),\n                position='after_conv3')\n        ]\n        BasicBlock(64, 64, plugins=plugins)\n\n    with pytest.raises(AssertionError):\n        # Not implemented yet\n        plugins = [\n            dict(\n                cfg=dict(\n                    type='GeneralizedAttention',\n                    spatial_range=-1,\n                    num_heads=8,\n                    attention_type='0010',\n                    kv_stride=2),\n                position='after_conv2')\n        ]\n        BasicBlock(64, 64, plugins=plugins)\n\n    # test BasicBlock structure and forward\n    block = BasicBlock(64, 64)\n    assert block.conv1.in_channels == 64\n    assert block.conv1.out_channels == 64\n    assert block.conv1.kernel_size == (3, 3)\n    assert block.conv2.in_channels == 64\n    assert block.conv2.out_channels == 64\n    assert block.conv2.kernel_size == (3, 3)\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test BasicBlock with checkpoint forward\n    block = BasicBlock(64, 64, with_cp=True)\n    assert block.with_cp\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n\ndef test_resnet_bottleneck():\n\n    with pytest.raises(AssertionError):\n        # Style must be in ['pytorch', 'caffe']\n        Bottleneck(64, 64, style='tensorflow')\n\n    with pytest.raises(AssertionError):\n        # Allowed positions are 'after_conv1', 'after_conv2', 'after_conv3'\n        plugins = [\n            dict(\n                cfg=dict(type='ContextBlock', ratio=1. / 16),\n                position='after_conv4')\n        ]\n        Bottleneck(64, 16, plugins=plugins)\n\n    with pytest.raises(AssertionError):\n        # Need to specify different postfix to avoid duplicate plugin name\n        plugins = [\n            dict(\n                cfg=dict(type='ContextBlock', ratio=1. / 16),\n                position='after_conv3'),\n            dict(\n                cfg=dict(type='ContextBlock', ratio=1. / 16),\n                position='after_conv3')\n        ]\n        Bottleneck(64, 16, plugins=plugins)\n\n    with pytest.raises(KeyError):\n        # Plugin type is not supported\n        plugins = [dict(cfg=dict(type='WrongPlugin'), position='after_conv3')]\n        Bottleneck(64, 16, plugins=plugins)\n\n    # Test Bottleneck with checkpoint forward\n    block = Bottleneck(64, 16, with_cp=True)\n    assert block.with_cp\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test Bottleneck style\n    block = Bottleneck(64, 64, stride=2, style='pytorch')\n    assert block.conv1.stride == (1, 1)\n    assert block.conv2.stride == (2, 2)\n    block = Bottleneck(64, 64, stride=2, style='caffe')\n    assert block.conv1.stride == (2, 2)\n    assert block.conv2.stride == (1, 1)\n\n    # Test Bottleneck DCN\n    dcn = dict(type='DCN', deformable_groups=1, fallback_on_stride=False)\n    with pytest.raises(AssertionError):\n        Bottleneck(64, 64, dcn=dcn, conv_cfg=dict(type='Conv'))\n    block = Bottleneck(64, 64, dcn=dcn)\n    assert isinstance(block.conv2, DeformConvPack)\n\n    # Test Bottleneck forward\n    block = Bottleneck(64, 16)\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test Bottleneck with 1 ContextBlock after conv3\n    plugins = [\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16),\n            position='after_conv3')\n    ]\n    block = Bottleneck(64, 16, plugins=plugins)\n    assert block.context_block.in_channels == 64\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test Bottleneck with 1 GeneralizedAttention after conv2\n    plugins = [\n        dict(\n            cfg=dict(\n                type='GeneralizedAttention',\n                spatial_range=-1,\n                num_heads=8,\n                attention_type='0010',\n                kv_stride=2),\n            position='after_conv2')\n    ]\n    block = Bottleneck(64, 16, plugins=plugins)\n    assert block.gen_attention_block.in_channels == 16\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test Bottleneck with 1 GeneralizedAttention after conv2, 1 NonLocal2D\n    # after conv2, 1 ContextBlock after conv3\n    plugins = [\n        dict(\n            cfg=dict(\n                type='GeneralizedAttention',\n                spatial_range=-1,\n                num_heads=8,\n                attention_type='0010',\n                kv_stride=2),\n            position='after_conv2'),\n        dict(cfg=dict(type='NonLocal2D'), position='after_conv2'),\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16),\n            position='after_conv3')\n    ]\n    block = Bottleneck(64, 16, plugins=plugins)\n    assert block.gen_attention_block.in_channels == 16\n    assert block.nonlocal_block.in_channels == 16\n    assert block.context_block.in_channels == 64\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test Bottleneck with 1 ContextBlock after conv2, 2 ContextBlock after\n    # conv3\n    plugins = [\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=1),\n            position='after_conv2'),\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=2),\n            position='after_conv3'),\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=3),\n            position='after_conv3')\n    ]\n    block = Bottleneck(64, 16, plugins=plugins)\n    assert block.context_block1.in_channels == 16\n    assert block.context_block2.in_channels == 64\n    assert block.context_block3.in_channels == 64\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n\ndef test_resnet_res_layer():\n    # Test ResLayer of 3 Bottleneck w\\o downsample\n    layer = ResLayer(Bottleneck, 64, 16, 3)\n    assert len(layer) == 3\n    assert layer[0].conv1.in_channels == 64\n    assert layer[0].conv1.out_channels == 16\n    for i in range(1, len(layer)):\n        assert layer[i].conv1.in_channels == 64\n        assert layer[i].conv1.out_channels == 16\n    for i in range(len(layer)):\n        assert layer[i].downsample is None\n    x = torch.randn(1, 64, 56, 56)\n    x_out = layer(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n    # Test ResLayer of 3 Bottleneck with downsample\n    layer = ResLayer(Bottleneck, 64, 64, 3)\n    assert layer[0].downsample[0].out_channels == 256\n    for i in range(1, len(layer)):\n        assert layer[i].downsample is None\n    x = torch.randn(1, 64, 56, 56)\n    x_out = layer(x)\n    assert x_out.shape == torch.Size([1, 256, 56, 56])\n\n    # Test ResLayer of 3 Bottleneck with stride=2\n    layer = ResLayer(Bottleneck, 64, 64, 3, stride=2)\n    assert layer[0].downsample[0].out_channels == 256\n    assert layer[0].downsample[0].stride == (2, 2)\n    for i in range(1, len(layer)):\n        assert layer[i].downsample is None\n    x = torch.randn(1, 64, 56, 56)\n    x_out = layer(x)\n    assert x_out.shape == torch.Size([1, 256, 28, 28])\n\n    # Test ResLayer of 3 Bottleneck with stride=2 and average downsample\n    layer = ResLayer(Bottleneck, 64, 64, 3, stride=2, avg_down=True)\n    assert isinstance(layer[0].downsample[0], AvgPool2d)\n    assert layer[0].downsample[1].out_channels == 256\n    assert layer[0].downsample[1].stride == (1, 1)\n    for i in range(1, len(layer)):\n        assert layer[i].downsample is None\n    x = torch.randn(1, 64, 56, 56)\n    x_out = layer(x)\n    assert x_out.shape == torch.Size([1, 256, 28, 28])\n\n    # Test ResLayer of 3 BasicBlock with stride=2 and downsample_first=False\n    layer = ResLayer(BasicBlock, 64, 64, 3, stride=2, downsample_first=False)\n    assert layer[2].downsample[0].out_channels == 64\n    assert layer[2].downsample[0].stride == (2, 2)\n    for i in range(len(layer) - 1):\n        assert layer[i].downsample is None\n    x = torch.randn(1, 64, 56, 56)\n    x_out = layer(x)\n    assert x_out.shape == torch.Size([1, 64, 28, 28])\n\n\ndef test_resnet_backbone():\n    \"\"\"Test resnet backbone\"\"\"\n    with pytest.raises(KeyError):\n        # ResNet depth should be in [18, 34, 50, 101, 152]\n        ResNet(20)\n\n    with pytest.raises(AssertionError):\n        # In ResNet: 1 <= num_stages <= 4\n        ResNet(50, num_stages=0)\n\n    with pytest.raises(AssertionError):\n        # len(stage_with_dcn) == num_stages\n        dcn = dict(type='DCN', deformable_groups=1, fallback_on_stride=False)\n        ResNet(50, dcn=dcn, stage_with_dcn=(True, ))\n\n    with pytest.raises(AssertionError):\n        # len(stage_with_plugin) == num_stages\n        plugins = [\n            dict(\n                cfg=dict(type='ContextBlock', ratio=1. / 16),\n                stages=(False, True, True),\n                position='after_conv3')\n        ]\n        ResNet(50, plugins=plugins)\n\n    with pytest.raises(AssertionError):\n        # In ResNet: 1 <= num_stages <= 4\n        ResNet(50, num_stages=5)\n\n    with pytest.raises(AssertionError):\n        # len(strides) == len(dilations) == num_stages\n        ResNet(50, strides=(1, ), dilations=(1, 1), num_stages=3)\n\n    with pytest.raises(TypeError):\n        # pretrained must be a string path\n        model = ResNet(50)\n        model.init_weights(pretrained=0)\n\n    with pytest.raises(AssertionError):\n        # Style must be in ['pytorch', 'caffe']\n        ResNet(50, style='tensorflow')\n\n    # Test ResNet50 norm_eval=True\n    model = ResNet(50, norm_eval=True)\n    model.init_weights()\n    model.train()\n    assert check_norm_state(model.modules(), False)\n\n    # Test ResNet50 with torchvision pretrained weight\n    model = ResNet(depth=50, norm_eval=True)\n    model.init_weights('torchvision://resnet50')\n    model.train()\n    assert check_norm_state(model.modules(), False)\n\n    # Test ResNet50 with first stage frozen\n    frozen_stages = 1\n    model = ResNet(50, frozen_stages=frozen_stages)\n    model.init_weights()\n    model.train()\n    assert model.norm1.training is False\n    for layer in [model.conv1, model.norm1]:\n        for param in layer.parameters():\n            assert param.requires_grad is False\n    for i in range(1, frozen_stages + 1):\n        layer = getattr(model, f'layer{i}')\n        for mod in layer.modules():\n            if isinstance(mod, _BatchNorm):\n                assert mod.training is False\n        for param in layer.parameters():\n            assert param.requires_grad is False\n\n    # Test ResNet50V1d with first stage frozen\n    model = ResNetV1d(depth=50, frozen_stages=frozen_stages)\n    assert len(model.stem) == 9\n    model.init_weights()\n    model.train()\n    check_norm_state(model.stem, False)\n    for param in model.stem.parameters():\n        assert param.requires_grad is False\n    for i in range(1, frozen_stages + 1):\n        layer = getattr(model, f'layer{i}')\n        for mod in layer.modules():\n            if isinstance(mod, _BatchNorm):\n                assert mod.training is False\n        for param in layer.parameters():\n            assert param.requires_grad is False\n\n    # Test ResNet18 forward\n    model = ResNet(18)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 64, 56, 56])\n    assert feat[1].shape == torch.Size([1, 128, 28, 28])\n    assert feat[2].shape == torch.Size([1, 256, 14, 14])\n    assert feat[3].shape == torch.Size([1, 512, 7, 7])\n\n    # Test ResNet18 with checkpoint forward\n    model = ResNet(18, with_cp=True)\n    for m in model.modules():\n        if is_block(m):\n            assert m.with_cp\n\n    # Test ResNet50 with BatchNorm forward\n    model = ResNet(50)\n    for m in model.modules():\n        if is_norm(m):\n            assert isinstance(m, _BatchNorm)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50 with layers 1, 2, 3 out forward\n    model = ResNet(50, out_indices=(0, 1, 2))\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 3\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n\n    # Test ResNet50 with checkpoint forward\n    model = ResNet(50, with_cp=True)\n    for m in model.modules():\n        if is_block(m):\n            assert m.with_cp\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50 with GroupNorm forward\n    model = ResNet(\n        50, norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))\n    for m in model.modules():\n        if is_norm(m):\n            assert isinstance(m, GroupNorm)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50 with 1 GeneralizedAttention after conv2, 1 NonLocal2D\n    # after conv2, 1 ContextBlock after conv3 in layers 2, 3, 4\n    plugins = [\n        dict(\n            cfg=dict(\n                type='GeneralizedAttention',\n                spatial_range=-1,\n                num_heads=8,\n                attention_type='0010',\n                kv_stride=2),\n            stages=(False, True, True, True),\n            position='after_conv2'),\n        dict(cfg=dict(type='NonLocal2D'), position='after_conv2'),\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16),\n            stages=(False, True, True, False),\n            position='after_conv3')\n    ]\n    model = ResNet(50, plugins=plugins)\n    for m in model.layer1.modules():\n        if is_block(m):\n            assert not hasattr(m, 'context_block')\n            assert not hasattr(m, 'gen_attention_block')\n            assert m.nonlocal_block.in_channels == 64\n    for m in model.layer2.modules():\n        if is_block(m):\n            assert m.nonlocal_block.in_channels == 128\n            assert m.gen_attention_block.in_channels == 128\n            assert m.context_block.in_channels == 512\n\n    for m in model.layer3.modules():\n        if is_block(m):\n            assert m.nonlocal_block.in_channels == 256\n            assert m.gen_attention_block.in_channels == 256\n            assert m.context_block.in_channels == 1024\n\n    for m in model.layer4.modules():\n        if is_block(m):\n            assert m.nonlocal_block.in_channels == 512\n            assert m.gen_attention_block.in_channels == 512\n            assert not hasattr(m, 'context_block')\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50 with 1 ContextBlock after conv2, 1 ContextBlock after\n    # conv3 in layers 2, 3, 4\n    plugins = [\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=1),\n            stages=(False, True, True, False),\n            position='after_conv3'),\n        dict(\n            cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=2),\n            stages=(False, True, True, False),\n            position='after_conv3')\n    ]\n\n    model = ResNet(50, plugins=plugins)\n    for m in model.layer1.modules():\n        if is_block(m):\n            assert not hasattr(m, 'context_block')\n            assert not hasattr(m, 'context_block1')\n            assert not hasattr(m, 'context_block2')\n    for m in model.layer2.modules():\n        if is_block(m):\n            assert not hasattr(m, 'context_block')\n            assert m.context_block1.in_channels == 512\n            assert m.context_block2.in_channels == 512\n\n    for m in model.layer3.modules():\n        if is_block(m):\n            assert not hasattr(m, 'context_block')\n            assert m.context_block1.in_channels == 1024\n            assert m.context_block2.in_channels == 1024\n\n    for m in model.layer4.modules():\n        if is_block(m):\n            assert not hasattr(m, 'context_block')\n            assert not hasattr(m, 'context_block1')\n            assert not hasattr(m, 'context_block2')\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50 zero initialization of residual\n    model = ResNet(50, zero_init_residual=True)\n    model.init_weights()\n    for m in model.modules():\n        if isinstance(m, Bottleneck):\n            assert all_zeros(m.norm3)\n        elif isinstance(m, BasicBlock):\n            assert all_zeros(m.norm2)\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNetV1d forward\n    model = ResNetV1d(depth=50)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50 stem_channels\n    model = ResNet(depth=50, stem_channels=128)\n    model.init_weights()\n    model.train()\n    assert model.conv1.out_channels == 128\n    assert model.layer1[0].conv1.in_channels == 128\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n    # Test ResNet50V1d stem_channels\n    model = ResNetV1d(depth=50, stem_channels=128)\n    model.init_weights()\n    model.train()\n    assert model.stem[0].out_channels == 64\n    assert model.stem[3].out_channels == 64\n    assert model.stem[6].out_channels == 128\n    assert model.layer1[0].conv1.in_channels == 128\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n\ndef test_renext_bottleneck():\n    with pytest.raises(AssertionError):\n        # Style must be in ['pytorch', 'caffe']\n        BottleneckX(64, 64, groups=32, base_width=4, style='tensorflow')\n\n    # Test ResNeXt Bottleneck structure\n    block = BottleneckX(\n        64, 64, groups=32, base_width=4, stride=2, style='pytorch')\n    assert block.conv2.stride == (2, 2)\n    assert block.conv2.groups == 32\n    assert block.conv2.out_channels == 128\n\n    # Test ResNeXt Bottleneck with DCN\n    dcn = dict(type='DCN', deformable_groups=1, fallback_on_stride=False)\n    with pytest.raises(AssertionError):\n        # conv_cfg must be None if dcn is not None\n        BottleneckX(\n            64,\n            64,\n            groups=32,\n            base_width=4,\n            dcn=dcn,\n            conv_cfg=dict(type='Conv'))\n    BottleneckX(64, 64, dcn=dcn)\n\n    # Test ResNeXt Bottleneck forward\n    block = BottleneckX(64, 16, groups=32, base_width=4)\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n\ndef test_resnext_backbone():\n    with pytest.raises(KeyError):\n        # ResNeXt depth should be in [50, 101, 152]\n        ResNeXt(depth=18)\n\n    # Test ResNeXt with group 32, base_width 4\n    model = ResNeXt(depth=50, groups=32, base_width=4)\n    for m in model.modules():\n        if is_block(m):\n            assert m.conv2.groups == 32\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n\nregnet_test_data = [\n    ('regnetx_400mf',\n     dict(w0=24, wa=24.48, wm=2.54, group_w=16, depth=22,\n          bot_mul=1.0), [32, 64, 160, 384]),\n    ('regnetx_800mf',\n     dict(w0=56, wa=35.73, wm=2.28, group_w=16, depth=16,\n          bot_mul=1.0), [64, 128, 288, 672]),\n    ('regnetx_1.6gf',\n     dict(w0=80, wa=34.01, wm=2.25, group_w=24, depth=18,\n          bot_mul=1.0), [72, 168, 408, 912]),\n    ('regnetx_3.2gf',\n     dict(w0=88, wa=26.31, wm=2.25, group_w=48, depth=25,\n          bot_mul=1.0), [96, 192, 432, 1008]),\n    ('regnetx_4.0gf',\n     dict(w0=96, wa=38.65, wm=2.43, group_w=40, depth=23,\n          bot_mul=1.0), [80, 240, 560, 1360]),\n    ('regnetx_6.4gf',\n     dict(w0=184, wa=60.83, wm=2.07, group_w=56, depth=17,\n          bot_mul=1.0), [168, 392, 784, 1624]),\n    ('regnetx_8.0gf',\n     dict(w0=80, wa=49.56, wm=2.88, group_w=120, depth=23,\n          bot_mul=1.0), [80, 240, 720, 1920]),\n    ('regnetx_12gf',\n     dict(w0=168, wa=73.36, wm=2.37, group_w=112, depth=19,\n          bot_mul=1.0), [224, 448, 896, 2240]),\n]\n\n\n@pytest.mark.parametrize('arch_name,arch,out_channels', regnet_test_data)\ndef test_regnet_backbone(arch_name, arch, out_channels):\n    with pytest.raises(AssertionError):\n        # ResNeXt depth should be in [50, 101, 152]\n        RegNet(arch_name + '233')\n\n    # Test RegNet with arch_name\n    model = RegNet(arch_name)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, out_channels[0], 56, 56])\n    assert feat[1].shape == torch.Size([1, out_channels[1], 28, 28])\n    assert feat[2].shape == torch.Size([1, out_channels[2], 14, 14])\n    assert feat[3].shape == torch.Size([1, out_channels[3], 7, 7])\n\n    # Test RegNet with arch\n    model = RegNet(arch)\n    assert feat[0].shape == torch.Size([1, out_channels[0], 56, 56])\n    assert feat[1].shape == torch.Size([1, out_channels[1], 28, 28])\n    assert feat[2].shape == torch.Size([1, out_channels[2], 14, 14])\n    assert feat[3].shape == torch.Size([1, out_channels[3], 7, 7])\n\n\ndef test_res2net_bottle2neck():\n    with pytest.raises(AssertionError):\n        # Style must be in ['pytorch', 'caffe']\n        Bottle2neck(64, 64, base_width=26, scales=4, style='tensorflow')\n\n    with pytest.raises(AssertionError):\n        # Scale must be larger than 1\n        Bottle2neck(64, 64, base_width=26, scales=1, style='pytorch')\n\n    # Test Res2Net Bottle2neck structure\n    block = Bottle2neck(\n        64, 64, base_width=26, stride=2, scales=4, style='pytorch')\n    assert block.scales == 4\n\n    # Test Res2Net Bottle2neck with DCN\n    dcn = dict(type='DCN', deformable_groups=1, fallback_on_stride=False)\n    with pytest.raises(AssertionError):\n        # conv_cfg must be None if dcn is not None\n        Bottle2neck(\n            64,\n            64,\n            base_width=26,\n            scales=4,\n            dcn=dcn,\n            conv_cfg=dict(type='Conv'))\n    Bottle2neck(64, 64, dcn=dcn)\n\n    # Test Res2Net Bottle2neck forward\n    block = Bottle2neck(64, 16, base_width=26, scales=4)\n    x = torch.randn(1, 64, 56, 56)\n    x_out = block(x)\n    assert x_out.shape == torch.Size([1, 64, 56, 56])\n\n\ndef test_res2net_backbone():\n    with pytest.raises(KeyError):\n        # Res2Net depth should be in [50, 101, 152]\n        Res2Net(depth=18)\n\n    # Test Res2Net with scales 4, base_width 26\n    model = Res2Net(depth=50, scales=4, base_width=26)\n    for m in model.modules():\n        if is_block(m):\n            assert m.scales == 4\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 224, 224)\n    feat = model(imgs)\n    assert len(feat) == 4\n    assert feat[0].shape == torch.Size([1, 256, 56, 56])\n    assert feat[1].shape == torch.Size([1, 512, 28, 28])\n    assert feat[2].shape == torch.Size([1, 1024, 14, 14])\n    assert feat[3].shape == torch.Size([1, 2048, 7, 7])\n\n\ndef test_hourglass_backbone():\n    with pytest.raises(AssertionError):\n        # HourglassNet's num_stacks should larger than 0\n        HourglassNet(num_stacks=0)\n\n    with pytest.raises(AssertionError):\n        # len(stage_channels) should equal len(stage_blocks)\n        HourglassNet(\n            stage_channels=[256, 256, 384, 384, 384],\n            stage_blocks=[2, 2, 2, 2, 2, 4])\n\n    with pytest.raises(AssertionError):\n        # len(stage_channels) should lagrer than downsample_times\n        HourglassNet(\n            downsample_times=5,\n            stage_channels=[256, 256, 384, 384, 384],\n            stage_blocks=[2, 2, 2, 2, 2])\n\n    # Test HourglassNet-52\n    model = HourglassNet(num_stacks=1)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 511, 511)\n    feat = model(imgs)\n    assert len(feat) == 1\n    assert feat[0].shape == torch.Size([1, 256, 128, 128])\n\n    # Test HourglassNet-104\n    model = HourglassNet(num_stacks=2)\n    model.init_weights()\n    model.train()\n\n    imgs = torch.randn(1, 3, 511, 511)\n    feat = model(imgs)\n    assert len(feat) == 2\n    assert feat[0].shape == torch.Size([1, 256, 128, 128])\n    assert feat[1].shape == torch.Size([1, 256, 128, 128])\n"
  },
  {
    "path": "code/tests/test_config.py",
    "content": "from os.path import dirname, exists, join, relpath\n\nimport torch\nfrom mmcv.runner import build_optimizer\n\nfrom mmdet.core import BitmapMasks, PolygonMasks\n\n\ndef _get_config_directory():\n    \"\"\" Find the predefined detector config directory \"\"\"\n    try:\n        # Assume we are running in the source mmdetection repo\n        repo_dpath = dirname(dirname(__file__))\n    except NameError:\n        # For IPython development when this __file__ is not defined\n        import mmdet\n        repo_dpath = dirname(dirname(mmdet.__file__))\n    config_dpath = join(repo_dpath, 'configs')\n    if not exists(config_dpath):\n        raise Exception('Cannot find config path')\n    return config_dpath\n\n\ndef test_config_build_detector():\n    \"\"\"\n    Test that all detection models defined in the configs can be initialized.\n    \"\"\"\n    from mmcv import Config\n    from mmdet.models import build_detector\n\n    config_dpath = _get_config_directory()\n    print(f'Found config_dpath = {config_dpath}')\n\n    import glob\n    config_fpaths = list(glob.glob(join(config_dpath, '**', '*.py')))\n    config_fpaths = [p for p in config_fpaths if p.find('_base_') == -1]\n    config_names = [relpath(p, config_dpath) for p in config_fpaths]\n\n    print(f'Using {len(config_names)} config files')\n\n    for config_fname in config_names:\n        config_fpath = join(config_dpath, config_fname)\n        config_mod = Config.fromfile(config_fpath)\n\n        config_mod.model\n        config_mod.train_cfg\n        config_mod.test_cfg\n        print(f'Building detector, config_fpath = {config_fpath}')\n\n        # Remove pretrained keys to allow for testing in an offline environment\n        if 'pretrained' in config_mod.model:\n            config_mod.model['pretrained'] = None\n\n        detector = build_detector(\n            config_mod.model,\n            train_cfg=config_mod.train_cfg,\n            test_cfg=config_mod.test_cfg)\n        assert detector is not None\n\n        optimizer = build_optimizer(detector, config_mod.optimizer)\n        assert isinstance(optimizer, torch.optim.Optimizer)\n\n        if 'roi_head' in config_mod.model.keys():\n            # for two stage detector\n            # detectors must have bbox head\n            assert detector.roi_head.with_bbox and detector.with_bbox\n            assert detector.roi_head.with_mask == detector.with_mask\n\n            head_config = config_mod.model['roi_head']\n            _check_roi_head(head_config, detector.roi_head)\n        # else:\n        #     # for single stage detector\n        #     # detectors must have bbox head\n        #     # assert detector.with_bbox\n        #     head_config = config_mod.model['bbox_head']\n        #     _check_bbox_head(head_config, detector.bbox_head)\n\n\ndef test_config_data_pipeline():\n    \"\"\"\n    Test whether the data pipeline is valid and can process corner cases.\n    CommandLine:\n        xdoctest -m tests/test_config.py test_config_build_data_pipeline\n    \"\"\"\n    from mmcv import Config\n    from mmdet.datasets.pipelines import Compose\n    import numpy as np\n\n    config_dpath = _get_config_directory()\n    print(f'Found config_dpath = {config_dpath}')\n\n    # Only tests a representative subset of configurations\n    # TODO: test pipelines using Albu, current Albu throw None given empty GT\n    config_names = [\n        'wider_face/ssd300_wider_face.py',\n        'pascal_voc/ssd300_voc0712.py',\n        'pascal_voc/ssd512_voc0712.py',\n        # 'albu_example/mask_rcnn_r50_fpn_1x.py',\n        'foveabox/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco.py',\n        'mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco.py',\n        'mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain_1x_coco.py',\n        'fp16/mask_rcnn_r50_fpn_fp16_1x_coco.py'\n    ]\n\n    def dummy_masks(h, w, num_obj=3, mode='bitmap'):\n        assert mode in ('polygon', 'bitmap')\n        if mode == 'bitmap':\n            masks = np.random.randint(0, 2, (num_obj, h, w), dtype=np.uint8)\n            masks = BitmapMasks(masks, h, w)\n        else:\n            masks = []\n            for i in range(num_obj):\n                masks.append([])\n                masks[-1].append(\n                    np.random.uniform(0, min(h - 1, w - 1), (8 + 4 * i, )))\n                masks[-1].append(\n                    np.random.uniform(0, min(h - 1, w - 1), (10 + 4 * i, )))\n            masks = PolygonMasks(masks, h, w)\n        return masks\n\n    print(f'Using {len(config_names)} config files')\n\n    for config_fname in config_names:\n        config_fpath = join(config_dpath, config_fname)\n        config_mod = Config.fromfile(config_fpath)\n\n        # remove loading pipeline\n        loading_pipeline = config_mod.train_pipeline.pop(0)\n        loading_ann_pipeline = config_mod.train_pipeline.pop(0)\n        config_mod.test_pipeline.pop(0)\n\n        train_pipeline = Compose(config_mod.train_pipeline)\n        test_pipeline = Compose(config_mod.test_pipeline)\n\n        print(f'Building data pipeline, config_fpath = {config_fpath}')\n\n        print(f'Test training data pipeline: \\n{train_pipeline!r}')\n        img = np.random.randint(0, 255, size=(888, 666, 3), dtype=np.uint8)\n        if loading_pipeline.get('to_float32', False):\n            img = img.astype(np.float32)\n        mode = 'bitmap' if loading_ann_pipeline.get('poly2mask',\n                                                    True) else 'polygon'\n        results = dict(\n            filename='test_img.png',\n            ori_filename='test_img.png',\n            img=img,\n            img_shape=img.shape,\n            ori_shape=img.shape,\n            gt_bboxes=np.array([[35.2, 11.7, 39.7, 15.7]], dtype=np.float32),\n            gt_labels=np.array([1], dtype=np.int64),\n            gt_masks=dummy_masks(img.shape[0], img.shape[1], mode=mode),\n        )\n        results['img_fields'] = ['img']\n        results['bbox_fields'] = ['gt_bboxes']\n        results['mask_fields'] = ['gt_masks']\n        output_results = train_pipeline(results)\n        assert output_results is not None\n\n        print(f'Test testing data pipeline: \\n{test_pipeline!r}')\n        results = dict(\n            filename='test_img.png',\n            ori_filename='test_img.png',\n            img=img,\n            img_shape=img.shape,\n            ori_shape=img.shape,\n            gt_bboxes=np.array([[35.2, 11.7, 39.7, 15.7]], dtype=np.float32),\n            gt_labels=np.array([1], dtype=np.int64),\n            gt_masks=dummy_masks(img.shape[0], img.shape[1], mode=mode),\n        )\n        results['img_fields'] = ['img']\n        results['bbox_fields'] = ['gt_bboxes']\n        results['mask_fields'] = ['gt_masks']\n        output_results = test_pipeline(results)\n        assert output_results is not None\n\n        # test empty GT\n        print('Test empty GT with training data pipeline: '\n              f'\\n{train_pipeline!r}')\n        results = dict(\n            filename='test_img.png',\n            ori_filename='test_img.png',\n            img=img,\n            img_shape=img.shape,\n            ori_shape=img.shape,\n            gt_bboxes=np.zeros((0, 4), dtype=np.float32),\n            gt_labels=np.array([], dtype=np.int64),\n            gt_masks=dummy_masks(\n                img.shape[0], img.shape[1], num_obj=0, mode=mode),\n        )\n        results['img_fields'] = ['img']\n        results['bbox_fields'] = ['gt_bboxes']\n        results['mask_fields'] = ['gt_masks']\n        output_results = train_pipeline(results)\n        assert output_results is not None\n\n        print(f'Test empty GT with testing data pipeline: \\n{test_pipeline!r}')\n        results = dict(\n            filename='test_img.png',\n            ori_filename='test_img.png',\n            img=img,\n            img_shape=img.shape,\n            ori_shape=img.shape,\n            gt_bboxes=np.zeros((0, 4), dtype=np.float32),\n            gt_labels=np.array([], dtype=np.int64),\n            gt_masks=dummy_masks(\n                img.shape[0], img.shape[1], num_obj=0, mode=mode),\n        )\n        results['img_fields'] = ['img']\n        results['bbox_fields'] = ['gt_bboxes']\n        results['mask_fields'] = ['gt_masks']\n        output_results = test_pipeline(results)\n        assert output_results is not None\n\n\ndef _check_roi_head(config, head):\n    # check consistency between head_config and roi_head\n    assert config['type'] == head.__class__.__name__\n\n    # check roi_align\n    bbox_roi_cfg = config.bbox_roi_extractor\n    bbox_roi_extractor = head.bbox_roi_extractor\n    _check_roi_extractor(bbox_roi_cfg, bbox_roi_extractor)\n\n    # check bbox head infos\n    bbox_cfg = config.bbox_head\n    bbox_head = head.bbox_head\n    _check_bbox_head(bbox_cfg, bbox_head)\n\n    if head.with_mask:\n        # check roi_align\n        if config.mask_roi_extractor:\n            mask_roi_cfg = config.mask_roi_extractor\n            mask_roi_extractor = head.mask_roi_extractor\n            _check_roi_extractor(mask_roi_cfg, mask_roi_extractor,\n                                 bbox_roi_extractor)\n\n        # check mask head infos\n        mask_head = head.mask_head\n        mask_cfg = config.mask_head\n        _check_mask_head(mask_cfg, mask_head)\n\n    # check arch specific settings, e.g., cascade/htc\n    if config['type'] in ['CascadeRoIHead', 'HybridTaskCascadeRoIHead']:\n        assert config.num_stages == len(head.bbox_head)\n        assert config.num_stages == len(head.bbox_roi_extractor)\n\n        if head.with_mask:\n            assert config.num_stages == len(head.mask_head)\n            assert config.num_stages == len(head.mask_roi_extractor)\n\n    elif config['type'] in ['MaskScoringRoIHead']:\n        assert (hasattr(head, 'mask_iou_head')\n                and head.mask_iou_head is not None)\n        mask_iou_cfg = config.mask_iou_head\n        mask_iou_head = head.mask_iou_head\n        assert (mask_iou_cfg.fc_out_channels ==\n                mask_iou_head.fc_mask_iou.in_features)\n\n    elif config['type'] in ['GridRoIHead']:\n        grid_roi_cfg = config.grid_roi_extractor\n        grid_roi_extractor = head.grid_roi_extractor\n        _check_roi_extractor(grid_roi_cfg, grid_roi_extractor,\n                             bbox_roi_extractor)\n\n        config.grid_head.grid_points = head.grid_head.grid_points\n\n\ndef _check_roi_extractor(config, roi_extractor, prev_roi_extractor=None):\n    import torch.nn as nn\n    if isinstance(roi_extractor, nn.ModuleList):\n        if prev_roi_extractor:\n            prev_roi_extractor = prev_roi_extractor[0]\n        roi_extractor = roi_extractor[0]\n\n    assert (len(config.featmap_strides) == len(roi_extractor.roi_layers))\n    assert (config.out_channels == roi_extractor.out_channels)\n    from torch.nn.modules.utils import _pair\n    assert (_pair(\n        config.roi_layer.out_size) == roi_extractor.roi_layers[0].out_size)\n\n    if 'use_torchvision' in config.roi_layer:\n        assert (config.roi_layer.use_torchvision ==\n                roi_extractor.roi_layers[0].use_torchvision)\n    elif 'aligned' in config.roi_layer:\n        assert (\n            config.roi_layer.aligned == roi_extractor.roi_layers[0].aligned)\n\n    if prev_roi_extractor:\n        assert (roi_extractor.roi_layers[0].aligned ==\n                prev_roi_extractor.roi_layers[0].aligned)\n        assert (roi_extractor.roi_layers[0].use_torchvision ==\n                prev_roi_extractor.roi_layers[0].use_torchvision)\n\n\ndef _check_mask_head(mask_cfg, mask_head):\n    import torch.nn as nn\n    if isinstance(mask_cfg, list):\n        for single_mask_cfg, single_mask_head in zip(mask_cfg, mask_head):\n            _check_mask_head(single_mask_cfg, single_mask_head)\n    elif isinstance(mask_head, nn.ModuleList):\n        for single_mask_head in mask_head:\n            _check_mask_head(mask_cfg, single_mask_head)\n    else:\n        assert mask_cfg['type'] == mask_head.__class__.__name__\n        assert mask_cfg.in_channels == mask_head.in_channels\n        class_agnostic = mask_cfg.get('class_agnostic', False)\n        out_dim = (1 if class_agnostic else mask_cfg.num_classes)\n        if hasattr(mask_head, 'conv_logits'):\n            assert (mask_cfg.conv_out_channels ==\n                    mask_head.conv_logits.in_channels)\n            assert mask_head.conv_logits.out_channels == out_dim\n        else:\n            assert mask_cfg.fc_out_channels == mask_head.fc_logits.in_features\n            assert (mask_head.fc_logits.out_features == out_dim *\n                    mask_head.output_area)\n\n\ndef _check_bbox_head(bbox_cfg, bbox_head):\n    import torch.nn as nn\n    if isinstance(bbox_cfg, list):\n        for single_bbox_cfg, single_bbox_head in zip(bbox_cfg, bbox_head):\n            _check_bbox_head(single_bbox_cfg, single_bbox_head)\n    elif isinstance(bbox_head, nn.ModuleList):\n        for single_bbox_head in bbox_head:\n            _check_bbox_head(bbox_cfg, single_bbox_head)\n    else:\n        assert bbox_cfg['type'] == bbox_head.__class__.__name__\n        assert bbox_cfg.in_channels == bbox_head.in_channels\n        with_cls = bbox_cfg.get('with_cls', True)\n        if with_cls:\n            fc_out_channels = bbox_cfg.get('fc_out_channels', 2048)\n            assert (fc_out_channels == bbox_head.fc_cls.in_features)\n            assert bbox_cfg.num_classes + 1 == bbox_head.fc_cls.out_features\n\n        with_reg = bbox_cfg.get('with_reg', True)\n        if with_reg:\n            out_dim = (4 if bbox_cfg.reg_class_agnostic else 4 *\n                       bbox_cfg.num_classes)\n            assert bbox_head.fc_reg.out_features == out_dim\n\n\ndef _check_anchorhead(config, head):\n    # check consistency between head_config and roi_head\n    assert config['type'] == head.__class__.__name__\n    assert config.in_channels == head.in_channels\n\n    num_classes = (\n        config.num_classes -\n        1 if config.loss_cls.get('use_sigmoid', False) else config.num_classes)\n    if config['type'] == 'ATSSHead':\n        assert (config.feat_channels == head.atss_cls.in_channels)\n        assert (config.feat_channels == head.atss_reg.in_channels)\n        assert (config.feat_channels == head.atss_centerness.in_channels)\n    else:\n        assert (config.in_channels == head.conv_cls.in_channels)\n        assert (config.in_channels == head.conv_reg.in_channels)\n        assert (head.conv_cls.out_channels == num_classes * head.num_anchors)\n        assert head.fc_reg.out_channels == 4 * head.num_anchors\n"
  },
  {
    "path": "code/tests/test_dataset.py",
    "content": "import bisect\nimport math\nfrom collections import defaultdict\nfrom unittest.mock import MagicMock\n\nimport numpy as np\nimport pytest\n\nfrom mmdet.datasets import (DATASETS, ClassBalancedDataset, ConcatDataset,\n                            CustomDataset, RepeatDataset)\n\n\n@pytest.mark.parametrize('dataset',\n                         ['CocoDataset', 'VOCDataset', 'CityscapesDataset'])\ndef test_custom_classes_override_default(dataset):\n    dataset_class = DATASETS.get(dataset)\n    dataset_class.load_annotations = MagicMock()\n    if dataset in ['CocoDataset', 'CityscapesDataset']:\n        dataset_class.coco = MagicMock()\n        dataset_class.cat_ids = MagicMock()\n\n    original_classes = dataset_class.CLASSES\n\n    # Test setting classes as a tuple\n    custom_dataset = dataset_class(\n        ann_file=MagicMock(),\n        pipeline=[],\n        classes=('bus', 'car'),\n        test_mode=True,\n        img_prefix='VOC2007' if dataset == 'VOCDataset' else '')\n\n    assert custom_dataset.CLASSES != original_classes\n    assert custom_dataset.CLASSES == ('bus', 'car')\n    assert custom_dataset.custom_classes\n\n    # Test setting classes as a list\n    custom_dataset = dataset_class(\n        ann_file=MagicMock(),\n        pipeline=[],\n        classes=['bus', 'car'],\n        test_mode=True,\n        img_prefix='VOC2007' if dataset == 'VOCDataset' else '')\n\n    assert custom_dataset.CLASSES != original_classes\n    assert custom_dataset.CLASSES == ['bus', 'car']\n    assert custom_dataset.custom_classes\n\n    # Test overriding not a subset\n    custom_dataset = dataset_class(\n        ann_file=MagicMock(),\n        pipeline=[],\n        classes=['foo'],\n        test_mode=True,\n        img_prefix='VOC2007' if dataset == 'VOCDataset' else '')\n\n    assert custom_dataset.CLASSES != original_classes\n    assert custom_dataset.CLASSES == ['foo']\n    assert custom_dataset.custom_classes\n\n    # Test default behavior\n    custom_dataset = dataset_class(\n        ann_file=MagicMock(),\n        pipeline=[],\n        classes=None,\n        test_mode=True,\n        img_prefix='VOC2007' if dataset == 'VOCDataset' else '')\n\n    assert custom_dataset.CLASSES == original_classes\n    assert not custom_dataset.custom_classes\n\n    # Test sending file path\n    import tempfile\n    tmp_file = tempfile.NamedTemporaryFile()\n    with open(tmp_file.name, 'w') as f:\n        f.write('bus\\ncar\\n')\n    custom_dataset = dataset_class(\n        ann_file=MagicMock(),\n        pipeline=[],\n        classes=tmp_file.name,\n        test_mode=True,\n        img_prefix='VOC2007' if dataset == 'VOCDataset' else '')\n    tmp_file.close()\n\n    assert custom_dataset.CLASSES != original_classes\n    assert custom_dataset.CLASSES == ['bus', 'car']\n    assert custom_dataset.custom_classes\n\n\ndef test_dataset_wrapper():\n    CustomDataset.load_annotations = MagicMock()\n    CustomDataset.__getitem__ = MagicMock(side_effect=lambda idx: idx)\n    dataset_a = CustomDataset(\n        ann_file=MagicMock(), pipeline=[], test_mode=True, img_prefix='')\n    len_a = 10\n    cat_ids_list_a = [\n        np.random.randint(0, 80, num).tolist()\n        for num in np.random.randint(1, 20, len_a)\n    ]\n    dataset_a.data_infos = MagicMock()\n    dataset_a.data_infos.__len__.return_value = len_a\n    dataset_a.get_cat_ids = MagicMock(\n        side_effect=lambda idx: cat_ids_list_a[idx])\n    dataset_b = CustomDataset(\n        ann_file=MagicMock(), pipeline=[], test_mode=True, img_prefix='')\n    len_b = 20\n    cat_ids_list_b = [\n        np.random.randint(0, 80, num).tolist()\n        for num in np.random.randint(1, 20, len_b)\n    ]\n    dataset_b.data_infos = MagicMock()\n    dataset_b.data_infos.__len__.return_value = len_b\n    dataset_b.get_cat_ids = MagicMock(\n        side_effect=lambda idx: cat_ids_list_b[idx])\n\n    concat_dataset = ConcatDataset([dataset_a, dataset_b])\n    assert concat_dataset[5] == 5\n    assert concat_dataset[25] == 15\n    assert concat_dataset.get_cat_ids(5) == cat_ids_list_a[5]\n    assert concat_dataset.get_cat_ids(25) == cat_ids_list_b[15]\n    assert len(concat_dataset) == len(dataset_a) + len(dataset_b)\n\n    repeat_dataset = RepeatDataset(dataset_a, 10)\n    assert repeat_dataset[5] == 5\n    assert repeat_dataset[15] == 5\n    assert repeat_dataset[27] == 7\n    assert repeat_dataset.get_cat_ids(5) == cat_ids_list_a[5]\n    assert repeat_dataset.get_cat_ids(15) == cat_ids_list_a[5]\n    assert repeat_dataset.get_cat_ids(27) == cat_ids_list_a[7]\n    assert len(repeat_dataset) == 10 * len(dataset_a)\n\n    category_freq = defaultdict(int)\n    for cat_ids in cat_ids_list_a:\n        cat_ids = set(cat_ids)\n        for cat_id in cat_ids:\n            category_freq[cat_id] += 1\n    for k, v in category_freq.items():\n        category_freq[k] = v / len(cat_ids_list_a)\n\n    mean_freq = np.mean(list(category_freq.values()))\n    repeat_thr = mean_freq\n\n    category_repeat = {\n        cat_id: max(1.0, math.sqrt(repeat_thr / cat_freq))\n        for cat_id, cat_freq in category_freq.items()\n    }\n\n    repeat_factors = []\n    for cat_ids in cat_ids_list_a:\n        cat_ids = set(cat_ids)\n        repeat_factor = max({category_repeat[cat_id] for cat_id in cat_ids})\n        repeat_factors.append(math.ceil(repeat_factor))\n    repeat_factors_cumsum = np.cumsum(repeat_factors)\n    repeat_factor_dataset = ClassBalancedDataset(dataset_a, repeat_thr)\n    assert len(repeat_factor_dataset) == repeat_factors_cumsum[-1]\n    for idx in np.random.randint(0, len(repeat_factor_dataset), 3):\n        assert repeat_factor_dataset[idx] == bisect.bisect_right(\n            repeat_factors_cumsum, idx)\n"
  },
  {
    "path": "code/tests/test_forward.py",
    "content": "\"\"\"\npytest tests/test_forward.py\n\"\"\"\nimport copy\nfrom os.path import dirname, exists, join\n\nimport numpy as np\nimport pytest\nimport torch\n\n\ndef _get_config_directory():\n    \"\"\" Find the predefined detector config directory \"\"\"\n    try:\n        # Assume we are running in the source mmdetection repo\n        repo_dpath = dirname(dirname(__file__))\n    except NameError:\n        # For IPython development when this __file__ is not defined\n        import mmdet\n        repo_dpath = dirname(dirname(mmdet.__file__))\n    config_dpath = join(repo_dpath, 'configs')\n    if not exists(config_dpath):\n        raise Exception('Cannot find config path')\n    return config_dpath\n\n\ndef _get_config_module(fname):\n    \"\"\"\n    Load a configuration as a python module\n    \"\"\"\n    from mmcv import Config\n    config_dpath = _get_config_directory()\n    config_fpath = join(config_dpath, fname)\n    config_mod = Config.fromfile(config_fpath)\n    return config_mod\n\n\ndef _get_detector_cfg(fname):\n    \"\"\"\n    Grab configs necessary to create a detector. These are deep copied to allow\n    for safe modification of parameters without influencing other tests.\n    \"\"\"\n    import mmcv\n    config = _get_config_module(fname)\n    model = copy.deepcopy(config.model)\n    train_cfg = mmcv.Config(copy.deepcopy(config.train_cfg))\n    test_cfg = mmcv.Config(copy.deepcopy(config.test_cfg))\n    return model, train_cfg, test_cfg\n\n\ndef test_rpn_forward():\n    model, train_cfg, test_cfg = _get_detector_cfg(\n        'rpn/rpn_r50_fpn_1x_coco.py')\n    model['pretrained'] = None\n\n    from mmdet.models import build_detector\n    detector = build_detector(model, train_cfg=train_cfg, test_cfg=test_cfg)\n\n    input_shape = (1, 3, 224, 224)\n    mm_inputs = _demo_mm_inputs(input_shape)\n\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n\n    # Test forward train\n    gt_bboxes = mm_inputs['gt_bboxes']\n    losses = detector.forward(\n        imgs, img_metas, gt_bboxes=gt_bboxes, return_loss=True)\n    assert isinstance(losses, dict)\n\n    # Test forward test\n    with torch.no_grad():\n        img_list = [g[None, :] for g in imgs]\n        batch_results = []\n        for one_img, one_meta in zip(img_list, img_metas):\n            result = detector.forward([one_img], [[one_meta]],\n                                      return_loss=False)\n            batch_results.append(result)\n\n\n@pytest.mark.parametrize(\n    'cfg_file',\n    [\n        'retinanet/retinanet_r50_fpn_1x_coco.py',\n        'guided_anchoring/ga_retinanet_r50_fpn_1x_coco.py',\n        'ghm/retinanet_ghm_r50_fpn_1x_coco.py',\n        'fcos/fcos_center_r50_caffe_fpn_gn-head_4x4_1x_coco.py',\n        'foveabox/fovea_align_r50_fpn_gn-head_4x4_2x_coco.py',\n        # 'free_anchor/retinanet_free_anchor_r50_fpn_1x_coco.py',\n        # 'atss/atss_r50_fpn_1x_coco.py',  # not ready for topk\n        'reppoints/reppoints_moment_r50_fpn_1x_coco.py'\n    ])\ndef test_single_stage_forward_gpu(cfg_file):\n    if not torch.cuda.is_available():\n        import pytest\n        pytest.skip('test requires GPU and torch+cuda')\n\n    model, train_cfg, test_cfg = _get_detector_cfg(cfg_file)\n    model['pretrained'] = None\n\n    from mmdet.models import build_detector\n    detector = build_detector(model, train_cfg=train_cfg, test_cfg=test_cfg)\n\n    input_shape = (2, 3, 224, 224)\n    mm_inputs = _demo_mm_inputs(input_shape)\n\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n\n    detector = detector.cuda()\n    imgs = imgs.cuda()\n    # Test forward train\n    gt_bboxes = [b.cuda() for b in mm_inputs['gt_bboxes']]\n    gt_labels = [g.cuda() for g in mm_inputs['gt_labels']]\n    losses = detector.forward(\n        imgs,\n        img_metas,\n        gt_bboxes=gt_bboxes,\n        gt_labels=gt_labels,\n        return_loss=True)\n    assert isinstance(losses, dict)\n\n    # Test forward test\n    with torch.no_grad():\n        img_list = [g[None, :] for g in imgs]\n        batch_results = []\n        for one_img, one_meta in zip(img_list, img_metas):\n            result = detector.forward([one_img], [[one_meta]],\n                                      return_loss=False)\n            batch_results.append(result)\n\n\ndef test_faster_rcnn_ohem_forward():\n    model, train_cfg, test_cfg = _get_detector_cfg(\n        'faster_rcnn/faster_rcnn_r50_fpn_ohem_1x_coco.py')\n    model['pretrained'] = None\n\n    from mmdet.models import build_detector\n    detector = build_detector(model, train_cfg=train_cfg, test_cfg=test_cfg)\n\n    input_shape = (1, 3, 256, 256)\n\n    # Test forward train with a non-empty truth batch\n    mm_inputs = _demo_mm_inputs(input_shape, num_items=[10])\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n    gt_bboxes = mm_inputs['gt_bboxes']\n    gt_labels = mm_inputs['gt_labels']\n    losses = detector.forward(\n        imgs,\n        img_metas,\n        gt_bboxes=gt_bboxes,\n        gt_labels=gt_labels,\n        return_loss=True)\n    assert isinstance(losses, dict)\n    loss, _ = detector._parse_losses(losses)\n    assert float(loss.item()) > 0\n\n    # Test forward train with an empty truth batch\n    mm_inputs = _demo_mm_inputs(input_shape, num_items=[0])\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n    gt_bboxes = mm_inputs['gt_bboxes']\n    gt_labels = mm_inputs['gt_labels']\n    losses = detector.forward(\n        imgs,\n        img_metas,\n        gt_bboxes=gt_bboxes,\n        gt_labels=gt_labels,\n        return_loss=True)\n    assert isinstance(losses, dict)\n    loss, _ = detector._parse_losses(losses)\n    assert float(loss.item()) > 0\n\n\n# HTC is not ready yet\n@pytest.mark.parametrize('cfg_file', [\n    'cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco.py',\n    'mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py',\n    'grid_rcnn/grid_rcnn_r50_fpn_gn-head_2x_coco.py',\n    'ms_rcnn/ms_rcnn_r50_fpn_1x_coco.py'\n])\ndef test_two_stage_forward(cfg_file):\n    model, train_cfg, test_cfg = _get_detector_cfg(cfg_file)\n    model['pretrained'] = None\n\n    from mmdet.models import build_detector\n    detector = build_detector(model, train_cfg=train_cfg, test_cfg=test_cfg)\n\n    input_shape = (1, 3, 256, 256)\n\n    # Test forward train with a non-empty truth batch\n    mm_inputs = _demo_mm_inputs(input_shape, num_items=[10])\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n    gt_bboxes = mm_inputs['gt_bboxes']\n    gt_labels = mm_inputs['gt_labels']\n    gt_masks = mm_inputs['gt_masks']\n    losses = detector.forward(\n        imgs,\n        img_metas,\n        gt_bboxes=gt_bboxes,\n        gt_labels=gt_labels,\n        gt_masks=gt_masks,\n        return_loss=True)\n    assert isinstance(losses, dict)\n    loss, _ = detector._parse_losses(losses)\n    loss.requires_grad_(True)\n    assert float(loss.item()) > 0\n    loss.backward()\n\n    # Test forward train with an empty truth batch\n    mm_inputs = _demo_mm_inputs(input_shape, num_items=[0])\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n    gt_bboxes = mm_inputs['gt_bboxes']\n    gt_labels = mm_inputs['gt_labels']\n    gt_masks = mm_inputs['gt_masks']\n    losses = detector.forward(\n        imgs,\n        img_metas,\n        gt_bboxes=gt_bboxes,\n        gt_labels=gt_labels,\n        gt_masks=gt_masks,\n        return_loss=True)\n    assert isinstance(losses, dict)\n    loss, _ = detector._parse_losses(losses)\n    loss.requires_grad_(True)\n    assert float(loss.item()) > 0\n    loss.backward()\n\n    # Test forward test\n    with torch.no_grad():\n        img_list = [g[None, :] for g in imgs]\n        batch_results = []\n        for one_img, one_meta in zip(img_list, img_metas):\n            result = detector.forward([one_img], [[one_meta]],\n                                      return_loss=False)\n            batch_results.append(result)\n\n\n@pytest.mark.parametrize(\n    'cfg_file', ['ghm/retinanet_ghm_r50_fpn_1x_coco.py', 'ssd/ssd300_coco.py'])\ndef test_single_stage_forward_cpu(cfg_file):\n    model, train_cfg, test_cfg = _get_detector_cfg(cfg_file)\n    model['pretrained'] = None\n\n    from mmdet.models import build_detector\n    detector = build_detector(model, train_cfg=train_cfg, test_cfg=test_cfg)\n\n    input_shape = (1, 3, 300, 300)\n    mm_inputs = _demo_mm_inputs(input_shape)\n\n    imgs = mm_inputs.pop('imgs')\n    img_metas = mm_inputs.pop('img_metas')\n\n    # Test forward train\n    gt_bboxes = mm_inputs['gt_bboxes']\n    gt_labels = mm_inputs['gt_labels']\n    losses = detector.forward(\n        imgs,\n        img_metas,\n        gt_bboxes=gt_bboxes,\n        gt_labels=gt_labels,\n        return_loss=True)\n    assert isinstance(losses, dict)\n\n    # Test forward test\n    with torch.no_grad():\n        img_list = [g[None, :] for g in imgs]\n        batch_results = []\n        for one_img, one_meta in zip(img_list, img_metas):\n            result = detector.forward([one_img], [[one_meta]],\n                                      return_loss=False)\n            batch_results.append(result)\n\n\ndef _demo_mm_inputs(input_shape=(1, 3, 300, 300),\n                    num_items=None, num_classes=10):  # yapf: disable\n    \"\"\"\n    Create a superset of inputs needed to run test or train batches.\n\n    Args:\n        input_shape (tuple):\n            input batch dimensions\n\n        num_items (None | List[int]):\n            specifies the number of boxes in each batch item\n\n        num_classes (int):\n            number of different labels a box might have\n    \"\"\"\n    from mmdet.core import BitmapMasks\n\n    (N, C, H, W) = input_shape\n\n    rng = np.random.RandomState(0)\n\n    imgs = rng.rand(*input_shape)\n\n    img_metas = [{\n        'img_shape': (H, W, C),\n        'ori_shape': (H, W, C),\n        'pad_shape': (H, W, C),\n        'filename': '<demo>.png',\n        'scale_factor': 1.0,\n        'flip': False,\n    } for _ in range(N)]\n\n    gt_bboxes = []\n    gt_labels = []\n    gt_masks = []\n\n    for batch_idx in range(N):\n        if num_items is None:\n            num_boxes = rng.randint(1, 10)\n        else:\n            num_boxes = num_items[batch_idx]\n\n        cx, cy, bw, bh = rng.rand(num_boxes, 4).T\n\n        tl_x = ((cx * W) - (W * bw / 2)).clip(0, W)\n        tl_y = ((cy * H) - (H * bh / 2)).clip(0, H)\n        br_x = ((cx * W) + (W * bw / 2)).clip(0, W)\n        br_y = ((cy * H) + (H * bh / 2)).clip(0, H)\n\n        boxes = np.vstack([tl_x, tl_y, br_x, br_y]).T\n        class_idxs = rng.randint(1, num_classes, size=num_boxes)\n\n        gt_bboxes.append(torch.FloatTensor(boxes))\n        gt_labels.append(torch.LongTensor(class_idxs))\n\n    mask = np.random.randint(0, 2, (len(boxes), H, W), dtype=np.uint8)\n    gt_masks.append(BitmapMasks(mask, H, W))\n\n    mm_inputs = {\n        'imgs': torch.FloatTensor(imgs).requires_grad_(True),\n        'img_metas': img_metas,\n        'gt_bboxes': gt_bboxes,\n        'gt_labels': gt_labels,\n        'gt_bboxes_ignore': None,\n        'gt_masks': gt_masks,\n    }\n    return mm_inputs\n"
  },
  {
    "path": "code/tests/test_fp16.py",
    "content": "import numpy as np\nimport pytest\nimport torch\nimport torch.nn as nn\n\nfrom mmdet.core import auto_fp16, force_fp32\nfrom mmdet.core.fp16.utils import cast_tensor_type\n\n\ndef test_cast_tensor_type():\n    inputs = torch.FloatTensor([5.])\n    src_type = torch.float32\n    dst_type = torch.int32\n    outputs = cast_tensor_type(inputs, src_type, dst_type)\n    assert isinstance(outputs, torch.Tensor)\n    assert outputs.dtype == dst_type\n\n    inputs = 'tensor'\n    src_type = str\n    dst_type = str\n    outputs = cast_tensor_type(inputs, src_type, dst_type)\n    assert isinstance(outputs, str)\n\n    inputs = np.array([5.])\n    src_type = np.ndarray\n    dst_type = np.ndarray\n    outputs = cast_tensor_type(inputs, src_type, dst_type)\n    assert isinstance(outputs, np.ndarray)\n\n    inputs = dict(\n        tensor_a=torch.FloatTensor([1.]), tensor_b=torch.FloatTensor([2.]))\n    src_type = torch.float32\n    dst_type = torch.int32\n    outputs = cast_tensor_type(inputs, src_type, dst_type)\n    assert isinstance(outputs, dict)\n    assert outputs['tensor_a'].dtype == dst_type\n    assert outputs['tensor_b'].dtype == dst_type\n\n    inputs = [torch.FloatTensor([1.]), torch.FloatTensor([2.])]\n    src_type = torch.float32\n    dst_type = torch.int32\n    outputs = cast_tensor_type(inputs, src_type, dst_type)\n    assert isinstance(outputs, list)\n    assert outputs[0].dtype == dst_type\n    assert outputs[1].dtype == dst_type\n\n    inputs = 5\n    outputs = cast_tensor_type(inputs, None, None)\n    assert isinstance(outputs, int)\n\n\ndef test_auto_fp16():\n\n    with pytest.raises(TypeError):\n        # ExampleObject is not a subclass of nn.Module\n\n        class ExampleObject(object):\n\n            @auto_fp16()\n            def __call__(self, x):\n                return x\n\n        model = ExampleObject()\n        input_x = torch.ones(1, dtype=torch.float32)\n        model(input_x)\n\n    # apply to all input args\n    class ExampleModule(nn.Module):\n\n        @auto_fp16()\n        def forward(self, x, y):\n            return x, y\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.float32)\n    input_y = torch.ones(1, dtype=torch.float32)\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.float32\n\n    model.fp16_enabled = True\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.half\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y = model(input_x.cuda(), input_y.cuda())\n        assert output_x.dtype == torch.half\n        assert output_y.dtype == torch.half\n\n    # apply to specified input args\n    class ExampleModule(nn.Module):\n\n        @auto_fp16(apply_to=('x', ))\n        def forward(self, x, y):\n            return x, y\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.float32)\n    input_y = torch.ones(1, dtype=torch.float32)\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.float32\n\n    model.fp16_enabled = True\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.float32\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y = model(input_x.cuda(), input_y.cuda())\n        assert output_x.dtype == torch.half\n        assert output_y.dtype == torch.float32\n\n    # apply to optional input args\n    class ExampleModule(nn.Module):\n\n        @auto_fp16(apply_to=('x', 'y'))\n        def forward(self, x, y=None, z=None):\n            return x, y, z\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.float32)\n    input_y = torch.ones(1, dtype=torch.float32)\n    input_z = torch.ones(1, dtype=torch.float32)\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.float32\n    assert output_z.dtype == torch.float32\n\n    model.fp16_enabled = True\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.half\n    assert output_z.dtype == torch.float32\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y, output_z = model(\n            input_x.cuda(), y=input_y.cuda(), z=input_z.cuda())\n        assert output_x.dtype == torch.half\n        assert output_y.dtype == torch.half\n        assert output_z.dtype == torch.float32\n\n    # out_fp32=True\n    class ExampleModule(nn.Module):\n\n        @auto_fp16(apply_to=('x', 'y'), out_fp32=True)\n        def forward(self, x, y=None, z=None):\n            return x, y, z\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.half)\n    input_y = torch.ones(1, dtype=torch.float32)\n    input_z = torch.ones(1, dtype=torch.float32)\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.float32\n    assert output_z.dtype == torch.float32\n\n    model.fp16_enabled = True\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.float32\n    assert output_z.dtype == torch.float32\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y, output_z = model(\n            input_x.cuda(), y=input_y.cuda(), z=input_z.cuda())\n        assert output_x.dtype == torch.float32\n        assert output_y.dtype == torch.float32\n        assert output_z.dtype == torch.float32\n\n\ndef test_force_fp32():\n\n    with pytest.raises(TypeError):\n        # ExampleObject is not a subclass of nn.Module\n\n        class ExampleObject(object):\n\n            @force_fp32()\n            def __call__(self, x):\n                return x\n\n        model = ExampleObject()\n        input_x = torch.ones(1, dtype=torch.float32)\n        model(input_x)\n\n    # apply to all input args\n    class ExampleModule(nn.Module):\n\n        @force_fp32()\n        def forward(self, x, y):\n            return x, y\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.half)\n    input_y = torch.ones(1, dtype=torch.half)\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.half\n\n    model.fp16_enabled = True\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.float32\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y = model(input_x.cuda(), input_y.cuda())\n        assert output_x.dtype == torch.float32\n        assert output_y.dtype == torch.float32\n\n    # apply to specified input args\n    class ExampleModule(nn.Module):\n\n        @force_fp32(apply_to=('x', ))\n        def forward(self, x, y):\n            return x, y\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.half)\n    input_y = torch.ones(1, dtype=torch.half)\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.half\n\n    model.fp16_enabled = True\n    output_x, output_y = model(input_x, input_y)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.half\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y = model(input_x.cuda(), input_y.cuda())\n        assert output_x.dtype == torch.float32\n        assert output_y.dtype == torch.half\n\n    # apply to optional input args\n    class ExampleModule(nn.Module):\n\n        @force_fp32(apply_to=('x', 'y'))\n        def forward(self, x, y=None, z=None):\n            return x, y, z\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.half)\n    input_y = torch.ones(1, dtype=torch.half)\n    input_z = torch.ones(1, dtype=torch.half)\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.half\n    assert output_z.dtype == torch.half\n\n    model.fp16_enabled = True\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.float32\n    assert output_z.dtype == torch.half\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y, output_z = model(\n            input_x.cuda(), y=input_y.cuda(), z=input_z.cuda())\n        assert output_x.dtype == torch.float32\n        assert output_y.dtype == torch.float32\n        assert output_z.dtype == torch.half\n\n    # out_fp16=True\n    class ExampleModule(nn.Module):\n\n        @force_fp32(apply_to=('x', 'y'), out_fp16=True)\n        def forward(self, x, y=None, z=None):\n            return x, y, z\n\n    model = ExampleModule()\n    input_x = torch.ones(1, dtype=torch.float32)\n    input_y = torch.ones(1, dtype=torch.half)\n    input_z = torch.ones(1, dtype=torch.half)\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.float32\n    assert output_y.dtype == torch.half\n    assert output_z.dtype == torch.half\n\n    model.fp16_enabled = True\n    output_x, output_y, output_z = model(input_x, y=input_y, z=input_z)\n    assert output_x.dtype == torch.half\n    assert output_y.dtype == torch.half\n    assert output_z.dtype == torch.half\n\n    if torch.cuda.is_available():\n        model.cuda()\n        output_x, output_y, output_z = model(\n            input_x.cuda(), y=input_y.cuda(), z=input_z.cuda())\n        assert output_x.dtype == torch.half\n        assert output_y.dtype == torch.half\n        assert output_z.dtype == torch.half\n"
  },
  {
    "path": "code/tests/test_heads.py",
    "content": "import mmcv\nimport torch\n\nfrom mmdet.core import bbox2roi, build_assigner, build_sampler\nfrom mmdet.models.dense_heads import (AnchorHead, FCOSHead, FSAFHead,\n                                      GuidedAnchorHead)\nfrom mmdet.models.roi_heads.bbox_heads import BBoxHead\nfrom mmdet.models.roi_heads.mask_heads import FCNMaskHead, MaskIoUHead\n\n\ndef test_fcos_head_loss():\n    \"\"\"\n    Tests fcos head loss when truth is empty and non-empty\n    \"\"\"\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n    train_cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.5,\n                neg_iou_thr=0.4,\n                min_pos_iou=0,\n                ignore_iof_thr=-1),\n            allowed_border=-1,\n            pos_weight=-1,\n            debug=False))\n    # since Focal Loss is not supported on CPU\n    self = FCOSHead(\n        num_classes=4,\n        in_channels=1,\n        train_cfg=train_cfg,\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0))\n    feat = [\n        torch.rand(1, 1, s // feat_size, s // feat_size)\n        for feat_size in [4, 8, 16, 32, 64]\n    ]\n    cls_scores, bbox_preds, centerness = self.forward(feat)\n    # Test that empty ground truth encourages the network to predict background\n    gt_bboxes = [torch.empty((0, 4))]\n    gt_labels = [torch.LongTensor([])]\n    gt_bboxes_ignore = None\n    empty_gt_losses = self.loss(cls_scores, bbox_preds, centerness, gt_bboxes,\n                                gt_labels, img_metas, gt_bboxes_ignore)\n    # When there is no truth, the cls loss should be nonzero but there should\n    # be no box loss.\n    empty_cls_loss = empty_gt_losses['loss_cls']\n    empty_box_loss = empty_gt_losses['loss_bbox']\n    assert empty_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert empty_box_loss.item() == 0, (\n        'there should be no box loss when there are no true boxes')\n\n    # When truth is non-empty then both cls and box loss should be nonzero for\n    # random inputs\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n    one_gt_losses = self.loss(cls_scores, bbox_preds, centerness, gt_bboxes,\n                              gt_labels, img_metas, gt_bboxes_ignore)\n    onegt_cls_loss = one_gt_losses['loss_cls']\n    onegt_box_loss = one_gt_losses['loss_bbox']\n    assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n\n\ndef test_anchor_head_loss():\n    \"\"\"\n    Tests anchor head loss when truth is empty and non-empty\n    \"\"\"\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n\n    cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.3,\n                min_pos_iou=0.3,\n                match_low_quality=True,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=256,\n                pos_fraction=0.5,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=False),\n            allowed_border=0,\n            pos_weight=-1,\n            debug=False))\n    self = AnchorHead(num_classes=4, in_channels=1, train_cfg=cfg)\n\n    # Anchor head expects a multiple levels of features per image\n    feat = [\n        torch.rand(1, 1, s // (2**(i + 2)), s // (2**(i + 2)))\n        for i in range(len(self.anchor_generator.strides))\n    ]\n    cls_scores, bbox_preds = self.forward(feat)\n\n    # Test that empty ground truth encourages the network to predict background\n    gt_bboxes = [torch.empty((0, 4))]\n    gt_labels = [torch.LongTensor([])]\n\n    gt_bboxes_ignore = None\n    empty_gt_losses = self.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                                img_metas, gt_bboxes_ignore)\n    # When there is no truth, the cls loss should be nonzero but there should\n    # be no box loss.\n    empty_cls_loss = sum(empty_gt_losses['loss_cls'])\n    empty_box_loss = sum(empty_gt_losses['loss_bbox'])\n    assert empty_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert empty_box_loss.item() == 0, (\n        'there should be no box loss when there are no true boxes')\n\n    # When truth is non-empty then both cls and box loss should be nonzero for\n    # random inputs\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n    one_gt_losses = self.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                              img_metas, gt_bboxes_ignore)\n    onegt_cls_loss = sum(one_gt_losses['loss_cls'])\n    onegt_box_loss = sum(one_gt_losses['loss_bbox'])\n    assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n\n\ndef test_fsaf_head_loss():\n    \"\"\"Tests anchor head loss when truth is empty and non-empty\n    \"\"\"\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n\n    cfg = dict(\n        reg_decoded_bbox=True,\n        anchor_generator=dict(\n            type='AnchorGenerator',\n            octave_base_scale=1,\n            scales_per_octave=1,\n            ratios=[1.0],\n            strides=[8, 16, 32, 64, 128]),\n        bbox_coder=dict(type='TBLRBBoxCoder', normalizer=4.0),\n        loss_cls=dict(\n            type='FocalLoss',\n            use_sigmoid=True,\n            gamma=2.0,\n            alpha=0.25,\n            loss_weight=1.0,\n            reduction='none'),\n        loss_bbox=dict(\n            type='IoULoss', eps=1e-6, loss_weight=1.0, reduction='none'))\n\n    train_cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='CenterRegionAssigner',\n                pos_scale=0.2,\n                neg_scale=0.2,\n                min_pos_iof=0.01),\n            allowed_border=-1,\n            pos_weight=-1,\n            debug=False))\n    head = FSAFHead(num_classes=4, in_channels=1, train_cfg=train_cfg, **cfg)\n    if torch.cuda.is_available():\n        head.cuda()\n        # FSAF head expects a multiple levels of features per image\n        feat = [\n            torch.rand(1, 1, s // (2**(i + 2)), s // (2**(i + 2))).cuda()\n            for i in range(len(head.anchor_generator.strides))\n        ]\n        cls_scores, bbox_preds = head.forward(feat)\n        gt_bboxes_ignore = None\n\n        # When truth is non-empty then both cls and box loss should be nonzero\n        #  for random inputs\n        gt_bboxes = [\n            torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]).cuda(),\n        ]\n        gt_labels = [torch.LongTensor([2]).cuda()]\n        one_gt_losses = head.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                                  img_metas, gt_bboxes_ignore)\n        onegt_cls_loss = sum(one_gt_losses['loss_cls'])\n        onegt_box_loss = sum(one_gt_losses['loss_bbox'])\n        assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n        assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n\n        # Test that empty ground truth encourages the network to predict bkg\n        gt_bboxes = [torch.empty((0, 4)).cuda()]\n        gt_labels = [torch.LongTensor([]).cuda()]\n\n        empty_gt_losses = head.loss(cls_scores, bbox_preds, gt_bboxes,\n                                    gt_labels, img_metas, gt_bboxes_ignore)\n        # When there is no truth, the cls loss should be nonzero but there\n        # should be no box loss.\n        empty_cls_loss = sum(empty_gt_losses['loss_cls'])\n        empty_box_loss = sum(empty_gt_losses['loss_bbox'])\n        assert empty_cls_loss.item() > 0, 'cls loss should be non-zero'\n        assert empty_box_loss.item() == 0, (\n            'there should be no box loss when there are no true boxes')\n\n\ndef test_ga_anchor_head_loss():\n    \"\"\"\n    Tests anchor head loss when truth is empty and non-empty\n    \"\"\"\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n\n    cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.3,\n                min_pos_iou=0.3,\n                match_low_quality=True,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=256,\n                pos_fraction=0.5,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=False),\n            ga_assigner=dict(\n                type='ApproxMaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.3,\n                min_pos_iou=0.3,\n                ignore_iof_thr=-1),\n            ga_sampler=dict(\n                type='RandomSampler',\n                num=256,\n                pos_fraction=0.5,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=False),\n            allowed_border=-1,\n            center_ratio=0.2,\n            ignore_ratio=0.5,\n            pos_weight=-1,\n            debug=False))\n    head = GuidedAnchorHead(num_classes=4, in_channels=4, train_cfg=cfg)\n\n    # Anchor head expects a multiple levels of features per image\n    if torch.cuda.is_available():\n        head.cuda()\n        feat = [\n            torch.rand(1, 4, s // (2**(i + 2)), s // (2**(i + 2))).cuda()\n            for i in range(len(head.approx_anchor_generator.base_anchors))\n        ]\n        cls_scores, bbox_preds, shape_preds, loc_preds = head.forward(feat)\n\n        # Test that empty ground truth encourages the network to predict\n        # background\n        gt_bboxes = [torch.empty((0, 4)).cuda()]\n        gt_labels = [torch.LongTensor([]).cuda()]\n\n        gt_bboxes_ignore = None\n\n        empty_gt_losses = head.loss(cls_scores, bbox_preds, shape_preds,\n                                    loc_preds, gt_bboxes, gt_labels, img_metas,\n                                    gt_bboxes_ignore)\n\n        # When there is no truth, the cls loss should be nonzero but there\n        # should be no box loss.\n        empty_cls_loss = sum(empty_gt_losses['loss_cls'])\n        empty_box_loss = sum(empty_gt_losses['loss_bbox'])\n        assert empty_cls_loss.item() > 0, 'cls loss should be non-zero'\n        assert empty_box_loss.item() == 0, (\n            'there should be no box loss when there are no true boxes')\n\n        # When truth is non-empty then both cls and box loss should be nonzero\n        # for random inputs\n        gt_bboxes = [\n            torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]).cuda(),\n        ]\n        gt_labels = [torch.LongTensor([2]).cuda()]\n        one_gt_losses = head.loss(cls_scores, bbox_preds, shape_preds,\n                                  loc_preds, gt_bboxes, gt_labels, img_metas,\n                                  gt_bboxes_ignore)\n        onegt_cls_loss = sum(one_gt_losses['loss_cls'])\n        onegt_box_loss = sum(one_gt_losses['loss_bbox'])\n        assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n        assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n\n\ndef test_bbox_head_loss():\n    \"\"\"\n    Tests bbox head loss when truth is empty and non-empty\n    \"\"\"\n    self = BBoxHead(in_channels=8, roi_feat_size=3)\n\n    # Dummy proposals\n    proposal_list = [\n        torch.Tensor([[23.6667, 23.8757, 228.6326, 153.8874]]),\n    ]\n\n    target_cfg = mmcv.Config(dict(pos_weight=1))\n\n    # Test bbox loss when truth is empty\n    gt_bboxes = [torch.empty((0, 4))]\n    gt_labels = [torch.LongTensor([])]\n\n    sampling_results = _dummy_bbox_sampling(proposal_list, gt_bboxes,\n                                            gt_labels)\n\n    bbox_targets = self.get_targets(sampling_results, gt_bboxes, gt_labels,\n                                    target_cfg)\n    labels, label_weights, bbox_targets, bbox_weights = bbox_targets\n\n    # Create dummy features \"extracted\" for each sampled bbox\n    num_sampled = sum(len(res.bboxes) for res in sampling_results)\n    rois = bbox2roi([res.bboxes for res in sampling_results])\n    dummy_feats = torch.rand(num_sampled, 8 * 3 * 3)\n    cls_scores, bbox_preds = self.forward(dummy_feats)\n\n    losses = self.loss(cls_scores, bbox_preds, rois, labels, label_weights,\n                       bbox_targets, bbox_weights)\n    assert losses.get('loss_cls', 0) > 0, 'cls-loss should be non-zero'\n    assert losses.get('loss_bbox', 0) == 0, 'empty gt loss should be zero'\n\n    # Test bbox loss when truth is non-empty\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n\n    sampling_results = _dummy_bbox_sampling(proposal_list, gt_bboxes,\n                                            gt_labels)\n    rois = bbox2roi([res.bboxes for res in sampling_results])\n\n    bbox_targets = self.get_targets(sampling_results, gt_bboxes, gt_labels,\n                                    target_cfg)\n    labels, label_weights, bbox_targets, bbox_weights = bbox_targets\n\n    # Create dummy features \"extracted\" for each sampled bbox\n    num_sampled = sum(len(res.bboxes) for res in sampling_results)\n    dummy_feats = torch.rand(num_sampled, 8 * 3 * 3)\n    cls_scores, bbox_preds = self.forward(dummy_feats)\n\n    losses = self.loss(cls_scores, bbox_preds, rois, labels, label_weights,\n                       bbox_targets, bbox_weights)\n    assert losses.get('loss_cls', 0) > 0, 'cls-loss should be non-zero'\n    assert losses.get('loss_bbox', 0) > 0, 'box-loss should be non-zero'\n\n\ndef test_refine_boxes():\n    \"\"\"\n    Mirrors the doctest in\n    ``mmdet.models.bbox_heads.bbox_head.BBoxHead.refine_boxes`` but checks for\n    multiple values of n_roi / n_img.\n    \"\"\"\n    self = BBoxHead(reg_class_agnostic=True)\n\n    test_settings = [\n\n        # Corner case: less rois than images\n        {\n            'n_roi': 2,\n            'n_img': 4,\n            'rng': 34285940\n        },\n\n        # Corner case: no images\n        {\n            'n_roi': 0,\n            'n_img': 0,\n            'rng': 52925222\n        },\n\n        # Corner cases: few images / rois\n        {\n            'n_roi': 1,\n            'n_img': 1,\n            'rng': 1200281\n        },\n        {\n            'n_roi': 2,\n            'n_img': 1,\n            'rng': 1200282\n        },\n        {\n            'n_roi': 2,\n            'n_img': 2,\n            'rng': 1200283\n        },\n        {\n            'n_roi': 1,\n            'n_img': 2,\n            'rng': 1200284\n        },\n\n        # Corner case: no rois few images\n        {\n            'n_roi': 0,\n            'n_img': 1,\n            'rng': 23955860\n        },\n        {\n            'n_roi': 0,\n            'n_img': 2,\n            'rng': 25830516\n        },\n\n        # Corner case: no rois many images\n        {\n            'n_roi': 0,\n            'n_img': 10,\n            'rng': 671346\n        },\n        {\n            'n_roi': 0,\n            'n_img': 20,\n            'rng': 699807\n        },\n\n        # Corner case: cal_similarity num rois and images\n        {\n            'n_roi': 20,\n            'n_img': 20,\n            'rng': 1200238\n        },\n        {\n            'n_roi': 10,\n            'n_img': 20,\n            'rng': 1200238\n        },\n        {\n            'n_roi': 5,\n            'n_img': 5,\n            'rng': 1200238\n        },\n\n        # ----------------------------------\n        # Common case: more rois than images\n        {\n            'n_roi': 100,\n            'n_img': 1,\n            'rng': 337156\n        },\n        {\n            'n_roi': 150,\n            'n_img': 2,\n            'rng': 275898\n        },\n        {\n            'n_roi': 500,\n            'n_img': 5,\n            'rng': 4903221\n        },\n    ]\n\n    for demokw in test_settings:\n        try:\n            n_roi = demokw['n_roi']\n            n_img = demokw['n_img']\n            rng = demokw['rng']\n\n            print(f'Test refine_boxes case: {demokw!r}')\n            tup = _demodata_refine_boxes(n_roi, n_img, rng=rng)\n            rois, labels, bbox_preds, pos_is_gts, img_metas = tup\n            bboxes_list = self.refine_bboxes(rois, labels, bbox_preds,\n                                             pos_is_gts, img_metas)\n            assert len(bboxes_list) == n_img\n            assert sum(map(len, bboxes_list)) <= n_roi\n            assert all(b.shape[1] == 4 for b in bboxes_list)\n        except Exception:\n            print(f'Test failed with demokw={demokw!r}')\n            raise\n\n\ndef _demodata_refine_boxes(n_roi, n_img, rng=0):\n    \"\"\"\n    Create random test data for the\n    ``mmdet.models.bbox_heads.bbox_head.BBoxHead.refine_boxes`` method\n    \"\"\"\n    import numpy as np\n    from mmdet.core.bbox.demodata import random_boxes\n    from mmdet.core.bbox.demodata import ensure_rng\n    try:\n        import kwarray\n    except ImportError:\n        import pytest\n        pytest.skip('kwarray is required for this test')\n    scale = 512\n    rng = ensure_rng(rng)\n    img_metas = [{'img_shape': (scale, scale)} for _ in range(n_img)]\n    # Create rois in the expected format\n    roi_boxes = random_boxes(n_roi, scale=scale, rng=rng)\n    if n_img == 0:\n        assert n_roi == 0, 'cannot have any rois if there are no images'\n        img_ids = torch.empty((0, ), dtype=torch.long)\n        roi_boxes = torch.empty((0, 4), dtype=torch.float32)\n    else:\n        img_ids = rng.randint(0, n_img, (n_roi, ))\n        img_ids = torch.from_numpy(img_ids)\n    rois = torch.cat([img_ids[:, None].float(), roi_boxes], dim=1)\n    # Create other args\n    labels = rng.randint(0, 2, (n_roi, ))\n    labels = torch.from_numpy(labels).long()\n    bbox_preds = random_boxes(n_roi, scale=scale, rng=rng)\n    # For each image, pretend random positive boxes are gts\n    is_label_pos = (labels.numpy() > 0).astype(np.int)\n    lbl_per_img = kwarray.group_items(is_label_pos, img_ids.numpy())\n    pos_per_img = [sum(lbl_per_img.get(gid, [])) for gid in range(n_img)]\n    # randomly generate with numpy then sort with torch\n    _pos_is_gts = [\n        rng.randint(0, 2, (npos, )).astype(np.uint8) for npos in pos_per_img\n    ]\n    pos_is_gts = [\n        torch.from_numpy(p).sort(descending=True)[0] for p in _pos_is_gts\n    ]\n    return rois, labels, bbox_preds, pos_is_gts, img_metas\n\n\ndef test_mask_head_loss():\n    \"\"\"\n    Test mask head loss when mask target is empty\n    \"\"\"\n    self = FCNMaskHead(\n        num_convs=1,\n        roi_feat_size=6,\n        in_channels=8,\n        conv_out_channels=8,\n        num_classes=8)\n\n    # Dummy proposals\n    proposal_list = [\n        torch.Tensor([[23.6667, 23.8757, 228.6326, 153.8874]]),\n    ]\n\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n    sampling_results = _dummy_bbox_sampling(proposal_list, gt_bboxes,\n                                            gt_labels)\n\n    # create dummy mask\n    import numpy as np\n    from mmdet.core import BitmapMasks\n    dummy_mask = np.random.randint(0, 2, (1, 160, 240), dtype=np.uint8)\n    gt_masks = [BitmapMasks(dummy_mask, 160, 240)]\n\n    # create dummy train_cfg\n    train_cfg = mmcv.Config(dict(mask_size=12, mask_thr_binary=0.5))\n\n    # Create dummy features \"extracted\" for each sampled bbox\n    num_sampled = sum(len(res.bboxes) for res in sampling_results)\n    dummy_feats = torch.rand(num_sampled, 8, 6, 6)\n\n    mask_pred = self.forward(dummy_feats)\n    mask_targets = self.get_targets(sampling_results, gt_masks, train_cfg)\n    pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results])\n    loss_mask = self.loss(mask_pred, mask_targets, pos_labels)\n\n    onegt_mask_loss = sum(loss_mask['loss_mask'])\n    assert onegt_mask_loss.item() > 0, 'mask loss should be non-zero'\n\n    # test mask_iou_head\n    mask_iou_head = MaskIoUHead(\n        num_convs=1,\n        num_fcs=1,\n        roi_feat_size=6,\n        in_channels=8,\n        conv_out_channels=8,\n        fc_out_channels=8,\n        num_classes=8)\n\n    pos_mask_pred = mask_pred[range(mask_pred.size(0)), pos_labels]\n    mask_iou_pred = mask_iou_head(dummy_feats, pos_mask_pred)\n    pos_mask_iou_pred = mask_iou_pred[range(mask_iou_pred.size(0)), pos_labels]\n\n    mask_iou_targets = mask_iou_head.get_targets(sampling_results, gt_masks,\n                                                 pos_mask_pred, mask_targets,\n                                                 train_cfg)\n    loss_mask_iou = mask_iou_head.loss(pos_mask_iou_pred, mask_iou_targets)\n    onegt_mask_iou_loss = loss_mask_iou['loss_mask_iou'].sum()\n    assert onegt_mask_iou_loss.item() >= 0\n\n\ndef _dummy_bbox_sampling(proposal_list, gt_bboxes, gt_labels):\n    \"\"\"\n    Create sample results that can be passed to BBoxHead.get_targets\n    \"\"\"\n    num_imgs = 1\n    feat = torch.rand(1, 1, 3, 3)\n    assign_config = dict(\n        type='MaxIoUAssigner',\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        min_pos_iou=0.5,\n        ignore_iof_thr=-1)\n    sampler_config = dict(\n        type='RandomSampler',\n        num=512,\n        pos_fraction=0.25,\n        neg_pos_ub=-1,\n        add_gt_as_proposals=True)\n    bbox_assigner = build_assigner(assign_config)\n    bbox_sampler = build_sampler(sampler_config)\n    gt_bboxes_ignore = [None for _ in range(num_imgs)]\n    sampling_results = []\n    for i in range(num_imgs):\n        assign_result = bbox_assigner.assign(proposal_list[i], gt_bboxes[i],\n                                             gt_bboxes_ignore[i], gt_labels[i])\n        sampling_result = bbox_sampler.sample(\n            assign_result,\n            proposal_list[i],\n            gt_bboxes[i],\n            gt_labels[i],\n            feats=feat)\n        sampling_results.append(sampling_result)\n\n    return sampling_results\n"
  },
  {
    "path": "code/tests/test_losses.py",
    "content": "import pytest\nimport torch\n\n\ndef test_ce_loss():\n    from mmdet.models import build_loss\n\n    # use_mask and use_sigmoid cannot be true at the same time\n    with pytest.raises(AssertionError):\n        loss_cfg = dict(\n            type='CrossEntropyLoss',\n            use_mask=True,\n            use_sigmoid=True,\n            loss_weight=1.0)\n        build_loss(loss_cfg)\n\n    # test loss with class weights\n    loss_cls_cfg = dict(\n        type='CrossEntropyLoss',\n        use_sigmoid=False,\n        class_weight=[0.8, 0.2],\n        loss_weight=1.0)\n    loss_cls = build_loss(loss_cls_cfg)\n    fake_pred = torch.Tensor([[100, -100]])\n    fake_label = torch.Tensor([1]).long()\n    assert torch.allclose(loss_cls(fake_pred, fake_label), torch.tensor(40.))\n\n    loss_cls_cfg = dict(\n        type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)\n    loss_cls = build_loss(loss_cls_cfg)\n    assert torch.allclose(loss_cls(fake_pred, fake_label), torch.tensor(200.))\n"
  },
  {
    "path": "code/tests/test_masks.py",
    "content": "import numpy as np\nimport pytest\nimport torch\n\nfrom mmdet.core import BitmapMasks, PolygonMasks\n\n\ndef dummy_raw_bitmap_masks(size):\n    \"\"\"\n    Args:\n        size (tuple): expected shape of dummy masks, (H, W) or (N, H, W)\n\n    Return:\n        ndarray: dummy mask\n    \"\"\"\n    return np.random.randint(0, 2, size, dtype=np.uint8)\n\n\ndef dummy_raw_polygon_masks(size):\n    \"\"\"\n    Args:\n        size (tuple): expected shape of dummy masks, (N, H, W)\n\n    Return:\n        list[list[ndarray]]: dummy mask\n    \"\"\"\n    num_obj, heigt, width = size\n    polygons = []\n    for _ in range(num_obj):\n        num_points = np.random.randint(5) * 2 + 6\n        polygons.append([np.random.uniform(0, min(heigt, width), num_points)])\n    return polygons\n\n\ndef dummy_bboxes(num, max_height, max_width):\n    x1y1 = np.random.randint(0, min(max_height // 2, max_width // 2), (num, 2))\n    wh = np.random.randint(0, min(max_height // 2, max_width // 2), (num, 2))\n    x2y2 = x1y1 + wh\n    return np.concatenate([x1y1, x2y2], axis=1).squeeze().astype(np.float32)\n\n\ndef test_bitmap_mask_init():\n    # init with empty ndarray masks\n    raw_masks = np.empty((0, 28, 28), dtype=np.uint8)\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    assert len(bitmap_masks) == 0\n    assert bitmap_masks.height == 28\n    assert bitmap_masks.width == 28\n\n    # init with empty list masks\n    raw_masks = []\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    assert len(bitmap_masks) == 0\n    assert bitmap_masks.height == 28\n    assert bitmap_masks.width == 28\n\n    # init with ndarray masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    assert len(bitmap_masks) == 3\n    assert bitmap_masks.height == 28\n    assert bitmap_masks.width == 28\n\n    # init with list masks contain 3 instances\n    raw_masks = [dummy_raw_bitmap_masks((28, 28)) for _ in range(3)]\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    assert len(bitmap_masks) == 3\n    assert bitmap_masks.height == 28\n    assert bitmap_masks.width == 28\n\n    # init with raw masks of unsupported type\n    with pytest.raises(AssertionError):\n        raw_masks = [[dummy_raw_bitmap_masks((28, 28))]]\n        BitmapMasks(raw_masks, 28, 28)\n\n\ndef test_bitmap_mask_rescale():\n    # rescale with empty bitmap masks\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    rescaled_masks = bitmap_masks.rescale((56, 72))\n    assert len(rescaled_masks) == 0\n    assert rescaled_masks.height == 56\n    assert rescaled_masks.width == 56\n\n    # rescale with bitmap masks contain 1 instances\n    raw_masks = np.array([[[1, 0, 0, 0], [0, 1, 0, 1]]])\n    bitmap_masks = BitmapMasks(raw_masks, 2, 4)\n    rescaled_masks = bitmap_masks.rescale((8, 8))\n    assert len(rescaled_masks) == 1\n    assert rescaled_masks.height == 4\n    assert rescaled_masks.width == 8\n    truth = np.array([[[1, 1, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0],\n                       [0, 0, 1, 1, 0, 0, 1, 1], [0, 0, 1, 1, 0, 0, 1, 1]]])\n    assert (rescaled_masks.masks == truth).all()\n\n\ndef test_bitmap_mask_resize():\n    # resize with empty bitmap masks\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    resized_masks = bitmap_masks.resize((56, 72))\n    assert len(resized_masks) == 0\n    assert resized_masks.height == 56\n    assert resized_masks.width == 72\n\n    # resize with bitmap masks contain 1 instances\n    raw_masks = np.diag(np.ones(4, dtype=np.uint8))[np.newaxis, ...]\n    bitmap_masks = BitmapMasks(raw_masks, 4, 4)\n    resized_masks = bitmap_masks.resize((8, 8))\n    assert len(resized_masks) == 1\n    assert resized_masks.height == 8\n    assert resized_masks.width == 8\n    truth = np.array([[[1, 1, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0],\n                       [0, 0, 1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0],\n                       [0, 0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 1, 1, 0, 0],\n                       [0, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 1, 1]]])\n    assert (resized_masks.masks == truth).all()\n\n\ndef test_bitmap_mask_flip():\n    # flip with empty bitmap masks\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    flipped_masks = bitmap_masks.flip(flip_direction='horizontal')\n    assert len(flipped_masks) == 0\n    assert flipped_masks.height == 28\n    assert flipped_masks.width == 28\n\n    # horizontally flip with bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    flipped_masks = bitmap_masks.flip(flip_direction='horizontal')\n    flipped_flipped_masks = flipped_masks.flip(flip_direction='horizontal')\n    assert flipped_masks.masks.shape == (3, 28, 28)\n    assert (bitmap_masks.masks == flipped_flipped_masks.masks).all()\n    assert (flipped_masks.masks == raw_masks[:, :, ::-1]).all()\n\n    # vertically flip with bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    flipped_masks = bitmap_masks.flip(flip_direction='vertical')\n    flipped_flipped_masks = flipped_masks.flip(flip_direction='vertical')\n    assert len(flipped_masks) == 3\n    assert flipped_masks.height == 28\n    assert flipped_masks.width == 28\n    assert (bitmap_masks.masks == flipped_flipped_masks.masks).all()\n    assert (flipped_masks.masks == raw_masks[:, ::-1, :]).all()\n\n\ndef test_bitmap_mask_pad():\n    # pad with empty bitmap masks\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    padded_masks = bitmap_masks.pad((56, 56))\n    assert len(padded_masks) == 0\n    assert padded_masks.height == 56\n    assert padded_masks.width == 56\n\n    # pad with bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    padded_masks = bitmap_masks.pad((56, 56))\n    assert len(padded_masks) == 3\n    assert padded_masks.height == 56\n    assert padded_masks.width == 56\n    assert (padded_masks.masks[:, 28:, 28:] == 0).all()\n\n\ndef test_bitmap_mask_crop():\n    # crop with empty bitmap masks\n    dummy_bbox = np.array([0, 10, 10, 27], dtype=np.int)\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    cropped_masks = bitmap_masks.crop(dummy_bbox)\n    assert len(cropped_masks) == 0\n    assert cropped_masks.height == 17\n    assert cropped_masks.width == 10\n\n    # crop with bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    cropped_masks = bitmap_masks.crop(dummy_bbox)\n    assert len(cropped_masks) == 3\n    assert cropped_masks.height == 17\n    assert cropped_masks.width == 10\n    x1, y1, x2, y2 = dummy_bbox\n    assert (cropped_masks.masks == raw_masks[:, y1:y2, x1:x2]).all()\n\n    # crop with invalid bbox\n    with pytest.raises(AssertionError):\n        dummy_bbox = dummy_bboxes(2, 28, 28)\n        bitmap_masks.crop(dummy_bbox)\n\n\ndef test_bitmap_mask_crop_and_resize():\n    dummy_bbox = dummy_bboxes(5, 28, 28)\n    inds = np.random.randint(0, 3, (5, ))\n\n    # crop and resize with empty bitmap masks\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    cropped_resized_masks = bitmap_masks.crop_and_resize(\n        dummy_bbox, (56, 56), inds)\n    assert len(cropped_resized_masks) == 0\n    assert cropped_resized_masks.height == 56\n    assert cropped_resized_masks.width == 56\n\n    # crop and resize with bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    cropped_resized_masks = bitmap_masks.crop_and_resize(\n        dummy_bbox, (56, 56), inds)\n    assert len(cropped_resized_masks) == 5\n    assert cropped_resized_masks.height == 56\n    assert cropped_resized_masks.width == 56\n\n\ndef test_bitmap_mask_expand():\n    # expand with empty bitmap masks\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    expanded_masks = bitmap_masks.expand(56, 56, 12, 14)\n    assert len(expanded_masks) == 0\n    assert expanded_masks.height == 56\n    assert expanded_masks.width == 56\n\n    # expand with bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    expanded_masks = bitmap_masks.expand(56, 56, 12, 14)\n    assert len(expanded_masks) == 3\n    assert expanded_masks.height == 56\n    assert expanded_masks.width == 56\n    assert (expanded_masks.masks[:, :12, :14] == 0).all()\n    assert (expanded_masks.masks[:, 12 + 28:, 14 + 28:] == 0).all()\n\n\ndef test_bitmap_mask_area():\n    # area of empty bitmap mask\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    assert bitmap_masks.areas.sum() == 0\n\n    # area of bitmap masks contain 3 instances\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    areas = bitmap_masks.areas\n    assert len(areas) == 3\n    assert (areas == raw_masks.sum((1, 2))).all()\n\n\ndef test_bitmap_mask_to_ndarray():\n    # empty bitmap masks to ndarray\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    ndarray_masks = bitmap_masks.to_ndarray()\n    assert isinstance(ndarray_masks, np.ndarray)\n    assert ndarray_masks.shape == (0, 28, 28)\n\n    # bitmap masks contain 3 instances to ndarray\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    ndarray_masks = bitmap_masks.to_ndarray()\n    assert isinstance(ndarray_masks, np.ndarray)\n    assert ndarray_masks.shape == (3, 28, 28)\n    assert (ndarray_masks == raw_masks).all()\n\n\ndef test_bitmap_mask_to_tensor():\n    # empty bitmap masks to tensor\n    raw_masks = dummy_raw_bitmap_masks((0, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    tensor_masks = bitmap_masks.to_tensor(dtype=torch.uint8, device='cpu')\n    assert isinstance(tensor_masks, torch.Tensor)\n    assert tensor_masks.shape == (0, 28, 28)\n\n    # bitmap masks contain 3 instances to tensor\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    tensor_masks = bitmap_masks.to_tensor(dtype=torch.uint8, device='cpu')\n    assert isinstance(tensor_masks, torch.Tensor)\n    assert tensor_masks.shape == (3, 28, 28)\n    assert (tensor_masks.numpy() == raw_masks).all()\n\n\ndef test_bitmap_mask_index():\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    assert (bitmap_masks[0].masks == raw_masks[0]).all()\n    assert (bitmap_masks[range(2)].masks == raw_masks[range(2)]).all()\n\n\ndef test_bitmap_mask_iter():\n    raw_masks = dummy_raw_bitmap_masks((3, 28, 28))\n    bitmap_masks = BitmapMasks(raw_masks, 28, 28)\n    for i, bitmap_mask in enumerate(bitmap_masks):\n        assert bitmap_mask.shape == (28, 28)\n        assert (bitmap_mask == raw_masks[i]).all()\n\n\ndef test_polygon_mask_init():\n    # init with empty masks\n    raw_masks = []\n    polygon_masks = BitmapMasks(raw_masks, 28, 28)\n    assert len(polygon_masks) == 0\n    assert polygon_masks.height == 28\n    assert polygon_masks.width == 28\n\n    # init with masks contain 3 instances\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    assert isinstance(polygon_masks.masks, list)\n    assert isinstance(polygon_masks.masks[0], list)\n    assert isinstance(polygon_masks.masks[0][0], np.ndarray)\n    assert len(polygon_masks) == 3\n    assert polygon_masks.height == 28\n    assert polygon_masks.width == 28\n    assert polygon_masks.to_ndarray().shape == (3, 28, 28)\n\n    # init with raw masks of unsupported type\n    with pytest.raises(AssertionError):\n        raw_masks = [[[]]]\n        PolygonMasks(raw_masks, 28, 28)\n\n        raw_masks = [dummy_raw_polygon_masks((3, 28, 28))]\n        PolygonMasks(raw_masks, 28, 28)\n\n\ndef test_polygon_mask_rescale():\n    # rescale with empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    rescaled_masks = polygon_masks.rescale((56, 72))\n    assert len(rescaled_masks) == 0\n    assert rescaled_masks.height == 56\n    assert rescaled_masks.width == 56\n    assert rescaled_masks.to_ndarray().shape == (0, 56, 56)\n\n    # rescale with polygon masks contain 3 instances\n    raw_masks = [[np.array([1, 1, 3, 1, 4, 3, 2, 4, 1, 3], dtype=np.float)]]\n    polygon_masks = PolygonMasks(raw_masks, 5, 5)\n    rescaled_masks = polygon_masks.rescale((12, 10))\n    assert len(rescaled_masks) == 1\n    assert rescaled_masks.height == 10\n    assert rescaled_masks.width == 10\n    assert rescaled_masks.to_ndarray().shape == (1, 10, 10)\n    truth = np.array(\n        [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n         [0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],\n         [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],\n         [0, 0, 0, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],\n         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n        np.uint8)\n    assert (rescaled_masks.to_ndarray() == truth).all()\n\n\ndef test_polygon_mask_resize():\n    # resize with empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    resized_masks = polygon_masks.resize((56, 72))\n    assert len(resized_masks) == 0\n    assert resized_masks.height == 56\n    assert resized_masks.width == 72\n    assert resized_masks.to_ndarray().shape == (0, 56, 72)\n\n    # resize with polygon masks contain 1 instance 1 part\n    raw_masks1 = [[np.array([1, 1, 3, 1, 4, 3, 2, 4, 1, 3], dtype=np.float)]]\n    polygon_masks1 = PolygonMasks(raw_masks1, 5, 5)\n    resized_masks1 = polygon_masks1.resize((10, 10))\n    assert len(resized_masks1) == 1\n    assert resized_masks1.height == 10\n    assert resized_masks1.width == 10\n    assert resized_masks1.to_ndarray().shape == (1, 10, 10)\n    truth1 = np.array(\n        [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n         [0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],\n         [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],\n         [0, 0, 0, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],\n         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n        np.uint8)\n    assert (resized_masks1.to_ndarray() == truth1).all()\n\n    # resize with polygon masks contain 1 instance 2 part\n    raw_masks2 = [[\n        np.array([0., 0., 1., 0., 1., 1.]),\n        np.array([1., 1., 2., 1., 2., 2., 1., 2.])\n    ]]\n    polygon_masks2 = PolygonMasks(raw_masks2, 3, 3)\n    resized_masks2 = polygon_masks2.resize((6, 6))\n    assert len(resized_masks2) == 1\n    assert resized_masks2.height == 6\n    assert resized_masks2.width == 6\n    assert resized_masks2.to_ndarray().shape == (1, 6, 6)\n    truth2 = np.array(\n        [[0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0],\n         [0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]], np.uint8)\n    assert (resized_masks2.to_ndarray() == truth2).all()\n\n    # resize with polygon masks contain 2 instances\n    raw_masks3 = [raw_masks1[0], raw_masks2[0]]\n    polygon_masks3 = PolygonMasks(raw_masks3, 5, 5)\n    resized_masks3 = polygon_masks3.resize((10, 10))\n    assert len(resized_masks3) == 2\n    assert resized_masks3.height == 10\n    assert resized_masks3.width == 10\n    assert resized_masks3.to_ndarray().shape == (2, 10, 10)\n    truth3 = np.stack([truth1, np.pad(truth2, ((0, 4), (0, 4)), 'constant')])\n    assert (resized_masks3.to_ndarray() == truth3).all()\n\n\ndef test_polygon_mask_flip():\n    # flip with empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    flipped_masks = polygon_masks.flip(flip_direction='horizontal')\n    assert len(flipped_masks) == 0\n    assert flipped_masks.height == 28\n    assert flipped_masks.width == 28\n    assert flipped_masks.to_ndarray().shape == (0, 28, 28)\n\n    # TODO: fixed flip correctness checking after v2.0_coord is merged\n    # horizontally flip with polygon masks contain 3 instances\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    flipped_masks = polygon_masks.flip(flip_direction='horizontal')\n    flipped_flipped_masks = flipped_masks.flip(flip_direction='horizontal')\n    assert len(flipped_masks) == 3\n    assert flipped_masks.height == 28\n    assert flipped_masks.width == 28\n    assert flipped_masks.to_ndarray().shape == (3, 28, 28)\n    assert (polygon_masks.to_ndarray() == flipped_flipped_masks.to_ndarray()\n            ).all()\n\n    # vertically flip with polygon masks contain 3 instances\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    flipped_masks = polygon_masks.flip(flip_direction='vertical')\n    flipped_flipped_masks = flipped_masks.flip(flip_direction='vertical')\n    assert len(flipped_masks) == 3\n    assert flipped_masks.height == 28\n    assert flipped_masks.width == 28\n    assert flipped_masks.to_ndarray().shape == (3, 28, 28)\n    assert (polygon_masks.to_ndarray() == flipped_flipped_masks.to_ndarray()\n            ).all()\n\n\ndef test_polygon_mask_crop():\n    dummy_bbox = np.array([0, 10, 10, 27], dtype=np.int)\n    # crop with empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    cropped_masks = polygon_masks.crop(dummy_bbox)\n    assert len(cropped_masks) == 0\n    assert cropped_masks.height == 17\n    assert cropped_masks.width == 10\n    assert cropped_masks.to_ndarray().shape == (0, 17, 10)\n\n    # crop with polygon masks contain 1 instances\n    raw_masks = [[np.array([1., 3., 5., 1., 5., 6., 1, 6])]]\n    polygon_masks = PolygonMasks(raw_masks, 7, 7)\n    bbox = np.array([0, 0, 3, 4])\n    cropped_masks = polygon_masks.crop(bbox)\n    assert len(cropped_masks) == 1\n    assert cropped_masks.height == 4\n    assert cropped_masks.width == 3\n    assert cropped_masks.to_ndarray().shape == (1, 4, 3)\n    truth = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 1], [0, 1, 1]])\n    assert (cropped_masks.to_ndarray() == truth).all()\n\n    # crop with invalid bbox\n    with pytest.raises(AssertionError):\n        dummy_bbox = dummy_bboxes(2, 28, 28)\n        polygon_masks.crop(dummy_bbox)\n\n\ndef test_polygon_mask_pad():\n    # pad with empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    padded_masks = polygon_masks.pad((56, 56))\n    assert len(padded_masks) == 0\n    assert padded_masks.height == 56\n    assert padded_masks.width == 56\n    assert padded_masks.to_ndarray().shape == (0, 56, 56)\n\n    # pad with polygon masks contain 3 instances\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    padded_masks = polygon_masks.pad((56, 56))\n    assert len(padded_masks) == 3\n    assert padded_masks.height == 56\n    assert padded_masks.width == 56\n    assert padded_masks.to_ndarray().shape == (3, 56, 56)\n    assert (padded_masks.to_ndarray()[:, 28:, 28:] == 0).all()\n\n\ndef test_polygon_mask_expand():\n    with pytest.raises(NotImplementedError):\n        raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n        polygon_masks = PolygonMasks(raw_masks, 28, 28)\n        polygon_masks.expand(56, 56, 10, 17)\n\n\ndef test_polygon_mask_crop_and_resize():\n    dummy_bbox = dummy_bboxes(5, 28, 28)\n    inds = np.random.randint(0, 3, (5, ))\n\n    # crop and resize with empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    cropped_resized_masks = polygon_masks.crop_and_resize(\n        dummy_bbox, (56, 56), inds)\n    assert len(cropped_resized_masks) == 0\n    assert cropped_resized_masks.height == 56\n    assert cropped_resized_masks.width == 56\n    assert cropped_resized_masks.to_ndarray().shape == (0, 56, 56)\n\n    # crop and resize with polygon masks contain 3 instances\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    cropped_resized_masks = polygon_masks.crop_and_resize(\n        dummy_bbox, (56, 56), inds)\n    assert len(cropped_resized_masks) == 5\n    assert cropped_resized_masks.height == 56\n    assert cropped_resized_masks.width == 56\n    assert cropped_resized_masks.to_ndarray().shape == (5, 56, 56)\n\n\ndef test_polygon_mask_area():\n    # area of empty polygon masks\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    assert polygon_masks.areas.sum() == 0\n\n    # area of polygon masks contain 1 instance\n    # here we hack a case that the gap between the area of bitmap and polygon\n    # is minor\n    raw_masks = [[np.array([1, 1, 5, 1, 3, 4])]]\n    polygon_masks = PolygonMasks(raw_masks, 6, 6)\n    polygon_area = polygon_masks.areas\n    bitmap_area = polygon_masks.to_bitmap().areas\n    assert len(polygon_area) == 1\n    assert np.isclose(polygon_area, bitmap_area).all()\n\n\ndef test_polygon_mask_to_bitmap():\n    # polygon masks contain 3 instances to bitmap\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    bitmap_masks = polygon_masks.to_bitmap()\n    assert (polygon_masks.to_ndarray() == bitmap_masks.to_ndarray()).all()\n\n\ndef test_polygon_mask_to_ndarray():\n    # empty polygon masks to ndarray\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    ndarray_masks = polygon_masks.to_ndarray()\n    assert isinstance(ndarray_masks, np.ndarray)\n    assert ndarray_masks.shape == (0, 28, 28)\n\n    # polygon masks contain 3 instances to ndarray\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    ndarray_masks = polygon_masks.to_ndarray()\n    assert isinstance(ndarray_masks, np.ndarray)\n    assert ndarray_masks.shape == (3, 28, 28)\n\n\ndef test_polygon_to_tensor():\n    # empty polygon masks to tensor\n    raw_masks = dummy_raw_polygon_masks((0, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    tensor_masks = polygon_masks.to_tensor(dtype=torch.uint8, device='cpu')\n    assert isinstance(tensor_masks, torch.Tensor)\n    assert tensor_masks.shape == (0, 28, 28)\n\n    # polygon masks contain 3 instances to tensor\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    tensor_masks = polygon_masks.to_tensor(dtype=torch.uint8, device='cpu')\n    assert isinstance(tensor_masks, torch.Tensor)\n    assert tensor_masks.shape == (3, 28, 28)\n    assert (tensor_masks.numpy() == polygon_masks.to_ndarray()).all()\n\n\ndef test_polygon_mask_index():\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    # index by integer\n    polygon_masks[0]\n    # index by list\n    polygon_masks[[0, 1]]\n    # index by ndarray\n    polygon_masks[np.asarray([0, 1])]\n    with pytest.raises(ValueError):\n        # invalid index\n        polygon_masks[torch.Tensor([1, 2])]\n\n\ndef test_polygon_mask_iter():\n    raw_masks = dummy_raw_polygon_masks((3, 28, 28))\n    polygon_masks = PolygonMasks(raw_masks, 28, 28)\n    for i, polygon_mask in enumerate(polygon_masks):\n        assert np.equal(polygon_mask, raw_masks[i]).all()\n"
  },
  {
    "path": "code/tests/test_necks.py",
    "content": "import pytest\nimport torch\nfrom torch.nn.modules.batchnorm import _BatchNorm\n\nfrom mmdet.models.necks import FPN\n\n\ndef test_fpn():\n    \"\"\"Tests fpn \"\"\"\n    s = 64\n    in_channels = [8, 16, 32, 64]\n    feat_sizes = [s // 2**i for i in range(4)]  # [64, 32, 16, 8]\n    out_channels = 8\n    # `num_outs` is not equal to len(in_channels) - start_level\n    with pytest.raises(AssertionError):\n        FPN(in_channels=in_channels,\n            out_channels=out_channels,\n            start_level=1,\n            num_outs=2)\n\n    # `end_level` is larger than len(in_channels) - 1\n    with pytest.raises(AssertionError):\n        FPN(in_channels=in_channels,\n            out_channels=out_channels,\n            start_level=1,\n            end_level=4,\n            num_outs=2)\n\n    # `num_outs` is not equal to end_level - start_level\n    with pytest.raises(AssertionError):\n        FPN(in_channels=in_channels,\n            out_channels=out_channels,\n            start_level=1,\n            end_level=3,\n            num_outs=1)\n\n    # Invalid `add_extra_convs` option\n    with pytest.raises(AssertionError):\n        FPN(in_channels=in_channels,\n            out_channels=out_channels,\n            start_level=1,\n            add_extra_convs='on_xxx',\n            num_outs=5)\n\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        start_level=1,\n        add_extra_convs=True,\n        num_outs=5)\n\n    # FPN expects a multiple levels of features per image\n    feats = [\n        torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])\n        for i in range(len(in_channels))\n    ]\n    outs = fpn_model(feats)\n    assert fpn_model.add_extra_convs == 'on_input'\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # Tests for fpn with no extra convs (pooling is used instead)\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        start_level=1,\n        add_extra_convs=False,\n        num_outs=5)\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    assert not fpn_model.add_extra_convs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # Tests for fpn with lateral bns\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        start_level=1,\n        add_extra_convs=True,\n        no_norm_on_lateral=False,\n        norm_cfg=dict(type='BN', requires_grad=True),\n        num_outs=5)\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    assert fpn_model.add_extra_convs == 'on_input'\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n    bn_exist = False\n    for m in fpn_model.modules():\n        if isinstance(m, _BatchNorm):\n            bn_exist = True\n    assert bn_exist\n\n    # Bilinear upsample\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        start_level=1,\n        add_extra_convs=True,\n        upsample_cfg=dict(mode='bilinear', align_corners=True),\n        num_outs=5)\n    fpn_model(feats)\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    assert fpn_model.add_extra_convs == 'on_input'\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # Scale factor instead of fixed upsample size upsample\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        start_level=1,\n        add_extra_convs=True,\n        upsample_cfg=dict(scale_factor=2),\n        num_outs=5)\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # Extra convs source is 'inputs'\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        add_extra_convs='on_input',\n        start_level=1,\n        num_outs=5)\n    assert fpn_model.add_extra_convs == 'on_input'\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # Extra convs source is 'laterals'\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        add_extra_convs='on_lateral',\n        start_level=1,\n        num_outs=5)\n    assert fpn_model.add_extra_convs == 'on_lateral'\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # Extra convs source is 'outputs'\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        add_extra_convs='on_output',\n        start_level=1,\n        num_outs=5)\n    assert fpn_model.add_extra_convs == 'on_output'\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # extra_convs_on_inputs=False is equal to extra convs source is 'on_output'\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        add_extra_convs=True,\n        extra_convs_on_inputs=False,\n        start_level=1,\n        num_outs=5,\n    )\n    assert fpn_model.add_extra_convs == 'on_output'\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n\n    # extra_convs_on_inputs=True is equal to extra convs source is 'on_input'\n    fpn_model = FPN(\n        in_channels=in_channels,\n        out_channels=out_channels,\n        add_extra_convs=True,\n        extra_convs_on_inputs=True,\n        start_level=1,\n        num_outs=5,\n    )\n    assert fpn_model.add_extra_convs == 'on_input'\n    outs = fpn_model(feats)\n    assert len(outs) == fpn_model.num_outs\n    for i in range(fpn_model.num_outs):\n        outs[i].shape[1] == out_channels\n        outs[i].shape[2] == outs[i].shape[3] == s // (2**i)\n"
  },
  {
    "path": "code/tests/test_ops/test_corner_pool.py",
    "content": "\"\"\"\nCommandLine:\n    pytest tests/test_corner_pool.py\n\"\"\"\nimport pytest\nimport torch\n\nfrom mmdet.ops import CornerPool\n\n\ndef test_corner_pool_device_and_dtypes_cpu():\n    \"\"\"\n    CommandLine:\n        xdoctest -m tests/test_corner_pool.py \\\n            test_corner_pool_device_and_dtypes_cpu\n    \"\"\"\n    with pytest.raises(AssertionError):\n        # pool mode must in ['bottom', 'left', 'right', 'top']\n        pool = CornerPool('corner')\n\n    lr_tensor = torch.tensor([[[[0, 0, 0, 0, 0], [2, 1, 3, 0, 2],\n                                [5, 4, 1, 1, 6], [0, 0, 0, 0, 0],\n                                [0, 0, 0, 0, 0]]]])\n    tb_tensor = torch.tensor([[[[0, 3, 1, 0, 0], [0, 1, 1, 0, 0],\n                                [0, 3, 4, 0, 0], [0, 2, 2, 0, 0],\n                                [0, 0, 2, 0, 0]]]])\n    # Left Pool\n    left_answer = torch.tensor([[[[0, 0, 0, 0, 0], [3, 3, 3, 2, 2],\n                                  [6, 6, 6, 6, 6], [0, 0, 0, 0, 0],\n                                  [0, 0, 0, 0, 0]]]])\n    pool = CornerPool('left')\n    left_tensor = pool(lr_tensor)\n    assert left_tensor.type() == lr_tensor.type()\n    assert torch.equal(left_tensor, left_answer)\n    # Right Pool\n    right_answer = torch.tensor([[[[0, 0, 0, 0, 0], [2, 2, 3, 3, 3],\n                                   [5, 5, 5, 5, 6], [0, 0, 0, 0, 0],\n                                   [0, 0, 0, 0, 0]]]])\n    pool = CornerPool('right')\n    right_tensor = pool(lr_tensor)\n    assert right_tensor.type() == lr_tensor.type()\n    assert torch.equal(right_tensor, right_answer)\n    # Top Pool\n    top_answer = torch.tensor([[[[0, 3, 4, 0, 0], [0, 3, 4, 0, 0],\n                                 [0, 3, 4, 0, 0], [0, 2, 2, 0, 0],\n                                 [0, 0, 2, 0, 0]]]])\n    pool = CornerPool('top')\n    top_tensor = pool(tb_tensor)\n    assert top_tensor.type() == tb_tensor.type()\n    assert torch.equal(top_tensor, top_answer)\n    # Bottom Pool\n    bottom_answer = torch.tensor([[[[0, 3, 1, 0, 0], [0, 3, 1, 0, 0],\n                                    [0, 3, 4, 0, 0], [0, 3, 4, 0, 0],\n                                    [0, 3, 4, 0, 0]]]])\n    pool = CornerPool('bottom')\n    bottom_tensor = pool(tb_tensor)\n    assert bottom_tensor.type() == tb_tensor.type()\n    assert torch.equal(bottom_tensor, bottom_answer)\n"
  },
  {
    "path": "code/tests/test_ops/test_merge_cells.py",
    "content": "\"\"\"\nCommandLine:\n    pytest tests/test_merge_cells.py\n\"\"\"\nimport torch\nimport torch.nn.functional as F\n\nfrom mmdet.ops.merge_cells import (BaseMergeCell, ConcatCell,\n                                   GlobalPoolingCell, SumCell)\n\n\ndef test_sum_cell():\n    inputs_x = torch.randn([2, 256, 32, 32])\n    inputs_y = torch.randn([2, 256, 16, 16])\n    sum_cell = SumCell(256, 256)\n    output = sum_cell(inputs_x, inputs_y, out_size=inputs_x.shape[-2:])\n    assert output.size() == inputs_x.size()\n    output = sum_cell(inputs_x, inputs_y, out_size=inputs_y.shape[-2:])\n    assert output.size() == inputs_y.size()\n    output = sum_cell(inputs_x, inputs_y)\n    assert output.size() == inputs_x.size()\n\n\ndef test_concat_cell():\n    inputs_x = torch.randn([2, 256, 32, 32])\n    inputs_y = torch.randn([2, 256, 16, 16])\n    concat_cell = ConcatCell(256, 256)\n    output = concat_cell(inputs_x, inputs_y, out_size=inputs_x.shape[-2:])\n    assert output.size() == inputs_x.size()\n    output = concat_cell(inputs_x, inputs_y, out_size=inputs_y.shape[-2:])\n    assert output.size() == inputs_y.size()\n    output = concat_cell(inputs_x, inputs_y)\n    assert output.size() == inputs_x.size()\n\n\ndef test_global_pool_cell():\n    inputs_x = torch.randn([2, 256, 32, 32])\n    inputs_y = torch.randn([2, 256, 32, 32])\n    gp_cell = GlobalPoolingCell(with_out_conv=False)\n    gp_cell_out = gp_cell(inputs_x, inputs_y, out_size=inputs_x.shape[-2:])\n    assert (gp_cell_out.size() == inputs_x.size())\n    gp_cell = GlobalPoolingCell(256, 256)\n    gp_cell_out = gp_cell(inputs_x, inputs_y, out_size=inputs_x.shape[-2:])\n    assert (gp_cell_out.size() == inputs_x.size())\n\n\ndef test_resize_methods():\n    inputs_x = torch.randn([2, 256, 128, 128])\n    target_resize_sizes = [(128, 128), (256, 256)]\n    resize_methods_list = ['nearest', 'bilinear']\n\n    for method in resize_methods_list:\n        merge_cell = BaseMergeCell(upsample_mode=method)\n        for target_size in target_resize_sizes:\n            merge_cell_out = merge_cell._resize(inputs_x, target_size)\n            gt_out = F.interpolate(inputs_x, size=target_size, mode=method)\n            assert merge_cell_out.equal(gt_out)\n\n    target_size = (64, 64)  # resize to a smaller size\n    merge_cell = BaseMergeCell()\n    merge_cell_out = merge_cell._resize(inputs_x, target_size)\n    kernel_size = inputs_x.shape[-1] // target_size[-1]\n    gt_out = F.max_pool2d(\n        inputs_x, kernel_size=kernel_size, stride=kernel_size)\n    assert (merge_cell_out == gt_out).all()\n"
  },
  {
    "path": "code/tests/test_ops/test_nms.py",
    "content": "\"\"\"\nCommandLine:\n    pytest tests/test_nms.py\n\"\"\"\nimport numpy as np\nimport pytest\nimport torch\n\nfrom mmdet.ops.nms.nms_wrapper import nms, nms_match\n\n\ndef test_nms_device_and_dtypes_cpu():\n    \"\"\"\n    CommandLine:\n        xdoctest -m tests/test_nms.py test_nms_device_and_dtypes_cpu\n    \"\"\"\n    iou_thr = 0.6\n    base_dets = np.array([[49.1, 32.4, 51.0, 35.9, 0.1],\n                          [49.3, 32.9, 51.0, 35.3, 0.05],\n                          [35.3, 11.5, 39.9, 14.5, 0.9],\n                          [35.2, 11.7, 39.7, 15.7, 0.3]])\n\n    base_expected_suppressed = np.array([[35.3, 11.5, 39.9, 14.5, 0.9],\n                                         [49.1, 32.4, 51.0, 35.9, 0.1]])\n    # CPU can handle float32 and float64\n    dets = base_dets.astype(np.float32)\n    expected_suppressed = base_expected_suppressed.astype(np.float32)\n    suppressed, inds = nms(dets, iou_thr)\n    assert dets.dtype == suppressed.dtype\n    assert np.array_equal(suppressed, expected_suppressed)\n\n    dets = torch.FloatTensor(base_dets)\n    expected_suppressed = torch.FloatTensor(base_expected_suppressed)\n    suppressed, inds = nms(dets, iou_thr)\n    assert dets.dtype == suppressed.dtype\n    assert torch.equal(suppressed, expected_suppressed)\n\n    dets = base_dets.astype(np.float64)\n    expected_suppressed = base_expected_suppressed.astype(np.float64)\n    suppressed, inds = nms(dets, iou_thr)\n    assert dets.dtype == suppressed.dtype\n    assert np.array_equal(suppressed, expected_suppressed)\n\n    dets = torch.DoubleTensor(base_dets)\n    expected_suppressed = torch.DoubleTensor(base_expected_suppressed)\n    suppressed, inds = nms(dets, iou_thr)\n    assert dets.dtype == suppressed.dtype\n    assert torch.equal(suppressed, expected_suppressed)\n\n\ndef test_nms_device_and_dtypes_gpu():\n    \"\"\"\n    CommandLine:\n        xdoctest -m tests/test_nms.py test_nms_device_and_dtypes_gpu\n    \"\"\"\n    if not torch.cuda.is_available():\n        import pytest\n        pytest.skip('test requires GPU and torch+cuda')\n\n    iou_thr = 0.6\n    base_dets = np.array([[49.1, 32.4, 51.0, 35.9, 0.1],\n                          [49.3, 32.9, 51.0, 35.3, 0.05],\n                          [35.3, 11.5, 39.9, 14.5, 0.9],\n                          [35.2, 11.7, 39.7, 15.7, 0.3]])\n\n    base_expected_suppressed = np.array([[35.3, 11.5, 39.9, 14.5, 0.9],\n                                         [49.1, 32.4, 51.0, 35.9, 0.1]])\n\n    for device_id in range(torch.cuda.device_count()):\n        print(f'Run NMS on device_id = {device_id!r}')\n        # GPU can handle float32 but not float64\n        dets = base_dets.astype(np.float32)\n        expected_suppressed = base_expected_suppressed.astype(np.float32)\n        suppressed, inds = nms(dets, iou_thr, device_id)\n        assert dets.dtype == suppressed.dtype\n        assert np.array_equal(suppressed, expected_suppressed)\n\n        dets = torch.FloatTensor(base_dets).to(device_id)\n        expected_suppressed = torch.FloatTensor(base_expected_suppressed).to(\n            device_id)\n        suppressed, inds = nms(dets, iou_thr)\n        assert dets.dtype == suppressed.dtype\n        assert torch.equal(suppressed, expected_suppressed)\n\n\ndef test_nms_match():\n    iou_thr = 0.6\n    # empty input\n    empty_dets = np.array([])\n    assert len(nms_match(empty_dets, iou_thr)) == 0\n\n    # non empty ndarray input\n    np_dets = np.array([[49.1, 32.4, 51.0, 35.9, 0.9],\n                        [49.3, 32.9, 51.0, 35.3, 0.9],\n                        [35.3, 11.5, 39.9, 14.5, 0.4],\n                        [35.2, 11.7, 39.7, 15.7, 0.3]])\n    np_groups = nms_match(np_dets, iou_thr)\n    assert isinstance(np_groups[0], np.ndarray)\n    assert len(np_groups) == 2\n    nms_keep_inds = nms(np_dets, iou_thr)[1]\n    assert set([g[0].item() for g in np_groups]) == set(nms_keep_inds.tolist())\n\n    # non empty tensor input\n    tensor_dets = torch.from_numpy(np_dets)\n    tensor_groups = nms_match(tensor_dets, iou_thr)\n    assert isinstance(tensor_groups[0], torch.Tensor)\n    for i in range(len(tensor_groups)):\n        assert np.equal(tensor_groups[i].numpy(), np_groups[i]).all()\n\n    # input of wrong shape\n    wrong_dets = np.zeros((2, 3))\n    with pytest.raises(AssertionError):\n        nms_match(wrong_dets, iou_thr)\n"
  },
  {
    "path": "code/tests/test_ops/test_soft_nms.py",
    "content": "\"\"\"\nCommandLine:\n    pytest tests/test_soft_nms.py\n\"\"\"\nimport numpy as np\nimport torch\n\nfrom mmdet.ops.nms.nms_wrapper import soft_nms\n\n\ndef test_soft_nms_device_and_dtypes_cpu():\n    \"\"\"\n    CommandLine:\n        xdoctest -m tests/test_soft_nms.py test_soft_nms_device_and_dtypes_cpu\n    \"\"\"\n    iou_thr = 0.7\n    base_dets = np.array([[49.1, 32.4, 51.0, 35.9, 0.9],\n                          [49.3, 32.9, 51.0, 35.3, 0.9],\n                          [35.3, 11.5, 39.9, 14.5, 0.4],\n                          [35.2, 11.7, 39.7, 15.7, 0.3]])\n\n    # CPU can handle float32 and float64\n    dets = base_dets.astype(np.float32)\n    new_dets, inds = soft_nms(dets, iou_thr)\n    assert dets.dtype == new_dets.dtype\n    assert len(inds) == len(new_dets) == 4\n\n    dets = torch.FloatTensor(base_dets)\n    new_dets, inds = soft_nms(dets, iou_thr)\n    assert dets.dtype == new_dets.dtype\n    assert len(inds) == len(new_dets) == 4\n\n    dets = base_dets.astype(np.float64)\n    new_dets, inds = soft_nms(dets, iou_thr)\n    assert dets.dtype == new_dets.dtype\n    assert len(inds) == len(new_dets) == 4\n\n    dets = torch.DoubleTensor(base_dets)\n    new_dets, inds = soft_nms(dets, iou_thr)\n    assert dets.dtype == new_dets.dtype\n    assert len(inds) == len(new_dets) == 4\n"
  },
  {
    "path": "code/tests/test_ops/test_wrappers.py",
    "content": "from collections import OrderedDict\nfrom itertools import product\nfrom unittest.mock import patch\n\nimport torch\nimport torch.nn as nn\n\nfrom mmdet.ops import Conv2d, ConvTranspose2d, Linear, MaxPool2d\n\ntorch.__version__ = '1.1'  # force test\n\n\ndef test_conv2d():\n    \"\"\"\n    CommandLine:\n        xdoctest -m tests/test_wrappers.py test_conv2d\n    \"\"\"\n\n    test_cases = OrderedDict([('in_w', [10, 20]), ('in_h', [10, 20]),\n                              ('in_channel', [1, 3]), ('out_channel', [1, 3]),\n                              ('kernel_size', [3, 5]), ('stride', [1, 2]),\n                              ('padding', [0, 1]), ('dilation', [1, 2])])\n\n    # train mode\n    for in_h, in_w, in_cha, out_cha, k, s, p, d in product(\n            *list(test_cases.values())):\n        # wrapper op with 0-dim input\n        x_empty = torch.randn(0, in_cha, in_h, in_w)\n        torch.manual_seed(0)\n        wrapper = Conv2d(in_cha, out_cha, k, stride=s, padding=p, dilation=d)\n        wrapper_out = wrapper(x_empty)\n\n        # torch op with 3-dim input as shape reference\n        x_normal = torch.randn(3, in_cha, in_h, in_w).requires_grad_(True)\n        torch.manual_seed(0)\n        ref = nn.Conv2d(in_cha, out_cha, k, stride=s, padding=p, dilation=d)\n        ref_out = ref(x_normal)\n\n        assert wrapper_out.shape[0] == 0\n        assert wrapper_out.shape[1:] == ref_out.shape[1:]\n\n        wrapper_out.sum().backward()\n        assert wrapper.weight.grad is not None\n        assert wrapper.weight.grad.shape == wrapper.weight.shape\n\n        assert torch.equal(wrapper(x_normal), ref_out)\n\n    # eval mode\n    x_empty = torch.randn(0, in_cha, in_h, in_w)\n    wrapper = Conv2d(in_cha, out_cha, k, stride=s, padding=p, dilation=d)\n    wrapper.eval()\n    wrapper(x_empty)\n\n\ndef test_conv_transposed_2d():\n    test_cases = OrderedDict([('in_w', [10, 20]), ('in_h', [10, 20]),\n                              ('in_channel', [1, 3]), ('out_channel', [1, 3]),\n                              ('kernel_size', [3, 5]), ('stride', [1, 2]),\n                              ('padding', [0, 1]), ('dilation', [1, 2])])\n\n    for in_h, in_w, in_cha, out_cha, k, s, p, d in product(\n            *list(test_cases.values())):\n        # wrapper op with 0-dim input\n        x_empty = torch.randn(0, in_cha, in_h, in_w, requires_grad=True)\n        # out padding must be smaller than either stride or dilation\n        op = min(s, d) - 1\n        torch.manual_seed(0)\n        wrapper = ConvTranspose2d(\n            in_cha,\n            out_cha,\n            k,\n            stride=s,\n            padding=p,\n            dilation=d,\n            output_padding=op)\n        wrapper_out = wrapper(x_empty)\n\n        # torch op with 3-dim input as shape reference\n        x_normal = torch.randn(3, in_cha, in_h, in_w)\n        torch.manual_seed(0)\n        ref = nn.ConvTranspose2d(\n            in_cha,\n            out_cha,\n            k,\n            stride=s,\n            padding=p,\n            dilation=d,\n            output_padding=op)\n        ref_out = ref(x_normal)\n\n        assert wrapper_out.shape[0] == 0\n        assert wrapper_out.shape[1:] == ref_out.shape[1:]\n\n        wrapper_out.sum().backward()\n        assert wrapper.weight.grad is not None\n        assert wrapper.weight.grad.shape == wrapper.weight.shape\n\n        assert torch.equal(wrapper(x_normal), ref_out)\n\n    # eval mode\n    x_empty = torch.randn(0, in_cha, in_h, in_w)\n    wrapper = ConvTranspose2d(\n        in_cha, out_cha, k, stride=s, padding=p, dilation=d, output_padding=op)\n    wrapper.eval()\n    wrapper(x_empty)\n\n\ndef test_max_pool_2d():\n    test_cases = OrderedDict([('in_w', [10, 20]), ('in_h', [10, 20]),\n                              ('in_channel', [1, 3]), ('out_channel', [1, 3]),\n                              ('kernel_size', [3, 5]), ('stride', [1, 2]),\n                              ('padding', [0, 1]), ('dilation', [1, 2])])\n\n    for in_h, in_w, in_cha, out_cha, k, s, p, d in product(\n            *list(test_cases.values())):\n        # wrapper op with 0-dim input\n        x_empty = torch.randn(0, in_cha, in_h, in_w, requires_grad=True)\n        wrapper = MaxPool2d(k, stride=s, padding=p, dilation=d)\n        wrapper_out = wrapper(x_empty)\n\n        # torch op with 3-dim input as shape reference\n        x_normal = torch.randn(3, in_cha, in_h, in_w)\n        ref = nn.MaxPool2d(k, stride=s, padding=p, dilation=d)\n        ref_out = ref(x_normal)\n\n        assert wrapper_out.shape[0] == 0\n        assert wrapper_out.shape[1:] == ref_out.shape[1:]\n\n        assert torch.equal(wrapper(x_normal), ref_out)\n\n\ndef test_linear():\n    test_cases = OrderedDict([\n        ('in_w', [10, 20]),\n        ('in_h', [10, 20]),\n        ('in_feature', [1, 3]),\n        ('out_feature', [1, 3]),\n    ])\n\n    for in_h, in_w, in_feature, out_feature in product(\n            *list(test_cases.values())):\n        # wrapper op with 0-dim input\n        x_empty = torch.randn(0, in_feature, requires_grad=True)\n        torch.manual_seed(0)\n        wrapper = Linear(in_feature, out_feature)\n        wrapper_out = wrapper(x_empty)\n\n        # torch op with 3-dim input as shape reference\n        x_normal = torch.randn(3, in_feature)\n        torch.manual_seed(0)\n        ref = nn.Linear(in_feature, out_feature)\n        ref_out = ref(x_normal)\n\n        assert wrapper_out.shape[0] == 0\n        assert wrapper_out.shape[1:] == ref_out.shape[1:]\n\n        wrapper_out.sum().backward()\n        assert wrapper.weight.grad is not None\n        assert wrapper.weight.grad.shape == wrapper.weight.shape\n\n        assert torch.equal(wrapper(x_normal), ref_out)\n\n    # eval mode\n    x_empty = torch.randn(0, in_feature)\n    wrapper = Linear(in_feature, out_feature)\n    wrapper.eval()\n    wrapper(x_empty)\n\n\ndef test_nn_op_forward_called():\n    torch.__version__ = '1.4.1'\n\n    for m in ['Conv2d', 'ConvTranspose2d', 'MaxPool2d']:\n        with patch(f'torch.nn.{m}.forward') as nn_module_forward:\n            # randn input\n            x_empty = torch.randn(0, 3, 10, 10)\n            wrapper = eval(m)(3, 2, 1)\n            wrapper(x_empty)\n            nn_module_forward.assert_called_with(x_empty)\n\n            # non-randn input\n            x_normal = torch.randn(1, 3, 10, 10)\n            wrapper = eval(m)(3, 2, 1)\n            wrapper(x_normal)\n            nn_module_forward.assert_called_with(x_normal)\n\n    with patch('torch.nn.Linear.forward') as nn_module_forward:\n        # randn input\n        x_empty = torch.randn(0, 3)\n        wrapper = Linear(3, 3)\n        wrapper(x_empty)\n        nn_module_forward.assert_not_called()\n\n        # non-randn input\n        x_normal = torch.randn(1, 3)\n        wrapper = Linear(3, 3)\n        wrapper(x_normal)\n        nn_module_forward.assert_called_with(x_normal)\n"
  },
  {
    "path": "code/tests/test_pipelines/test_formatting.py",
    "content": "import os.path as osp\n\nfrom mmcv.utils import build_from_cfg\n\nfrom mmdet.datasets.builder import PIPELINES\n\n\ndef test_default_format_bundle():\n    results = dict(\n        img_prefix=osp.join(osp.dirname(__file__), '../data'),\n        img_info=dict(filename='color.jpg'))\n    load = dict(type='LoadImageFromFile')\n    load = build_from_cfg(load, PIPELINES)\n    bundle = dict(type='DefaultFormatBundle')\n    bundle = build_from_cfg(bundle, PIPELINES)\n    results = load(results)\n    assert 'pad_shape' not in results\n    assert 'scale_factor' not in results\n    assert 'img_norm_cfg' not in results\n    results = bundle(results)\n    assert 'pad_shape' in results\n    assert 'scale_factor' in results\n    assert 'img_norm_cfg' in results\n"
  },
  {
    "path": "code/tests/test_pipelines/test_loading.py",
    "content": "import copy\nimport os.path as osp\n\nimport numpy as np\n\nfrom mmdet.datasets.pipelines import (LoadImageFromFile,\n                                      LoadMultiChannelImageFromFiles)\n\n\nclass TestLoading(object):\n\n    @classmethod\n    def setup_class(cls):\n        cls.data_prefix = osp.join(osp.dirname(__file__), '../data')\n\n    def test_load_img(self):\n        results = dict(\n            img_prefix=self.data_prefix, img_info=dict(filename='color.jpg'))\n        transform = LoadImageFromFile()\n        results = transform(copy.deepcopy(results))\n        assert results['filename'] == osp.join(self.data_prefix, 'color.jpg')\n        assert results['ori_filename'] == 'color.jpg'\n        assert results['img'].shape == (288, 512, 3)\n        assert results['img'].dtype == np.uint8\n        assert results['img_shape'] == (288, 512, 3)\n        assert results['ori_shape'] == (288, 512, 3)\n        assert repr(transform) == transform.__class__.__name__ + \\\n            \"(to_float32=False, color_type='color', \" + \\\n            \"file_client_args={'backend': 'disk'})\"\n\n        # no img_prefix\n        results = dict(\n            img_prefix=None, img_info=dict(filename='tests/data/color.jpg'))\n        transform = LoadImageFromFile()\n        results = transform(copy.deepcopy(results))\n        assert results['filename'] == 'tests/data/color.jpg'\n        assert results['ori_filename'] == 'tests/data/color.jpg'\n        assert results['img'].shape == (288, 512, 3)\n\n        # to_float32\n        transform = LoadImageFromFile(to_float32=True)\n        results = transform(copy.deepcopy(results))\n        assert results['img'].dtype == np.float32\n\n        # gray image\n        results = dict(\n            img_prefix=self.data_prefix, img_info=dict(filename='gray.jpg'))\n        transform = LoadImageFromFile()\n        results = transform(copy.deepcopy(results))\n        assert results['img'].shape == (288, 512, 3)\n        assert results['img'].dtype == np.uint8\n\n        transform = LoadImageFromFile(color_type='unchanged')\n        results = transform(copy.deepcopy(results))\n        assert results['img'].shape == (288, 512)\n        assert results['img'].dtype == np.uint8\n\n    def test_load_multi_channel_img(self):\n        results = dict(\n            img_prefix=self.data_prefix,\n            img_info=dict(filename=['color.jpg', 'color.jpg']))\n        transform = LoadMultiChannelImageFromFiles()\n        results = transform(copy.deepcopy(results))\n        assert results['filename'] == [\n            osp.join(self.data_prefix, 'color.jpg'),\n            osp.join(self.data_prefix, 'color.jpg')\n        ]\n        assert results['ori_filename'] == ['color.jpg', 'color.jpg']\n        assert results['img'].shape == (288, 512, 3, 2)\n        assert results['img'].dtype == np.uint8\n        assert results['img_shape'] == (288, 512, 3, 2)\n        assert results['ori_shape'] == (288, 512, 3, 2)\n        assert results['pad_shape'] == (288, 512, 3, 2)\n        assert results['scale_factor'] == 1.0\n        assert repr(transform) == transform.__class__.__name__ + \\\n            \"(to_float32=False, color_type='unchanged', \" + \\\n            \"file_client_args={'backend': 'disk'})\"\n"
  },
  {
    "path": "code/tests/test_pipelines/test_models_aug_test.py",
    "content": "import os.path as osp\n\nimport mmcv\nimport torch\nfrom mmcv.parallel import collate\nfrom mmcv.utils import build_from_cfg\n\nfrom mmdet.datasets.builder import PIPELINES\nfrom mmdet.models import build_detector\n\n\ndef model_aug_test_template(cfg_file):\n    # get config\n    cfg = mmcv.Config.fromfile(cfg_file)\n    # init model\n    cfg.model.pretrained = None\n    model = build_detector(cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)\n\n    # init test pipeline and set aug test\n    load_cfg, multi_scale_cfg = cfg.test_pipeline\n    multi_scale_cfg['flip'] = True\n    multi_scale_cfg['img_scale'] = [(1333, 800), (800, 600), (640, 480)]\n\n    load = build_from_cfg(load_cfg, PIPELINES)\n    transform = build_from_cfg(multi_scale_cfg, PIPELINES)\n\n    results = dict(\n        img_prefix=osp.join(osp.dirname(__file__), '../data'),\n        img_info=dict(filename='color.jpg'))\n    results = transform(load(results))\n    assert len(results['img']) == 6\n    assert len(results['img_metas']) == 6\n\n    results['img'] = [collate([x]) for x in results['img']]\n    results['img_metas'] = [collate([x]).data[0] for x in results['img_metas']]\n    # aug test the model\n    model.eval()\n    with torch.no_grad():\n        aug_result = model(return_loss=False, rescale=True, **results)\n    return aug_result\n\n\ndef test_cascade_rcnn_aug_test():\n    aug_result = model_aug_test_template(\n        'configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco.py')\n    assert len(aug_result) == 80\n\n\ndef test_mask_rcnn_aug_test():\n    aug_result = model_aug_test_template(\n        'configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py')\n    assert len(aug_result) == 2\n    assert len(aug_result[0]) == 80\n    assert len(aug_result[1]) == 80\n\n\ndef test_htc_aug_test():\n    aug_result = model_aug_test_template('configs/htc/htc_r50_fpn_1x_coco.py')\n    assert len(aug_result) == 2\n    assert len(aug_result[0]) == 80\n    assert len(aug_result[1]) == 80\n"
  },
  {
    "path": "code/tests/test_pipelines/test_transform.py",
    "content": "import copy\nimport os.path as osp\n\nimport mmcv\nimport numpy as np\nimport pytest\nimport torch\nfrom mmcv.utils import build_from_cfg\n\nfrom mmdet.core.evaluation.bbox_overlaps import bbox_overlaps\nfrom mmdet.datasets.builder import PIPELINES\n\n\ndef test_resize():\n    # test assertion if img_scale is a list\n    with pytest.raises(AssertionError):\n        transform = dict(type='Resize', img_scale=[1333, 800], keep_ratio=True)\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion if len(img_scale) while ratio_range is not None\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='Resize',\n            img_scale=[(1333, 800), (1333, 600)],\n            ratio_range=(0.9, 1.1),\n            keep_ratio=True)\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid multiscale_mode\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='Resize',\n            img_scale=[(1333, 800), (1333, 600)],\n            keep_ratio=True,\n            multiscale_mode='2333')\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion if both scale and scale_factor are setted\n    with pytest.raises(AssertionError):\n        results = dict(\n            img_prefix=osp.join(osp.dirname(__file__), '../data'),\n            img_info=dict(filename='color.jpg'))\n        load = dict(type='LoadImageFromFile')\n        load = build_from_cfg(load, PIPELINES)\n        transform = dict(type='Resize', img_scale=(1333, 800), keep_ratio=True)\n        transform = build_from_cfg(transform, PIPELINES)\n        results = load(results)\n        results['scale'] = (1333, 800)\n        results['scale_factor'] = 1.0\n        results = transform(results)\n\n    transform = dict(type='Resize', img_scale=(1333, 800), keep_ratio=True)\n    resize_module = build_from_cfg(transform, PIPELINES)\n\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    results['img'] = img\n    results['img2'] = copy.deepcopy(img)\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['img_fields'] = ['img', 'img2']\n\n    results = resize_module(results)\n    assert np.equal(results['img'], results['img2']).all()\n\n    results.pop('scale')\n    results.pop('scale_factor')\n    transform = dict(\n        type='Resize',\n        img_scale=(1280, 800),\n        multiscale_mode='value',\n        keep_ratio=False)\n    resize_module = build_from_cfg(transform, PIPELINES)\n    results = resize_module(results)\n    assert np.equal(results['img'], results['img2']).all()\n    assert results['img_shape'] == (800, 1280, 3)\n\n\ndef test_flip():\n    # test assertion for invalid flip_ratio\n    with pytest.raises(AssertionError):\n        transform = dict(type='RandomFlip', flip_ratio=1.5)\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid direction\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomFlip', flip_ratio=1, direction='horizonta')\n        build_from_cfg(transform, PIPELINES)\n\n    transform = dict(type='RandomFlip', flip_ratio=1)\n    flip_module = build_from_cfg(transform, PIPELINES)\n\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    original_img = copy.deepcopy(img)\n    results['img'] = img\n    results['img2'] = copy.deepcopy(img)\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['scale_factor'] = 1.0\n    results['img_fields'] = ['img', 'img2']\n\n    results = flip_module(results)\n    assert np.equal(results['img'], results['img2']).all()\n\n    flip_module = build_from_cfg(transform, PIPELINES)\n    results = flip_module(results)\n    assert np.equal(results['img'], results['img2']).all()\n    assert np.equal(original_img, results['img']).all()\n\n\ndef test_random_crop():\n    # test assertion for invalid random crop\n    with pytest.raises(AssertionError):\n        transform = dict(type='RandomCrop', crop_size=(-1, 0))\n        build_from_cfg(transform, PIPELINES)\n\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    results['img'] = img\n\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    # TODO: add img_fields test\n    results['bbox_fields'] = ['gt_bboxes', 'gt_bboxes_ignore']\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['scale_factor'] = 1.0\n\n    def create_random_bboxes(num_bboxes, img_w, img_h):\n        bboxes_left_top = np.random.uniform(0, 0.5, size=(num_bboxes, 2))\n        bboxes_right_bottom = np.random.uniform(0.5, 1, size=(num_bboxes, 2))\n        bboxes = np.concatenate((bboxes_left_top, bboxes_right_bottom), 1)\n        bboxes = (bboxes * np.array([img_w, img_h, img_w, img_h])).astype(\n            np.int)\n        return bboxes\n\n    h, w, _ = img.shape\n    gt_bboxes = create_random_bboxes(8, w, h)\n    gt_bboxes_ignore = create_random_bboxes(2, w, h)\n    results['gt_bboxes'] = gt_bboxes\n    results['gt_bboxes_ignore'] = gt_bboxes_ignore\n    transform = dict(type='RandomCrop', crop_size=(h - 20, w - 20))\n    crop_module = build_from_cfg(transform, PIPELINES)\n    results = crop_module(results)\n    assert results['img'].shape[:2] == (h - 20, w - 20)\n    # All bboxes should be reserved after crop\n    assert results['img_shape'][:2] == (h - 20, w - 20)\n    assert results['gt_bboxes'].shape[0] == 8\n    assert results['gt_bboxes_ignore'].shape[0] == 2\n\n    def area(bboxes):\n        return np.prod(bboxes[:, 2:4] - bboxes[:, 0:2], axis=1)\n\n    assert (area(results['gt_bboxes']) <= area(gt_bboxes)).all()\n    assert (area(results['gt_bboxes_ignore']) <= area(gt_bboxes_ignore)).all()\n\n\ndef test_min_iou_random_crop():\n\n    def create_random_bboxes(num_bboxes, img_w, img_h):\n        bboxes_left_top = np.random.uniform(0, 0.5, size=(num_bboxes, 2))\n        bboxes_right_bottom = np.random.uniform(0.5, 1, size=(num_bboxes, 2))\n        bboxes = np.concatenate((bboxes_left_top, bboxes_right_bottom), 1)\n        bboxes = (bboxes * np.array([img_w, img_h, img_w, img_h])).astype(\n            np.int)\n        return bboxes\n\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    results['img'] = img\n\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    results['bbox_fields'] = ['gt_bboxes', 'gt_bboxes_ignore']\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['scale_factor'] = 1.0\n    h, w, _ = img.shape\n    gt_bboxes = create_random_bboxes(1, w, h)\n    gt_bboxes_ignore = create_random_bboxes(1, w, h)\n    results['gt_bboxes'] = gt_bboxes\n    results['gt_bboxes_ignore'] = gt_bboxes_ignore\n    transform = dict(type='MinIoURandomCrop')\n    crop_module = build_from_cfg(transform, PIPELINES)\n\n    # Test for img_fields\n    results_test = copy.deepcopy(results)\n    results_test['img1'] = results_test['img']\n    results_test['img_fields'] = ['img', 'img1']\n    with pytest.raises(AssertionError):\n        crop_module(results_test)\n    results = crop_module(results)\n    patch = np.array([0, 0, results['img_shape'][1], results['img_shape'][0]])\n    ious = bbox_overlaps(patch.reshape(-1, 4),\n                         results['gt_bboxes']).reshape(-1)\n    ious_ignore = bbox_overlaps(\n        patch.reshape(-1, 4), results['gt_bboxes_ignore']).reshape(-1)\n    mode = crop_module.mode\n    if mode == 1:\n        assert np.equal(results['gt_bboxes'], gt_bboxes).all()\n        assert np.equal(results['gt_bboxes_ignore'], gt_bboxes_ignore).all()\n    else:\n        assert (ious >= mode).all()\n        assert (ious_ignore >= mode).all()\n\n\ndef test_pad():\n    # test assertion if both size_divisor and size is None\n    with pytest.raises(AssertionError):\n        transform = dict(type='Pad')\n        build_from_cfg(transform, PIPELINES)\n\n    transform = dict(type='Pad', size_divisor=32)\n    transform = build_from_cfg(transform, PIPELINES)\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    original_img = copy.deepcopy(img)\n    results['img'] = img\n    results['img2'] = copy.deepcopy(img)\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['scale_factor'] = 1.0\n    results['img_fields'] = ['img', 'img2']\n\n    results = transform(results)\n    assert np.equal(results['img'], results['img2']).all()\n    # original img already divisible by 32\n    assert np.equal(results['img'], original_img).all()\n    img_shape = results['img'].shape\n    assert img_shape[0] % 32 == 0\n    assert img_shape[1] % 32 == 0\n\n    resize_transform = dict(\n        type='Resize', img_scale=(1333, 800), keep_ratio=True)\n    resize_module = build_from_cfg(resize_transform, PIPELINES)\n    results = resize_module(results)\n    results = transform(results)\n    img_shape = results['img'].shape\n    assert np.equal(results['img'], results['img2']).all()\n    assert img_shape[0] % 32 == 0\n    assert img_shape[1] % 32 == 0\n\n\ndef test_normalize():\n    img_norm_cfg = dict(\n        mean=[123.675, 116.28, 103.53],\n        std=[58.395, 57.12, 57.375],\n        to_rgb=True)\n    transform = dict(type='Normalize', **img_norm_cfg)\n    transform = build_from_cfg(transform, PIPELINES)\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    original_img = copy.deepcopy(img)\n    results['img'] = img\n    results['img2'] = copy.deepcopy(img)\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['scale_factor'] = 1.0\n    results['img_fields'] = ['img', 'img2']\n\n    results = transform(results)\n    assert np.equal(results['img'], results['img2']).all()\n\n    mean = np.array(img_norm_cfg['mean'])\n    std = np.array(img_norm_cfg['std'])\n    converted_img = (original_img[..., ::-1] - mean) / std\n    assert np.allclose(results['img'], converted_img)\n\n\ndef test_albu_transform():\n    results = dict(\n        img_prefix=osp.join(osp.dirname(__file__), '../data'),\n        img_info=dict(filename='color.jpg'))\n\n    # Define simple pipeline\n    load = dict(type='LoadImageFromFile')\n    load = build_from_cfg(load, PIPELINES)\n\n    albu_transform = dict(\n        type='Albu', transforms=[dict(type='ChannelShuffle', p=1)])\n    albu_transform = build_from_cfg(albu_transform, PIPELINES)\n\n    normalize = dict(type='Normalize', mean=[0] * 3, std=[0] * 3, to_rgb=True)\n    normalize = build_from_cfg(normalize, PIPELINES)\n\n    # Execute transforms\n    results = load(results)\n    results = albu_transform(results)\n    results = normalize(results)\n\n    assert results['img'].dtype == np.float32\n\n\ndef test_random_center_crop_pad():\n    # test assertion for invalid crop_size while test_mode=False\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=(-1, 0),\n            test_mode=False,\n            test_pad_mode=None)\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid ratios while test_mode=False\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=(511, 511),\n            ratios=(1.0),\n            test_mode=False,\n            test_pad_mode=None)\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid mean, std and to_rgb\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=(511, 511),\n            mean=None,\n            std=None,\n            to_rgb=None,\n            test_mode=False,\n            test_pad_mode=None)\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid crop_size while test_mode=True\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=(511, 511),\n            ratios=None,\n            border=None,\n            mean=[123.675, 116.28, 103.53],\n            std=[58.395, 57.12, 57.375],\n            to_rgb=True,\n            test_mode=True,\n            test_pad_mode=('logical_or', 127))\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid ratios while test_mode=True\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=None,\n            ratios=(0.9, 1.0, 1.1),\n            border=None,\n            mean=[123.675, 116.28, 103.53],\n            std=[58.395, 57.12, 57.375],\n            to_rgb=True,\n            test_mode=True,\n            test_pad_mode=('logical_or', 127))\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid border while test_mode=True\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=None,\n            ratios=None,\n            border=128,\n            mean=[123.675, 116.28, 103.53],\n            std=[58.395, 57.12, 57.375],\n            to_rgb=True,\n            test_mode=True,\n            test_pad_mode=('logical_or', 127))\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion for invalid test_pad_mode while test_mode=True\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='RandomCenterCropPad',\n            crop_size=None,\n            ratios=None,\n            border=None,\n            mean=[123.675, 116.28, 103.53],\n            std=[58.395, 57.12, 57.375],\n            to_rgb=True,\n            test_mode=True,\n            test_pad_mode=('do_nothing', 100))\n        build_from_cfg(transform, PIPELINES)\n\n    results = dict(\n        img_prefix=osp.join(osp.dirname(__file__), '../data'),\n        img_info=dict(filename='color.jpg'))\n\n    load = dict(type='LoadImageFromFile', to_float32=True)\n    load = build_from_cfg(load, PIPELINES)\n    results = load(results)\n    test_results = copy.deepcopy(results)\n\n    def create_random_bboxes(num_bboxes, img_w, img_h):\n        bboxes_left_top = np.random.uniform(0, 0.5, size=(num_bboxes, 2))\n        bboxes_right_bottom = np.random.uniform(0.5, 1, size=(num_bboxes, 2))\n        bboxes = np.concatenate((bboxes_left_top, bboxes_right_bottom), 1)\n        bboxes = (bboxes * np.array([img_w, img_h, img_w, img_h])).astype(\n            np.int)\n        return bboxes\n\n    h, w, _ = results['img_shape']\n    gt_bboxes = create_random_bboxes(8, w, h)\n    gt_bboxes_ignore = create_random_bboxes(2, w, h)\n    results['gt_bboxes'] = gt_bboxes\n    results['gt_bboxes_ignore'] = gt_bboxes_ignore\n    train_transform = dict(\n        type='RandomCenterCropPad',\n        crop_size=(h - 20, w - 20),\n        ratios=(1.0, ),\n        border=128,\n        mean=[123.675, 116.28, 103.53],\n        std=[58.395, 57.12, 57.375],\n        to_rgb=True,\n        test_mode=False,\n        test_pad_mode=None)\n    crop_module = build_from_cfg(train_transform, PIPELINES)\n    train_results = crop_module(results)\n    assert train_results['img'].shape[:2] == (h - 20, w - 20)\n    # All bboxes should be reserved after crop\n    assert train_results['pad_shape'][:2] == (h - 20, w - 20)\n    assert train_results['gt_bboxes'].shape[0] == 8\n    assert train_results['gt_bboxes_ignore'].shape[0] == 2\n\n    test_transform = dict(\n        type='RandomCenterCropPad',\n        crop_size=None,\n        ratios=None,\n        border=None,\n        mean=[123.675, 116.28, 103.53],\n        std=[58.395, 57.12, 57.375],\n        to_rgb=True,\n        test_mode=True,\n        test_pad_mode=('logical_or', 127))\n    crop_module = build_from_cfg(test_transform, PIPELINES)\n\n    test_results = crop_module(test_results)\n    assert test_results['img'].shape[:2] == (h | 127, w | 127)\n    assert test_results['pad_shape'][:2] == (h | 127, w | 127)\n    assert 'border' in test_results\n\n\ndef test_multi_scale_flip_aug():\n    # test assertion if give both scale_factor and img_scale\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='MultiScaleFlipAug',\n            scale_factor=1.0,\n            img_scale=[(1333, 800)],\n            transforms=[dict(type='Resize')])\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion if both scale_factor and img_scale are None\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='MultiScaleFlipAug',\n            scale_factor=None,\n            img_scale=None,\n            transforms=[dict(type='Resize')])\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion if img_scale is not tuple or list of tuple\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='MultiScaleFlipAug',\n            img_scale=[1333, 800],\n            transforms=[dict(type='Resize')])\n        build_from_cfg(transform, PIPELINES)\n\n    # test assertion if flip_direction is not str or list of str\n    with pytest.raises(AssertionError):\n        transform = dict(\n            type='MultiScaleFlipAug',\n            img_scale=[(1333, 800)],\n            flip_direction=1,\n            transforms=[dict(type='Resize')])\n        build_from_cfg(transform, PIPELINES)\n\n    scale_transform = dict(\n        type='MultiScaleFlipAug',\n        img_scale=[(1333, 800), (1333, 640)],\n        transforms=[dict(type='Resize', keep_ratio=True)])\n    transform = build_from_cfg(scale_transform, PIPELINES)\n\n    results = dict()\n    img = mmcv.imread(\n        osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')\n    results['img'] = img\n    results['img_shape'] = img.shape\n    results['ori_shape'] = img.shape\n    # Set initial values for default meta_keys\n    results['pad_shape'] = img.shape\n    results['img_fields'] = ['img']\n\n    scale_results = transform(copy.deepcopy(results))\n    assert len(scale_results['img']) == 2\n    assert scale_results['img'][0].shape == (750, 1333, 3)\n    assert scale_results['img_shape'][0] == (750, 1333, 3)\n    assert scale_results['img'][1].shape == (640, 1138, 3)\n    assert scale_results['img_shape'][1] == (640, 1138, 3)\n\n    scale_factor_transform = dict(\n        type='MultiScaleFlipAug',\n        scale_factor=[0.8, 1.0, 1.2],\n        transforms=[dict(type='Resize', keep_ratio=False)])\n    transform = build_from_cfg(scale_factor_transform, PIPELINES)\n    scale_factor_results = transform(copy.deepcopy(results))\n    assert len(scale_factor_results['img']) == 3\n    assert scale_factor_results['img'][0].shape == (230, 409, 3)\n    assert scale_factor_results['img_shape'][0] == (230, 409, 3)\n    assert scale_factor_results['img'][1].shape == (288, 512, 3)\n    assert scale_factor_results['img_shape'][1] == (288, 512, 3)\n    assert scale_factor_results['img'][2].shape == (345, 614, 3)\n    assert scale_factor_results['img_shape'][2] == (345, 614, 3)\n\n    # test pipeline of coco_detection\n    results = dict(\n        img_prefix=osp.join(osp.dirname(__file__), '../data'),\n        img_info=dict(filename='color.jpg'))\n    load_cfg, multi_scale_cfg = mmcv.Config.fromfile(\n        'configs/_base_/datasets/coco_detection.py').test_pipeline\n    load = build_from_cfg(load_cfg, PIPELINES)\n    transform = build_from_cfg(multi_scale_cfg, PIPELINES)\n    results = transform(load(results))\n    assert len(results['img']) == 1\n    assert len(results['img_metas']) == 1\n    assert isinstance(results['img'][0], torch.Tensor)\n    assert isinstance(results['img_metas'][0], mmcv.parallel.DataContainer)\n    assert results['img_metas'][0].data['ori_shape'] == (288, 512, 3)\n    assert results['img_metas'][0].data['img_shape'] == (750, 1333, 3)\n    assert results['img_metas'][0].data['pad_shape'] == (768, 1344, 3)\n    assert results['img_metas'][0].data['scale_factor'].tolist() == [\n        2.603515625, 2.6041667461395264, 2.603515625, 2.6041667461395264\n    ]\n"
  },
  {
    "path": "code/tests/test_pisa_heads.py",
    "content": "import mmcv\nimport torch\n\nfrom mmdet.models.dense_heads import PISARetinaHead, PISASSDHead\nfrom mmdet.models.roi_heads import PISARoIHead\n\n\ndef test_pisa_retinanet_head_loss():\n    \"\"\"\n    Tests pisa retinanet head loss when truth is empty and non-empty\n    \"\"\"\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n\n    cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.3,\n                min_pos_iou=0.3,\n                match_low_quality=True,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='RandomSampler',\n                num=256,\n                pos_fraction=0.5,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=False),\n            isr=dict(k=2., bias=0.),\n            carl=dict(k=1., bias=0.2),\n            allowed_border=0,\n            pos_weight=-1,\n            debug=False))\n    self = PISARetinaHead(num_classes=4, in_channels=1, train_cfg=cfg)\n\n    # Anchor head expects a multiple levels of features per image\n    feat = [\n        torch.rand(1, 1, s // (2**(i + 2)), s // (2**(i + 2)))\n        for i in range(len(self.anchor_generator.strides))\n    ]\n    cls_scores, bbox_preds = self.forward(feat)\n\n    # Test that empty ground truth encourages the network to predict background\n    gt_bboxes = [torch.empty((0, 4))]\n    gt_labels = [torch.LongTensor([])]\n\n    gt_bboxes_ignore = None\n    empty_gt_losses = self.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                                img_metas, gt_bboxes_ignore)\n    # When there is no truth, the cls loss should be nonzero but there should\n    # be no box loss.\n    empty_cls_loss = empty_gt_losses['loss_cls'].sum()\n    empty_box_loss = empty_gt_losses['loss_bbox'].sum()\n    assert empty_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert empty_box_loss.item() == 0, (\n        'there should be no box loss when there are no true boxes')\n\n    # When truth is non-empty then both cls and box loss should be nonzero for\n    # random inputs\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n    one_gt_losses = self.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                              img_metas, gt_bboxes_ignore)\n    onegt_cls_loss = one_gt_losses['loss_cls'].sum()\n    onegt_box_loss = one_gt_losses['loss_bbox'].sum()\n    assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n\n\ndef test_pisa_ssd_head_loss():\n    \"\"\"\n    Tests pisa ssd head loss when truth is empty and non-empty\n    \"\"\"\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n\n    cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.5,\n                neg_iou_thr=0.5,\n                min_pos_iou=0.,\n                ignore_iof_thr=-1,\n                gt_max_assign_all=False),\n            isr=dict(k=2., bias=0.),\n            carl=dict(k=1., bias=0.2),\n            smoothl1_beta=1.,\n            allowed_border=-1,\n            pos_weight=-1,\n            neg_pos_ratio=3,\n            debug=False))\n    ssd_anchor_generator = dict(\n        type='SSDAnchorGenerator',\n        scale_major=False,\n        input_size=300,\n        strides=[1],\n        ratios=([2], ),\n        basesize_ratio_range=(0.15, 0.9))\n    self = PISASSDHead(\n        num_classes=4,\n        in_channels=(1, ),\n        train_cfg=cfg,\n        anchor_generator=ssd_anchor_generator)\n\n    # Anchor head expects a multiple levels of features per image\n    feat = [\n        torch.rand(1, 1, s // (2**(i + 2)), s // (2**(i + 2)))\n        for i in range(len(self.anchor_generator.strides))\n    ]\n    cls_scores, bbox_preds = self.forward(feat)\n\n    # Test that empty ground truth encourages the network to predict background\n    gt_bboxes = [torch.empty((0, 4))]\n    gt_labels = [torch.LongTensor([])]\n\n    gt_bboxes_ignore = None\n    empty_gt_losses = self.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                                img_metas, gt_bboxes_ignore)\n    # When there is no truth, the cls loss should be nonzero but there should\n    # be no box loss.\n    empty_cls_loss = sum(empty_gt_losses['loss_cls'])\n    empty_box_loss = sum(empty_gt_losses['loss_bbox'])\n    # SSD is special, #pos:#neg = 1: 3, so empth gt will also lead loss cls = 0\n    assert empty_cls_loss.item() == 0, 'cls loss should be non-zero'\n    assert empty_box_loss.item() == 0, (\n        'there should be no box loss when there are no true boxes')\n\n    # When truth is non-empty then both cls and box loss should be nonzero for\n    # random inputs\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n    one_gt_losses = self.loss(cls_scores, bbox_preds, gt_bboxes, gt_labels,\n                              img_metas, gt_bboxes_ignore)\n    onegt_cls_loss = sum(one_gt_losses['loss_cls'])\n    onegt_box_loss = sum(one_gt_losses['loss_bbox'])\n    assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n\n\ndef test_pisa_roi_head_loss():\n    \"\"\"\n    Tests pisa roi head loss when truth is empty and non-empty\n    \"\"\"\n    train_cfg = mmcv.Config(\n        dict(\n            assigner=dict(\n                type='MaxIoUAssigner',\n                pos_iou_thr=0.7,\n                neg_iou_thr=0.3,\n                min_pos_iou=0.3,\n                match_low_quality=True,\n                ignore_iof_thr=-1),\n            sampler=dict(\n                type='ScoreHLRSampler',\n                num=4,\n                pos_fraction=0.25,\n                neg_pos_ub=-1,\n                add_gt_as_proposals=True,\n                k=0.5,\n                bias=0.),\n            isr=dict(k=2., bias=0.),\n            carl=dict(k=1., bias=0.2),\n            allowed_border=0,\n            pos_weight=-1,\n            debug=False))\n\n    bbox_roi_extractor = dict(\n        type='SingleRoIExtractor',\n        roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),\n        out_channels=1,\n        featmap_strides=[1])\n\n    bbox_head = dict(\n        type='Shared2FCBBoxHead',\n        in_channels=1,\n        fc_out_channels=2,\n        roi_feat_size=7,\n        num_classes=4,\n        bbox_coder=dict(\n            type='DeltaXYWHBBoxCoder',\n            target_means=[0., 0., 0., 0.],\n            target_stds=[0.1, 0.1, 0.2, 0.2]),\n        reg_class_agnostic=False,\n        loss_cls=dict(\n            type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),\n        loss_bbox=dict(type='L1Loss', loss_weight=1.0))\n\n    self = PISARoIHead(bbox_roi_extractor, bbox_head, train_cfg=train_cfg)\n\n    s = 256\n    img_metas = [{\n        'img_shape': (s, s, 3),\n        'scale_factor': 1,\n        'pad_shape': (s, s, 3)\n    }]\n\n    # Anchor head expects a multiple levels of features per image\n    feat = [\n        torch.rand(1, 1, s // (2**(i + 2)), s // (2**(i + 2)))\n        for i in range(1)\n    ]\n\n    proposal_list = [\n        torch.Tensor([[22.6667, 22.8757, 238.6326, 151.8874], [0, 3, 5, 7]])\n    ]\n\n    # Test that empty ground truth encourages the network to predict background\n    gt_bboxes = [torch.empty((0, 4))]\n    gt_labels = [torch.LongTensor([])]\n    gt_bboxes_ignore = None\n\n    empty_gt_losses = self.forward_train(feat, img_metas, proposal_list,\n                                         gt_bboxes, gt_labels,\n                                         gt_bboxes_ignore)\n\n    # When there is no truth, the cls loss should be nonzero but there should\n    # be no box loss.\n    empty_cls_loss = empty_gt_losses['loss_cls'].sum()\n    empty_box_loss = empty_gt_losses['loss_bbox'].sum()\n    assert empty_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert empty_box_loss.item() == 0, (\n        'there should be no box loss when there are no true boxes')\n\n    # When truth is non-empty then both cls and box loss should be nonzero for\n    # random inputs\n    gt_bboxes = [\n        torch.Tensor([[23.6667, 23.8757, 238.6326, 151.8874]]),\n    ]\n    gt_labels = [torch.LongTensor([2])]\n\n    one_gt_losses = self.forward_train(feat, img_metas, proposal_list,\n                                       gt_bboxes, gt_labels, gt_bboxes_ignore)\n    onegt_cls_loss = one_gt_losses['loss_cls'].sum()\n    onegt_box_loss = one_gt_losses['loss_bbox'].sum()\n    assert onegt_cls_loss.item() > 0, 'cls loss should be non-zero'\n    assert onegt_box_loss.item() > 0, 'box loss should be non-zero'\n"
  },
  {
    "path": "code/tests/test_roi_extractor.py",
    "content": "import pytest\nimport torch\n\nfrom mmdet.models.roi_heads.roi_extractors import GenericRoIExtractor\n\n\ndef test_groie():\n    # test with pre/post\n    cfg = dict(\n        roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),\n        out_channels=256,\n        featmap_strides=[4, 8, 16, 32],\n        pre_cfg=dict(\n            type='ConvModule',\n            in_channels=256,\n            out_channels=256,\n            kernel_size=5,\n            padding=2,\n            inplace=False,\n        ),\n        post_cfg=dict(\n            type='ConvModule',\n            in_channels=256,\n            out_channels=256,\n            kernel_size=5,\n            padding=2,\n            inplace=False))\n\n    groie = GenericRoIExtractor(**cfg)\n\n    feats = (\n        torch.rand((1, 256, 200, 336)),\n        torch.rand((1, 256, 100, 168)),\n        torch.rand((1, 256, 50, 84)),\n        torch.rand((1, 256, 25, 42)),\n    )\n\n    rois = torch.tensor([[0.0000, 587.8285, 52.1405, 886.2484, 341.5644]])\n\n    res = groie(feats, rois)\n    assert res.shape == torch.Size([1, 256, 7, 7])\n\n    # test w.o. pre/post\n    cfg = dict(\n        roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),\n        out_channels=256,\n        featmap_strides=[4, 8, 16, 32])\n\n    groie = GenericRoIExtractor(**cfg)\n\n    feats = (\n        torch.rand((1, 256, 200, 336)),\n        torch.rand((1, 256, 100, 168)),\n        torch.rand((1, 256, 50, 84)),\n        torch.rand((1, 256, 25, 42)),\n    )\n\n    rois = torch.tensor([[0.0000, 587.8285, 52.1405, 886.2484, 341.5644]])\n\n    res = groie(feats, rois)\n    assert res.shape == torch.Size([1, 256, 7, 7])\n\n    # test w.o. pre/post concat\n    cfg = dict(\n        aggregation='concat',\n        roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),\n        out_channels=256 * 4,\n        featmap_strides=[4, 8, 16, 32])\n\n    groie = GenericRoIExtractor(**cfg)\n\n    feats = (\n        torch.rand((1, 256, 200, 336)),\n        torch.rand((1, 256, 100, 168)),\n        torch.rand((1, 256, 50, 84)),\n        torch.rand((1, 256, 25, 42)),\n    )\n\n    rois = torch.tensor([[0.0000, 587.8285, 52.1405, 886.2484, 341.5644]])\n\n    res = groie(feats, rois)\n    assert res.shape == torch.Size([1, 1024, 7, 7])\n\n    # test not supported aggregate method\n    with pytest.raises(AssertionError):\n        cfg = dict(\n            aggregation='not support',\n            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),\n            out_channels=1024,\n            featmap_strides=[4, 8, 16, 32])\n        _ = GenericRoIExtractor(**cfg)\n\n    # test concat channels number\n    cfg = dict(\n        aggregation='concat',\n        roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),\n        out_channels=256 * 5,  # 256*5 != 256*4\n        featmap_strides=[4, 8, 16, 32])\n\n    groie = GenericRoIExtractor(**cfg)\n\n    feats = (\n        torch.rand((1, 256, 200, 336)),\n        torch.rand((1, 256, 100, 168)),\n        torch.rand((1, 256, 50, 84)),\n        torch.rand((1, 256, 25, 42)),\n    )\n\n    rois = torch.tensor([[0.0000, 587.8285, 52.1405, 886.2484, 341.5644]])\n\n    # out_channels does not sum of feat channels\n    with pytest.raises(AssertionError):\n        _ = groie(feats, rois)\n"
  },
  {
    "path": "code/tests/test_sampler.py",
    "content": "import torch\n\nfrom mmdet.core.bbox.assigners import MaxIoUAssigner\nfrom mmdet.core.bbox.samplers import (OHEMSampler, RandomSampler,\n                                      ScoreHLRSampler)\n\n\ndef test_random_sampler():\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_labels = torch.LongTensor([1, 2])\n    gt_bboxes_ignore = torch.Tensor([\n        [30, 30, 40, 40],\n    ])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n\n    sampler = RandomSampler(\n        num=10, pos_fraction=0.5, neg_pos_ub=-1, add_gt_as_proposals=True)\n\n    sample_result = sampler.sample(assign_result, bboxes, gt_bboxes, gt_labels)\n\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n\ndef test_random_sampler_empty_gt():\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.empty(0, 4)\n    gt_labels = torch.empty(0, ).long()\n    assign_result = assigner.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n\n    sampler = RandomSampler(\n        num=10, pos_fraction=0.5, neg_pos_ub=-1, add_gt_as_proposals=True)\n\n    sample_result = sampler.sample(assign_result, bboxes, gt_bboxes, gt_labels)\n\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n\ndef test_random_sampler_empty_pred():\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.empty(0, 4)\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_labels = torch.LongTensor([1, 2])\n    assign_result = assigner.assign(bboxes, gt_bboxes, gt_labels=gt_labels)\n\n    sampler = RandomSampler(\n        num=10, pos_fraction=0.5, neg_pos_ub=-1, add_gt_as_proposals=True)\n\n    sample_result = sampler.sample(assign_result, bboxes, gt_bboxes, gt_labels)\n\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n\ndef _context_for_ohem():\n    try:\n        from test_forward import _get_detector_cfg\n    except ImportError:\n        # Hack: grab testing utils from test_forward to make a context for ohem\n        import sys\n        from os.path import dirname\n        sys.path.insert(0, dirname(__file__))\n        from test_forward import _get_detector_cfg\n    model, train_cfg, test_cfg = _get_detector_cfg(\n        'faster_rcnn/faster_rcnn_r50_fpn_ohem_1x_coco.py')\n    model['pretrained'] = None\n\n    from mmdet.models import build_detector\n    context = build_detector(\n        model, train_cfg=train_cfg, test_cfg=test_cfg).roi_head\n    return context\n\n\ndef test_ohem_sampler():\n\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 9],\n        [0, 10, 10, 19],\n    ])\n    gt_labels = torch.LongTensor([1, 2])\n    gt_bboxes_ignore = torch.Tensor([\n        [30, 30, 40, 40],\n    ])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n\n    context = _context_for_ohem()\n\n    sampler = OHEMSampler(\n        num=10,\n        pos_fraction=0.5,\n        context=context,\n        neg_pos_ub=-1,\n        add_gt_as_proposals=True)\n\n    feats = [torch.rand(1, 256, int(2**i), int(2**i)) for i in [6, 5, 4, 3, 2]]\n    sample_result = sampler.sample(\n        assign_result, bboxes, gt_bboxes, gt_labels, feats=feats)\n\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n\ndef test_ohem_sampler_empty_gt():\n\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.empty(0, 4)\n    gt_labels = torch.LongTensor([])\n    gt_bboxes_ignore = torch.Tensor([])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n\n    context = _context_for_ohem()\n\n    sampler = OHEMSampler(\n        num=10,\n        pos_fraction=0.5,\n        context=context,\n        neg_pos_ub=-1,\n        add_gt_as_proposals=True)\n\n    feats = [torch.rand(1, 256, int(2**i), int(2**i)) for i in [6, 5, 4, 3, 2]]\n\n    sample_result = sampler.sample(\n        assign_result, bboxes, gt_bboxes, gt_labels, feats=feats)\n\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n\ndef test_ohem_sampler_empty_pred():\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    bboxes = torch.empty(0, 4)\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_labels = torch.LongTensor([1, 2, 2, 3])\n    gt_bboxes_ignore = torch.Tensor([])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n\n    context = _context_for_ohem()\n\n    sampler = OHEMSampler(\n        num=10,\n        pos_fraction=0.5,\n        context=context,\n        neg_pos_ub=-1,\n        add_gt_as_proposals=True)\n\n    feats = [torch.rand(1, 256, int(2**i), int(2**i)) for i in [6, 5, 4, 3, 2]]\n\n    sample_result = sampler.sample(\n        assign_result, bboxes, gt_bboxes, gt_labels, feats=feats)\n\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n\ndef test_random_sample_result():\n    from mmdet.core.bbox.samplers.sampling_result import SamplingResult\n    SamplingResult.random(num_gts=0, num_preds=0)\n    SamplingResult.random(num_gts=0, num_preds=3)\n    SamplingResult.random(num_gts=3, num_preds=3)\n    SamplingResult.random(num_gts=0, num_preds=3)\n    SamplingResult.random(num_gts=7, num_preds=7)\n    SamplingResult.random(num_gts=7, num_preds=64)\n    SamplingResult.random(num_gts=24, num_preds=3)\n\n    for i in range(3):\n        SamplingResult.random(rng=i)\n\n\ndef test_score_hlr_sampler_empty_pred():\n    assigner = MaxIoUAssigner(\n        pos_iou_thr=0.5,\n        neg_iou_thr=0.5,\n        ignore_iof_thr=0.5,\n        ignore_wrt_candidates=False,\n    )\n    context = _context_for_ohem()\n    sampler = ScoreHLRSampler(\n        num=10,\n        pos_fraction=0.5,\n        context=context,\n        neg_pos_ub=-1,\n        add_gt_as_proposals=True)\n    gt_bboxes_ignore = torch.Tensor([])\n    feats = [torch.rand(1, 256, int(2**i), int(2**i)) for i in [6, 5, 4, 3, 2]]\n\n    # empty bbox\n    bboxes = torch.empty(0, 4)\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_labels = torch.LongTensor([1, 2, 2, 3])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n    sample_result, _ = sampler.sample(\n        assign_result, bboxes, gt_bboxes, gt_labels, feats=feats)\n    assert len(sample_result.neg_inds) == 0\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n    # empty gt\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.empty(0, 4)\n    gt_labels = torch.LongTensor([])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n    sample_result, _ = sampler.sample(\n        assign_result, bboxes, gt_bboxes, gt_labels, feats=feats)\n    assert len(sample_result.pos_inds) == 0\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n\n    # non-empty input\n    bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_bboxes = torch.FloatTensor([\n        [0, 0, 10, 10],\n        [10, 10, 20, 20],\n        [5, 5, 15, 15],\n        [32, 32, 38, 42],\n    ])\n    gt_labels = torch.LongTensor([1, 2, 2, 3])\n    assign_result = assigner.assign(\n        bboxes,\n        gt_bboxes,\n        gt_bboxes_ignore=gt_bboxes_ignore,\n        gt_labels=gt_labels)\n    sample_result, _ = sampler.sample(\n        assign_result, bboxes, gt_bboxes, gt_labels, feats=feats)\n    assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds)\n    assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds)\n"
  },
  {
    "path": "code/tools/analyze_logs.py",
    "content": "import argparse\nimport json\nfrom collections import defaultdict\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\n\n\ndef cal_train_time(log_dicts, args):\n    for i, log_dict in enumerate(log_dicts):\n        print(f'{\"-\" * 5}Analyze train time of {args.json_logs[i]}{\"-\" * 5}')\n        all_times = []\n        for epoch in log_dict.keys():\n            if args.include_outliers:\n                all_times.append(log_dict[epoch]['time'])\n            else:\n                all_times.append(log_dict[epoch]['time'][1:])\n        all_times = np.array(all_times)\n        epoch_ave_time = all_times.mean(-1)\n        slowest_epoch = epoch_ave_time.argmax()\n        fastest_epoch = epoch_ave_time.argmin()\n        std_over_epoch = epoch_ave_time.std()\n        print(f'slowest epoch {slowest_epoch + 1}, '\n              f'average time is {epoch_ave_time[slowest_epoch]:.4f}')\n        print(f'fastest epoch {fastest_epoch + 1}, '\n              f'average time is {epoch_ave_time[fastest_epoch]:.4f}')\n        print(f'time std over epochs is {std_over_epoch:.4f}')\n        print(f'average iter time: {np.mean(all_times):.4f} s/iter')\n        print()\n\n\ndef plot_curve(log_dicts, args):\n    if args.backend is not None:\n        plt.switch_backend(args.backend)\n    sns.set_style(args.style)\n    # if legend is None, use {filename}_{key} as legend\n    legend = args.legend\n    if legend is None:\n        legend = []\n        for json_log in args.json_logs:\n            for metric in args.keys:\n                legend.append(f'{json_log}_{metric}')\n    assert len(legend) == (len(args.json_logs) * len(args.keys))\n    metrics = args.keys\n\n    num_metrics = len(metrics)\n    for i, log_dict in enumerate(log_dicts):\n        epochs = list(log_dict.keys())\n        for j, metric in enumerate(metrics):\n            print(f'plot curve of {args.json_logs[i]}, metric is {metric}')\n            if metric not in log_dict[epochs[0]]:\n                raise KeyError(\n                    f'{args.json_logs[i]} does not contain metric {metric}')\n\n            if 'mAP' in metric:\n                xs = np.arange(1, max(epochs) + 1)\n                ys = []\n                for epoch in epochs:\n                    ys += log_dict[epoch][metric]\n                ax = plt.gca()\n                ax.set_xticks(xs)\n                plt.xlabel('epoch')\n                plt.plot(xs, ys, label=legend[i * num_metrics + j], marker='o')\n            else:\n                xs = []\n                ys = []\n                num_iters_per_epoch = log_dict[epochs[0]]['iter'][-1]\n                for epoch in epochs:\n                    iters = log_dict[epoch]['iter']\n                    if log_dict[epoch]['mode'][-1] == 'val':\n                        iters = iters[:-1]\n                    xs.append(\n                        np.array(iters) + (epoch - 1) * num_iters_per_epoch)\n                    ys.append(np.array(log_dict[epoch][metric][:len(iters)]))\n                xs = np.concatenate(xs)\n                ys = np.concatenate(ys)\n                plt.xlabel('iter')\n                plt.plot(\n                    xs, ys, label=legend[i * num_metrics + j], linewidth=0.5)\n            plt.legend()\n        if args.title is not None:\n            plt.title(args.title)\n    if args.out is None:\n        plt.show()\n    else:\n        print(f'save curve to: {args.out}')\n        plt.savefig(args.out)\n        plt.cla()\n\n\ndef add_plot_parser(subparsers):\n    parser_plt = subparsers.add_parser(\n        'plot_curve', help='parser for plotting curves')\n    parser_plt.add_argument(\n        'json_logs',\n        type=str,\n        nargs='+',\n        help='path of train log in json format')\n    parser_plt.add_argument(\n        '--keys',\n        type=str,\n        nargs='+',\n        default=['bbox_mAP'],\n        help='the metric that you want to plot')\n    parser_plt.add_argument('--title', type=str, help='title of figure')\n    parser_plt.add_argument(\n        '--legend',\n        type=str,\n        nargs='+',\n        default=None,\n        help='legend of each plot')\n    parser_plt.add_argument(\n        '--backend', type=str, default=None, help='backend of plt')\n    parser_plt.add_argument(\n        '--style', type=str, default='dark', help='style of plt')\n    parser_plt.add_argument('--out', type=str, default=None)\n\n\ndef add_time_parser(subparsers):\n    parser_time = subparsers.add_parser(\n        'cal_train_time',\n        help='parser for computing the average time per training iteration')\n    parser_time.add_argument(\n        'json_logs',\n        type=str,\n        nargs='+',\n        help='path of train log in json format')\n    parser_time.add_argument(\n        '--include-outliers',\n        action='store_true',\n        help='include the first value of every epoch when computing '\n        'the average time')\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='Analyze Json Log')\n    # currently only support plot curve and calculate average train time\n    subparsers = parser.add_subparsers(dest='task', help='task parser')\n    add_plot_parser(subparsers)\n    add_time_parser(subparsers)\n    args = parser.parse_args()\n    return args\n\n\ndef load_json_logs(json_logs):\n    # load and convert json_logs to log_dict, key is epoch, value is a sub dict\n    # keys of sub dict is different metrics, e.g. memory, bbox_mAP\n    # value of sub dict is a list of corresponding values of all iterations\n    log_dicts = [dict() for _ in json_logs]\n    for json_log, log_dict in zip(json_logs, log_dicts):\n        with open(json_log, 'r') as log_file:\n            for line in log_file:\n                log = json.loads(line.strip())\n                # skip lines without `epoch` field\n                if 'epoch' not in log:\n                    continue\n                epoch = log.pop('epoch')\n                if epoch not in log_dict:\n                    log_dict[epoch] = defaultdict(list)\n                for k, v in log.items():\n                    log_dict[epoch][k].append(v)\n    return log_dicts\n\n\ndef main():\n    args = parse_args()\n\n    json_logs = args.json_logs\n    for json_log in json_logs:\n        assert json_log.endswith('.json')\n\n    log_dicts = load_json_logs(json_logs)\n\n    eval(args.task)(log_dicts, args)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/benchmark.py",
    "content": "import argparse\nimport time\n\nimport torch\nfrom mmcv import Config\nfrom mmcv.parallel import MMDataParallel\nfrom mmcv.runner import load_checkpoint\nfrom tools.fuse_conv_bn import fuse_module\n\nfrom mmdet.core import wrap_fp16_model\nfrom mmdet.datasets import build_dataloader, build_dataset\nfrom mmdet.models import build_detector\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='MMDet benchmark a model')\n    parser.add_argument('config', help='test config file path')\n    parser.add_argument('checkpoint', help='checkpoint file')\n    parser.add_argument(\n        '--log-interval', default=50, help='interval of logging')\n    parser.add_argument(\n        '--fuse-conv-bn',\n        action='store_true',\n        help='Whether to fuse conv and bn, this will slightly increase'\n        'the inference speed')\n    args = parser.parse_args()\n    return args\n\n\ndef main():\n    args = parse_args()\n\n    cfg = Config.fromfile(args.config)\n    # set cudnn_benchmark\n    if cfg.get('cudnn_benchmark', False):\n        torch.backends.cudnn.benchmark = True\n    cfg.model.pretrained = None\n    cfg.data.test.test_mode = True\n\n    # build the dataloader\n    # TODO: support multiple images per gpu (only minor changes are needed)\n    dataset = build_dataset(cfg.data.test)\n    data_loader = build_dataloader(\n        dataset,\n        samples_per_gpu=1,\n        workers_per_gpu=cfg.data.workers_per_gpu,\n        dist=False,\n        shuffle=False)\n\n    # build the model and load checkpoint\n    model = build_detector(cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)\n    fp16_cfg = cfg.get('fp16', None)\n    if fp16_cfg is not None:\n        wrap_fp16_model(model)\n    load_checkpoint(model, args.checkpoint, map_location='cpu')\n    if args.fuse_conv_bn:\n        model = fuse_module(model)\n\n    model = MMDataParallel(model, device_ids=[0])\n\n    model.eval()\n\n    # the first several iterations may be very slow so skip them\n    num_warmup = 5\n    pure_inf_time = 0\n\n    # benchmark with 2000 image and take the average\n    for i, data in enumerate(data_loader):\n\n        torch.cuda.synchronize()\n        start_time = time.perf_counter()\n\n        with torch.no_grad():\n            model(return_loss=False, rescale=True, **data)\n\n        torch.cuda.synchronize()\n        elapsed = time.perf_counter() - start_time\n\n        if i >= num_warmup:\n            pure_inf_time += elapsed\n            if (i + 1) % args.log_interval == 0:\n                fps = (i + 1 - num_warmup) / pure_inf_time\n                print(f'Done image [{i + 1:<3}/ 2000], fps: {fps:.1f} img / s')\n\n        if (i + 1) == 2000:\n            pure_inf_time += elapsed\n            fps = (i + 1 - num_warmup) / pure_inf_time\n            print(f'Overall fps: {fps:.1f} img / s')\n            break\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/browse_dataset.py",
    "content": "import argparse\nimport os\nfrom pathlib import Path\n\nimport mmcv\nfrom mmcv import Config\n\nfrom mmdet.datasets.builder import build_dataset\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='Browse a dataset')\n    parser.add_argument('config', help='train config file path')\n    parser.add_argument(\n        '--skip-type',\n        type=str,\n        nargs='+',\n        default=['DefaultFormatBundle', 'Normalize', 'Collect'],\n        help='skip some useless pipeline')\n    parser.add_argument(\n        '--output-dir',\n        default=None,\n        type=str,\n        help='If there is no display interface, you can save it')\n    parser.add_argument('--not-show', default=False, action='store_true')\n    parser.add_argument(\n        '--show-interval',\n        type=int,\n        default=999,\n        help='the interval of show (ms)')\n    args = parser.parse_args()\n    return args\n\n\ndef retrieve_data_cfg(config_path, skip_type):\n    cfg = Config.fromfile(config_path)\n    train_data_cfg = cfg.data.train\n    train_data_cfg['pipeline'] = [\n        x for x in train_data_cfg.pipeline if x['type'] not in skip_type\n    ]\n\n    return cfg\n\n\ndef main():\n    args = parse_args()\n    cfg = retrieve_data_cfg(args.config, args.skip_type)\n\n    dataset = build_dataset(cfg.data.train)\n\n    progress_bar = mmcv.ProgressBar(len(dataset))\n    for item in dataset:\n        filename = os.path.join(args.output_dir,\n                                Path(item['filename']).name\n                                ) if args.output_dir is not None else None\n        mmcv.imshow_det_bboxes(\n            item['img'],\n            item['gt_bboxes'],\n            item['gt_labels'] - 1,\n            class_names=dataset.CLASSES,\n            show=not args.not_show,\n            out_file=filename,\n            wait_time=args.show_interval)\n        progress_bar.update()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/coco_error_analysis.py",
    "content": "import copy\nimport os\nfrom argparse import ArgumentParser\nfrom multiprocessing import Pool\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom pycocotools.coco import COCO\nfrom pycocotools.cocoeval import COCOeval\n\n\ndef makeplot(rs, ps, outDir, class_name, iou_type):\n    cs = np.vstack([\n        np.ones((2, 3)),\n        np.array([.31, .51, .74]),\n        np.array([.75, .31, .30]),\n        np.array([.36, .90, .38]),\n        np.array([.50, .39, .64]),\n        np.array([1, .6, 0])\n    ])\n    areaNames = ['allarea', 'small', 'medium', 'large']\n    types = ['C75', 'C50', 'Loc', 'Sim', 'Oth', 'BG', 'FN']\n    for i in range(len(areaNames)):\n        area_ps = ps[..., i, 0]\n        figure_tile = iou_type + '-' + class_name + '-' + areaNames[i]\n        aps = [ps_.mean() for ps_ in area_ps]\n        ps_curve = [\n            ps_.mean(axis=1) if ps_.ndim > 1 else ps_ for ps_ in area_ps\n        ]\n        ps_curve.insert(0, np.zeros(ps_curve[0].shape))\n        fig = plt.figure()\n        ax = plt.subplot(111)\n        for k in range(len(types)):\n            ax.plot(rs, ps_curve[k + 1], color=[0, 0, 0], linewidth=0.5)\n            ax.fill_between(\n                rs,\n                ps_curve[k],\n                ps_curve[k + 1],\n                color=cs[k],\n                label=str(f'[{aps[k]:.3f}]' + types[k]))\n        plt.xlabel('recall')\n        plt.ylabel('precision')\n        plt.xlim(0, 1.)\n        plt.ylim(0, 1.)\n        plt.title(figure_tile)\n        plt.legend()\n        # plt.show()\n        fig.savefig(outDir + f'/{figure_tile}.png')\n        plt.close(fig)\n\n\ndef analyze_individual_category(k, cocoDt, cocoGt, catId, iou_type):\n    nm = cocoGt.loadCats(catId)[0]\n    print(f'--------------analyzing {k + 1}-{nm[\"name\"]}---------------')\n    ps_ = {}\n    dt = copy.deepcopy(cocoDt)\n    nm = cocoGt.loadCats(catId)[0]\n    imgIds = cocoGt.getImgIds()\n    dt_anns = dt.dataset['annotations']\n    select_dt_anns = []\n    for ann in dt_anns:\n        if ann['category_id'] == catId:\n            select_dt_anns.append(ann)\n    dt.dataset['annotations'] = select_dt_anns\n    dt.createIndex()\n    # compute precision but ignore superclass confusion\n    gt = copy.deepcopy(cocoGt)\n    child_catIds = gt.getCatIds(supNms=[nm['supercategory']])\n    for idx, ann in enumerate(gt.dataset['annotations']):\n        if (ann['category_id'] in child_catIds\n                and ann['category_id'] != catId):\n            gt.dataset['annotations'][idx]['ignore'] = 1\n            gt.dataset['annotations'][idx]['iscrowd'] = 1\n            gt.dataset['annotations'][idx]['category_id'] = catId\n    cocoEval = COCOeval(gt, copy.deepcopy(dt), iou_type)\n    cocoEval.params.imgIds = imgIds\n    cocoEval.params.maxDets = [100]\n    cocoEval.params.iouThrs = [.1]\n    cocoEval.params.useCats = 1\n    cocoEval.evaluate()\n    cocoEval.accumulate()\n    ps_supercategory = cocoEval.eval['precision'][0, :, k, :, :]\n    ps_['ps_supercategory'] = ps_supercategory\n    # compute precision but ignore any class confusion\n    gt = copy.deepcopy(cocoGt)\n    for idx, ann in enumerate(gt.dataset['annotations']):\n        if ann['category_id'] != catId:\n            gt.dataset['annotations'][idx]['ignore'] = 1\n            gt.dataset['annotations'][idx]['iscrowd'] = 1\n            gt.dataset['annotations'][idx]['category_id'] = catId\n    cocoEval = COCOeval(gt, copy.deepcopy(dt), iou_type)\n    cocoEval.params.imgIds = imgIds\n    cocoEval.params.maxDets = [100]\n    cocoEval.params.iouThrs = [.1]\n    cocoEval.params.useCats = 1\n    cocoEval.evaluate()\n    cocoEval.accumulate()\n    ps_allcategory = cocoEval.eval['precision'][0, :, k, :, :]\n    ps_['ps_allcategory'] = ps_allcategory\n    return k, ps_\n\n\ndef analyze_results(res_file, ann_file, res_types, out_dir):\n    for res_type in res_types:\n        assert res_type in ['bbox', 'segm']\n\n    directory = os.path.dirname(out_dir + '/')\n    if not os.path.exists(directory):\n        print(f'-------------create {out_dir}-----------------')\n        os.makedirs(directory)\n\n    cocoGt = COCO(ann_file)\n    cocoDt = cocoGt.loadRes(res_file)\n    imgIds = cocoGt.getImgIds()\n    for res_type in res_types:\n        res_out_dir = out_dir + '/' + res_type + '/'\n        res_directory = os.path.dirname(res_out_dir)\n        if not os.path.exists(res_directory):\n            print(f'-------------create {res_out_dir}-----------------')\n            os.makedirs(res_directory)\n        iou_type = res_type\n        cocoEval = COCOeval(\n            copy.deepcopy(cocoGt), copy.deepcopy(cocoDt), iou_type)\n        cocoEval.params.imgIds = imgIds\n        cocoEval.params.iouThrs = [.75, .5, .1]\n        cocoEval.params.maxDets = [100]\n        cocoEval.evaluate()\n        cocoEval.accumulate()\n        ps = cocoEval.eval['precision']\n        ps = np.vstack([ps, np.zeros((4, *ps.shape[1:]))])\n        catIds = cocoGt.getCatIds()\n        recThrs = cocoEval.params.recThrs\n        with Pool(processes=48) as pool:\n            args = [(k, cocoDt, cocoGt, catId, iou_type)\n                    for k, catId in enumerate(catIds)]\n            analyze_results = pool.starmap(analyze_individual_category, args)\n        for k, catId in enumerate(catIds):\n            nm = cocoGt.loadCats(catId)[0]\n            print(f'--------------saving {k + 1}-{nm[\"name\"]}---------------')\n            analyze_result = analyze_results[k]\n            assert k == analyze_result[0]\n            ps_supercategory = analyze_result[1]['ps_supercategory']\n            ps_allcategory = analyze_result[1]['ps_allcategory']\n            # compute precision but ignore superclass confusion\n            ps[3, :, k, :, :] = ps_supercategory\n            # compute precision but ignore any class confusion\n            ps[4, :, k, :, :] = ps_allcategory\n            # fill in background and false negative errors and plot\n            ps[ps == -1] = 0\n            ps[5, :, k, :, :] = (ps[4, :, k, :, :] > 0)\n            ps[6, :, k, :, :] = 1.0\n            makeplot(recThrs, ps[:, :, k], res_out_dir, nm['name'], iou_type)\n        makeplot(recThrs, ps, res_out_dir, 'allclass', iou_type)\n\n\ndef main():\n    parser = ArgumentParser(description='COCO Error Analysis Tool')\n    parser.add_argument('result', help='result file (json format) path')\n    parser.add_argument('out_dir', help='dir to save analyze result images')\n    parser.add_argument(\n        '--ann',\n        default='data/coco/annotations/instances_val2017.json',\n        help='annotation file path')\n    parser.add_argument(\n        '--types', type=str, nargs='+', default=['bbox'], help='result types')\n    args = parser.parse_args()\n    analyze_results(args.result, args.ann, args.types, out_dir=args.out_dir)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/convert_datasets/cityscapes.py",
    "content": "import argparse\nimport glob\nimport os.path as osp\n\nimport cityscapesscripts.helpers.labels as CSLabels\nimport mmcv\nimport numpy as np\nimport pycocotools.mask as maskUtils\n\n\ndef collect_files(img_dir, gt_dir):\n    suffix = 'leftImg8bit.png'\n    files = []\n    for img_file in glob.glob(osp.join(img_dir, '**/*.png')):\n        assert img_file.endswith(suffix), img_file\n        inst_file = gt_dir + img_file[\n            len(img_dir):-len(suffix)] + 'gtFine_instanceIds.png'\n        # Note that labelIds are not converted to trainId for seg map\n        segm_file = gt_dir + img_file[\n            len(img_dir):-len(suffix)] + 'gtFine_labelIds.png'\n        files.append((img_file, inst_file, segm_file))\n    assert len(files), f'No images found in {img_dir}'\n    print(f'Loaded {len(files)} images from {img_dir}')\n\n    return files\n\n\ndef collect_annotations(files, nproc=1):\n    print('Loading annotation images')\n    if nproc > 1:\n        images = mmcv.track_parallel_progress(\n            load_img_info, files, nproc=nproc)\n    else:\n        images = mmcv.track_progress(load_img_info, files)\n\n    return images\n\n\ndef load_img_info(files):\n    img_file, inst_file, segm_file = files\n    inst_img = mmcv.imread(inst_file, 'unchanged')\n    # ids < 24 are stuff labels (filtering them first is about 5% faster)\n    unique_inst_ids = np.unique(inst_img[inst_img >= 24])\n    anno_info = []\n    for inst_id in unique_inst_ids:\n        # For non-crowd annotations, inst_id // 1000 is the label_id\n        # Crowd annotations have <1000 instance ids\n        label_id = inst_id // 1000 if inst_id >= 1000 else inst_id\n        label = CSLabels.id2label[label_id]\n        if not label.hasInstances or label.ignoreInEval:\n            continue\n\n        category_id = label.id\n        iscrowd = int(inst_id < 1000)\n        mask = np.asarray(inst_img == inst_id, dtype=np.uint8, order='F')\n        mask_rle = maskUtils.encode(mask[:, :, None])[0]\n\n        area = maskUtils.area(mask_rle)\n        # convert to COCO style XYWH format\n        bbox = maskUtils.toBbox(mask_rle)\n\n        # for json encoding\n        mask_rle['counts'] = mask_rle['counts'].decode()\n\n        anno = dict(\n            iscrowd=iscrowd,\n            category_id=category_id,\n            bbox=bbox.tolist(),\n            area=area.tolist(),\n            segmentation=mask_rle)\n        anno_info.append(anno)\n    video_name = osp.basename(osp.dirname(img_file))\n    img_info = dict(\n        # remove img_prefix for filename\n        file_name=osp.join(video_name, osp.basename(img_file)),\n        height=inst_img.shape[0],\n        width=inst_img.shape[1],\n        anno_info=anno_info,\n        segm_file=osp.join(video_name, osp.basename(segm_file)))\n\n    return img_info\n\n\ndef cvt_annotations(image_infos, out_json_name):\n    out_json = dict()\n    img_id = 0\n    ann_id = 0\n    out_json['images'] = []\n    out_json['categories'] = []\n    out_json['annotations'] = []\n    for image_info in image_infos:\n        image_info['id'] = img_id\n        anno_infos = image_info.pop('anno_info')\n        out_json['images'].append(image_info)\n        for anno_info in anno_infos:\n            anno_info['image_id'] = img_id\n            anno_info['id'] = ann_id\n            out_json['annotations'].append(anno_info)\n            ann_id += 1\n        img_id += 1\n    for label in CSLabels.labels:\n        if label.hasInstances and not label.ignoreInEval:\n            cat = dict(id=label.id, name=label.name)\n            out_json['categories'].append(cat)\n\n    if len(out_json['annotations']) == 0:\n        out_json.pop('annotations')\n\n    mmcv.dump(out_json, out_json_name)\n    return out_json\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description='Convert Cityscapes annotations to COCO format')\n    parser.add_argument('cityscapes_path', help='cityscapes data path')\n    parser.add_argument('--img-dir', default='leftImg8bit', type=str)\n    parser.add_argument('--gt-dir', default='gtFine', type=str)\n    parser.add_argument('-o', '--out-dir', help='output path')\n    parser.add_argument(\n        '--nproc', default=1, type=int, help='number of process')\n    args = parser.parse_args()\n    return args\n\n\ndef main():\n    args = parse_args()\n    cityscapes_path = args.cityscapes_path\n    out_dir = args.out_dir if args.out_dir else cityscapes_path\n    mmcv.mkdir_or_exist(out_dir)\n\n    img_dir = osp.join(cityscapes_path, args.img_dir)\n    gt_dir = osp.join(cityscapes_path, args.gt_dir)\n\n    set_name = dict(\n        train='instancesonly_filtered_gtFine_train.json',\n        val='instancesonly_filtered_gtFine_val.json',\n        test='instancesonly_filtered_gtFine_test.json')\n\n    for split, json_name in set_name.items():\n        print(f'Converting {split} into {json_name}')\n        with mmcv.Timer(\n                print_tmpl='It tooks {}s to convert Cityscapes annotation'):\n            files = collect_files(\n                osp.join(img_dir, split), osp.join(gt_dir, split))\n            image_infos = collect_annotations(files, nproc=args.nproc)\n            cvt_annotations(image_infos, osp.join(out_dir, json_name))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/convert_datasets/pascal_voc.py",
    "content": "import argparse\nimport os.path as osp\nimport xml.etree.ElementTree as ET\n\nimport mmcv\nimport numpy as np\n\nfrom mmdet.core import voc_classes\n\nlabel_ids = {name: i for i, name in enumerate(voc_classes())}\n\n\ndef parse_xml(args):\n    xml_path, img_path = args\n    tree = ET.parse(xml_path)\n    root = tree.getroot()\n    size = root.find('size')\n    w = int(size.find('width').text)\n    h = int(size.find('height').text)\n    bboxes = []\n    labels = []\n    bboxes_ignore = []\n    labels_ignore = []\n    for obj in root.findall('object'):\n        name = obj.find('name').text\n        label = label_ids[name]\n        difficult = int(obj.find('difficult').text)\n        bnd_box = obj.find('bndbox')\n        bbox = [\n            int(bnd_box.find('xmin').text),\n            int(bnd_box.find('ymin').text),\n            int(bnd_box.find('xmax').text),\n            int(bnd_box.find('ymax').text)\n        ]\n        if difficult:\n            bboxes_ignore.append(bbox)\n            labels_ignore.append(label)\n        else:\n            bboxes.append(bbox)\n            labels.append(label)\n    if not bboxes:\n        bboxes = np.zeros((0, 4))\n        labels = np.zeros((0, ))\n    else:\n        bboxes = np.array(bboxes, ndmin=2) - 1\n        labels = np.array(labels)\n    if not bboxes_ignore:\n        bboxes_ignore = np.zeros((0, 4))\n        labels_ignore = np.zeros((0, ))\n    else:\n        bboxes_ignore = np.array(bboxes_ignore, ndmin=2) - 1\n        labels_ignore = np.array(labels_ignore)\n    annotation = {\n        'filename': img_path,\n        'width': w,\n        'height': h,\n        'ann': {\n            'bboxes': bboxes.astype(np.float32),\n            'labels': labels.astype(np.int64),\n            'bboxes_ignore': bboxes_ignore.astype(np.float32),\n            'labels_ignore': labels_ignore.astype(np.int64)\n        }\n    }\n    return annotation\n\n\ndef cvt_annotations(devkit_path, years, split, out_file):\n    if not isinstance(years, list):\n        years = [years]\n    annotations = []\n    for year in years:\n        filelist = osp.join(devkit_path,\n                            f'VOC{year}/ImageSets/Main/{split}.txt')\n        if not osp.isfile(filelist):\n            print(f'filelist does not exist: {filelist}, '\n                  f'skip voc{year} {split}')\n            return\n        img_names = mmcv.list_from_file(filelist)\n        xml_paths = [\n            osp.join(devkit_path, f'VOC{year}/Annotations/{img_name}.xml')\n            for img_name in img_names\n        ]\n        img_paths = [\n            f'VOC{year}/JPEGImages/{img_name}.jpg' for img_name in img_names\n        ]\n        part_annotations = mmcv.track_progress(parse_xml,\n                                               list(zip(xml_paths, img_paths)))\n        annotations.extend(part_annotations)\n    mmcv.dump(annotations, out_file)\n    return annotations\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description='Convert PASCAL VOC annotations to mmdetection format')\n    parser.add_argument('devkit_path', help='pascal voc devkit path')\n    parser.add_argument('-o', '--out-dir', help='output path')\n    args = parser.parse_args()\n    return args\n\n\ndef main():\n    args = parse_args()\n    devkit_path = args.devkit_path\n    out_dir = args.out_dir if args.out_dir else devkit_path\n    mmcv.mkdir_or_exist(out_dir)\n\n    years = []\n    if osp.isdir(osp.join(devkit_path, 'VOC2007')):\n        years.append('2007')\n    if osp.isdir(osp.join(devkit_path, 'VOC2012')):\n        years.append('2012')\n    if '2007' in years and '2012' in years:\n        years.append(['2007', '2012'])\n    if not years:\n        raise IOError(f'The devkit path {devkit_path} contains neither '\n                      '\"VOC2007\" nor \"VOC2012\" subfolder')\n    for year in years:\n        if year == '2007':\n            prefix = 'voc07'\n        elif year == '2012':\n            prefix = 'voc12'\n        elif year == ['2007', '2012']:\n            prefix = 'voc0712'\n        for split in ['train', 'val', 'trainval']:\n            dataset_name = prefix + '_' + split\n            print(f'processing {dataset_name} ...')\n            cvt_annotations(devkit_path, year, split,\n                            osp.join(out_dir, dataset_name + '.pkl'))\n        if not isinstance(year, list):\n            dataset_name = prefix + '_test'\n            print(f'processing {dataset_name} ...')\n            cvt_annotations(devkit_path, year, 'test',\n                            osp.join(out_dir, dataset_name + '.pkl'))\n    print('Done!')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/detectron2pytorch.py",
    "content": "import argparse\nfrom collections import OrderedDict\n\nimport mmcv\nimport torch\n\narch_settings = {50: (3, 4, 6, 3), 101: (3, 4, 23, 3)}\n\n\ndef convert_bn(blobs, state_dict, caffe_name, torch_name, converted_names):\n    # detectron replace bn with affine channel layer\n    state_dict[torch_name + '.bias'] = torch.from_numpy(blobs[caffe_name +\n                                                              '_b'])\n    state_dict[torch_name + '.weight'] = torch.from_numpy(blobs[caffe_name +\n                                                                '_s'])\n    bn_size = state_dict[torch_name + '.weight'].size()\n    state_dict[torch_name + '.running_mean'] = torch.zeros(bn_size)\n    state_dict[torch_name + '.running_var'] = torch.ones(bn_size)\n    converted_names.add(caffe_name + '_b')\n    converted_names.add(caffe_name + '_s')\n\n\ndef convert_conv_fc(blobs, state_dict, caffe_name, torch_name,\n                    converted_names):\n    state_dict[torch_name + '.weight'] = torch.from_numpy(blobs[caffe_name +\n                                                                '_w'])\n    converted_names.add(caffe_name + '_w')\n    if caffe_name + '_b' in blobs:\n        state_dict[torch_name + '.bias'] = torch.from_numpy(blobs[caffe_name +\n                                                                  '_b'])\n        converted_names.add(caffe_name + '_b')\n\n\ndef convert(src, dst, depth):\n    \"\"\"Convert keys in detectron pretrained ResNet models to pytorch style.\"\"\"\n    # load arch_settings\n    if depth not in arch_settings:\n        raise ValueError('Only support ResNet-50 and ResNet-101 currently')\n    block_nums = arch_settings[depth]\n    # load caffe model\n    caffe_model = mmcv.load(src, encoding='latin1')\n    blobs = caffe_model['blobs'] if 'blobs' in caffe_model else caffe_model\n    # convert to pytorch style\n    state_dict = OrderedDict()\n    converted_names = set()\n    convert_conv_fc(blobs, state_dict, 'conv1', 'conv1', converted_names)\n    convert_bn(blobs, state_dict, 'res_conv1_bn', 'bn1', converted_names)\n    for i in range(1, len(block_nums) + 1):\n        for j in range(block_nums[i - 1]):\n            if j == 0:\n                convert_conv_fc(blobs, state_dict, f'res{i + 1}_{j}_branch1',\n                                f'layer{i}.{j}.downsample.0', converted_names)\n                convert_bn(blobs, state_dict, f'res{i + 1}_{j}_branch1_bn',\n                           f'layer{i}.{j}.downsample.1', converted_names)\n            for k, letter in enumerate(['a', 'b', 'c']):\n                convert_conv_fc(blobs, state_dict,\n                                f'res{i + 1}_{j}_branch2{letter}',\n                                f'layer{i}.{j}.conv{k+1}', converted_names)\n                convert_bn(blobs, state_dict,\n                           f'res{i + 1}_{j}_branch2{letter}_bn',\n                           f'layer{i}.{j}.bn{k + 1}', converted_names)\n    # check if all layers are converted\n    for key in blobs:\n        if key not in converted_names:\n            print(f'Not Convert: {key}')\n    # save checkpoint\n    checkpoint = dict()\n    checkpoint['state_dict'] = state_dict\n    torch.save(checkpoint, dst)\n\n\ndef main():\n    parser = argparse.ArgumentParser(description='Convert model keys')\n    parser.add_argument('src', help='src detectron model path')\n    parser.add_argument('dst', help='save path')\n    parser.add_argument('depth', type=int, help='ResNet model depth')\n    args = parser.parse_args()\n    convert(args.src, args.dst, args.depth)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/dist_test.sh",
    "content": "#!/usr/bin/env bash\n\nCONFIG=$1\nCHECKPOINT=$2\nGPUS=$3\nPORT=${PORT:-29500}\n\nPYTHONPATH=\"$(dirname $0)/..\":$PYTHONPATH \\\npython -m torch.distributed.launch --nproc_per_node=$GPUS --master_port=$PORT \\\n    $(dirname \"$0\")/test.py $CONFIG $CHECKPOINT --launcher pytorch ${@:4}\n"
  },
  {
    "path": "code/tools/dist_train.sh",
    "content": "#!/usr/bin/env bash\n\nCONFIG=$1\nGPUS=$2\nPORT=${PORT:-29500}\n\nPYTHONPATH=\"$(dirname $0)/..\":$PYTHONPATH \\\npython -m torch.distributed.launch --nproc_per_node=$GPUS --master_port=$PORT \\\n    $(dirname \"$0\")/train.py $CONFIG --launcher pytorch ${@:3}\n"
  },
  {
    "path": "code/tools/fuse_conv_bn.py",
    "content": "import argparse\n\nimport torch\nimport torch.nn as nn\nfrom mmcv.runner import save_checkpoint\n\nfrom mmdet.apis import init_detector\n\n\ndef fuse_conv_bn(conv, bn):\n    \"\"\" During inference, the functionary of batch norm layers is turned off\n    but only the mean and var alone channels are used, which exposes the\n    chance to fuse it with the preceding conv layers to save computations and\n    simplify network structures.\n    \"\"\"\n    conv_w = conv.weight\n    conv_b = conv.bias if conv.bias is not None else torch.zeros_like(\n        bn.running_mean)\n\n    factor = bn.weight / torch.sqrt(bn.running_var + bn.eps)\n    conv.weight = nn.Parameter(conv_w *\n                               factor.reshape([conv.out_channels, 1, 1, 1]))\n    conv.bias = nn.Parameter((conv_b - bn.running_mean) * factor + bn.bias)\n    return conv\n\n\ndef fuse_module(m):\n    last_conv = None\n    last_conv_name = None\n\n    for name, child in m.named_children():\n        if isinstance(child, (nn.BatchNorm2d, nn.SyncBatchNorm)):\n            if last_conv is None:  # only fuse BN that is after Conv\n                continue\n            fused_conv = fuse_conv_bn(last_conv, child)\n            m._modules[last_conv_name] = fused_conv\n            # To reduce changes, set BN as Identity instead of deleting it.\n            m._modules[name] = nn.Identity()\n            last_conv = None\n        elif isinstance(child, nn.Conv2d):\n            last_conv = child\n            last_conv_name = name\n        else:\n            fuse_module(child)\n    return m\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description='fuse Conv and BN layers in a model')\n    parser.add_argument('config', help='config file path')\n    parser.add_argument('checkpoint', help='checkpoint file path')\n    parser.add_argument('out', help='output path of the converted model')\n    args = parser.parse_args()\n    return args\n\n\ndef main():\n    args = parse_args()\n    # build the model from a config file and a checkpoint file\n    model = init_detector(args.config, args.checkpoint)\n    # fuse conv and bn layers of the model\n    fused_model = fuse_module(model)\n    save_checkpoint(fused_model, args.out)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/gen_coco_lsvr.py",
    "content": "# code from: https://github.com/xingyizhou/ExtremeNet\n\nimport pdb\nimport pycocotools.coco as cocoapi\nimport sys\nimport cv2\nimport numpy as np\nimport pickle\nimport json\nSPLITS = ['val', 'train']\nANN_PATH = '/home/ma-user/work/coco/annotations/instances_{}2017.json'\nOUT_PATH = '/home/ma-user/work/coco/annotations/instances_lsvr_{}2017.json'\nIMG_DIR = '/home/ma-user/work/coco/images/{}2017/'\nDEBUG = False\nfrom scipy.spatial import ConvexHull\n\ndef _coco_box_to_bbox(box):\n  bbox = np.array([box[0], box[1], box[0] + box[2], box[1] + box[3]])\n  return bbox\n\ndef _get_extreme_points(pts):\n  l, t = min(pts[:, 0]), min(pts[:, 1])\n  r, b = max(pts[:, 0]), max(pts[:, 1])\n  # 3 degrees\n  thresh = 0.02\n  w = r - l + 1\n  h = b - t + 1\n  \n  pts = np.concatenate([pts[-1:], pts, pts[:1]], axis=0)\n  t_idx = np.argmin(pts[:, 1])\n  t_idxs = [t_idx]\n  tmp = t_idx + 1\n  while tmp < pts.shape[0] and pts[tmp, 1] - pts[t_idx, 1] <= thresh * h:\n    t_idxs.append(tmp)\n    tmp += 1\n  tmp = t_idx - 1\n  while tmp >= 0 and pts[tmp, 1] - pts[t_idx, 1] <= thresh * h:\n    t_idxs.append(tmp)\n    tmp -= 1\n  tt = [(max(pts[t_idxs, 0]) + min(pts[t_idxs, 0])) // 2, t]\n\n  b_idx = np.argmax(pts[:, 1])\n  b_idxs = [b_idx]\n  tmp = b_idx + 1\n  while tmp < pts.shape[0] and pts[b_idx, 1] - pts[tmp, 1] <= thresh * h:\n    b_idxs.append(tmp)\n    tmp += 1\n  tmp = b_idx - 1\n  while tmp >= 0 and pts[b_idx, 1] - pts[tmp, 1] <= thresh * h:\n    b_idxs.append(tmp)\n    tmp -= 1\n  bb = [(max(pts[b_idxs, 0]) + min(pts[b_idxs, 0])) // 2, b]\n\n  l_idx = np.argmin(pts[:, 0])\n  l_idxs = [l_idx]\n  tmp = l_idx + 1\n  while tmp < pts.shape[0] and pts[tmp, 0] - pts[l_idx, 0] <= thresh * w:\n    l_idxs.append(tmp)\n    tmp += 1\n  tmp = l_idx - 1\n  while tmp >= 0 and pts[tmp, 0] - pts[l_idx, 0] <= thresh * w:\n    l_idxs.append(tmp)\n    tmp -= 1\n  ll = [l, (max(pts[l_idxs, 1]) + min(pts[l_idxs, 1])) // 2]\n\n  r_idx = np.argmax(pts[:, 0])\n  r_idxs = [r_idx]\n  tmp = r_idx + 1\n  while tmp < pts.shape[0] and pts[r_idx, 0] - pts[tmp, 0] <= thresh * w:\n    r_idxs.append(tmp)\n    tmp += 1\n  tmp = r_idx - 1\n  while tmp >= 0 and pts[r_idx, 0] - pts[tmp, 0] <= thresh * w:\n    r_idxs.append(tmp)\n    tmp -= 1\n  rr = [r, (max(pts[r_idxs, 1]) + min(pts[r_idxs, 1])) // 2]\n\n  return np.array([tt, ll, bb, rr])\n\nif __name__ == '__main__':\n  for split in SPLITS:\n    data = json.load(open(ANN_PATH.format(split), 'r'))\n    coco = cocoapi.COCO(ANN_PATH.format(split))\n    img_ids = coco.getImgIds()\n    num_images = len(img_ids)\n    num_classes = 80\n    tot_box = 0\n    print('num_images', num_images)\n    anns_all = data['annotations']\n    for i, ann in enumerate(anns_all):\n      tot_box += 1\n      bbox = _coco_box_to_bbox(ann['bbox']) \n      seg = ann['segmentation']\n      if type(seg) == list:\n        if len(seg) == 1:\n          pts = np.array(seg[0]).reshape(-1, 2)\n        else:\n          pts = []\n          for v in seg:\n            pts += v\n          pts = np.array(pts).reshape(-1, 2)\n      else:\n        mask = coco.annToMask(ann) * 255\n        tmp = np.where(mask > 0)\n        pts = np.asarray(tmp).transpose()[:, ::-1].astype(np.int32)\n\n      extreme_points = _get_extreme_points(pts).reshape(-1)\n      xct = (bbox[2:3] + bbox[0:1])/2.\n      yct = (bbox[3:4] + bbox[1:2])/2.\n      extreme_points = np.concatenate((extreme_points, xct, yct), axis=0)\n      anns_all[i]['extreme_points'] = extreme_points.copy().tolist()\n      \n    print('tot_box', tot_box)   \n    data['annotations'] = anns_all\n    json.dump(data, open(OUT_PATH.format(split), 'w'))\n  \n"
  },
  {
    "path": "code/tools/get_flops.py",
    "content": "import argparse\n\nimport torch\nfrom mmcv import Config\n\nfrom mmdet.models import build_detector\n\ntry:\n    from mmcv.cnn import get_model_complexity_info\nexcept ImportError:\n    raise ImportError('Please upgrade mmcv to >0.6.2')\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='Train a detector')\n    parser.add_argument('config', help='train config file path')\n    parser.add_argument(\n        '--shape',\n        type=int,\n        nargs='+',\n        default=[1280, 800],\n        help='input image size')\n    args = parser.parse_args()\n    return args\n\n\ndef main():\n\n    args = parse_args()\n\n    if len(args.shape) == 1:\n        input_shape = (3, args.shape[0], args.shape[0])\n    elif len(args.shape) == 2:\n        input_shape = (3, ) + tuple(args.shape)\n    else:\n        raise ValueError('invalid input shape')\n\n    cfg = Config.fromfile(args.config)\n    model = build_detector(\n        cfg.model, train_cfg=cfg.train_cfg, test_cfg=cfg.test_cfg)\n    if torch.cuda.is_available():\n        model.cuda()\n    model.eval()\n\n    if hasattr(model, 'forward_dummy'):\n        model.forward = model.forward_dummy\n    else:\n        raise NotImplementedError(\n            'FLOPs counter is currently not currently supported with {}'.\n            format(model.__class__.__name__))\n\n    flops, params = get_model_complexity_info(model, input_shape)\n    split_line = '=' * 30\n    print(f'{split_line}\\nInput shape: {input_shape}\\n'\n          f'Flops: {flops}\\nParams: {params}\\n{split_line}')\n    print('!!!Please be cautious if you use the results in papers. '\n          'You may need to check if all ops are supported and verify that the '\n          'flops computation is correct.')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/print_config.py",
    "content": "import argparse\n\nfrom mmcv import Config, DictAction\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='Print the whole config')\n    parser.add_argument('config', help='config file path')\n    parser.add_argument(\n        '--options', nargs='+', action=DictAction, help='arguments in dict')\n    args = parser.parse_args()\n\n    return args\n\n\ndef main():\n    args = parse_args()\n\n    cfg = Config.fromfile(args.config)\n    if args.options is not None:\n        cfg.merge_from_dict(args.options)\n    print(f'Config:\\n{cfg.pretty_text}')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/publish_model.py",
    "content": "import argparse\nimport subprocess\n\nimport torch\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description='Process a checkpoint to be published')\n    parser.add_argument('in_file', help='input checkpoint filename')\n    parser.add_argument('out_file', help='output checkpoint filename')\n    args = parser.parse_args()\n    return args\n\n\ndef process_checkpoint(in_file, out_file):\n    checkpoint = torch.load(in_file, map_location='cpu')\n    # remove optimizer for smaller file size\n    if 'optimizer' in checkpoint:\n        del checkpoint['optimizer']\n    # if it is necessary to remove some sensitive data in checkpoint['meta'],\n    # add the code here.\n    torch.save(checkpoint, out_file)\n    sha = subprocess.check_output(['sha256sum', out_file]).decode()\n    if out_file.endswith('.pth'):\n        out_file = out_file[:-4]\n    final_file = out_file + f'-{sha[:8]}.pth'\n    subprocess.Popen(['mv', out_file, final_file])\n\n\ndef main():\n    args = parse_args()\n    process_checkpoint(args.in_file, args.out_file)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/pytorch2onnx.py",
    "content": "import argparse\nimport io\n\nimport mmcv\nimport onnx\nimport torch\nfrom mmcv.runner import load_checkpoint\nfrom onnx import optimizer\nfrom torch.onnx import OperatorExportTypes\n\nfrom mmdet.models import build_detector\nfrom mmdet.ops import RoIAlign, RoIPool\n\n\ndef export_onnx_model(model, inputs, passes):\n    \"\"\"\n    Trace and export a model to onnx format.\n    Modified from https://github.com/facebookresearch/detectron2/\n\n    Args:\n        model (nn.Module):\n        inputs (tuple[args]): the model will be called by `model(*inputs)`\n        passes (None or list[str]): the optimization passed for ONNX model\n\n    Returns:\n        an onnx model\n    \"\"\"\n    assert isinstance(model, torch.nn.Module)\n\n    # make sure all modules are in eval mode, onnx may change the training\n    # state of the module if the states are not consistent\n    def _check_eval(module):\n        assert not module.training\n\n    model.apply(_check_eval)\n\n    # Export the model to ONNX\n    with torch.no_grad():\n        with io.BytesIO() as f:\n            torch.onnx.export(\n                model,\n                inputs,\n                f,\n                operator_export_type=OperatorExportTypes.ONNX_ATEN_FALLBACK,\n                # verbose=True,  # NOTE: uncomment this for debugging\n                # export_params=True,\n            )\n            onnx_model = onnx.load_from_string(f.getvalue())\n\n    # Apply ONNX's Optimization\n    if passes is not None:\n        all_passes = optimizer.get_available_passes()\n        assert all(p in all_passes for p in passes), \\\n            f'Only {all_passes} are supported'\n    onnx_model = optimizer.optimize(onnx_model, passes)\n    return onnx_model\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description='MMDet pytorch model conversion to ONNX')\n    parser.add_argument('config', help='test config file path')\n    parser.add_argument('checkpoint', help='checkpoint file')\n    parser.add_argument(\n        '--out', type=str, required=True, help='output ONNX filename')\n    parser.add_argument(\n        '--shape',\n        type=int,\n        nargs='+',\n        default=[1280, 800],\n        help='input image size')\n    parser.add_argument(\n        '--passes', type=str, nargs='+', help='ONNX optimization passes')\n    args = parser.parse_args()\n    return args\n\n\ndef main():\n    args = parse_args()\n\n    if not args.out.endswith('.onnx'):\n        raise ValueError('The output file must be a onnx file.')\n\n    if len(args.shape) == 1:\n        input_shape = (3, args.shape[0], args.shape[0])\n    elif len(args.shape) == 2:\n        input_shape = (3, ) + tuple(args.shape)\n    else:\n        raise ValueError('invalid input shape')\n\n    cfg = mmcv.Config.fromfile(args.config)\n    cfg.model.pretrained = None\n\n    # build the model and load checkpoint\n    model = build_detector(cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)\n    load_checkpoint(model, args.checkpoint, map_location='cpu')\n    # Only support CPU mode for now\n    model.cpu().eval()\n    # Customized ops are not supported, use torchvision ops instead.\n    for m in model.modules():\n        if isinstance(m, (RoIPool, RoIAlign)):\n            # set use_torchvision on-the-fly\n            m.use_torchvision = True\n\n    # TODO: a better way to override forward function\n    if hasattr(model, 'forward_dummy'):\n        model.forward = model.forward_dummy\n    else:\n        raise NotImplementedError(\n            'ONNX conversion is currently not currently supported with '\n            f'{model.__class__.__name__}')\n\n    input_data = torch.empty((1, *input_shape),\n                             dtype=next(model.parameters()).dtype,\n                             device=next(model.parameters()).device)\n\n    onnx_model = export_onnx_model(model, (input_data, ), args.passes)\n    # Print a human readable representation of the graph\n    onnx.helper.printable_graph(onnx_model.graph)\n    print(f'saving model in {args.out}')\n    onnx.save(onnx_model, args.out)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/regnet2mmdet.py",
    "content": "import argparse\nfrom collections import OrderedDict\n\nimport torch\n\n\ndef convert_stem(model_key, model_weight, state_dict, converted_names):\n    new_key = model_key.replace('stem.conv', 'conv1')\n    new_key = new_key.replace('stem.bn', 'bn1')\n    state_dict[new_key] = model_weight\n    converted_names.add(model_key)\n    print(f'Convert {model_key} to {new_key}')\n\n\ndef convert_head(model_key, model_weight, state_dict, converted_names):\n    new_key = model_key.replace('head.fc', 'fc')\n    state_dict[new_key] = model_weight\n    converted_names.add(model_key)\n    print(f'Convert {model_key} to {new_key}')\n\n\ndef convert_reslayer(model_key, model_weight, state_dict, converted_names):\n    split_keys = model_key.split('.')\n    layer, block, module = split_keys[:3]\n    block_id = int(block[1:])\n    layer_name = f'layer{int(layer[1:])}'\n    block_name = f'{block_id - 1}'\n\n    if block_id == 1 and module == 'bn':\n        new_key = f'{layer_name}.{block_name}.downsample.1.{split_keys[-1]}'\n    elif block_id == 1 and module == 'proj':\n        new_key = f'{layer_name}.{block_name}.downsample.0.{split_keys[-1]}'\n    elif module == 'f':\n        if split_keys[3] == 'a_bn':\n            module_name = 'bn1'\n        elif split_keys[3] == 'b_bn':\n            module_name = 'bn2'\n        elif split_keys[3] == 'c_bn':\n            module_name = 'bn3'\n        elif split_keys[3] == 'a':\n            module_name = 'conv1'\n        elif split_keys[3] == 'b':\n            module_name = 'conv2'\n        elif split_keys[3] == 'c':\n            module_name = 'conv3'\n        new_key = f'{layer_name}.{block_name}.{module_name}.{split_keys[-1]}'\n    else:\n        raise ValueError(f'Unsupported conversion of key {model_key}')\n    print(f'Convert {model_key} to {new_key}')\n    state_dict[new_key] = model_weight\n    converted_names.add(model_key)\n\n\ndef convert(src, dst):\n    \"\"\"Convert keys in pycls pretrained RegNet models to mmdet style.\"\"\"\n    # load caffe model\n    regnet_model = torch.load(src)\n    blobs = regnet_model['model_state']\n    # convert to pytorch style\n    state_dict = OrderedDict()\n    converted_names = set()\n    for key, weight in blobs.items():\n        if 'stem' in key:\n            convert_stem(key, weight, state_dict, converted_names)\n        elif 'head' in key:\n            convert_head(key, weight, state_dict, converted_names)\n        elif key.startswith('s'):\n            convert_reslayer(key, weight, state_dict, converted_names)\n\n    # check if all layers are converted\n    for key in blobs:\n        if key not in converted_names:\n            print(f'not converted: {key}')\n    # save checkpoint\n    checkpoint = dict()\n    checkpoint['state_dict'] = state_dict\n    torch.save(checkpoint, dst)\n\n\ndef main():\n    parser = argparse.ArgumentParser(description='Convert model keys')\n    parser.add_argument('src', help='src detectron model path')\n    parser.add_argument('dst', help='save path')\n    args = parser.parse_args()\n    convert(args.src, args.dst)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/robustness_eval.py",
    "content": "import os.path as osp\nfrom argparse import ArgumentParser\n\nimport mmcv\nimport numpy as np\n\n\ndef print_coco_results(results):\n\n    def _print(result, ap=1, iouThr=None, areaRng='all', maxDets=100):\n        titleStr = 'Average Precision' if ap == 1 else 'Average Recall'\n        typeStr = '(AP)' if ap == 1 else '(AR)'\n        iouStr = '0.50:0.95' \\\n            if iouThr is None else f'{iouThr:0.2f}'\n        iStr = f' {titleStr:<18} {typeStr} @[ IoU={iouStr:<9} | '\n        iStr += f'area={areaRng:>6s} | maxDets={maxDets:>3d} ] = {result:0.3f}'\n        print(iStr)\n\n    stats = np.zeros((12, ))\n    stats[0] = _print(results[0], 1)\n    stats[1] = _print(results[1], 1, iouThr=.5)\n    stats[2] = _print(results[2], 1, iouThr=.75)\n    stats[3] = _print(results[3], 1, areaRng='small')\n    stats[4] = _print(results[4], 1, areaRng='medium')\n    stats[5] = _print(results[5], 1, areaRng='large')\n    stats[6] = _print(results[6], 0, maxDets=1)\n    stats[7] = _print(results[7], 0, maxDets=10)\n    stats[8] = _print(results[8], 0)\n    stats[9] = _print(results[9], 0, areaRng='small')\n    stats[10] = _print(results[10], 0, areaRng='medium')\n    stats[11] = _print(results[11], 0, areaRng='large')\n\n\ndef get_coco_style_results(filename,\n                           task='bbox',\n                           metric=None,\n                           prints='mPC',\n                           aggregate='benchmark'):\n\n    assert aggregate in ['benchmark', 'all']\n\n    if prints == 'all':\n        prints = ['P', 'mPC', 'rPC']\n    elif isinstance(prints, str):\n        prints = [prints]\n    for p in prints:\n        assert p in ['P', 'mPC', 'rPC']\n\n    if metric is None:\n        metrics = [\n            'AP', 'AP50', 'AP75', 'APs', 'APm', 'APl', 'AR1', 'AR10', 'AR100',\n            'ARs', 'ARm', 'ARl'\n        ]\n    elif isinstance(metric, list):\n        metrics = metric\n    else:\n        metrics = [metric]\n\n    for metric_name in metrics:\n        assert metric_name in [\n            'AP', 'AP50', 'AP75', 'APs', 'APm', 'APl', 'AR1', 'AR10', 'AR100',\n            'ARs', 'ARm', 'ARl'\n        ]\n\n    eval_output = mmcv.load(filename)\n\n    num_distortions = len(list(eval_output.keys()))\n    results = np.zeros((num_distortions, 6, len(metrics)), dtype='float32')\n\n    for corr_i, distortion in enumerate(eval_output):\n        for severity in eval_output[distortion]:\n            for metric_j, metric_name in enumerate(metrics):\n                mAP = eval_output[distortion][severity][task][metric_name]\n                results[corr_i, severity, metric_j] = mAP\n\n    P = results[0, 0, :]\n    if aggregate == 'benchmark':\n        mPC = np.mean(results[:15, 1:, :], axis=(0, 1))\n    else:\n        mPC = np.mean(results[:, 1:, :], axis=(0, 1))\n    rPC = mPC / P\n\n    print(f'\\nmodel: {osp.basename(filename)}')\n    if metric is None:\n        if 'P' in prints:\n            print(f'Performance on Clean Data [P] ({task})')\n            print_coco_results(P)\n        if 'mPC' in prints:\n            print(f'Mean Performance under Corruption [mPC] ({task})')\n            print_coco_results(mPC)\n        if 'rPC' in prints:\n            print(f'Realtive Performance under Corruption [rPC] ({task})')\n            print_coco_results(rPC)\n    else:\n        if 'P' in prints:\n            print(f'Performance on Clean Data [P] ({task})')\n            for metric_i, metric_name in enumerate(metrics):\n                print(f'{metric_name:5} =  {P[metric_i]:0.3f}')\n        if 'mPC' in prints:\n            print(f'Mean Performance under Corruption [mPC] ({task})')\n            for metric_i, metric_name in enumerate(metrics):\n                print(f'{metric_name:5} =  {mPC[metric_i]:0.3f}')\n        if 'rPC' in prints:\n            print(f'Relative Performance under Corruption [rPC] ({task})')\n            for metric_i, metric_name in enumerate(metrics):\n                print(f'{metric_name:5} => {rPC[metric_i] * 100:0.1f} %')\n\n    return results\n\n\ndef get_voc_style_results(filename, prints='mPC', aggregate='benchmark'):\n\n    assert aggregate in ['benchmark', 'all']\n\n    if prints == 'all':\n        prints = ['P', 'mPC', 'rPC']\n    elif isinstance(prints, str):\n        prints = [prints]\n    for p in prints:\n        assert p in ['P', 'mPC', 'rPC']\n\n    eval_output = mmcv.load(filename)\n\n    num_distortions = len(list(eval_output.keys()))\n    results = np.zeros((num_distortions, 6, 20), dtype='float32')\n\n    for i, distortion in enumerate(eval_output):\n        for severity in eval_output[distortion]:\n            mAP = [\n                eval_output[distortion][severity][j]['ap']\n                for j in range(len(eval_output[distortion][severity]))\n            ]\n            results[i, severity, :] = mAP\n\n    P = results[0, 0, :]\n    if aggregate == 'benchmark':\n        mPC = np.mean(results[:15, 1:, :], axis=(0, 1))\n    else:\n        mPC = np.mean(results[:, 1:, :], axis=(0, 1))\n    rPC = mPC / P\n\n    print(f'\\nmodel: {osp.basename(filename)}')\n    if 'P' in prints:\n        print(f'Performance on Clean Data [P] in AP50 = {np.mean(P):0.3f}')\n    if 'mPC' in prints:\n        print('Mean Performance under Corruption [mPC] in AP50 = '\n              f'{np.mean(mPC):0.3f}')\n    if 'rPC' in prints:\n        print('Realtive Performance under Corruption [rPC] in % = '\n              f'{np.mean(rPC) * 100:0.1f}')\n\n    return np.mean(results, axis=2, keepdims=True)\n\n\ndef get_results(filename,\n                dataset='coco',\n                task='bbox',\n                metric=None,\n                prints='mPC',\n                aggregate='benchmark'):\n    assert dataset in ['coco', 'voc', 'cityscapes']\n\n    if dataset in ['coco', 'cityscapes']:\n        results = get_coco_style_results(\n            filename,\n            task=task,\n            metric=metric,\n            prints=prints,\n            aggregate=aggregate)\n    elif dataset == 'voc':\n        if task != 'bbox':\n            print('Only bbox analysis is supported for Pascal VOC')\n            print('Will report bbox results\\n')\n        if metric not in [None, ['AP'], ['AP50']]:\n            print('Only the AP50 metric is supported for Pascal VOC')\n            print('Will report AP50 metric\\n')\n        results = get_voc_style_results(\n            filename, prints=prints, aggregate=aggregate)\n\n    return results\n\n\ndef get_distortions_from_file(filename):\n\n    eval_output = mmcv.load(filename)\n\n    return get_distortions_from_results(eval_output)\n\n\ndef get_distortions_from_results(eval_output):\n    distortions = []\n    for i, distortion in enumerate(eval_output):\n        distortions.append(distortion.replace('_', ' '))\n    return distortions\n\n\ndef main():\n    parser = ArgumentParser(description='Corruption Result Analysis')\n    parser.add_argument('filename', help='result file path')\n    parser.add_argument(\n        '--dataset',\n        type=str,\n        choices=['coco', 'voc', 'cityscapes'],\n        default='coco',\n        help='dataset type')\n    parser.add_argument(\n        '--task',\n        type=str,\n        nargs='+',\n        choices=['bbox', 'segm'],\n        default=['bbox'],\n        help='task to report')\n    parser.add_argument(\n        '--metric',\n        nargs='+',\n        choices=[\n            None, 'AP', 'AP50', 'AP75', 'APs', 'APm', 'APl', 'AR1', 'AR10',\n            'AR100', 'ARs', 'ARm', 'ARl'\n        ],\n        default=None,\n        help='metric to report')\n    parser.add_argument(\n        '--prints',\n        type=str,\n        nargs='+',\n        choices=['P', 'mPC', 'rPC'],\n        default='mPC',\n        help='corruption benchmark metric to print')\n    parser.add_argument(\n        '--aggregate',\n        type=str,\n        choices=['all', 'benchmark'],\n        default='benchmark',\n        help='aggregate all results or only those \\\n        for benchmark corruptions')\n\n    args = parser.parse_args()\n\n    for task in args.task:\n        get_results(\n            args.filename,\n            dataset=args.dataset,\n            task=task,\n            metric=args.metric,\n            prints=args.prints,\n            aggregate=args.aggregate)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/slurm_test.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\n\nPARTITION=$1\nJOB_NAME=$2\nCONFIG=$3\nCHECKPOINT=$4\nGPUS=${GPUS:-8}\nGPUS_PER_NODE=${GPUS_PER_NODE:-8}\nCPUS_PER_TASK=${CPUS_PER_TASK:-5}\nPY_ARGS=${@:5}\nSRUN_ARGS=${SRUN_ARGS:-\"\"}\n\nPYTHONPATH=\"$(dirname $0)/..\":$PYTHONPATH \\\nsrun -p ${PARTITION} \\\n    --job-name=${JOB_NAME} \\\n    --gres=gpu:${GPUS_PER_NODE} \\\n    --ntasks=${GPUS} \\\n    --ntasks-per-node=${GPUS_PER_NODE} \\\n    --cpus-per-task=${CPUS_PER_TASK} \\\n    --kill-on-bad-exit=1 \\\n    ${SRUN_ARGS} \\\n    python -u tools/test.py ${CONFIG} ${CHECKPOINT} --launcher=\"slurm\" ${PY_ARGS}\n"
  },
  {
    "path": "code/tools/slurm_train.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\n\nPARTITION=$1\nJOB_NAME=$2\nCONFIG=$3\nWORK_DIR=$4\nGPUS=${GPUS:-8}\nGPUS_PER_NODE=${GPUS_PER_NODE:-8}\nCPUS_PER_TASK=${CPUS_PER_TASK:-5}\nSRUN_ARGS=${SRUN_ARGS:-\"\"}\nPY_ARGS=${@:5}\n\nPYTHONPATH=\"$(dirname $0)/..\":$PYTHONPATH \\\nsrun -p ${PARTITION} \\\n    --job-name=${JOB_NAME} \\\n    --gres=gpu:${GPUS_PER_NODE} \\\n    --ntasks=${GPUS} \\\n    --ntasks-per-node=${GPUS_PER_NODE} \\\n    --cpus-per-task=${CPUS_PER_TASK} \\\n    --kill-on-bad-exit=1 \\\n    ${SRUN_ARGS} \\\n    python -u tools/train.py ${CONFIG} --work-dir=${WORK_DIR} --launcher=\"slurm\" ${PY_ARGS}\n"
  },
  {
    "path": "code/tools/test.py",
    "content": "import argparse\nimport os\n\nimport mmcv\nimport torch\nfrom mmcv import Config, DictAction\nfrom mmcv.parallel import MMDataParallel, MMDistributedDataParallel\nfrom mmcv.runner import get_dist_info, init_dist, load_checkpoint\nfrom tools.fuse_conv_bn import fuse_module\n\nfrom mmdet.apis import multi_gpu_test, single_gpu_test\nfrom mmdet.core import wrap_fp16_model\nfrom mmdet.datasets import build_dataloader, build_dataset\nfrom mmdet.models import build_detector\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description='MMDet test (and eval) a model')\n    parser.add_argument('config', help='test config file path')\n    parser.add_argument('checkpoint', help='checkpoint file')\n    parser.add_argument('--out', help='output result file in pickle format')\n    parser.add_argument(\n        '--fuse-conv-bn',\n        action='store_true',\n        help='Whether to fuse conv and bn, this will slightly increase'\n        'the inference speed')\n    parser.add_argument(\n        '--format-only',\n        action='store_true',\n        help='Format the output results without perform evaluation. It is'\n        'useful when you want to format the result to a specific format and '\n        'submit it to the test server')\n    parser.add_argument(\n        '--eval',\n        type=str,\n        nargs='+',\n        help='evaluation metrics, which depends on the dataset, e.g., \"bbox\",'\n        ' \"segm\", \"proposal\" for COCO, and \"mAP\", \"recall\" for PASCAL VOC')\n    parser.add_argument('--show', action='store_true', help='show results')\n    parser.add_argument(\n        '--show-dir', help='directory where painted images will be saved')\n    parser.add_argument(\n        '--show-score-thr',\n        type=float,\n        default=0.3,\n        help='score threshold (default: 0.3)')\n    parser.add_argument(\n        '--gpu-collect',\n        action='store_true',\n        help='whether to use gpu to collect results.')\n    parser.add_argument(\n        '--tmpdir',\n        help='tmp directory used for collecting results from multiple '\n        'workers, available when gpu-collect is not specified')\n    parser.add_argument(\n        '--options', nargs='+', action=DictAction, help='arguments in dict')\n    parser.add_argument(\n        '--launcher',\n        choices=['none', 'pytorch', 'slurm', 'mpi'],\n        default='none',\n        help='job launcher')\n    parser.add_argument('--local_rank', type=int, default=0)\n    args = parser.parse_args()\n    if 'LOCAL_RANK' not in os.environ:\n        os.environ['LOCAL_RANK'] = str(args.local_rank)\n    return args\n\n\ndef main():\n    args = parse_args()\n\n    assert args.out or args.eval or args.format_only or args.show \\\n        or args.show_dir, \\\n        ('Please specify at least one operation (save/eval/format/show the '\n         'results / save the results) with the argument \"--out\", \"--eval\"'\n         ', \"--format-only\", \"--show\" or \"--show-dir\"')\n\n    if args.eval and args.format_only:\n        raise ValueError('--eval and --format_only cannot be both specified')\n\n    if args.out is not None and not args.out.endswith(('.pkl', '.pickle')):\n        raise ValueError('The output file must be a pkl file.')\n\n    cfg = Config.fromfile(args.config)\n    # set cudnn_benchmark\n    if cfg.get('cudnn_benchmark', False):\n        torch.backends.cudnn.benchmark = True\n    cfg.model.pretrained = None\n    if cfg.model.get('neck'):\n        if cfg.model.neck.get('rfp_backbone'):\n            if cfg.model.neck.rfp_backbone.get('pretrained'):\n                cfg.model.neck.rfp_backbone.pretrained = None\n    cfg.data.test.test_mode = True\n\n    # init distributed env first, since logger depends on the dist info.\n    if args.launcher == 'none':\n        distributed = False\n    else:\n        distributed = True\n        init_dist(args.launcher, **cfg.dist_params)\n\n    # build the dataloader\n    # TODO: support multiple images per gpu (only minor changes are needed)\n    dataset = build_dataset(cfg.data.test)\n    data_loader = build_dataloader(\n        dataset,\n        samples_per_gpu=1,\n        workers_per_gpu=cfg.data.workers_per_gpu,\n        dist=distributed,\n        shuffle=False)\n\n    # build the model and load checkpoint\n    model = build_detector(cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)\n    fp16_cfg = cfg.get('fp16', None)\n    if fp16_cfg is not None:\n        wrap_fp16_model(model)\n    checkpoint = load_checkpoint(model, args.checkpoint, map_location='cpu')\n    if args.fuse_conv_bn:\n        model = fuse_module(model)\n    # old versions did not save class info in checkpoints, this walkaround is\n    # for backward compatibility\n    if 'CLASSES' in checkpoint['meta']:\n        model.CLASSES = checkpoint['meta']['CLASSES']\n    else:\n        model.CLASSES = dataset.CLASSES\n\n    if not distributed:\n        model = MMDataParallel(model, device_ids=[0])\n        outputs = single_gpu_test(model, data_loader, cfg.model.bbox_head, args.show,\n                                  args.show_dir, args.show_score_thr)\n    else:\n        model = MMDistributedDataParallel(\n            model.cuda(),\n            device_ids=[torch.cuda.current_device()],\n            broadcast_buffers=False)\n        outputs = multi_gpu_test(model, data_loader, args.tmpdir,\n                                 args.gpu_collect, cfg.model.bbox_head)\n\n    rank, _ = get_dist_info()\n    if rank == 0:\n        if args.out:\n            print(f'\\nwriting results to {args.out}')\n            mmcv.dump(outputs, args.out)\n        kwargs = {} if args.options is None else args.options\n        if args.format_only:\n            dataset.format_results(outputs, **kwargs)\n        if args.eval:\n            dataset.evaluate(outputs, args.eval, **kwargs)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/test_robustness.py",
    "content": "import argparse\nimport copy\nimport os\nimport os.path as osp\nimport shutil\nimport tempfile\n\nimport mmcv\nimport torch\nimport torch.distributed as dist\nfrom mmcv.parallel import MMDataParallel, MMDistributedDataParallel\nfrom mmcv.runner import get_dist_info, init_dist, load_checkpoint\nfrom pycocotools.coco import COCO\nfrom pycocotools.cocoeval import COCOeval\nfrom robustness_eval import get_results\n\nfrom mmdet import datasets\nfrom mmdet.apis import set_random_seed\nfrom mmdet.core import encode_mask_results, eval_map, wrap_fp16_model\nfrom mmdet.datasets import build_dataloader, build_dataset\nfrom mmdet.models import build_detector\n\n\ndef coco_eval_with_return(result_files,\n                          result_types,\n                          coco,\n                          max_dets=(100, 300, 1000)):\n    for res_type in result_types:\n        assert res_type in ['proposal', 'bbox', 'segm', 'keypoints']\n\n    if mmcv.is_str(coco):\n        coco = COCO(coco)\n    assert isinstance(coco, COCO)\n\n    eval_results = {}\n    for res_type in result_types:\n        result_file = result_files[res_type]\n        assert result_file.endswith('.json')\n\n        coco_dets = coco.loadRes(result_file)\n        img_ids = coco.getImgIds()\n        iou_type = 'bbox' if res_type == 'proposal' else res_type\n        cocoEval = COCOeval(coco, coco_dets, iou_type)\n        cocoEval.params.imgIds = img_ids\n        if res_type == 'proposal':\n            cocoEval.params.useCats = 0\n            cocoEval.params.maxDets = list(max_dets)\n        cocoEval.evaluate()\n        cocoEval.accumulate()\n        cocoEval.summarize()\n        if res_type == 'segm' or res_type == 'bbox':\n            metric_names = [\n                'AP', 'AP50', 'AP75', 'APs', 'APm', 'APl', 'AR1', 'AR10',\n                'AR100', 'ARs', 'ARm', 'ARl'\n            ]\n            eval_results[res_type] = {\n                metric_names[i]: cocoEval.stats[i]\n                for i in range(len(metric_names))\n            }\n        else:\n            eval_results[res_type] = cocoEval.stats\n\n    return eval_results\n\n\ndef voc_eval_with_return(result_file,\n                         dataset,\n                         iou_thr=0.5,\n                         logger='print',\n                         only_ap=True):\n    det_results = mmcv.load(result_file)\n    annotations = [dataset.get_ann_info(i) for i in range(len(dataset))]\n    if hasattr(dataset, 'year') and dataset.year == 2007:\n        dataset_name = 'voc07'\n    else:\n        dataset_name = dataset.CLASSES\n    mean_ap, eval_results = eval_map(\n        det_results,\n        annotations,\n        scale_ranges=None,\n        iou_thr=iou_thr,\n        dataset=dataset_name,\n        logger=logger)\n\n    if only_ap:\n        eval_results = [{\n            'ap': eval_results[i]['ap']\n        } for i in range(len(eval_results))]\n\n    return mean_ap, eval_results\n\n\ndef single_gpu_test(model, data_loader, show=False):\n    model.eval()\n    results = []\n    dataset = data_loader.dataset\n    prog_bar = mmcv.ProgressBar(len(dataset))\n    for i, data in enumerate(data_loader):\n        with torch.no_grad():\n            result = model(return_loss=False, rescale=not show, **data)\n\n        if show:\n            model.module.show_result(data, result, dataset.img_norm_cfg)\n\n        # encode mask results\n        if isinstance(result, tuple):\n            bbox_results, mask_results = result\n            encoded_mask_results = encode_mask_results(mask_results)\n            result = bbox_results, encoded_mask_results\n        results.append(result)\n\n        batch_size = data['img'][0].size(0)\n        for _ in range(batch_size):\n            prog_bar.update()\n    return results\n\n\ndef multi_gpu_test(model, data_loader, tmpdir=None):\n    model.eval()\n    results = []\n    dataset = data_loader.dataset\n    rank, world_size = get_dist_info()\n    if rank == 0:\n        prog_bar = mmcv.ProgressBar(len(dataset))\n    for i, data in enumerate(data_loader):\n        with torch.no_grad():\n            result = model(return_loss=False, rescale=True, **data)\n            # encode mask results\n            if isinstance(result, tuple):\n                bbox_results, mask_results = result\n                encoded_mask_results = encode_mask_results(mask_results)\n                result = bbox_results, encoded_mask_results\n        results.append(result)\n\n        results.append(result)\n\n        if rank == 0:\n            batch_size = data['img'][0].size(0)\n            for _ in range(batch_size * world_size):\n                prog_bar.update()\n\n    # collect results from all ranks\n    results = collect_results(results, len(dataset), tmpdir)\n\n    return results\n\n\ndef collect_results(result_part, size, tmpdir=None):\n    rank, world_size = get_dist_info()\n    # create a tmp dir if it is not specified\n    if tmpdir is None:\n        MAX_LEN = 512\n        # 32 is whitespace\n        dir_tensor = torch.full((MAX_LEN, ),\n                                32,\n                                dtype=torch.uint8,\n                                device='cuda')\n        if rank == 0:\n            tmpdir = tempfile.mkdtemp()\n            tmpdir = torch.tensor(\n                bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')\n            dir_tensor[:len(tmpdir)] = tmpdir\n        dist.broadcast(dir_tensor, 0)\n        tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()\n    else:\n        mmcv.mkdir_or_exist(tmpdir)\n    # dump the part result to the dir\n    mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))\n    dist.barrier()\n    # collect all parts\n    if rank != 0:\n        return None\n    else:\n        # load results of all parts from tmp dir\n        part_list = []\n        for i in range(world_size):\n            part_file = osp.join(tmpdir, f'part_{i}.pkl')\n            part_list.append(mmcv.load(part_file))\n        # sort the results\n        ordered_results = []\n        for res in zip(*part_list):\n            ordered_results.extend(list(res))\n        # the dataloader may pad some samples\n        ordered_results = ordered_results[:size]\n        # remove tmp dir\n        shutil.rmtree(tmpdir)\n        return ordered_results\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='MMDet test detector')\n    parser.add_argument('config', help='test config file path')\n    parser.add_argument('checkpoint', help='checkpoint file')\n    parser.add_argument('--out', help='output result file')\n    parser.add_argument(\n        '--corruptions',\n        type=str,\n        nargs='+',\n        default='benchmark',\n        choices=[\n            'all', 'benchmark', 'noise', 'blur', 'weather', 'digital',\n            'holdout', 'None', 'gaussian_noise', 'shot_noise', 'impulse_noise',\n            'defocus_blur', 'glass_blur', 'motion_blur', 'zoom_blur', 'snow',\n            'frost', 'fog', 'brightness', 'contrast', 'elastic_transform',\n            'pixelate', 'jpeg_compression', 'speckle_noise', 'gaussian_blur',\n            'spatter', 'saturate'\n        ],\n        help='corruptions')\n    parser.add_argument(\n        '--severities',\n        type=int,\n        nargs='+',\n        default=[0, 1, 2, 3, 4, 5],\n        help='corruption severity levels')\n    parser.add_argument(\n        '--eval',\n        type=str,\n        nargs='+',\n        choices=['proposal', 'proposal_fast', 'bbox', 'segm', 'keypoints'],\n        help='eval types')\n    parser.add_argument(\n        '--iou-thr',\n        type=float,\n        default=0.5,\n        help='IoU threshold for pascal voc evaluation')\n    parser.add_argument(\n        '--summaries',\n        type=bool,\n        default=False,\n        help='Print summaries for every corruption and severity')\n    parser.add_argument(\n        '--workers', type=int, default=32, help='workers per gpu')\n    parser.add_argument('--show', action='store_true', help='show results')\n    parser.add_argument('--tmpdir', help='tmp dir for writing some results')\n    parser.add_argument('--seed', type=int, default=None, help='random seed')\n    parser.add_argument(\n        '--launcher',\n        choices=['none', 'pytorch', 'slurm', 'mpi'],\n        default='none',\n        help='job launcher')\n    parser.add_argument('--local_rank', type=int, default=0)\n    parser.add_argument(\n        '--final-prints',\n        type=str,\n        nargs='+',\n        choices=['P', 'mPC', 'rPC'],\n        default='mPC',\n        help='corruption benchmark metric to print at the end')\n    parser.add_argument(\n        '--final-prints-aggregate',\n        type=str,\n        choices=['all', 'benchmark'],\n        default='benchmark',\n        help='aggregate all results or only those for benchmark corruptions')\n    args = parser.parse_args()\n    if 'LOCAL_RANK' not in os.environ:\n        os.environ['LOCAL_RANK'] = str(args.local_rank)\n    return args\n\n\ndef main():\n    args = parse_args()\n\n    assert args.out or args.show, \\\n        ('Please specify at least one operation (save or show the results) '\n         'with the argument \"--out\" or \"--show\"')\n\n    if args.out is not None and not args.out.endswith(('.pkl', '.pickle')):\n        raise ValueError('The output file must be a pkl file.')\n\n    cfg = mmcv.Config.fromfile(args.config)\n    # set cudnn_benchmark\n    if cfg.get('cudnn_benchmark', False):\n        torch.backends.cudnn.benchmark = True\n    cfg.model.pretrained = None\n    cfg.data.test.test_mode = True\n    if args.workers == 0:\n        args.workers = cfg.data.workers_per_gpu\n\n    # init distributed env first, since logger depends on the dist info.\n    if args.launcher == 'none':\n        distributed = False\n    else:\n        distributed = True\n        init_dist(args.launcher, **cfg.dist_params)\n\n    # set random seeds\n    if args.seed is not None:\n        set_random_seed(args.seed)\n\n    if 'all' in args.corruptions:\n        corruptions = [\n            'gaussian_noise', 'shot_noise', 'impulse_noise', 'defocus_blur',\n            'glass_blur', 'motion_blur', 'zoom_blur', 'snow', 'frost', 'fog',\n            'brightness', 'contrast', 'elastic_transform', 'pixelate',\n            'jpeg_compression', 'speckle_noise', 'gaussian_blur', 'spatter',\n            'saturate'\n        ]\n    elif 'benchmark' in args.corruptions:\n        corruptions = [\n            'gaussian_noise', 'shot_noise', 'impulse_noise', 'defocus_blur',\n            'glass_blur', 'motion_blur', 'zoom_blur', 'snow', 'frost', 'fog',\n            'brightness', 'contrast', 'elastic_transform', 'pixelate',\n            'jpeg_compression'\n        ]\n    elif 'noise' in args.corruptions:\n        corruptions = ['gaussian_noise', 'shot_noise', 'impulse_noise']\n    elif 'blur' in args.corruptions:\n        corruptions = [\n            'defocus_blur', 'glass_blur', 'motion_blur', 'zoom_blur'\n        ]\n    elif 'weather' in args.corruptions:\n        corruptions = ['snow', 'frost', 'fog', 'brightness']\n    elif 'digital' in args.corruptions:\n        corruptions = [\n            'contrast', 'elastic_transform', 'pixelate', 'jpeg_compression'\n        ]\n    elif 'holdout' in args.corruptions:\n        corruptions = ['speckle_noise', 'gaussian_blur', 'spatter', 'saturate']\n    elif 'None' in args.corruptions:\n        corruptions = ['None']\n        args.severities = [0]\n    else:\n        corruptions = args.corruptions\n\n    rank, _ = get_dist_info()\n    aggregated_results = {}\n    for corr_i, corruption in enumerate(corruptions):\n        aggregated_results[corruption] = {}\n        for sev_i, corruption_severity in enumerate(args.severities):\n            # evaluate severity 0 (= no corruption) only once\n            if corr_i > 0 and corruption_severity == 0:\n                aggregated_results[corruption][0] = \\\n                    aggregated_results[corruptions[0]][0]\n                continue\n\n            test_data_cfg = copy.deepcopy(cfg.data.test)\n            # assign corruption and severity\n            if corruption_severity > 0:\n                corruption_trans = dict(\n                    type='Corrupt',\n                    corruption=corruption,\n                    severity=corruption_severity)\n                # TODO: hard coded \"1\", we assume that the first step is\n                # loading images, which needs to be fixed in the future\n                test_data_cfg['pipeline'].insert(1, corruption_trans)\n\n            # print info\n            print(f'\\nTesting {corruption} at severity {corruption_severity}')\n\n            # build the dataloader\n            # TODO: support multiple images per gpu\n            #       (only minor changes are needed)\n            dataset = build_dataset(test_data_cfg)\n            data_loader = build_dataloader(\n                dataset,\n                samples_per_gpu=1,\n                workers_per_gpu=args.workers,\n                dist=distributed,\n                shuffle=False)\n\n            # build the model and load checkpoint\n            model = build_detector(\n                cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)\n            fp16_cfg = cfg.get('fp16', None)\n            if fp16_cfg is not None:\n                wrap_fp16_model(model)\n            checkpoint = load_checkpoint(\n                model, args.checkpoint, map_location='cpu')\n            # old versions did not save class info in checkpoints,\n            # this walkaround is for backward compatibility\n            if 'CLASSES' in checkpoint['meta']:\n                model.CLASSES = checkpoint['meta']['CLASSES']\n            else:\n                model.CLASSES = dataset.CLASSES\n\n            if not distributed:\n                model = MMDataParallel(model, device_ids=[0])\n                outputs = single_gpu_test(model, data_loader, args.show)\n            else:\n                model = MMDistributedDataParallel(\n                    model.cuda(),\n                    device_ids=[torch.cuda.current_device()],\n                    broadcast_buffers=False)\n                outputs = multi_gpu_test(model, data_loader, args.tmpdir)\n\n            if args.out and rank == 0:\n                eval_results_filename = (\n                    osp.splitext(args.out)[0] + '_results' +\n                    osp.splitext(args.out)[1])\n                mmcv.dump(outputs, args.out)\n                eval_types = args.eval\n                if cfg.dataset_type == 'VOCDataset':\n                    if eval_types:\n                        for eval_type in eval_types:\n                            if eval_type == 'bbox':\n                                test_dataset = mmcv.runner.obj_from_dict(\n                                    cfg.data.test, datasets)\n                                logger = 'print' if args.summaries else None\n                                mean_ap, eval_results = \\\n                                    voc_eval_with_return(\n                                        args.out, test_dataset,\n                                        args.iou_thr, logger)\n                                aggregated_results[corruption][\n                                    corruption_severity] = eval_results\n                            else:\n                                print('\\nOnly \"bbox\" evaluation \\\n                                is supported for pascal voc')\n                else:\n                    if eval_types:\n                        print(f'Starting evaluate {\" and \".join(eval_types)}')\n                        if eval_types == ['proposal_fast']:\n                            result_file = args.out\n                        else:\n                            if not isinstance(outputs[0], dict):\n                                result_files = dataset.results2json(\n                                    outputs, args.out)\n                            else:\n                                for name in outputs[0]:\n                                    print(f'\\nEvaluating {name}')\n                                    outputs_ = [out[name] for out in outputs]\n                                    result_file = args.out\n                                    + f'.{name}'\n                                    result_files = dataset.results2json(\n                                        outputs_, result_file)\n                        eval_results = coco_eval_with_return(\n                            result_files, eval_types, dataset.coco)\n                        aggregated_results[corruption][\n                            corruption_severity] = eval_results\n                    else:\n                        print('\\nNo task was selected for evaluation;'\n                              '\\nUse --eval to select a task')\n\n                # save results after each evaluation\n                mmcv.dump(aggregated_results, eval_results_filename)\n\n    if rank == 0:\n        # print filan results\n        print('\\nAggregated results:')\n        prints = args.final_prints\n        aggregate = args.final_prints_aggregate\n\n        if cfg.dataset_type == 'VOCDataset':\n            get_results(\n                eval_results_filename,\n                dataset='voc',\n                prints=prints,\n                aggregate=aggregate)\n        else:\n            get_results(\n                eval_results_filename,\n                dataset='coco',\n                prints=prints,\n                aggregate=aggregate)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/train.py",
    "content": "import argparse\nimport copy\nimport os\nimport pdb\nimport os.path as osp\nimport time\n\nimport mmcv\nimport torch\nfrom mmcv import Config, DictAction\nfrom mmcv.runner import init_dist\n\nfrom mmdet import __version__\nfrom mmdet.apis import set_random_seed, train_detector\nfrom mmdet.datasets import build_dataset\nfrom mmdet.models import build_detector\nfrom mmdet.utils import collect_env, get_root_logger\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='Train a detector')\n    parser.add_argument('config', help='train config file path')\n    parser.add_argument('--work-dir', help='the dir to save logs and models')\n    parser.add_argument(\n        '--resume-from', help='the checkpoint file to resume from')\n    parser.add_argument(\n        '--load-from', help='the checkpoint file to load from')\n    parser.add_argument(\n        '--no-validate',\n        action='store_true',\n        help='whether not to evaluate the checkpoint during training')\n    group_gpus = parser.add_mutually_exclusive_group()\n    group_gpus.add_argument(\n        '--gpus',\n        type=int,\n        help='number of gpus to use '\n        '(only applicable to non-distributed training)')\n    group_gpus.add_argument(\n        '--gpu-ids',\n        type=int,\n        nargs='+',\n        help='ids of gpus to use '\n        '(only applicable to non-distributed training)')\n    parser.add_argument('--seed', type=int, default=None, help='random seed')\n    parser.add_argument(\n        '--deterministic',\n        action='store_true',\n        help='whether to set deterministic options for CUDNN backend.')\n    parser.add_argument(\n        '--options', nargs='+', action=DictAction, help='arguments in dict')\n    parser.add_argument(\n        '--launcher',\n        choices=['none', 'pytorch', 'slurm', 'mpi'],\n        default='none',\n        help='job launcher')\n    parser.add_argument('--local_rank', type=int, default=0)\n    args = parser.parse_args()\n    if 'LOCAL_RANK' not in os.environ:\n        os.environ['LOCAL_RANK'] = str(args.local_rank)\n\n    return args\n\n\ndef main():\n    args = parse_args()\n\n    cfg = Config.fromfile(args.config)\n    if args.options is not None:\n        cfg.merge_from_dict(args.options)\n    # set cudnn_benchmark\n    if cfg.get('cudnn_benchmark', False):\n        torch.backends.cudnn.benchmark = True\n\n    # work_dir is determined in this priority: CLI > segment in file > filename\n    if args.work_dir is not None:\n        # update configs according to CLI args if args.work_dir is not None\n        cfg.work_dir = args.work_dir\n    elif cfg.get('work_dir', None) is None:\n        # use config filename as default work_dir if cfg.work_dir is None\n        cfg.work_dir = osp.join('/cache/work_dirs', osp.splitext(osp.basename(args.config))[0])\n    if args.resume_from is not None:\n        cfg.resume_from = args.resume_from\n\n    if args.load_from is not None:\n        cfg.load_from = args.load_from\n\n    if args.gpu_ids is not None:\n        cfg.gpu_ids = args.gpu_ids\n    else:\n        cfg.gpu_ids = range(1) if args.gpus is None else range(args.gpus)\n\n    # init distributed env first, since logger depends on the dist info.\n    if args.launcher == 'none':\n        distributed = False\n    else:\n        distributed = True\n        init_dist(args.launcher, **cfg.dist_params)\n\n    # create work_dir\n    mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir))\n    # dump config\n    cfg.dump(osp.join(cfg.work_dir, osp.basename(args.config)))\n    # init the logger before other steps\n    timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime())\n    log_file = osp.join(cfg.work_dir, f'{timestamp}.log')\n    logger = get_root_logger(log_file=log_file, log_level=cfg.log_level)\n\n    # init the meta dict to record some important information such as\n    # environment info and seed, which will be logged\n    meta = dict()\n    # log env info\n    env_info_dict = collect_env()\n    env_info = '\\n'.join([(f'{k}: {v}') for k, v in env_info_dict.items()])\n    dash_line = '-' * 60 + '\\n'\n    logger.info('Environment info:\\n' + dash_line + env_info + '\\n' +\n                dash_line)\n    meta['env_info'] = env_info\n\n    # log some basic info\n    logger.info(f'Distributed training: {distributed}')\n    logger.info(f'Config:\\n{cfg.pretty_text}')\n\n    # set random seeds\n    if args.seed is not None:\n        logger.info(f'Set random seed to {args.seed}, '\n                    f'deterministic: {args.deterministic}')\n        set_random_seed(args.seed, deterministic=args.deterministic)\n    cfg.seed = args.seed\n    meta['seed'] = args.seed\n\n    model = build_detector(\n        cfg.model, train_cfg=cfg.train_cfg, test_cfg=cfg.test_cfg)\n\n    datasets = [build_dataset(cfg.data.train)]\n    if len(cfg.workflow) == 2:\n        val_dataset = copy.deepcopy(cfg.data.val)\n        val_dataset.pipeline = cfg.data.train.pipeline\n        datasets.append(build_dataset(val_dataset))\n    if cfg.checkpoint_config is not None:\n        # save mmdet version, config file content and class names in\n        # checkpoints as meta data\n        cfg.checkpoint_config.meta = dict(\n            mmdet_version=__version__,\n            config=cfg.pretty_text,\n            CLASSES=datasets[0].CLASSES)\n    # add an attribute for visualization convenience\n    model.CLASSES = datasets[0].CLASSES\n    train_detector(\n        model,\n        datasets,\n        cfg,\n        distributed=distributed,\n        validate=(not args.no_validate),\n        timestamp=timestamp,\n        meta=meta)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/tools/upgrade_model_version.py",
    "content": "import argparse\nimport re\nimport tempfile\nfrom collections import OrderedDict\n\nimport torch\nfrom mmcv import Config\n\n\ndef is_head(key):\n    valid_head_list = [\n        'bbox_head', 'mask_head', 'semantic_head', 'grid_head', 'mask_iou_head'\n    ]\n\n    return any(key.startswith(h) for h in valid_head_list)\n\n\ndef parse_config(config_strings):\n    temp_file = tempfile.NamedTemporaryFile()\n    config_path = f'{temp_file.name}.py'\n    with open(config_path, 'w') as f:\n        f.write(config_strings)\n\n    config = Config.fromfile(config_path)\n    is_two_stage = True\n    is_ssd = False\n    is_retina = False\n    reg_cls_agnostic = False\n    if 'rpn_head' not in config.model:\n        is_two_stage = False\n        # check whether it is SSD\n        if config.model.bbox_head.type == 'SSDHead':\n            is_ssd = True\n        elif config.model.bbox_head.type == 'RetinaHead':\n            is_retina = True\n    elif isinstance(config.model['bbox_head'], list):\n        reg_cls_agnostic = True\n    elif 'reg_class_agnostic' in config.model.bbox_head:\n        reg_cls_agnostic = config.model.bbox_head \\\n            .reg_class_agnostic\n    temp_file.close()\n    return is_two_stage, is_ssd, is_retina, reg_cls_agnostic\n\n\ndef reorder_cls_channel(val, num_classes=81):\n    # bias\n    if val.dim() == 1:\n        new_val = torch.cat((val[1:], val[:1]), dim=0)\n    # weight\n    else:\n        out_channels, in_channels = val.shape[:2]\n        # conv_cls for softmax output\n        if out_channels != num_classes and out_channels % num_classes == 0:\n            new_val = val.reshape(-1, num_classes, in_channels, *val.shape[2:])\n            new_val = torch.cat((new_val[:, 1:], new_val[:, :1]), dim=1)\n            new_val = new_val.reshape(val.size())\n        # fc_cls\n        elif out_channels == num_classes:\n            new_val = torch.cat((val[1:], val[:1]), dim=0)\n        # agnostic | retina_cls | rpn_cls\n        else:\n            new_val = val\n\n    return new_val\n\n\ndef truncate_cls_channel(val, num_classes=81):\n\n    # bias\n    if val.dim() == 1:\n        if val.size(0) % num_classes == 0:\n            new_val = val[:num_classes - 1]\n        else:\n            new_val = val\n    # weight\n    else:\n        out_channels, in_channels = val.shape[:2]\n        # conv_logits\n        if out_channels % num_classes == 0:\n            new_val = val.reshape(num_classes, in_channels, *val.shape[2:])[1:]\n            new_val = new_val.reshape(-1, *val.shape[1:])\n        # agnostic\n        else:\n            new_val = val\n\n    return new_val\n\n\ndef truncate_reg_channel(val, num_classes=81):\n    # bias\n    if val.dim() == 1:\n        # fc_reg|rpn_reg\n        if val.size(0) % num_classes == 0:\n            new_val = val.reshape(num_classes, -1)[:num_classes - 1]\n            new_val = new_val.reshape(-1)\n        # agnostic\n        else:\n            new_val = val\n    # weight\n    else:\n        out_channels, in_channels = val.shape[:2]\n        # fc_reg|rpn_reg\n        if out_channels % num_classes == 0:\n            new_val = val.reshape(num_classes, -1, in_channels,\n                                  *val.shape[2:])[1:]\n            new_val = new_val.reshape(-1, *val.shape[1:])\n        # agnostic\n        else:\n            new_val = val\n\n    return new_val\n\n\ndef convert(in_file, out_file, num_classes):\n    \"\"\"Convert keys in checkpoints.\n\n    There can be some breaking changes during the development of mmdetection,\n    and this tool is used for upgrading checkpoints trained with old versions\n    to the latest one.\n    \"\"\"\n    checkpoint = torch.load(in_file)\n    in_state_dict = checkpoint.pop('state_dict')\n    out_state_dict = OrderedDict()\n    meta_info = checkpoint['meta']\n    is_two_stage, is_ssd, is_retina, reg_cls_agnostic = parse_config(\n        meta_info['config'])\n    if meta_info['mmdet_version'] <= '0.5.3' and is_retina:\n        upgrade_retina = True\n    else:\n        upgrade_retina = False\n\n    for key, val in in_state_dict.items():\n        new_key = key\n        new_val = val\n        if is_two_stage and is_head(key):\n            new_key = 'roi_head.{}'.format(key)\n\n        # classification\n        m = re.search(\n            r'(conv_cls|retina_cls|rpn_cls|fc_cls|fcos_cls|'\n            r'fovea_cls).(weight|bias)', new_key)\n        if m is not None:\n            print(f'reorder cls channels of {new_key}')\n            new_val = reorder_cls_channel(val, num_classes)\n\n        # regression\n        m = re.search(r'(fc_reg|rpn_reg).(weight|bias)', new_key)\n        if m is not None and not reg_cls_agnostic:\n            print(f'truncate regression channels of {new_key}')\n            new_val = truncate_reg_channel(val, num_classes)\n\n        # mask head\n        m = re.search(r'(conv_logits).(weight|bias)', new_key)\n        if m is not None:\n            print(f'truncate mask prediction channels of {new_key}')\n            new_val = truncate_cls_channel(val, num_classes)\n\n        m = re.search(r'(cls_convs|reg_convs).\\d.(weight|bias)', key)\n        # Legacy issues in RetinaNet since V1.x\n        # Use ConvModule instead of nn.Conv2d in RetinaNet\n        # cls_convs.0.weight -> cls_convs.0.conv.weight\n        if m is not None and upgrade_retina:\n            param = m.groups()[1]\n            new_key = key.replace(param, f'conv.{param}')\n            out_state_dict[new_key] = val\n            print(f'rename the name of {key} to {new_key}')\n            continue\n\n        m = re.search(r'(cls_convs).\\d.(weight|bias)', key)\n        if m is not None and is_ssd:\n            print(f'reorder cls channels of {new_key}')\n            new_val = reorder_cls_channel(val, num_classes)\n\n        out_state_dict[new_key] = new_val\n    checkpoint['state_dict'] = out_state_dict\n    torch.save(checkpoint, out_file)\n\n\ndef main():\n    parser = argparse.ArgumentParser(description='Upgrade model version')\n    parser.add_argument('in_file', help='input checkpoint file')\n    parser.add_argument('out_file', help='output checkpoint file')\n    parser.add_argument(\n        '--num-classes',\n        type=int,\n        default=81,\n        help='number of classes of the original model')\n    args = parser.parse_args()\n    convert(args.in_file, args.out_file, args.num_classes)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "code/visualization/000.txt",
    "content": ""
  },
  {
    "path": "logs/000.txt",
    "content": ""
  }
]