[
  {
    "path": ".github/ISSUE_TEMPLATE/--bug.md",
    "content": "---\nname: 反馈bug\nabout: 反馈使用过程中出现的各种错误\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**bug描述**\n请大致描述出错的现象，在什么情况下或操作过程中遇到，在上述条件下是否总是出现等。\n\n**复现方法**\n我们可以如何操作重现这个bug\n如：\n1. 启动软件\n2. 点击....\n3. 打开....\n4. 出现问题 ....\n\n**截屏**\n如果条件允许可以添加针对问题的截屏，可以帮助我们理解问题。\n\n**运行环境（请尽量填写，这可以帮助我们定位问题）：**\n - 系统： [e.g. Windows/Mac os/Linux]\n - 安装方式：[e.g. 克隆代码/pip/Windows exe]\n - 软件版本：[运行时显示在软件最上方]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\ntest/\n__pycache__/\n*.py[cod]\n*$py.class\ndzq*\n.vscode\n.vscode/\nvis_temp.py\ntest.txt\n*.pdparams\noutput/\ntemp*\ntemp/\n\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/\npip-wheel-metadata/\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/\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\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\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# vscode\n.vscode/*\n\n# pycharm\n.idea/*\n\n# exe\nout/\neiseg/requirements_with_opt.txt\n\n# test\ntest/\ntest_output/\n*.npy\n\n# qsetting\n*.ini\n\n# mask_sm\ntool/mask.png\n\n# static_weights\n*.pdiparams\n*.pdiparams.info\n*.pdmodel"
  },
  {
    "path": "LICENSE",
    "content": "                                 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 [yyyy] [name of copyright owner]\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": "MANIFEST.in",
    "content": "include eiseg/config/*\ninclude eiseg/resource/*\ninclude eiseg/util/translate/*"
  },
  {
    "path": "README.md",
    "content": "# EISeg\r\n\r\n[![Python 3.6](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/release/python-360/) [![PaddlePaddle 2.2](https://img.shields.io/badge/paddlepaddle-2.2-blue.svg)](https://www.python.org/downloads/release/python-360/) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) [![Downloads](https://pepy.tech/badge/eiseg)](https://pepy.tech/project/eiseg)\r\n<!-- [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/PaddleCV-SIG/iseg/releases) -->\r\n\r\n简体中文 | [English](README_EN.md)\r\n\r\n## 最新动向\r\n\r\n- [2022.7.20] 为了减少重复工作以集中精力做好后续的开发和维护工作，**后续EISeg将仅在飞桨官方仓库[PaddlePaddle/PaddeSeg](https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.6/EISeg)下进行更新，现有代码仓库将不再更新**。非常感谢大家的一路相伴与支持，EISeg将竭诚为大家提供更多更好用的标注功能，欢迎大家去新的地址体验，也希望大家继续支持。\r\n"
  },
  {
    "path": "README_EN.md",
    "content": "# EISeg\n\n[![Python 3.6](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/release/python-360/) [![PaddlePaddle 2.2](https://img.shields.io/badge/paddlepaddle-2.2-blue.svg)](https://www.python.org/downloads/release/python-360/) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) [![Downloads](https://pepy.tech/badge/eiseg)](https://pepy.tech/project/eiseg)\n<!-- [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/PaddleCV-SIG/iseg/releases) -->\n[Chinese (Simplified)](README.md) | English\n\n## Latest Developments\n\n- [2022.7.20] To reduce repetitive work and focus on development and maintenance in later versions, **EISeg will only be updated on the official repository [PaddlePaddle/PaddeSeg](https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.6/EISeg) later, and this repository will not be updated**. Thank you very much for your company and support all the way. EISeg will do its best to provide you with more and better annotation functions. Welcome to experience the new address and hope you will continue to support me."
  },
  {
    "path": "docs/medical.md",
    "content": "# 医疗相关\n\n以下内容为EISeg中医疗垂类相关的文档，主要包括环境配置和功能介绍。\n\n## 1 环境配置\n\n使用医疗组件需要额外安装SimpleITK包用于读取医学影像，安装方式如下：\n\n```shell\npip install SimpleITK\n```\n\n## 2 功能介绍\n\n目前EISeg支持打开**单层的Dicom格式图像**，对Nitfi格式和多张Dicom的支持正在开发中。EISeg通过图像拓展名判断图像格式。打开单张图像时需要在右下角类型下拉菜单中选择医疗图像，如下图所示\n\n打开文件夹时和自然图像过程相同。打开 .dcm 后缀的图像后会询问是否开启医疗组件。\n\n![med-prompt](https://linhandev.github.io/assets/img/post/Med/med-prompt.png)\n\n点击确定后会出现图像窗宽窗位设置面板\n\n![med-widget](https://linhandev.github.io/assets/img/post/Med/med-widget.png)\n\n窗宽窗位的作用是聚焦一定的强度区间，方便观察CT扫描。CT扫描中每个像素点存储的数值代表人体在该位置的密度，密度越高数值越大，图像的数据范围通常为-1024～1024。不过查看扫描时人眼无法分辨2048个灰度，因此通常选择一个更小的强度范围，将这一区间内图像的灰度差异拉大，从而方便观察。具体的操作是取扫描中强度范围在 窗位-窗宽/2～窗位+窗宽/2 的部分，将这一部分数据放入256灰度的图片中展示给用户。\n\n推理方面，目前EISeg针对医疗场景提供[肝脏分割预训练模型](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18s_ocr48_lits.zip)，推荐窗宽窗位400, 0。该模型用于肝脏分割效果最佳，也可以用于其他组织或器官的分割。\n"
  },
  {
    "path": "docs/medical_en.md",
    "content": "# Medical Treatment\r\n\r\nThis part presents documents related to medical treatment in EISeg, including its environment configuration and functions.\r\n\r\n## 1 Environment Configuration\r\n\r\nThe SimpleITK package should be additionally installed for image reading, please try the following:\r\n\r\n```\r\npip install SimpleITK\r\n```\r\n\r\n## 2 Functions\r\n\r\nEISeg can open **single-layer Dicom format images**, while the support for Nitfi format and multiple Dicom is under development. EISeg fines the image format by its expansion name. To open a single image you need to select Medical Image in the drop-down menu of type at the bottom right corner, as shown below.\r\n\r\nThe folder and natural image share the same process. When opening an image with a .dcm suffix, you will be asked whether to turn on the medical component.\r\n\r\n[![med-prompt](https://camo.githubusercontent.com/ba9ab11d3e602ae61769d2926bd6774d1dfa633346cc483ab04bf4c89e65d2d0/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d70726f6d70742e706e67)](https://camo.githubusercontent.com/ba9ab11d3e602ae61769d2926bd6774d1dfa633346cc483ab04bf4c89e65d2d0/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d70726f6d70742e706e67)\r\n\r\n\r\n\r\nClick Yes and there appears the setting panel of the image window width and position.\r\n\r\n[![med-widget](https://camo.githubusercontent.com/05e9c84842f9b18ad94d5a9d7610642607f569d3ef6a9d97fd445a60df9ece46/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d7769646765742e706e67)](https://camo.githubusercontent.com/05e9c84842f9b18ad94d5a9d7610642607f569d3ef6a9d97fd445a60df9ece46/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d7769646765742e706e67)\r\n\r\nThe window width and position serve to limit the intensity range for easy observation of the CT scanning. The value stored at each pixel point in the CT scan represents the density of the human body at that location, so the higher the density the larger the value. The data range of the image is usually -1024 to 1024. However, the human eye cannot distinguish 2048 shades of gray when viewing the scan, so a smaller intensity range is usually adopted to increase the grayscale differences of the images within, thus facilitating the observation. This is done by selecting the section ranging from Window - Window Width/2 to Window + Window Width/2, and presenting the data in a 256-grayscale image.\r\n\r\nFor inference, EISeg provides the [pre-trained model for liver segmentation](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18s_ocr48_lits.zip) for medical scenarios, with recommended window widths of 400, 0. This model performs best for liver segmentation and can also be used for other tissues or organs. \r\n\r\n"
  },
  {
    "path": "docs/remote_sensing.md",
    "content": "# 遥感相关\r\n\r\n以下内容为EISeg中遥感垂类相关的文档，主要包括环境配置和功能介绍两大方面。\r\n\r\n## 1 环境配置\r\n\r\nEISeg中对遥感数据的支持来自GDAL/OGR，GDAL是一个在X/MIT许可协议下的开源栅格空间数据转换库，OGR与其功能类似但主要提供对矢量数据的支持。\r\n\r\n### 1.1 依赖安装\r\n\r\n关于GDAL的安装，可参考如下安装方式：\r\n\r\n#### 1.1.1 Windows\r\n\r\nWindows用户可以通过[这里](https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal)下载对应Python和系统版本的二进制文件（*.whl）到本地，以GDAL‑3.3.3‑cp39‑cp39‑win_amd64.whl为例，进入下载目录：\r\n\r\n```shell\r\ncd download\r\n```\r\n\r\n安装依赖：\r\n\r\n```shell\r\npip install GDAL‑3.3.3‑cp39‑cp39‑win_amd64.whl\r\n```\r\n\r\n#### 1.1.2 Linux/Mac安装\r\n\r\nMac用户建议利用conda安装，如下：\r\n\r\n```shell script\r\nconda install gdal\r\n```\r\n\r\n\r\n## 2 功能介绍\r\n\r\n目前EISeg中的遥感垂类功能建设还比较简单，基本完成了GTiff类数据加载、大幅遥感影像切片与合并、地理栅格/矢量数据（GTiff/ESRI Shapefile）导出。并基于各类建筑提取数据集40余万张数据训练了一个建筑分割的交互式模型。\r\n\r\n### 2.1 数据加载\r\n\r\n目前EISeg仅支持了*.tif/tiff图像后缀的的遥感影像读取，由于训练数据都是来自于RGB三通道的遥感图像切片，因此交互分割也仅在RGB三通道上完成，也就表示EISeg支持多波段数据的波段选择。\r\n\r\n当使用EISeg打开GTiff图像时，会获取当前波段数，可通过波段设置的下拉列表进行设置。默认为[b1, b1, b1]。下例展示的是天宫一号多光谱数据设置真彩色：\r\n\r\n![yd6fa-hqvvb](https://user-images.githubusercontent.com/71769312/141137443-a327309e-0987-4b2a-88fd-f698e08d3294.gif)\r\n\r\n### 2.2 大幅数据切片\r\n\r\n目前EISeg对于大幅遥感图像（目前最大尝试为900M，17000*10000大小三通道图像），支持切片预测后合并，其中切片的重叠区域overlap为24。\r\n\r\n![140916007-86076366-62ce-49ba-b1d9-18239baafc90](https://user-images.githubusercontent.com/71769312/141139282-854dcb4f-bcab-4ccc-aa3c-577cc52ca385.png)\r\n\r\n\r\n下面是一副来自谷歌地球的重庆部分地区的切片演示：\r\n\r\n![7kevx-q90hv](https://user-images.githubusercontent.com/71769312/141137447-60b305b1-a8ef-4b06-a45e-6db0b1ef2516.gif)\r\n\r\n### 2.3 地理数据保存\r\n\r\n当打开标注的GTiff图像带有地理参考，可设置EISeg保存时保存为带有地理参考的GTiff和ESRI Shapefile。\r\n\r\n- GTiff：已成为GIS和卫星遥感应用的行业图像标准文件。\r\n- ESRI Shapefile：是最常见的的矢量数据格式，Shapefile文件是美国环境系统研究所（ESRI）所研制的GIS文件系统格式文件，是工业标准的矢量数据文件。 所有的商业和开源GIS软件都支持。无处不在的它已成为行业标准。\r\n\r\n![82jlu-no59o](https://user-images.githubusercontent.com/71769312/141137726-76457454-5e9c-4ad0-85d6-d03f658ee63c.gif)\r\n\r\n### 2.4 遥感标注模型选择\r\n\r\n建筑物标注建议使用[static_hrnet18_ocr48_rsbuilding_instance](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18_ocr48_rsbuilding_instance.zip)\r\n"
  },
  {
    "path": "docs/remote_sensing_en.md",
    "content": "# Remote Sensing\r\n\r\n以下内容为EISeg中遥感垂类相关的文档，主要包括环境配置和功能介绍两大方面。\r\n\r\nThis part presents documents related to remote sensing in EISeg, including its environment configuration and functions.\r\n\r\n## 1 Environment Configuration\r\n\r\nEISeg supports remote sensing data with GDAL and OGR. The former is a translator library for raster spatial data formats under the X/MIT style Open Source License, while the latter has similar functions but mainly supports vector data.\r\n\r\n### 1.1 Install Dependencies\r\n\r\nGDAL can be installed as follows:\r\n\r\n#### 1.1.1 Windows\r\n\r\nWindows users can download the corresponding  binaries (*.whl) of Python and system versions [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal). Here we take GDAL-3.3.3 -cp39-cp39-win_amd64.whl as an example, go to the download directory:\r\n\r\n```\r\ncd download\r\n```\r\n\r\nInstall the dependencies:\r\n\r\n```\r\npip install GDAL‑3.3.3‑cp39‑cp39‑win_amd64.whl\r\n```\r\n\r\n#### 1.1.2 Linux/Mac\r\n\r\nMac users are recommended to install with conda:\r\n\r\n```\r\nconda install gdal\r\n```\r\n\r\n## 2 Functions\r\n\r\nAt present, functions of remote sensing in EISeg are relatively simple including GTiff class data loading, large remote sensing image slicing and merging, and geographic raster/vector data (GTiff/ESRI Shapefile) export. What's more, an interactive model of building segmentation is trained based on more than 400,000 data from various building datasets.\r\n\r\n### 2.1 Data Loading\r\n\r\nFor the moment, EISeg can only read remote sensing images with *.tif/tiff suffix. Since the training data are all remote sensing image slices of RGB three-channel, the interactive segmentation shares the same basis, which means EISeg supports band selection of multi-band data.\r\n\r\nWhen adopting EISeg to open the GTiff image, the current number of bands is obtained, which can be set by the drop-down list of band settings. The default is [b1, b1, b1]. The following example shows the true color setting of Tiangong-1 multispectral data.\r\n\r\n[![yd6fa-hqvvb](https://user-images.githubusercontent.com/71769312/141137443-a327309e-0987-4b2a-88fd-f698e08d3294.gif)](https://user-images.githubusercontent.com/71769312/141137443-a327309e-0987-4b2a-88fd-f698e08d3294.gif)\r\n\r\n### 2.2 large Image Slicing\r\n\r\nEISeg supports the post-prediction merging of sliced large remote sensing images (the latest attempt is 900M three-channel images with a size of 17000*10000), in which the overlap (overlapping area) of slices is 24.\r\n\r\n[![140916007-86076366-62ce-49ba-b1d9-18239baafc90](https://user-images.githubusercontent.com/71769312/141139282-854dcb4f-bcab-4ccc-aa3c-577cc52ca385.png)](https://user-images.githubusercontent.com/71769312/141139282-854dcb4f-bcab-4ccc-aa3c-577cc52ca385.png)\r\n\r\nThe following demonstrates the slicing of some districts in Chongqing from Google Earth:\r\n\r\n[![7kevx-q90hv](https://user-images.githubusercontent.com/71769312/141137447-60b305b1-a8ef-4b06-a45e-6db0b1ef2516.gif)](https://user-images.githubusercontent.com/71769312/141137447-60b305b1-a8ef-4b06-a45e-6db0b1ef2516.gif)\r\n\r\n### 2.3 Geographic Data Saving\r\n\r\nWhen the GTiff images to be labeled are accompanied by georeferencing, you can set EISeg to save them as GTiff with georeferencing or ESRI Shapefile.\r\n\r\n- GTiff: A standard image file for industries of GIS and satellite remote sensing.\r\n- ESRI Shapefile: The most common vector data format.The Shapefile file is a GIS file format developed by the U.S. Environmental Systems Research Institute (ESRI) and is the industry-standard vector data file. It is supported by all commercial and open source GIS software and now represents the industry standard.\r\n\r\n[![82jlu-no59o](https://user-images.githubusercontent.com/71769312/141137726-76457454-5e9c-4ad0-85d6-d03f658ee63c.gif)](https://user-images.githubusercontent.com/71769312/141137726-76457454-5e9c-4ad0-85d6-d03f658ee63c.gif)\r\n\r\n### 2.4 Labeling Model for Remote Sensing\r\n\r\n[static_hrnet18_ocr48_rsbuilding_instance](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18_ocr48_rsbuilding_instance.zip) are recommended for building labeling.\r\n\r\n"
  },
  {
    "path": "docs/tools.md",
    "content": "# 脚本工具相关\r\n\r\n以下内容为EISeg中的相关工具使用。位置位于EISeg/tool\r\n\r\n## 语义标签转实例标签\r\n\r\n语义分割标签转实例分割标签（原标签为0/255），结果为单通道图像采用调色板调色。通过`tool`中的`semantic2instance`，可以将EISeg标注好的语义分割数据转为实例分割数据。使用以下方法：\r\n\r\n``` shell\r\npython semantic2instance.py -o label_path -d save_path\r\n```\r\n\r\n其中:\r\n\r\n- `label_path`: 语义标签存放路径，必填\r\n- `save_path`: 实例标签保存路径，必填\r\n\r\n![68747470733a2f2f73332e626d702e6f76682f696d67732f323032312f30392f303038633562373638623765343737612e706e67](https://user-images.githubusercontent.com/71769312/141392781-d99ec177-f445-4336-9ab2-0ba7ae75d664.png)\r\n\r\n"
  },
  {
    "path": "eiseg/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport sys\nimport os\nimport os.path as osp\nimport logging\nfrom datetime import datetime\n\nfrom qtpy import QtCore\nimport cv2\n\n__APPNAME__ = \"EISeg\"\n__VERSION__ = \"0.5.0\"\n\n\npjpath = osp.dirname(osp.realpath(__file__))\nsys.path.append(pjpath)\n\nfor k, v in os.environ.items():\n    if k.startswith(\"QT_\") and \"cv2\" in v:\n        del os.environ[k]\n\n# log\nsettings = QtCore.QSettings(\n    osp.join(pjpath, \"config/setting.ini\"), QtCore.QSettings.IniFormat\n)\n\nlogFolder = settings.value(\"logFolder\")\nlogLevel = bool(settings.value(\"log\"))\nlogDays = settings.value(\"logDays\")\n\nif logFolder is None or len(logFolder) == 0:\n    logFolder = osp.normcase(osp.join(pjpath, \"log\"))\nif not osp.exists(logFolder):\n    os.makedirs(logFolder)\n\nif logLevel:\n    logLevel = logging.DEBUG\nelse:\n    logLevel = logging.CRITICAL\nif logDays:\n    logDays = int(logDays)\nelse:\n    logDays = 7\n# TODO: 删除大于logDays 的 log\n\nt = datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\nlogger = logging.getLogger(\"EISeg Logger\")\nhandler = logging.FileHandler(osp.normcase(osp.join(logFolder, f\"eiseg-{t}.log\")))\nhandler.setFormatter(\n    logging.Formatter(\n        \"%(levelname)s - %(asctime)s - %(filename)s - %(funcName)s - %(message)s\"\n    )\n)\nlogger.setLevel(logLevel)\nlogger.addHandler(handler)\n"
  },
  {
    "path": "eiseg/__main__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom run import main\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "eiseg/app.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport logging\nimport os\nimport os.path as osp\nfrom functools import partial\nimport json\nfrom distutils.util import strtobool\nimport webbrowser\nfrom easydict import EasyDict as edict\n\nfrom qtpy import QtGui, QtCore, QtWidgets\nfrom qtpy.QtWidgets import QMainWindow, QMessageBox, QTableWidgetItem\nfrom qtpy.QtGui import QImage, QPixmap\nfrom qtpy.QtCore import Qt, QByteArray, QVariant, QCoreApplication, QThread, Signal\nimport cv2\nimport numpy as np\n\nfrom eiseg import pjpath, __APPNAME__, logger\nfrom widget import ShortcutWidget, PolygonAnnotation\nfrom controller import InteractiveController\nfrom ui import Ui_EISeg\nimport util\nfrom util import COCO\nfrom util import check_cn, normcase\n\nimport plugin.remotesensing as rs\nfrom plugin.medical import med\nfrom plugin.remotesensing import Raster\nfrom plugin.n2grid import RSGrids, Grids, checkOpenGrid\n\n\n# TODO: 研究paddle子线程\nclass ModelThread(QThread):\n    _signal = Signal(dict)\n\n    def __init__(self, controller, param_path):\n        super().__init__()\n        self.controller = controller\n        self.param_path = param_path\n\n    def run(self):\n        success, res = self.controller.setModel(self.param_path, False)\n        self._signal.emit(\n            {\"success\": success, \"res\": res, \"param_path\": self.param_path}\n        )\n\n\nclass APP_EISeg(QMainWindow, Ui_EISeg):\n    IDILE, ANNING, EDITING = 0, 1, 2\n    # IDILE：网络，权重，图像三者任一没有加载\n    # EDITING：多边形编辑，可以交互式，但是多边形内部不能点\n    # ANNING：交互式标注，只能交互式，不能编辑多边形，多边形不接hover\n\n    # 宫格标注背景颜色\n    GRID_COLOR = {\n        \"idle\": QtGui.QColor(255, 255, 255),\n        \"current\": QtGui.QColor(192, 220, 243),\n        \"finised\": QtGui.QColor(185, 185, 225),\n        \"overlying\": QtGui.QColor(51, 52, 227),\n    }\n\n    def __init__(self, parent=None):\n        super(APP_EISeg, self).__init__(parent)\n\n        self.settings = QtCore.QSettings(\n            osp.join(pjpath, \"config/setting.ini\"), QtCore.QSettings.IniFormat\n        )\n        currentLang = self.settings.value(\"language\")\n        layoutdir = Qt.RightToLeft if currentLang == \"Arabic\" else Qt.LeftToRight\n        self.setLayoutDirection(layoutdir)\n\n        # 初始化界面\n        self.setupUi(self)\n\n        # app变量\n        self._anning = False  # self.status替代\n        self.isDirty = False  # 是否需要保存\n        self.image = None  # 可能先加载图片后加载模型，只用于暂存图片\n        self.predictor_params = {\n            \"brs_mode\": \"NoBRS\",\n            \"with_flip\": False,\n            \"zoom_in_params\": {\n                \"skip_clicks\": -1,\n                \"target_size\": (400, 400),\n                \"expansion_ratio\": 1.4,\n            },\n            \"predictor_params\": {\n                \"net_clicks_limit\": None,\n                \"max_size\": 800,\n                \"with_mask\": True,\n            },\n        }\n        self.controller = InteractiveController(\n            predictor_params=self.predictor_params,\n            prob_thresh=self.segThresh,\n        )\n        # self.controller.labelList = util.LabelList()  # 标签列表\n        self.save_status = {\n            \"gray_scale\": True,\n            \"pseudo_color\": True,\n            \"json\": False,\n            \"coco\": True,\n            \"cutout\": True,\n        }  # 是否保存这几个格式\n        self.outputDir = None  # 标签保存路径\n        self.labelPaths = []  # 所有outputdir中的标签文件路径\n        self.imagePaths = []  # 文件夹下所有待标注图片路径\n        self.currIdx = 0  # 文件夹标注当前图片下标\n        self.origExt = False  # 是否使用图片本身拓展名，防止重名覆盖\n        if self.save_status[\"coco\"]:\n            self.coco = COCO()\n        else:\n            self.coco = None\n        self.colorMap = util.colorMap\n\n        if self.settings.value(\"cutout_background\"):\n            self.cutoutBackground = [\n                int(c) for c in self.settings.value(\"cutout_background\")\n            ]\n            if len(self.cutoutBackground) == 3:\n                self.cutoutBackground += tuple([255])\n        else:\n            self.cutoutBackground = [0, 0, 128, 255]\n\n        if self.settings.value(\"cross_color\"):\n            self.crossColor = [\n                int(c) for c in self.settings.value(\"cross_color\")\n            ]\n        else:\n            self.crossColor = [0, 0, 0, 127]\n        self.scene.setPenColor(self.crossColor)\n\n        # widget\n        self.dockWidgets = {\n            \"model\": self.ModelDock,\n            \"data\": self.DataDock,\n            \"label\": self.LabelDock,\n            \"seg\": self.SegSettingDock,\n            \"rs\": self.RSDock,\n            \"med\": self.MedDock,\n            \"grid\": self.GridDock,\n        }\n        # self.display_dockwidget = [True, True, True, True, False, False, False]\n        self.dockStatus = self.settings.value(\n            \"dock_status\", QVariant([]), type=list\n        )  # 所有widget是否展示\n        if len(self.dockStatus) != len(self.dockWidgets):\n            self.dockStatus = [True] * 4 + [False] * (len(self.dockWidgets) - 4)\n            self.settings.setValue(\"dock_status\", self.dockStatus)\n        else:\n            self.dockStatus = [strtobool(s) for s in self.dockStatus]\n\n        self.layoutStatus = self.settings.value(\"layout_status\", QByteArray())  # 界面元素位置\n\n        self.recentModels = self.settings.value(\n            \"recent_models\", QVariant([]), type=list\n        )\n        self.recentFiles = self.settings.value(\"recent_files\", QVariant([]), type=list)\n\n        self.config = util.parse_configs(osp.join(pjpath, \"config/config.yaml\"))\n\n        # 支持的图像格式\n        rs_ext = [\".tif\", \".tiff\"]\n        img_ext = []\n        for fmt in QtGui.QImageReader.supportedImageFormats():\n            fmt = \".{}\".format(fmt.data().decode())\n            if fmt not in rs_ext:\n                img_ext.append(fmt)\n        self.formats = [\n            img_ext,  # 自然图像\n            [\".dcm\"],  # 医学影像\n            rs_ext,  # 遥感影像\n        ]\n\n        # 遥感\n        self.raster = None\n        self.grid = None\n        self.rsRGB = [1, 1, 1]  # 遥感索引\n\n        # 医疗参数\n        self.midx = 0  # 医疗切片索引\n\n        # 大图限制\n        self.thumbnail_min = 2000\n\n        # 初始化action\n        self.initActions()\n\n        # 更新近期记录\n        self.loadLayout()  # 放前面\n        self.toggleWidget(\"all\", warn=False)\n        self.updateModelMenu()\n        self.updateRecentFile()\n\n        # 窗口\n        ## 快捷键\n        self.ShortcutWidget = ShortcutWidget(self.actions, pjpath)\n\n        ## 画布\n        self.scene.clickRequest.connect(self.canvasClick)\n        self.canvas.zoomRequest.connect(self.viewZoomed)\n        self.canvas.mousePosChanged.connect(self.scene.onMouseChanged)\n        self.annImage = QtWidgets.QGraphicsPixmapItem()\n        self.scene.addItem(self.annImage)\n\n        ## 按钮点击\n        self.btnSave.clicked.connect(self.exportLabel)  # 保存\n        self.listFiles.itemDoubleClicked.connect(self.imageListClicked)  # 标签列表点击\n\n        self.btnAddClass.clicked.connect(self.addLabel)\n        self.btnParamsSelect.clicked.connect(self.changeParam)  # 模型参数选择\n        self.cheWithMask.stateChanged.connect(self.chooseMode)  # with_mask\n\n        ## 滑动\n        self.sldOpacity.valueChanged.connect(self.maskOpacityChanged)\n        self.sldClickRadius.valueChanged.connect(self.clickRadiusChanged)\n        self.sldThresh.valueChanged.connect(self.threshChanged)\n        self.sliderWw.valueChanged.connect(self.swwChanged)\n        self.sliderWc.valueChanged.connect(self.swcChanged)\n        self.textWw.returnPressed.connect(self.twwChanged)\n        self.textWc.returnPressed.connect(self.twcChanged)\n\n        ## 标签列表点击\n        self.labelListTable.cellDoubleClicked.connect(self.labelListDoubleClick)\n        self.labelListTable.cellClicked.connect(self.labelListClicked)\n        self.labelListTable.cellChanged.connect(self.labelListItemChanged)\n\n        ## 功能区选择\n        # self.rsShow.currentIndexChanged.connect(self.rsShowModeChange)  # 显示模型\n        for bandCombo in self.bandCombos:\n            bandCombo.currentIndexChanged.connect(self.rsBandSet)  # 设置波段\n        # self.btnInitGrid.clicked.connect(self.initGrid)  # 打开宫格\n        self.btnFinishedGrid.clicked.connect(self.saveGridLabel)\n\n    def initActions(self):\n        tr = partial(QtCore.QCoreApplication.translate, \"APP_EISeg\")\n        action = partial(util.newAction, self)\n        start = dir()\n\n        # 打开/加载/保存\n        open_image = action(\n            tr(\"&打开图像\"),\n            self.openImage,\n            \"open_image\",\n            \"OpenImage\",\n            tr(\"打开一张图像进行标注\"),\n        )\n        open_folder = action(\n            tr(\"&打开文件夹\"),\n            self.openFolder,\n            \"open_folder\",\n            \"OpenFolder\",\n            tr(\"打开一个文件夹下所有的图像进行标注\"),\n        )\n        change_output_dir = action(\n            tr(\"&改变标签保存路径\"),\n            partial(self.changeOutputDir, None),\n            \"change_output_dir\",\n            \"ChangeOutputDir\",\n            tr(\"改变标签保存的文件夹路径\"),\n        )\n        load_param = action(\n            tr(\"&加载模型参数\"),\n            self.changeParam,\n            \"load_param\",\n            \"Model\",\n            tr(\"加载一个模型参数\"),\n        )\n        save = action(\n            tr(\"&保存\"),\n            self.exportLabel,\n            \"save\",\n            \"Save\",\n            tr(\"保存图像标签\"),\n        )\n        save_as = action(\n            tr(\"&另存为\"),\n            partial(self.exportLabel, saveAs=True),\n            \"save_as\",\n            \"SaveAs\",\n            tr(\"在指定位置另存为标签\"),\n        )\n        auto_save = action(\n            tr(\"&自动保存\"),\n            self.toggleAutoSave,\n            \"auto_save\",\n            \"AutoSave\",\n            tr(\"翻页同时自动保存\"),\n            checkable=True,\n        )\n        # auto_save.setChecked(self.config.get(\"auto_save\", False))\n\n        # 标注\n        turn_prev = action(\n            tr(\"&上一张\"),\n            partial(self.turnImg, -1),\n            \"turn_prev\",\n            \"Prev\",\n            tr(\"翻到上一张图片\"),\n        )\n        turn_next = action(\n            tr(\"&下一张\"),\n            partial(self.turnImg, 1),\n            \"turn_next\",\n            \"Next\",\n            tr(\"翻到下一张图片\"),\n        )\n        finish_object = action(\n            tr(\"&完成当前目标\"),\n            self.finishObject,\n            \"finish_object\",\n            \"Ok\",\n            tr(\"完成当前目标的标注\"),\n        )\n        clear = action(\n            tr(\"&清除所有标注\"),\n            self.clearAll,\n            \"clear\",\n            \"Clear\",\n            tr(\"清除所有标注信息\"),\n        )\n        undo = action(\n            tr(\"&撤销\"),\n            self.undoClick,\n            \"undo\",\n            \"Undo\",\n            tr(\"撤销一次点击\"),\n        )\n        redo = action(\n            tr(\"&重做\"),\n            self.redoClick,\n            \"redo\",\n            \"Redo\",\n            tr(\"重做一次点击\"),\n        )\n        del_active_polygon = action(\n            tr(\"&删除多边形\"),\n            self.delActivePolygon,\n            \"del_active_polygon\",\n            \"DeletePolygon\",\n            tr(\"删除当前选中的多边形\"),\n        )\n        del_all_polygon = action(\n            tr(\"&删除所有多边形\"),\n            self.delAllPolygon,\n            \"del_all_polygon\",\n            \"DeleteAllPolygon\",\n            tr(\"删除所有的多边形\"),\n        )\n        largest_component = action(\n            tr(\"&保留最大连通块\"),\n            self.toggleLargestCC,\n            \"largest_component\",\n            \"SaveLargestCC\",\n            tr(\"保留最大的连通块\"),\n            checkable=True,\n        )\n        origional_extension = action(\n            tr(\"&标签和图像使用相同拓展名\"),\n            self.toggleOrigExt,\n            \"origional_extension\",\n            \"Same\",\n            tr(\"标签和图像使用相同拓展名，用于图像中有文件名相同但拓展名不同的情况，防止标签覆盖\"),\n            checkable=True,\n        )\n        save_pseudo = action(\n            tr(\"&伪彩色保存\"),\n            partial(self.toggleSave, \"pseudo_color\"),\n            \"save_pseudo\",\n            \"SavePseudoColor\",\n            tr(\"保存为伪彩色图像\"),\n            checkable=True,\n        )\n        save_pseudo.setChecked(self.save_status[\"pseudo_color\"])\n        save_grayscale = action(\n            tr(\"&灰度保存\"),\n            partial(self.toggleSave, \"gray_scale\"),\n            \"save_grayscale\",\n            \"SaveGrayScale\",\n            tr(\"保存为灰度图像，像素的灰度为对应类型的标签\"),\n            checkable=True,\n        )\n        save_grayscale.setChecked(self.save_status[\"gray_scale\"])\n        save_json = action(\n            tr(\"&JSON保存\"),\n            partial(self.toggleSave, \"json\"),\n            \"save_json\",\n            \"SaveJson\",\n            tr(\"保存为JSON格式\"),\n            checkable=True,\n        )\n        save_json.setChecked(self.save_status[\"json\"])\n        save_coco = action(\n            tr(\"&COCO保存\"),\n            partial(self.toggleSave, \"coco\"),\n            \"save_coco\",\n            \"SaveCOCO\",\n            tr(\"保存为COCO格式\"),\n            checkable=True,\n        )\n        save_coco.setChecked(self.save_status[\"coco\"])\n        # test func\n        self.show_rs_poly = action(\n            tr(\"&显示遥感多边形\"),\n            None,\n            \"show_rs_poly\",\n            \"Show\",\n            tr(\"显示遥感大图的多边形结果\"),\n            checkable=True,\n        )\n        self.show_rs_poly.setChecked(False)\n        self.grid_message = action(\n            tr(\"&启用宫格检测\"),\n            None,\n            \"grid_message\",\n            \"Show\",\n            tr(\"针对每张图片启用宫格检测\"),\n            checkable=True,\n        )\n        self.grid_message.setChecked(True)\n        save_cutout = action(\n            tr(\"&抠图保存\"),\n            partial(self.toggleSave, \"cutout\"),\n            \"save_cutout\",\n            \"SaveCutout\",\n            tr(\"只保留前景，背景设置为背景色\"),\n            checkable=True,\n        )\n        save_cutout.setChecked(self.save_status[\"cutout\"])\n        set_cutout_background = action(\n            tr(\"&设置抠图背景色\"),\n            self.setCutoutBackground,\n            \"set_cutout_background\",\n            self.cutoutBackground,\n            tr(\"抠图后背景像素的颜色\"),\n        )\n        set_cross_color = action(\n            tr(\"&设置十字丝颜色\"),\n            self.setCrossColor,\n            \"set_cross_color\",\n            self.crossColor,\n            tr(\"十字丝的显示颜色\"),\n        )\n        close = action(\n            tr(\"&关闭\"),\n            partial(self.saveImage, True),\n            \"close\",\n            \"Close\",\n            tr(\"关闭当前图像\"),\n        )\n        quit = action(\n            tr(\"&退出\"),\n            self.close,\n            \"quit\",\n            \"Quit\",\n            tr(\"退出软件\"),\n        )\n        export_label_list = action(\n            tr(\"&导出标签列表\"),\n            partial(self.exportLabelList, None),\n            \"export_label_list\",\n            \"ExportLabel\",\n            tr(\"将标签列表导出成标签配置文件\"),\n        )\n        import_label_list = action(\n            tr(\"&载入标签列表\"),\n            partial(self.importLabelList, None),\n            \"import_label_list\",\n            \"ImportLabel\",\n            tr(\"从标签配置文件载入标签列表\"),\n        )\n        clear_label_list = action(\n            tr(\"&清空标签列表\"),\n            self.clearLabelList,\n            \"clear_label_list\",\n            \"ClearLabel\",\n            tr(\"清空所有的标签\"),\n        )\n        clear_recent = action(\n            tr(\"&清除近期文件记录\"),\n            self.clearRecentFile,\n            \"clear_recent\",\n            \"ClearRecent\",\n            tr(\"清除近期标注文件记录\"),\n        )\n        model_widget = action(\n            tr(\"&模型选择\"),\n            partial(self.toggleWidget, 0),\n            \"model_widget\",\n            \"Net\",\n            tr(\"隐藏/展示模型选择面板\"),\n            checkable=True,\n        )\n        data_widget = action(\n            tr(\"&数据列表\"),\n            partial(self.toggleWidget, 1),\n            \"data_widget\",\n            \"Data\",\n            tr(\"隐藏/展示数据列表面板\"),\n            checkable=True,\n        )\n        label_widget = action(\n            tr(\"&标签列表\"),\n            partial(self.toggleWidget, 2),\n            \"label_widget\",\n            \"Label\",\n            tr(\"隐藏/展示标签列表面板\"),\n            checkable=True,\n        )\n        segmentation_widget = action(\n            tr(\"&分割设置\"),\n            partial(self.toggleWidget, 3),\n            \"segmentation_widget\",\n            \"Setting\",\n            tr(\"隐藏/展示分割设置面板\"),\n            checkable=True,\n        )\n        rs_widget = action(\n            tr(\"&遥感设置\"),\n            partial(self.toggleWidget, 4),\n            \"rs_widget\",\n            \"RemoteSensing\",\n            tr(\"隐藏/展示遥感设置面板\"),\n            checkable=True,\n        )\n        mi_widget = action(\n            tr(\"&医疗设置\"),\n            partial(self.toggleWidget, 5),\n            \"mi_widget\",\n            \"MedicalImaging\",\n            tr(\"隐藏/展示医疗设置面板\"),\n            checkable=True,\n        )\n        grid_ann_widget = action(\n            tr(\"&N2宫格标注\"),\n            partial(self.toggleWidget, 6),\n            \"grid_ann_widget\",\n            \"N2\",\n            tr(\"隐藏/展示N^2宫格细粒度标注面板\"),\n            checkable=True,\n        )\n        quick_start = action(\n            tr(\"&快速入门\"),\n            self.quickStart,\n            \"quick_start\",\n            \"Use\",\n            tr(\"主要功能使用介绍\"),\n        )\n        report_bug = action(\n            tr(\"&反馈问题\"),\n            self.reportBug,\n            \"report_bug\",\n            \"ReportBug\",\n            tr(\"通过Github Issue反馈使用过程中遇到的问题。我们会尽快进行修复\"),\n        )\n        edit_shortcuts = action(\n            tr(\"&编辑快捷键\"),\n            self.editShortcut,\n            \"edit_shortcuts\",\n            \"Shortcut\",\n            tr(\"编辑软件快捷键\"),\n        )\n        toggle_logging = action(\n            tr(\"&调试日志\"),\n            self.toggleLogging,\n            \"toggle_logging\",\n            \"Log\",\n            tr(\"用于观察软件执行过程和进行debug。我们不会自动收集任何日志，可能会希望您在反馈问题时间打开此功能，帮助我们定位问题。\"),\n            checkable=True,\n        )\n        toggle_logging.setChecked(bool(self.settings.value(\"log\", False)))\n        use_qt_widget = action(\n            tr(\"&使用QT文件窗口\"),\n            self.useQtWidget,\n            \"use_qt_widget\",\n            \"Qt\",\n            tr(\"如果使用文件选择窗口时遇到问题可以选择使用Qt窗口\"),\n            checkable=True,\n        )\n        # print(\n        #     \"use_qt_widget\",\n        #     self.settings.value(\"use_qt_widget\", type=bool),\n        # )\n        use_qt_widget.setChecked(self.settings.value(\"use_qt_widget\", False, type=bool))\n\n        self.actions = util.struct()\n        for name in dir():\n            if name not in start:\n                self.actions.append(eval(name))\n\n        def newWidget(text, icon, showAction):\n            widget = QtWidgets.QMenu(tr(text))\n            widget.setIcon(util.newIcon(icon))\n            widget.aboutToShow.connect(showAction)\n            return widget\n\n        recent_files = newWidget(self.tr(\"近期文件\"), \"Data\", self.updateRecentFile)\n        recent_params = newWidget(self.tr(\"近期模型及参数\"), \"Net\", self.updateModelMenu)\n        languages = newWidget(self.tr(\"语言\"), \"Language\", self.updateLanguage)\n\n        self.menus = util.struct(\n            recent_files=recent_files,\n            recent_params=recent_params,\n            languages=languages,\n            fileMenu=(\n                open_image,\n                open_folder,\n                change_output_dir,\n                load_param,\n                clear_recent,\n                recent_files,\n                recent_params,\n                None,\n                save,\n                save_as,\n                auto_save,\n                None,\n                turn_next,\n                turn_prev,\n                close,\n                None,\n                quit,\n            ),\n            labelMenu=(\n                export_label_list,\n                import_label_list,\n                clear_label_list,\n            ),\n            functionMenu=(\n                largest_component,\n                del_active_polygon,\n                del_all_polygon,\n                None,\n                origional_extension,\n                save_pseudo,\n                save_grayscale,\n                save_cutout,\n                set_cutout_background,\n                None,\n                set_cross_color,\n                None,\n                save_json,\n                save_coco,\n                None,\n                # test\n                self.show_rs_poly,\n                None,\n                self.grid_message,\n            ),\n            showMenu=(\n                model_widget,\n                data_widget,\n                label_widget,\n                segmentation_widget,\n                rs_widget,\n                mi_widget,\n                grid_ann_widget,\n            ),\n            helpMenu=(\n                languages,\n                use_qt_widget,\n                quick_start,\n                report_bug,\n                edit_shortcuts,\n                toggle_logging,\n            ),\n            toolBar=(\n                finish_object,\n                clear,\n                undo,\n                redo,\n                turn_prev,\n                turn_next,\n                None,\n                save_pseudo,\n                save_grayscale,\n                save_cutout,\n                save_json,\n                save_coco,\n                origional_extension,\n                None,\n                largest_component,\n            ),\n        )\n\n        def menu(title, actions=None):\n            menu = self.menuBar().addMenu(title)\n            if actions:\n                util.addActions(menu, actions)\n            return menu\n\n        menu(tr(\"文件\"), self.menus.fileMenu)\n        menu(tr(\"标注\"), self.menus.labelMenu)\n        menu(tr(\"功能\"), self.menus.functionMenu)\n        menu(tr(\"显示\"), self.menus.showMenu)\n        menu(tr(\"帮助\"), self.menus.helpMenu)\n        util.addActions(self.toolBar, self.menus.toolBar)\n\n    def __setColor(self, action, setting_name):\n        c = action\n        color = QtWidgets.QColorDialog.getColor(\n            QtGui.QColor(*c),\n            self,\n            options=QtWidgets.QColorDialog.ShowAlphaChannel,\n        )\n        action = color.getRgb()\n        self.settings.setValue(\n            setting_name, [int(c) for c in action]\n        )\n        return action\n\n    def setCutoutBackground(self):\n        self.cutoutBackground = self.__setColor(self.cutoutBackground, \"cutout_background\")\n        self.actions.set_cutout_background.setIcon(util.newIcon(self.cutoutBackground))\n\n    def setCrossColor(self):\n        self.crossColor = self.__setColor(self.crossColor, \"cross_color\")\n        self.actions.set_cross_color.setIcon(util.newIcon(self.crossColor))\n        self.scene.setPenColor(self.crossColor)\n\n    def editShortcut(self):\n        self.ShortcutWidget.center()\n        self.ShortcutWidget.show()\n\n    # 多语言\n    def updateLanguage(self):\n        self.menus.languages.clear()\n        langs = os.listdir(osp.join(pjpath, \"util/translate\"))\n        langs = [n.split(\".\")[0] for n in langs if n.endswith(\"qm\")]\n        langs.append(\"中文\")\n        for lang in langs:\n            if lang == self.currLanguage:\n                continue\n            entry = util.newAction(\n                self,\n                lang,\n                partial(self.changeLanguage, lang),\n                None,\n                lang if lang != \"Arabic\" else \"Egypt\",\n            )\n            self.menus.languages.addAction(entry)\n\n    def changeLanguage(self, lang):\n        self.settings.setValue(\"language\", lang)\n        self.warn(self.tr(\"切换语言\"), self.tr(\"切换语言需要重启软件才能生效\"))\n\n    # 近期图像\n    def updateRecentFile(self):\n        menu = self.menus.recent_files\n        menu.clear()\n        recentFiles = self.settings.value(\"recent_files\", QVariant([]), type=list)\n        files = [f for f in recentFiles if osp.exists(f)]\n        for i, f in enumerate(files):\n            icon = util.newIcon(\"File\")\n            action = QtWidgets.QAction(\n                icon, \"&【%d】 %s\" % (i + 1, QtCore.QFileInfo(f).fileName()), self\n            )\n            action.triggered.connect(partial(self.openRecentImage, f))\n            menu.addAction(action)\n        if len(files) == 0:\n            menu.addAction(self.tr(\"无近期文件\"))\n        self.settings.setValue(\"recent_files\", files)\n\n    def addRecentFile(self, path):\n        if not osp.exists(path):\n            return\n        paths = self.settings.value(\"recent_files\", QVariant([]), type=list)\n        if path not in paths:\n            paths.append(path)\n        if len(paths) > 15:\n            del paths[0]\n        self.settings.setValue(\"recent_files\", paths)\n        self.updateRecentFile()\n\n    def clearRecentFile(self):\n        self.settings.remove(\"recent_files\")\n        self.statusbar.showMessage(self.tr(\"已清除最近打开文件\"), 10000)\n\n    # 模型加载\n    def updateModelMenu(self):\n        menu = self.menus.recent_params\n        menu.clear()\n\n        self.recentModels = [\n            m for m in self.recentModels if osp.exists(m[\"param_path\"])\n        ]\n        for idx, m in enumerate(self.recentModels):\n            icon = util.newIcon(\"Model\")\n            action = QtWidgets.QAction(\n                icon,\n                f\"{osp.basename(m['param_path'])}\",\n                self,\n            )\n            action.triggered.connect(partial(self.setModelParam, m[\"param_path\"]))\n            menu.addAction(action)\n        if len(self.recentModels) == 0:\n            menu.addAction(self.tr(\"无近期模型记录\"))\n        self.settings.setValue(\"recent_params\", self.recentModels)\n\n    def setModelParam(self, paramPath):\n        res = self.changeParam(paramPath)\n        if res:\n            return True\n        return False\n\n    def changeParam(self, param_path: str = None):\n        if not param_path:\n            filters = self.tr(\"Paddle静态模型权重文件(*.pdiparams)\")\n            start_path = (\n                \".\"\n                if len(self.recentModels) == 0\n                else osp.dirname(self.recentModels[-1][\"param_path\"])\n            )\n            if self.settings.value(\"use_qt_widget\", False, type=bool):\n                options = QtWidgets.QFileDialog.DontUseNativeDialog\n            else:\n                options = QtWidgets.QFileDialog.ReadOnly\n            param_path, _ = QtWidgets.QFileDialog.getOpenFileName(\n                self,\n                self.tr(\"选择模型参数\") + \" - \" + __APPNAME__,\n                start_path,\n                filters,\n                options=options,\n            )\n            # QtWidgets.QFileDialog.DontUseNativeDialog\n        if not param_path:\n            return False\n\n        # 中文路径打不开\n        if check_cn(param_path):\n            self.warn(self.tr(\"参数路径存在中文\"), self.tr(\"请修改参数路径为非中文路径！\"))\n            return False\n\n        # success, res = self.controller.setModel(param_path)\n        self.load_thread = ModelThread(self.controller, param_path)\n        self.load_thread._signal.connect(self.__change_model_callback)\n        self.load_thread.start()\n\n    def __change_model_callback(self, signal_dict: dict):\n        success = signal_dict[\"success\"]\n        res = signal_dict[\"res\"]\n        param_path = signal_dict[\"param_path\"]\n        if success:\n            model_dict = {\"param_path\": param_path}\n            if model_dict not in self.recentModels:\n                self.recentModels.insert(0, model_dict)\n                if len(self.recentModels) > 10:\n                    del self.recentModels[-1]\n            else:  # 如果存在移动位置，确保加载最近模型的正确\n                self.recentModels.remove(model_dict)\n                self.recentModels.insert(0, model_dict)\n            self.settings.setValue(\"recent_models\", self.recentModels)\n            self.statusbar.showMessage(\n                osp.basename(param_path) + self.tr(\" 模型加载成功\"), 10000\n            )\n            return True\n        else:\n            self.warnException(res)\n            return False\n\n    def chooseMode(self):\n        self.predictor_params[\"predictor_params\"][\n            \"with_mask\"\n        ] = self.cheWithMask.isChecked()\n        self.controller.reset_predictor(predictor_params=self.predictor_params)\n        if self.cheWithMask.isChecked():\n            self.statusbar.showMessage(self.tr(\"掩膜已启用\"), 10000)\n        else:\n            self.statusbar.showMessage(self.tr(\"掩膜已关闭\"), 10000)\n\n    def loadRecentModelParam(self):\n        if len(self.recentModels) == 0:\n            self.statusbar.showMessage(self.tr(\"没有最近使用模型信息，请加载模型\"), 10000)\n            return\n        m = self.recentModels[0]\n        param_path = m[\"param_path\"]\n        self.setModelParam(param_path)\n\n    # 标签列表\n    def importLabelList(self, filePath=None):\n        if filePath is None:\n            if self.settings.value(\"use_qt_widget\", False, type=bool):\n                options = QtWidgets.QFileDialog.DontUseNativeDialog\n            else:\n                options = QtWidgets.QFileDialog.ReadOnly\n            filters = self.tr(\"标签配置文件\") + \" (*.txt)\"\n            filePath, _ = QtWidgets.QFileDialog.getOpenFileName(\n                self,\n                self.tr(\"选择标签配置文件路径\") + \" - \" + __APPNAME__,\n                \".\",\n                filters,\n                options=options,\n            )\n        filePath = normcase(filePath)\n        if not osp.exists(filePath):\n            return\n        self.controller.importLabel(filePath)\n        logger.info(f\"Loaded label list: {self.controller.labelList.labelList}\")\n        self.refreshLabelList()\n\n    def exportLabelList(self, savePath: str = None):\n        if len(self.controller.labelList) == 0:\n            self.warn(self.tr(\"没有需要保存的标签\"), self.tr(\"请先添加标签之后再进行保存！\"))\n            return\n        if savePath is None:\n            filters = self.tr(\"标签配置文件\") + \"(*.txt)\"\n            dlg = QtWidgets.QFileDialog(\n                self,\n                self.tr(\"保存标签配置文件\"),\n                \".\",\n                filters,\n            )\n            dlg.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)\n            if self.settings.value(\"use_qt_widget\", False, type=bool):\n                options = QtWidgets.QFileDialog.DontUseNativeDialog\n            else:\n                options = QtWidgets.QFileDialog.DontUseCustomDirectoryIcons\n            dlg.setDefaultSuffix(\"txt\")\n            dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)\n            savePath, _ = dlg.getSaveFileName(\n                self,\n                self.tr(\"选择保存标签配置文件路径\") + \" - \" + __APPNAME__,\n                \".\",\n                filters,\n                options=options,\n            )\n        self.controller.exportLabel(savePath)\n\n    def addLabel(self):\n        c = self.colorMap.get_color()\n        table = self.labelListTable\n        idx = table.rowCount()\n        table.insertRow(table.rowCount())\n        self.controller.addLabel(idx + 1, \"\", c)\n        numberItem = QTableWidgetItem(str(idx + 1))\n        numberItem.setFlags(QtCore.Qt.ItemIsEnabled)\n        table.setItem(idx, 0, numberItem)\n        table.setItem(idx, 1, QTableWidgetItem())\n        colorItem = QTableWidgetItem()\n        colorItem.setBackground(QtGui.QColor(c[0], c[1], c[2]))\n        colorItem.setFlags(QtCore.Qt.ItemIsEnabled)\n        table.setItem(idx, 2, colorItem)\n        delItem = QTableWidgetItem()\n        delItem.setIcon(util.newIcon(\"Clear\"))\n        delItem.setTextAlignment(Qt.AlignCenter)\n        delItem.setFlags(QtCore.Qt.ItemIsEnabled)\n        table.setItem(idx, 3, delItem)\n        self.adjustTableSize()\n        self.labelListClicked(self.labelListTable.rowCount() - 1, 0)\n\n    def adjustTableSize(self):\n        self.labelListTable.horizontalHeader().setDefaultSectionSize(25)\n        self.labelListTable.horizontalHeader().setSectionResizeMode(\n            0, QtWidgets.QHeaderView.Fixed\n        )\n        self.labelListTable.horizontalHeader().setSectionResizeMode(\n            3, QtWidgets.QHeaderView.Fixed\n        )\n        self.labelListTable.horizontalHeader().setSectionResizeMode(\n            2, QtWidgets.QHeaderView.Fixed\n        )\n        self.labelListTable.setColumnWidth(2, 50)\n\n    def clearLabelList(self):\n        if len(self.controller.labelList) == 0:\n            return True\n        res = self.warn(\n            self.tr(\"清空标签列表?\"),\n            self.tr(\"请确认是否要清空标签列表\"),\n            QMessageBox.Yes | QMessageBox.Cancel,\n        )\n        if res == QMessageBox.Cancel:\n            return False\n        self.controller.labelList.clear()\n        if self.controller:\n            self.controller.label_list = []\n            self.controller.curr_label_number = 0\n        self.labelListTable.clear()\n        self.labelListTable.setRowCount(0)\n        return True\n\n    def refreshLabelList(self):\n        table = self.labelListTable\n        table.clearContents()\n        table.setRowCount(len(self.controller.labelList))\n        table.setColumnCount(4)\n        for idx, lab in enumerate(self.controller.labelList):\n            numberItem = QTableWidgetItem(str(lab.idx))\n            numberItem.setFlags(QtCore.Qt.ItemIsEnabled)\n            table.setItem(idx, 0, numberItem)\n            table.setItem(idx, 1, QTableWidgetItem(lab.name))\n            c = lab.color\n            colorItem = QTableWidgetItem()\n            colorItem.setBackground(QtGui.QColor(c[0], c[1], c[2]))\n            colorItem.setFlags(QtCore.Qt.ItemIsEnabled)\n            table.setItem(idx, 2, colorItem)\n            delItem = QTableWidgetItem()\n            delItem.setIcon(util.newIcon(\"Clear\"))\n            delItem.setTextAlignment(Qt.AlignCenter)\n            delItem.setFlags(QtCore.Qt.ItemIsEnabled)\n            table.setItem(idx, 3, delItem)\n            self.adjustTableSize()\n\n        cols = [0, 1, 3]\n        for idx in cols:\n            table.resizeColumnToContents(idx)\n        self.adjustTableSize()\n\n    def labelListDoubleClick(self, row, col):\n        if col != 2:\n            return\n        table = self.labelListTable\n        color = QtWidgets.QColorDialog.getColor()\n        if color.getRgb() == (0, 0, 0, 255):\n            return\n        table.item(row, col).setBackground(color)\n        self.controller.labelList[row].color = color.getRgb()[:3]\n        if self.controller:\n            self.controller.label_list = self.controller.labelList\n        for p in self.scene.polygon_items:\n            idlab = self.controller.labelList.getLabelById(p.labelIndex)\n            if idlab is not None:\n                color = idlab.color\n                p.setColor(color, color)\n        self.labelListClicked(row, 0)\n\n    @property\n    def currLabelIdx(self):\n        return self.controller.curr_label_number - 1\n\n    def labelListClicked(self, row, col):\n        table = self.labelListTable\n        if col == 3:\n            labelIdx = int(table.item(row, 0).text())\n            self.controller.labelList.remove(labelIdx)\n            table.removeRow(row)\n\n        if col == 0 or col == 1:\n            for cl in range(2):\n                for idx in range(len(self.controller.labelList)):\n                    table.item(idx, cl).setBackground(QtGui.QColor(255, 255, 255))\n                table.item(row, cl).setBackground(QtGui.QColor(48, 140, 198))\n                table.item(row, 0).setSelected(True)\n            if self.controller:\n                self.controller.setCurrLabelIdx(int(table.item(row, 0).text()))\n                self.controller.label_list = self.controller.labelList\n\n    def labelListItemChanged(self, row, col):\n        self.colorMap.usedColors = self.controller.labelList.colors\n        try:\n            if col == 1:\n                name = self.labelListTable.item(row, col).text()\n                self.controller.labelList[row].name = name\n        except:\n            pass\n\n    # 多边形标注\n    def createPoly(self, curr_polygon, color):\n        if curr_polygon is None:\n            return\n        for points in curr_polygon:\n            if len(points) < 3:\n                continue\n            poly = PolygonAnnotation(\n                self.controller.labelList[self.currLabelIdx].idx,\n                self.controller.image.shape,\n                self.delPolygon,\n                self.setDirty,\n                color,\n                color,\n                self.opacity,\n            )\n            poly.labelIndex = self.controller.labelList[self.currLabelIdx].idx\n            self.scene.addItem(poly)\n            self.scene.polygon_items.append(poly)\n            for p in points:\n                poly.addPointLast(QtCore.QPointF(p[0], p[1]))\n            self.setDirty(True)\n\n    def delActivePolygon(self):\n        for idx, polygon in enumerate(self.scene.polygon_items):\n            if polygon.hasFocus():\n                res = self.warn(\n                    self.tr(\"确认删除？\"),\n                    self.tr(\"确认要删除当前选中多边形标注？\"),\n                    QMessageBox.Yes | QMessageBox.Cancel,\n                )\n                if res == QMessageBox.Yes:\n                    self.delPolygon(polygon)\n\n    def delPolygon(self, polygon):\n        polygon.remove()\n        if self.save_status[\"coco\"]:\n            if polygon.coco_id:\n                self.coco.delAnnotation(\n                    polygon.coco_id,\n                    self.coco.imgNameToId[osp.basename(self.imagePath)],\n                )\n        self.setDirty(True)\n\n    def delAllPolygon(self):\n        for p in self.scene.polygon_items[::-1]:  # 删除所有多边形\n            self.delPolygon(p)\n\n    def delActivePoint(self):\n        for polygon in self.scene.polygon_items:\n            polygon.removeFocusPoint()\n\n    # 图片/标签 io\n    def getMask(self):\n        if not self.controller or self.controller.image is None:\n            return\n        s = self.controller.imgShape\n        pesudo = np.zeros([s[0], s[1]])\n        # 覆盖顺序，从上往下\n        # TODO: 是标签数值大的会覆盖小的吗?\n        # A: 是列表中上面的覆盖下面的，由于标签可以移动，不一定是大小按顺序覆盖\n        # RE: 我们做医学的时候覆盖比较多，感觉一般是数值大的标签覆盖数值小的标签。按照上面覆盖下面的话可能跟常见的情况正好是反过来的，感觉可能从下往上覆盖会比较好\n        len_lab = self.labelListTable.rowCount()\n        for i in range(len_lab - 1, -1, -1):\n            idx = int(self.labelListTable.item(len_lab - i - 1, 0).text())\n            for poly in self.scene.polygon_items:\n                if poly.labelIndex == idx:\n                    pts = np.int32([np.array(poly.scnenePoints)])\n                    cv2.fillPoly(pesudo, pts=pts, color=idx)\n        return pesudo\n\n    def openRecentImage(self, file_path):\n        self.queueEvent(partial(self.loadImage, file_path))\n        self.listFiles.addItems([file_path.replace(\"\\\\\", \"/\")])\n        self.currIdx = self.listFiles.count() - 1\n        self.listFiles.setCurrentRow(self.currIdx)  # 移动位置\n        self.imagePaths.append(file_path)\n\n    def openImage(self, filePath: str = None):\n        # 在triggered.connect中使用不管默认filePath为什么返回值都为False\n        if not isinstance(filePath, str) or filePath is False:\n            prompts = [\"图片\", \"医学影像\", \"遥感影像\"]\n            filters = \"\"\n            for fmts, p in zip(self.formats, prompts):\n                filters += f\"{p} ({' '.join(['*' + f for f in fmts])}) ;; \"\n            filters = filters[:-3]\n            recentPath = self.settings.value(\"recent_files\", [])\n            if len(recentPath) == 0:\n                recentPath = \".\"\n            else:\n                recentPath = osp.dirname(recentPath[0])\n            if self.settings.value(\"use_qt_widget\", False, type=bool):\n                options = QtWidgets.QFileDialog.DontUseNativeDialog\n            else:\n                options = QtWidgets.QFileDialog.ReadOnly\n            filePath, _ = QtWidgets.QFileDialog.getOpenFileName(\n                self,\n                self.tr(\"选择待标注图片\") + \" - \" + __APPNAME__,\n                recentPath,\n                filters,\n                options=options,\n            )\n            if len(filePath) == 0:  # 用户没选就直接关闭窗口\n                return\n        filePath = normcase(filePath)\n        if not self.loadImage(filePath):\n            return False\n\n        # 3. 添加记录\n        self.listFiles.addItems([filePath])\n        self.currIdx = self.listFiles.count() - 1\n        self.listFiles.setCurrentRow(self.currIdx)  # 移动位置\n        self.imagePaths.append(filePath)\n        return True\n\n    def openFolder(self, inputDir: str = None):\n        # 1. 如果没传文件夹，弹框让用户选\n        if not isinstance(inputDir, str):\n            recentPath = self.settings.value(\"recent_files\", [])\n            if len(recentPath) == 0:\n                recentPath = \".\"\n            else:\n                recentPath = osp.dirname(recentPath[-1])\n            options = (\n                QtWidgets.QFileDialog.ShowDirsOnly\n                | QtWidgets.QFileDialog.DontResolveSymlinks\n            )\n            if self.settings.value(\"use_qt_widget\", False, type=bool):\n                options = options | QtWidgets.QFileDialog.DontUseNativeDialog\n            inputDir = QtWidgets.QFileDialog.getExistingDirectory(\n                self,\n                self.tr(\"选择待标注图片文件夹\") + \" - \" + __APPNAME__,\n                recentPath,\n                options,\n            )\n            if not osp.exists(inputDir):\n                return\n\n        # 2. 关闭当前图片，清空文件列表\n        self.saveImage(close=True)\n        self.imagePaths = []\n        self.listFiles.clear()\n\n        # 3. 扫描文件夹下所有图片\n        # 3.1 获取所有文件名\n        imagePaths = os.listdir(inputDir)\n        exts = tuple(f for fmts in self.formats for f in fmts)\n        imagePaths = [n for n in imagePaths if n.lower().endswith(exts)]\n        imagePaths = [n for n in imagePaths if not n[0] == \".\"]\n        imagePaths.sort()\n        if len(imagePaths) == 0:\n            return\n        # 3.2 设置默认输出路径\n        if self.outputDir is None:\n            # 没设置为文件夹下的 label 文件夹\n            self.outputDir = osp.join(inputDir, \"label\")\n        if not osp.exists(self.outputDir):\n            os.makedirs(self.outputDir)\n        # 3.3 有重名图片，标签保留原来拓展名\n        names = []\n        for name in imagePaths:\n            name = osp.splitext(name)[0]\n            if name not in names:\n                names.append(name)\n            else:\n                self.toggleOrigExt(True)\n                break\n        imagePaths = [osp.join(inputDir, n) for n in imagePaths]\n        for p in imagePaths:\n            p = normcase(p)\n            self.imagePaths.append(p)\n            self.listFiles.addItem(p)\n\n        # 3.4 加载已有的标注\n        if self.outputDir is not None and osp.exists(self.outputDir):\n            self.changeOutputDir(self.outputDir)\n        if len(self.imagePaths) != 0:\n            self.currIdx = 0\n            self.turnImg(0)\n        self.inputDir = inputDir\n\n    def loadImage(self, path):\n        if self.controller.model is None:\n            self.warn(\"未检测到模型\", \"请先加载模型参数\")\n            return\n        # 1. 拒绝None和不存在的路径，关闭当前图像\n        if not path:\n            return\n        path = normcase(path)\n        if not osp.exists(path):\n            return\n        self.saveImage(True)  # 关闭当前图像\n        self.eximgsInit()  # TODO: 将grid的部分整合到saveImage里\n\n        # 2. 判断图像类型，打开\n        # TODO: 加用户指定类型的功能\n        image = None\n\n        # 直接if会报错，因为打开遥感图像后多波段不存在，现在把遥感图像的单独抽出来了\n        # 自然图像\n        if path.lower().endswith(tuple(self.formats[0])):\n            image = cv2.imdecode(np.fromfile(path, dtype=np.uint8), 1)\n            image = image[:, :, ::-1]  # BGR转RGB\n            if self.grid_message.isChecked():\n                if checkOpenGrid(image, self.thumbnail_min):\n                    if self.loadGrid(image, False):\n                        image, _ = self.grid.getGrid(0, 0)\n            else:\n                if self.dockWidgets[\"grid\"].isVisible() is True:\n                    self.grid = Grids(image)\n                    self.initGrid()\n                    image, _ = self.grid.getGrid(0, 0)\n\n        # 医学影像\n        if path.lower().endswith(tuple(self.formats[1])):\n            if not self.dockStatus[5]:\n                res = self.warn(\n                    self.tr(\"未启用医疗组件\"),\n                    self.tr(\"加载医疗影像需启用医疗组件，是否立即启用？\"),\n                    QMessageBox.Yes | QMessageBox.Cancel,\n                )\n                if res == QMessageBox.Cancel:\n                    return False\n                self.toggleWidget(5)\n                if not self.dockStatus[5]:\n                    return False\n            image = med.dcm_reader(path)  # TODO: 添加多层支持\n            if image.shape[-1] != 1:\n                self.warn(\"医学影像打开错误\", \"暂不支持打开多层医学影像\")\n                return False\n\n            maxValue = np.max(image)  # 根据数据模态自适应窗宽窗位\n            minValue = np.min(image)\n            if minValue == 0:\n                ww = maxValue\n                wc = int(maxValue / 2)\n            else:\n                ww = maxValue + int(abs(minValue))\n                wc = int((minValue + maxValue) / 2)\n            self.sliderWw.setValue(int(ww))\n            self.textWw.setText(str(ww))\n            self.sliderWc.setValue(int(wc))\n            self.textWc.setText(str(wc))\n\n            self.controller.rawImage = self.image = image\n            image = med.windowlize(image, self.ww, self.wc)\n\n        # 遥感图像\n        if path.lower().endswith(\n            tuple(self.formats[2])\n        ):  # imghdr.what(path) == \"tiff\":\n            if not self.dockStatus[4]:\n                res = self.warn(\n                    self.tr(\"未打开遥感组件\"),\n                    self.tr(\"打开遥感图像需启用遥感组件，是否立即启用？\"),\n                    QMessageBox.Yes | QMessageBox.Cancel,\n                )\n                if res == QMessageBox.Cancel:\n                    return False\n                self.toggleWidget(4)\n                if not self.dockStatus[4]:\n                    return False\n            self.raster = Raster(path)\n            gi = self.raster.showGeoInfo()\n            self.edtGeoinfo.setText(self.tr(\"● 波段数：\") + gi[0] + \"\\n\" + \n                                    self.tr(\"● 数据类型：\") + gi[1] + \"\\n\" + \n                                    self.tr(\"● 行数：\") + gi[2] + \"\\n\" + \n                                    self.tr(\"● 列数：\") + gi[3] + \"\\n\" + \n                                    \"● EPSG：\" + gi[4])\n            if max(self.rsRGB) > self.raster.geoinfo.count:\n                self.rsRGB = [1, 1, 1]\n            self.raster.setBand(self.rsRGB)\n            if self.grid_message.isChecked():\n                if self.raster.checkOpenGrid(self.thumbnail_min):\n                    if self.loadGrid(self.raster):\n                        image, _ = self.raster.getGrid(0, 0)\n                    else:\n                        image, _ = self.raster.getArray()\n                else:\n                    image, _ = self.raster.getArray()\n            else:\n                if self.dockWidgets[\"grid\"].isVisible() is True:\n                    self.grid = RSGrids(self.raster)\n                    self.raster.open_grid = True\n                    self.initGrid()\n                    image, _ = self.raster.getGrid(0, 0)\n                else:\n                    image, _ = self.raster.getArray()\n            self.updateBandList()\n            # self.updateSlideSld(True)\n        else:\n            self.edtGeoinfo.setText(self.tr(\"无\"))\n\n        # 如果没找到图片的reader\n        if image is None:\n            self.warn(\"打开图像失败\", f\"未找到{path}文件对应的读取程序\")\n            return\n\n        self.image = image\n        self.controller.setImage(image)\n        self.updateImage(True)\n\n        # 2. 加载标签\n        self.loadLabel(path)\n        self.addRecentFile(path)\n        self.imagePath = path\n        return True\n\n    def loadLabel(self, imgPath):\n        if imgPath == \"\":\n            return None\n\n        # 1. 读取json格式标签\n        if self.save_status[\"json\"]:\n\n            def getName(path):\n                return osp.splitext(osp.basename(path))[0]\n\n            imgName = getName(imgPath)\n            labelPath = None\n            for path in self.labelPaths:\n                if not path.endswith(\".json\"):\n                    continue\n                if self.origExt:\n                    if getName(path) == osp.basename(imgPath):\n                        labelPath = path\n                        break\n                else:\n                    if getName(path) == imgName:\n                        labelPath = path\n                        break\n            if not labelPath:\n                return\n\n            labels = json.loads(open(labelPath, \"r\").read())\n\n            for label in labels:\n                color = label[\"color\"]\n                labelIdx = label[\"labelIdx\"]\n                points = label[\"points\"]\n                poly = PolygonAnnotation(\n                    labelIdx,\n                    self.controller.image.shape,\n                    self.delPolygon,\n                    self.setDirty,\n                    color,\n                    color,\n                    self.opacity,\n                )\n                self.scene.addItem(poly)\n                self.scene.polygon_items.append(poly)\n                for p in points:\n                    poly.addPointLast(QtCore.QPointF(p[0], p[1]))\n\n        # 2. 读取coco格式标签\n        if self.save_status[\"coco\"]:\n            imgId = self.coco.imgNameToId.get(osp.basename(imgPath), None)\n            if imgId is None:\n                return\n            anns = self.coco.imgToAnns[imgId]\n            for ann in anns:\n                xys = ann[\"segmentation\"][0]\n                points = []\n                for idx in range(0, len(xys), 2):\n                    points.append([xys[idx], xys[idx + 1]])\n                labelIdx = ann[\"category_id\"]\n                idlab = self.controller.labelList.getLabelById(labelIdx)\n                if idlab is not None:\n                    color = idlab.color\n                    poly = PolygonAnnotation(\n                        ann[\"category_id\"],\n                        self.controller.image.shape,\n                        self.delPolygon,\n                        self.setDirty,\n                        color,\n                        color,\n                        self.opacity,\n                        ann[\"id\"],\n                    )\n                    self.scene.addItem(poly)\n                    self.scene.polygon_items.append(poly)\n                    for p in points:\n                        poly.addPointLast(QtCore.QPointF(p[0], p[1]))\n\n    def turnImg(self, delta, list_click=False):\n        if (self.grid is None or self.grid.curr_idx is None) or list_click:\n            # 1. 检查是否有图可翻，保存标签\n            self.currIdx += delta\n            if self.currIdx >= len(self.imagePaths) or self.currIdx < 0:\n                self.currIdx -= delta\n                if delta == 1:\n                    self.statusbar.showMessage(self.tr(f\"没有后一张图片\"))\n                else:\n                    self.statusbar.showMessage(self.tr(f\"没有前一张图片\"))\n                self.saveImage(False)\n                return\n            else:\n                self.saveImage(True)\n\n            # 2. 打开新图\n            self.loadImage(self.imagePaths[self.currIdx])\n            self.listFiles.setCurrentRow(self.currIdx)\n        else:\n            self.turnGrid(delta)\n        self.setDirty(False)\n\n    def imageListClicked(self):\n        if not self.controller:\n            self.warn(self.tr(\"模型未加载\"), self.tr(\"尚未加载模型，请先加载模型！\"))\n            self.changeParam()\n            if not self.controller:\n                return\n        if self.controller.is_incomplete_mask:\n            self.exportLabel()\n        toRow = self.listFiles.currentRow()\n        delta = toRow - self.currIdx\n        self.turnImg(delta, True)\n\n    def finishObject(self):\n        if not self.controller or self.image is None:\n            return\n        current_mask, curr_polygon = self.controller.finishObject(\n            building=self.boundaryRegular.isChecked()\n        )\n        if curr_polygon is not None:\n            self.updateImage()\n            if current_mask is not None:\n                # current_mask = current_mask.astype(np.uint8) * 255\n                # polygon = util.get_polygon(current_mask)\n                color = self.controller.labelList[self.currLabelIdx].color\n                self.createPoly(curr_polygon, color)\n        # 状态改变\n        if self.status == self.EDITING:\n            self.status = self.ANNING\n            for p in self.scene.polygon_items:\n                p.setAnning(isAnning=True)\n        else:\n            self.status = self.EDITING\n            for p in self.scene.polygon_items:\n                p.setAnning(isAnning=False)\n        self.getMask()\n\n    def completeLastMask(self):\n        # 返回最后一个标签是否完成，false就是还有带点的\n        if not self.controller or self.controller.image is None:\n            return True\n        if not self.controller.is_incomplete_mask:\n            return True\n        res = self.warn(\n            self.tr(\"完成最后一个目标？\"),\n            self.tr(\"是否完成最后一个目标的标注，不完成不会进行保存。\"),\n            QMessageBox.Yes | QMessageBox.Cancel,\n        )\n        if res == QMessageBox.Yes:\n            self.finishObject()\n            self.exportLabel()\n            self.setDirty(False)\n            return True\n        return False\n\n    def saveImage(self, close=False):\n        if self.controller and self.controller.image is not None:\n            # 1. 完成正在交互式标注的标签\n            self.completeLastMask()\n            # 2. 进行保存\n            if self.isDirty:\n                if self.actions.auto_save.isChecked():\n                    self.exportLabel()\n                else:\n                    res = self.warn(\n                        self.tr(\"保存标签？\"),\n                        self.tr(\"标签尚未保存，是否保存标签\"),\n                        QMessageBox.Yes | QMessageBox.Cancel,\n                    )\n                    if res == QMessageBox.Yes:\n                        self.exportLabel()\n                self.setDirty(False)\n            if close:\n                # 3. 清空多边形标注，删掉图片\n                for p in self.scene.polygon_items[::-1]:\n                    p.remove()\n                self.scene.polygon_items = []\n                self.controller.resetLastObject()\n                self.updateImage()\n                self.controller.image = None\n        if close:\n            self.annImage.setPixmap(QPixmap())\n\n    def exportLabel(self, saveAs=False, savePath=None, lab_input=None):\n        # 1. 需要处于标注状态\n        if not self.controller or self.controller.image is None:\n            return\n        # 2. 完成正在交互式标注的标签\n        self.completeLastMask()\n        # 3. 确定保存路径\n        # 3.1 如果参数指定了保存路径直接存到savePath\n        if not savePath:\n            if not saveAs and self.outputDir is not None:\n                # 3.2 指定了标签文件夹，而且不是另存为：根据标签文件夹和文件名出保存路径\n                name, ext = osp.splitext(osp.basename(self.imagePath))\n                if not self.origExt:\n                    ext = \".png\"\n                savePath = osp.join(\n                    self.outputDir,\n                    name + ext,\n                )\n            else:\n                # 3.3 没有指定标签存到哪，或者是另存为：弹框让用户选\n                savePath = self.chooseSavePath()\n        if savePath is None or not osp.exists(osp.dirname(savePath)):\n            return\n\n        if savePath not in self.labelPaths:\n            self.labelPaths.append(savePath)\n\n        if lab_input is None:\n            mask_output = self.getMask()\n            s = self.controller.imgShape\n        else:\n            mask_output = lab_input\n            s = lab_input.shape\n\n        # BUG: 如果用了多边形标注从多边形生成mask\n        # 4.1 保存灰度图\n        if self.save_status[\"gray_scale\"]:\n            if self.raster is not None:\n                # FIXME: when big map saved, self.raster is None, \n                #        so adjust polygon can't saved in tif's mask.\n                pathHead, _ = osp.splitext(savePath)\n                # if self.rsSave.isChecked():\n                tifPath = pathHead + \"_mask.tif\"\n                self.raster.saveMask(mask_output, tifPath)\n                if self.shpSave.isChecked():\n                    shpPath = pathHead + \".shp\"\n                    # geocode_list = self.mask2poly(mask_output, False)\n                    print(rs.save_shp(shpPath, tifPath))\n            else:\n                ext = osp.splitext(savePath)[1]\n                cv2.imencode(ext, mask_output)[1].tofile(savePath)\n                # self.labelPaths.append(savePath)\n\n        # 4.2 保存伪彩色\n        if self.save_status[\"pseudo_color\"]:\n            if self.raster is None:\n                pseudoPath, ext = osp.splitext(savePath)\n                pseudoPath = pseudoPath + \"_pseudo\" + ext\n                pseudo = np.zeros([s[0], s[1], 3])\n                # mask = self.controller.result_mask\n                mask = mask_output\n                # print(pseudo.shape, mask.shape)\n                for lab in self.controller.labelList:\n                    pseudo[mask == lab.idx, :] = lab.color[::-1]\n                cv2.imencode(ext, pseudo)[1].tofile(pseudoPath)\n\n        # 4.3 保存前景抠图\n        if self.save_status[\"cutout\"]:\n            if self.raster is None:\n                mattingPath, ext = osp.splitext(savePath)\n                mattingPath = mattingPath + \"_cutout\" + ext\n                img = np.ones([s[0], s[1], 4], dtype=\"uint8\") * 255\n                img[:, :, :3] = self.controller.image.copy()\n                img[mask_output == 0] = self.cutoutBackground\n                img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)\n                cv2.imencode(ext, img)[1].tofile(mattingPath)\n\n        # 4.4 保存json\n        if self.save_status[\"json\"]:\n            polygons = self.scene.polygon_items\n            labels = []\n            for polygon in polygons:\n                l = self.controller.labelList[polygon.labelIndex - 1]\n                label = {\n                    \"name\": l.name,\n                    \"labelIdx\": l.idx,\n                    \"color\": l.color,\n                    \"points\": [],\n                }\n                for p in polygon.scnenePoints:\n                    label[\"points\"].append(p)\n                labels.append(label)\n            if self.origExt:\n                jsonPath = savePath + \".json\"\n            else:\n                jsonPath = osp.splitext(savePath)[0] + \".json\"\n            open(jsonPath, \"w\", encoding=\"utf-8\").write(json.dumps(labels))\n            self.labelPaths.append(jsonPath)\n\n        # 4.5 保存coco\n        if self.save_status[\"coco\"]:\n            if not self.coco.hasImage(osp.basename(self.imagePath)):\n                imgId = self.coco.addImage(osp.basename(self.imagePath), s[1], s[0])\n            else:\n                imgId = self.coco.imgNameToId[osp.basename(self.imagePath)]\n            for polygon in self.scene.polygon_items:\n                points = []\n                for p in polygon.scnenePoints:\n                    for val in p:\n                        points.append(val)\n\n                if not polygon.coco_id:\n                    annId = self.coco.addAnnotation(imgId, polygon.labelIndex, points)\n                    polygon.coco_id = annId\n                else:\n                    self.coco.updateAnnotation(polygon.coco_id, imgId, points)\n            for lab in self.controller.labelList:\n                if self.coco.hasCat(lab.idx):\n                    self.coco.updateCategory(lab.idx, lab.name, lab.color)\n                else:\n                    self.coco.addCategory(lab.idx, lab.name, lab.color)\n            saveDir = (\n                self.outputDir if self.outputDir is not None else osp.dirname(savePath)\n            )\n            cocoPath = osp.join(saveDir, \"annotations.json\")\n            open(cocoPath, \"w\", encoding=\"utf-8\").write(json.dumps(self.coco.dataset))\n\n        self.setDirty(False)\n        self.statusbar.showMessage(self.tr(\"标签成功保存至\") + \" \" + savePath, 5000)\n\n    def chooseSavePath(self):\n        formats = [\n            \"*.{}\".format(fmt.data().decode())\n            for fmt in QtGui.QImageReader.supportedImageFormats()\n        ]\n        filters = \"Label file (%s)\" % \" \".join(formats)\n        dlg = QtWidgets.QFileDialog(\n            self,\n            self.tr(\"保存标签文件路径\"),\n            osp.dirname(self.imagePath),\n            filters,\n        )\n        dlg.setDefaultSuffix(\"png\")\n        dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)\n        dlg.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)\n        dlg.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, False)\n        savePath, _ = dlg.getSaveFileName(\n            self,\n            self.tr(\"选择标签文件保存路径\"),\n            osp.splitext(osp.basename(self.imagePath))[0] + \".png\",\n        )\n        return savePath\n\n    def eximgsInit(self):\n        self.gridTable.setRowCount(0)\n        self.gridTable.clearContents()\n        # 清零\n        self.raster = None\n        self.grid = None\n\n    def setDirty(self, isDirty):\n        self.isDirty = isDirty\n\n    def changeOutputDir(self, outputDir=None):\n        # 1. 弹框选择标签路径\n        if outputDir is None:\n            options = (\n                QtWidgets.QFileDialog.ShowDirsOnly\n                | QtWidgets.QFileDialog.DontResolveSymlinks\n            )\n            if self.settings.value(\"use_qt_widget\", False, type=bool):\n                options = options | QtWidgets.QFileDialog.DontUseNativeDialog\n            outputDir = QtWidgets.QFileDialog.getExistingDirectory(\n                self,\n                self.tr(\"选择标签保存路径\") + \" - \" + __APPNAME__,\n                self.settings.value(\"output_dir\", \".\"),\n                options,\n            )\n        if not osp.exists(outputDir):\n            return False\n        self.settings.setValue(\"output_dir\", outputDir)\n        self.outputDir = outputDir\n\n        # 2. 加载标签\n        # 2.1 如果保存coco格式，加载coco标签\n        if self.save_status[\"coco\"]:\n            defaultPath = osp.join(self.outputDir, \"annotations.json\")\n            if osp.exists(defaultPath):\n                self.initCoco(defaultPath)\n\n        # 2.2 如果保存json格式，获取所有json文件名\n        if self.save_status[\"json\"]:\n            labelPaths = os.listdir(outputDir)\n            labelPaths = [n for n in labelPaths if n.endswith(\".json\")]\n            labelPaths = [osp.join(outputDir, n) for n in labelPaths]\n            self.labelPaths = labelPaths\n\n            # 加载对应的标签列表\n            lab_auto_save = osp.join(self.outputDir, \"autosave_label.txt\")\n            if osp.exists(lab_auto_save) == False:\n                lab_auto_save = osp.join(self.outputDir, \"label/autosave_label.txt\")\n            if osp.exists(lab_auto_save):\n                try:\n                    self.importLabelList(lab_auto_save)\n                except:\n                    pass\n        return True\n\n    def maskOpacityChanged(self):\n        self.sldOpacity.textLab.setText(str(self.opacity))\n        if not self.controller or self.controller.image is None:\n            return\n        for polygon in self.scene.polygon_items:\n            polygon.setOpacity(self.opacity)\n        self.updateImage()\n\n    def clickRadiusChanged(self):\n        self.sldClickRadius.textLab.setText(str(self.clickRadius))\n        if not self.controller or self.controller.image is None:\n            return\n        self.updateImage()\n\n    def threshChanged(self):\n        self.sldThresh.textLab.setText(str(self.segThresh))\n        if not self.controller or self.controller.image is None:\n            return\n        self.controller.prob_thresh = self.segThresh\n        self.updateImage()\n\n    # def slideChanged(self):\n    #     self.sldMISlide.textLab.setText(str(self.slideMi))\n    #     if not self.controller or self.controller.image is None:\n    #         return\n    #     self.midx = int(self.slideMi) - 1\n    #     self.miSlideSet()\n    #     self.updateImage()\n\n    def undoClick(self):\n        if self.image is None:\n            return\n        if not self.controller:\n            return\n        self.controller.undoClick()\n        self.updateImage()\n        if not self.controller.is_incomplete_mask:\n            self.setDirty(False)\n\n    def clearAll(self):\n        if not self.controller or self.controller.image is None:\n            return\n        self.controller.resetLastObject()\n        self.updateImage()\n        self.setDirty(False)\n\n    def redoClick(self):\n        if self.image is None:\n            return\n        if not self.controller:\n            return\n        self.controller.redoClick()\n        self.updateImage()\n\n    def canvasClick(self, x, y, isLeft):\n        c = self.controller\n        if c.image is None:\n            return\n        if not c.inImage(x, y):\n            return\n        if not c.modelSet:\n            self.warn(self.tr(\"未选择模型\", self.tr(\"尚未选择模型，请先在右上角选择模型\")))\n            return\n\n        if self.status == self.IDILE:\n            return\n        currLabel = self.controller.curr_label_number\n        if not currLabel or currLabel == 0:\n            self.warn(self.tr(\"未选择当前标签\"), self.tr(\"请先在标签列表中单击点选标签\"))\n            return\n\n        self.controller.addClick(x, y, isLeft)\n        self.updateImage()\n        self.status = self.ANNING\n\n    def updateImage(self, reset_canvas=False):\n        if not self.controller:\n            return\n        image = self.controller.get_visualization(\n            alpha_blend=self.opacity,\n            click_radius=self.clickRadius,\n        )\n        height, width, _ = image.shape\n        bytesPerLine = 3 * width\n        image = QImage(image.data, width, height, bytesPerLine, QImage.Format_RGB888)\n        if reset_canvas:\n            self.resetZoom(width, height)\n        self.annImage.setPixmap(QPixmap(image))\n\n    def viewZoomed(self, scale):\n        self.scene.scale = scale\n        self.scene.updatePolygonSize()\n\n    # 界面缩放重置\n    def resetZoom(self, width, height):\n        # 每次加载图像前设定下当前的显示框，解决图像缩小后不在中心的问题\n        self.scene.setSceneRect(0, 0, width, height)\n        # 缩放清除\n        self.canvas.scale(1 / self.canvas.zoom_all, 1 / self.canvas.zoom_all)  # 重置缩放\n        self.canvas.zoom_all = 1\n        # 最佳缩放\n        s_eps = 0.98\n        scr_cont = [\n            (self.scrollArea.width() * s_eps) / width,\n            (self.scrollArea.height() * s_eps) / height,\n        ]\n        if scr_cont[0] * height > self.scrollArea.height():\n            self.canvas.zoom_all = scr_cont[1]\n        else:\n            self.canvas.zoom_all = scr_cont[0]\n        self.canvas.scale(self.canvas.zoom_all, self.canvas.zoom_all)\n        self.scene.scale = self.canvas.zoom_all\n\n    def keyReleaseEvent(self, event):\n        # print(event.key(), Qt.Key_Control)\n        # 释放ctrl的时候刷新图像，对应自适应点大小在缩放后刷新\n        if not self.controller or self.controller.image is None:\n            return\n        if event.key() == Qt.Key_Control:\n            self.updateImage()\n\n    def queueEvent(self, function):\n        QtCore.QTimer.singleShot(0, function)\n\n    def toggleOrigExt(self, dst=None):\n        if dst:\n            self.origExt = dst\n        else:\n            self.origExt = not self.origExt\n        self.actions.origional_extension.setChecked(self.origExt)\n\n    def toggleAutoSave(self, save):\n        if save and not self.outputDir:\n            self.changeOutputDir(None)\n        if save and not self.outputDir:\n            save = False\n        self.actions.auto_save.setChecked(save)\n        self.settings.setValue(\"auto_save\", save)\n\n    def toggleSave(self, type):\n        self.save_status[type] = not self.save_status[type]\n        if type == \"coco\" and self.save_status[\"coco\"]:\n            self.initCoco()\n        if type == \"coco\":\n            self.save_status[\"json\"] = not self.save_status[\"coco\"]\n            self.actions.save_json.setChecked(self.save_status[\"json\"])\n        if type == \"json\":\n            self.save_status[\"coco\"] = not self.save_status[\"json\"]\n            self.actions.save_coco.setChecked(self.save_status[\"coco\"])\n\n    def initCoco(self, coco_path: str = None):\n        if not coco_path:\n            if not self.outputDir or not osp.exists(self.outputDir):\n                coco_path = None\n            else:\n                coco_path = osp.join(self.outputDir, \"annotations.json\")\n        else:\n            if not osp.exists(coco_path):\n                coco_path = None\n        self.coco = COCO(coco_path)\n        if self.clearLabelList():\n            self.controller.labelList = util.LabelList(self.coco.dataset[\"categories\"])\n            self.refreshLabelList()\n\n    def toggleWidget(self, index=None, warn=True):\n        # TODO: 输入从数字改成名字\n\n        # 1. 改变\n        if isinstance(index, int):\n            self.dockStatus[index] = not self.dockStatus[index]\n\n        # 2. 判断widget是否可以开启\n        # 2.1 遥感\n        if self.dockStatus[4] and not (rs.check_gdal() and rs.check_rasterio()):\n            if warn:\n                self.warn(\n                    self.tr(\"无法导入GDAL\"),\n                    self.tr(\"使用遥感工具需要安装GDAL！\"),\n                    QMessageBox.Yes,\n                )\n            self.statusbar.showMessage(self.tr(\"打开遥感工具失败，请安装GDAL库\"))\n            self.dockStatus[4] = False\n\n        # 2.2 医疗\n        if self.dockStatus[5] and not med.has_sitk():\n            if warn:\n                self.warn(\n                    self.tr(\"无法导入SimpleITK\"),\n                    self.tr(\"使用医疗工具需要安装SimpleITK！\"),\n                    QMessageBox.Yes,\n                )\n            self.statusbar.showMessage(self.tr(\"打开医疗工具失败，请安装SimpleITK\"))\n            self.dockStatus[5] = False\n        widgets = list(self.dockWidgets.values())\n\n        for idx, s in enumerate(self.dockStatus):\n            self.menus.showMenu[idx].setChecked(s)\n            if s:\n                widgets[idx].show()\n            else:\n                widgets[idx].hide()\n\n        self.settings.setValue(\"dock_status\", self.dockStatus)\n        # self.display_dockwidget[index] = bool(self.display_dockwidget[index] - 1)\n        # self.toggleDockWidgets()\n        self.saveLayout()\n\n    # def toggleDockWidgets(self, is_init=False):\n    #     if is_init == True:\n    #         if self.dockStatus != []:\n    #             if len(self.dockStatus) != len(self.menus.showMenu):\n    #                 self.settings.remove(\"dock_status\")\n    #             else:\n    #                 self.display_dockwidget = [strtobool(w) for w in self.dockStatus]\n    #         for i in range(len(self.menus.showMenu)):\n    #             self.menus.showMenu[i].setChecked(bool(self.display_dockwidget[i]))\n    #     else:\n    #         self.settings.setValue(\"dock_status\", self.display_dockwidget)\n    #     for t, w in zip(self.display_dockwidget, self.dockWidgets.values()):\n    #         if t == True:\n    #             w.show()\n    #         else:\n    #             w.hide()\n\n    def rsBandSet(self, idx):\n        if self.raster is None:\n            return\n        for i in range(len(self.bandCombos)):\n            self.rsRGB[i] = self.bandCombos[i].currentIndex() + 1  # 从1开始\n        self.raster.setBand(self.rsRGB)\n        if self.grid is not None:\n            if isinstance(self.grid.curr_idx, (list, tuple)):\n                row, col = self.grid.curr_idx\n                image, _ = self.raster.getGrid(row, col)\n            else:\n                image, _ = self.raster.getArray()\n        else:\n            image, _ = self.raster.getArray()\n        self.image = image\n        self.controller.image = image\n        self.updateImage()\n\n    # def miSlideSet(self):\n    #     image = rs.slice_img(self.controller.rawImage, self.midx)\n    #     self.test_show(image)\n\n    # def changeWorkerShow(self, index):\n    #     self.display_dockwidget[index] = bool(self.display_dockwidget[index] - 1)\n    #     self.toggleDockWidgets()\n\n    def updateBandList(self, clean=False):\n        if clean:\n            for i in range(len(self.bandCombos)):\n                try:  # 避免打开jpg后再打开tif报错\n                    self.bandCombos[i].currentIndexChanged.disconnect()\n                except TypeError:\n                    pass\n                self.bandCombos[i].clear()\n                self.bandCombos[i].addItems([\"band_1\"])\n            return\n        bands = self.raster.geoinfo.count\n        for i in range(len(self.bandCombos)):\n            try:  # 避免打开jpg后再打开tif报错\n                self.bandCombos[i].currentIndexChanged.disconnect()\n            except TypeError:\n                pass\n            self.bandCombos[i].clear()\n            self.bandCombos[i].addItems([(\"band_\" + str(j + 1)) for j in range(bands)])\n            try:\n                self.bandCombos[i].setCurrentIndex(self.rsRGB[i] - 1)\n            except IndexError:\n                pass\n        for bandCombo in self.bandCombos:\n            bandCombo.currentIndexChanged.connect(self.rsBandSet)  # 设置波段\n\n    # def updateSlideSld(self, clean=False):\n    #     if clean:\n    #         self.sldMISlide.setMaximum(1)\n    #         return\n    #     C = self.controller.rawImage.shape[-1] if len(self.controller.rawImage.shape) == 3 else 1\n    #     self.sldMISlide.setMaximum(C)\n\n    def toggleLargestCC(self, on):\n        try:\n            self.controller.filterLargestCC(on)\n        except:\n            pass\n\n    # 宫格标注\n    def initGrid(self):\n        self.delAllPolygon()\n        grid_row_count, grid_col_count = self.grid.createGrids()\n        self.gridTable.setRowCount(grid_row_count)\n        self.gridTable.setColumnCount(grid_col_count)\n        for r in range(grid_row_count):\n            for c in range(grid_col_count):\n                self.gridTable.setItem(r, c, QtWidgets.QTableWidgetItem())\n                self.gridTable.item(r, c).setBackground(self.GRID_COLOR[\"idle\"])\n                self.gridTable.item(r, c).setFlags(Qt.ItemIsSelectable)  # 无法高亮选择\n        # 初始显示第一个\n        self.grid.curr_idx = (0, 0)\n        self.gridTable.item(0, 0).setBackground(self.GRID_COLOR[\"overlying\"])\n        # 事件注册\n        self.gridTable.cellClicked.connect(self.changeGrid)\n\n    def changeGrid(self, row, col):\n        # 清除未保存的切换\n        # TODO: 这块应该通过dirty判断?\n        if self.grid.curr_idx is not None:\n            self.saveGrid()  # 切换时自动保存上一块\n            last_r, last_c = self.grid.curr_idx\n            if self.grid.mask_grids[last_r][last_c] is None:\n                self.gridTable.item(last_r, last_c).setBackground(\n                    self.GRID_COLOR[\"idle\"]\n                )\n            else:\n                self.gridTable.item(last_r, last_c).setBackground(\n                    self.GRID_COLOR[\"finised\"]\n                )\n        self.delAllPolygon()\n        image, mask = self.grid.getGrid(row, col)\n        self.controller.setImage(image)\n        self.grid.curr_idx = (row, col)\n        if mask is None:\n            self.gridTable.item(row, col).setBackground(self.GRID_COLOR[\"current\"])\n        else:\n            self.gridTable.item(row, col).setBackground(self.GRID_COLOR[\"overlying\"])\n            self.mask2poly(mask)\n        # 刷新\n        self.updateImage(True)\n\n    def mask2poly(self, mask, show=True):\n        labs = np.unique(mask)[1:]\n        colors = []\n        for i in range(len(labs)):\n            idx = int(labs[i]) - 1\n            if idx < len(self.controller.labelList):\n                c = self.controller.labelList[idx].color\n            else:\n                if self.currLabelIdx != -1:\n                    c = self.controller.labelList[self.currLabelIdx].color\n                else:\n                    c = None\n            colors.append(c)\n        geocode_list = []\n        for idx, (l, c) in enumerate(zip(labs, colors)):\n            if c is not None:\n                curr_polygon = util.get_polygon(\n                    ((mask == l).astype(np.uint8) * 255),\n                    building=self.boundaryRegular.isChecked(),\n                )\n                if show == True:\n                    self.createPoly(curr_polygon, c)\n                    for p in self.scene.polygon_items:\n                        p.setAnning(isAnning=False)\n                else:\n                    for g in curr_polygon:\n                        points = [gi.tolist() for gi in g]\n                        geocode_list.append(\n                            {\n                                \"name\": self.controller.labelList[idx].name,\n                                \"points\": points,\n                            }\n                        )\n        return geocode_list\n\n    def saveGrid(self):\n        row, col = self.grid.curr_idx\n        if self.grid.curr_idx is None:\n            return\n        self.gridTable.item(row, col).setBackground(self.GRID_COLOR[\"overlying\"])\n        # if len(np.unique(self.grid.mask_grids[row][col])) == 1:\n        self.grid.mask_grids[row][col] = np.array(self.getMask())\n        if self.cheSaveEvery.isChecked():\n            if self.outputDir is None:\n                self.changeOutputDir()\n            _, fullflname = osp.split(self.listFiles.currentItem().text())\n            fname, _ = os.path.splitext(fullflname)\n            path = osp.join(\n                self.outputDir, (fname + \"_data_\" + str(row) + \"_\" + str(col) + \".tif\")\n            )\n            im, tf = self.raster.getGrid(row, col)\n            h, w = im.shape[:2]\n            geoinfo = edict()\n            geoinfo.xsize = w\n            geoinfo.ysize = h\n            geoinfo.dtype = self.raster.geoinfo.dtype\n            geoinfo.crs = self.raster.geoinfo.crs\n            geoinfo.geotf = tf\n            self.raster.saveMask(\n                self.grid.mask_grids[row][col], path.replace(\"data\", \"mask\"), geoinfo\n            )  # 保存mask\n            self.raster.saveMask(im, path, geoinfo, 3)  # 保存图像\n\n    def turnGrid(self, delta):\n        # 切换下一个宫格\n        r, c = self.grid.curr_idx if self.grid.curr_idx is not None else (0, -1)\n        c += delta\n        if c >= self.grid.grid_count[1]:\n            c = 0\n            r += 1\n            if r >= self.grid.grid_count[0]:\n                r = 0\n        if c < 0:\n            c = self.grid.grid_count[1] - 1\n            r -= 1\n            if r < 0:\n                r = self.grid.grid_count[0] - 1\n        self.changeGrid(r, c)\n\n    def closeGrid(self):\n        self.grid = None\n        self.gridTable.setRowCount(0)\n        self.gridTable.clearContents()\n\n    def saveGridLabel(self):\n        if self.outputDir is not None:\n            name, ext = osp.splitext(osp.basename(self.imagePath))\n            if not self.origExt:\n                ext = \".png\"\n            save_path = osp.join(self.outputDir, name + ext)\n        else:\n            save_path = self.chooseSavePath()\n            if save_path == \"\":\n                return\n        try:\n            self.finishObject()\n            self.saveGrid()  # 先保存当前\n        except:\n            pass\n        self.delAllPolygon()  # 清理\n        mask = self.grid.splicingList(save_path)\n        if self.grid.__class__.__name__ == \"RSGrids\":\n            self.image, is_big = self.raster.getArray()\n        else:\n            self.image = self.grid.detimg\n            is_big = checkOpenGrid(self.image, self.thumbnail_min)\n        if is_big is None:\n            self.statusbar.showMessage(self.tr(\"图像过大，已显示缩略图\"))\n        self.controller.image = self.image\n        self.controller._result_mask = mask\n        self.exportLabel(savePath=save_path, lab_input=mask)\n        # -- RS Show polygon demo --\n        if self.show_rs_poly.isChecked():\n            h, w = self.image.shape[:2]\n            th_mask = cv2.resize(mask, dsize=(w, h), interpolation=cv2.INTER_NEAREST)\n            indexs = np.unique(th_mask)[1:]\n            for i in indexs:\n                i_mask = np.zeros_like(th_mask, dtype=\"uint8\")\n                i_mask[th_mask == i] = 255\n                curr_polygon = util.get_polygon(i_mask)\n                color = self.controller.labelList[i - 1].color\n                self.createPoly(curr_polygon, color)\n                for p in self.scene.polygon_items:\n                    p.setAnning(isAnning=False)\n        # -- RS Show polygon demo --\n        # 刷新\n        grid_row_count = self.gridTable.rowCount()\n        grid_col_count = self.gridTable.colorCount()\n        for r in range(grid_row_count):\n            for c in range(grid_col_count):\n                try:\n                    self.gridTable.item(r, c).setBackground(self.GRID_COLOR[\"idle\"])\n                except:\n                    pass\n        self.raster = None\n        self.closeGrid()\n        self.updateBandList(True)\n        self.controller.setImage(self.image)\n        self.updateImage(True)\n        self.setDirty(False)\n\n    @property\n    def opacity(self):\n        return self.sldOpacity.value() / 100\n\n    @property\n    def clickRadius(self):\n        return self.sldClickRadius.value()\n\n    @property\n    def segThresh(self):\n        return self.sldThresh.value() / 100\n\n    # @property\n    # def slideMi(self):\n    #     return self.sldMISlide.value()\n\n    def warnException(self, e):\n        e = str(e)\n        title = e.split(\"。\")[0]\n        self.warn(title, e)\n\n    def warn(self, title, text, buttons=QMessageBox.Yes):\n        msg = QMessageBox()\n        # msg.setIcon(QMessageBox.Warning)\n        msg.setWindowTitle(title)\n        msg.setText(text)\n        msg.setStandardButtons(buttons)\n        return msg.exec_()\n\n    @property\n    def status(self):\n        # TODO: 图片，模型\n        if not self.controller:\n            return self.IDILE\n        c = self.controller\n        if c.model is None or c.image is None:\n            return self.IDILE\n        if self._anning:\n            return self.ANNING\n        return self.EDITING\n\n    @status.setter\n    def status(self, status):\n        if status not in [self.ANNING, self.EDITING]:\n            return\n        if status == self.ANNING:\n            self._anning = True\n        else:\n            self._anning = False\n\n    def loadGrid(self, img, is_rs=True):\n        res = self.warn(self.tr(\"图像过大\"), self.tr(\"图像过大，将启用宫格功能！\"), \\\n                        buttons=QMessageBox.Yes | QMessageBox.No)\n        if res == QMessageBox.Yes:\n            # 打开宫格功能\n            if self.dockWidgets[\"grid\"].isVisible() is False:\n                # TODO: 改成self.dockStatus\n                self.menus.showMenu[-1].setChecked(True)\n                # self.display_dockwidget[-1] = True\n                self.dockWidgets[\"grid\"].show()\n            self.grid = RSGrids(img) if is_rs else Grids(img)\n            self.initGrid()\n            return True\n        return False\n\n    # 界面布局\n    def loadLayout(self):\n        self.restoreState(self.layoutStatus)\n        # TODO: 这里检查环境，判断是不是开医疗和遥感widget\n\n    def saveLayout(self):\n        # 保存界面\n        self.settings.setValue(\"layout_status\", QByteArray(self.saveState()))\n        self.settings.setValue(\n            \"save_status\", [(k, self.save_status[k]) for k in self.save_status.keys()]\n        )\n        # # 如果设置了保存路径，把标签也保存下\n        # if self.outputDir is not None and len(self.controller.labelList) != 0:\n        #     self.exportLabelList(osp.join(self.outputDir, \"autosave_label.txt\"))\n\n    def closeEvent(self, event):\n        self.saveImage()\n        self.saveLayout()\n        QCoreApplication.quit()\n        # sys.exit(0)\n\n    def reportBug(self):\n        webbrowser.open(\"https://github.com/PaddleCV-SIG/EISeg/issues/new/choose\")\n\n    def quickStart(self):\n        # self.saveImage(True)\n        # self.canvas.setStyleSheet(self.note_style)\n        webbrowser.open(\"https://github.com/PaddleCV-SIG/EISeg/tree/release/0.4.0\")\n\n    def toggleLogging(self, s):\n        if s:\n            logger.setLevel(logging.DEBUG)\n        else:\n            logger.setLevel(logging.CRITICAL)\n        self.settings.setValue(\"log\", s)\n\n    def toBeImplemented(self):\n        self.statusbar.showMessage(self.tr(\"功能尚在开发\"))\n\n    # 医疗\n    def wwChanged(self):\n        if not self.controller or self.image is None:\n            return\n        try:  # 那种jpg什么格式的医疗图像调整窗宽等会造成崩溃\n            self.textWw.selectAll()\n            self.controller.image = med.windowlize(\n                self.controller.rawImage, self.ww, self.wc\n            )\n            self.updateImage()\n        except:\n            pass\n\n    def wcChanged(self):\n        if not self.controller or self.image is None:\n            return\n        try:\n            self.textWc.selectAll()\n            self.controller.image = med.windowlize(\n                self.controller.rawImage, self.ww, self.wc\n            )\n            self.updateImage()\n        except:\n            pass\n\n    @property\n    def ww(self):\n        return int(self.textWw.text())\n\n    @property\n    def wc(self):\n        return int(self.textWc.text())\n\n    def twwChanged(self):\n        if self.ww > self.sliderWw.maximum():\n            self.textWw.setText(str(self.sliderWw.maximum()))\n        if self.ww < self.sliderWw.minimum():\n            self.textWw.setText(str(self.sliderWw.minimum()))\n        self.sliderWw.setProperty(\"value\", self.ww)\n        self.wwChanged()\n\n    def swwChanged(self):\n        self.textWw.setText(str(self.sliderWw.value()))\n        self.wwChanged()\n\n    def twcChanged(self):\n        if self.wc > self.sliderWc.maximum():\n            self.textWc.setText(str(self.sliderWc.maximum()))\n        if self.wc < self.sliderWc.minimum():\n            self.textWc.setText(str(self.sliderWc.minimum()))\n        self.sliderWc.setProperty(\"value\", self.wc)\n        self.wcChanged()\n\n    def swcChanged(self):\n        self.textWc.setText(str(self.sliderWc.value()))\n        self.wcChanged()\n\n    def useQtWidget(self, s):\n        print(\"checked\", s)\n        self.settings.setValue(\"use_qt_widget\", s)\n"
  },
  {
    "path": "eiseg/config/colormap.txt",
    "content": "53,119,181\r\n245,128,6\r\n67,159,36\r\n204,43,41\r\n145,104,190\r\n135,86,75\r\n219,120,195\r\n127,127,127\r\n187,189,18\r\n72,190,207\r\n178,199,233\r\n248,187,118\r\n160,222,135\r\n247,153,150\r\n195,176,214\r\n192,156,148\r\n241,183,211\r\n199,199,199\r\n218,219,139\r\n166,218,229"
  },
  {
    "path": "eiseg/config/config.yaml",
    "content": "shortcut:\n  about: Q\n  auto_save: X\n  change_output_dir: Shift+Z\n  clear: Ctrl+Shift+Z\n  clear_label: ''\n  clear_recent: ''\n  close: Ctrl+W\n  data_worker: ''\n  del_active_polygon: Backspace\n  edit_shortcuts: E\n  finish_object: Space\n  grid_ann: ''\n  label_worker: ''\n  largest_component: ''\n  load_label: ''\n  load_param: Ctrl+M\n  medical_worker: ''\n  model_worker: ''\n  open_folder: Shift+A\n  open_image: Ctrl+A\n  origional_extension: ''\n  quick_start: ''\n  quit: ''\n  redo: Ctrl+Y\n  remote_worker: ''\n  save: ''\n  save_as: ''\n  save_coco: ''\n  save_json: ''\n  save_label: ''\n  save_pseudo: ''\n  set_worker: ''\n  turn_next: F\n  turn_prev: S\n  undo: Ctrl+Z\n"
  },
  {
    "path": "eiseg/controller.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport os.path as osp\nimport time\nimport json\nimport logging\n\nimport cv2\nimport numpy as np\nfrom skimage.measure import label\nimport paddle\n\nfrom eiseg import logger\nfrom inference import clicker\nfrom inference.predictor import get_predictor\nimport util\nfrom util.vis import draw_with_blend_and_clicks\nfrom models import EISegModel\nfrom util import LabelList\n\n\nclass InteractiveController:\n    def __init__(\n        self,\n        predictor_params: dict = None,\n        prob_thresh: float = 0.5,\n    ):\n        \"\"\"初始化控制器.\n\n        Parameters\n        ----------\n        predictor_params : dict\n            推理器配置\n        prob_thresh : float\n            区分前景和背景结果的阈值\n\n        \"\"\"\n        self.predictor_params = predictor_params\n        self.prob_thresh = prob_thresh\n        self.model = None\n        self.image = None\n        self.rawImage = None\n        self.predictor = None\n        self.clicker = clicker.Clicker()\n        self.states = []\n        self.probs_history = []\n        self.polygons = []\n\n        # 用于redo\n        self.undo_states = []\n        self.undo_probs_history = []\n\n        self.curr_label_number = 0\n        self._result_mask = None\n        self.labelList = LabelList()\n        self.lccFilter = False\n        self.log = logging.getLogger(__name__)\n\n    def filterLargestCC(self, do_filter: bool):\n        \"\"\"设置是否只保留推理结果中的最大联通块\n\n        Parameters\n        ----------\n        do_filter : bool\n            是否只保存推理结果中的最大联通块\n        \"\"\"\n        if not isinstance(do_filter, bool):\n            return\n        self.lccFilter = do_filter\n\n    def setModel(self, param_path=None, use_gpu=None):\n        \"\"\"设置推理其模型.\n\n        Parameters\n        ----------\n        params_path : str\n            模型路径\n\n        use_gpu : bool\n            None:检测，根据paddle版本判断\n            bool:按照指定是否开启GPU\n\n        Returns\n        -------\n        bool, str\n            是否成功设置模型, 失败原因\n\n        \"\"\"\n        if param_path is not None:\n            model_path = param_path.replace(\".pdiparams\", \".pdmodel\")\n            if not osp.exists(model_path):\n                raise Exception(f\"未在 {model_path} 找到模型文件\")\n            if use_gpu is None:\n                if paddle.device.is_compiled_with_cuda():  # TODO: 可以使用GPU却返回False\n                    use_gpu = True\n                else:\n                    use_gpu = False\n            logger.info(f\"User paddle compiled with gpu: use_gpu {use_gpu}\")\n            tic = time.time()\n            try:\n                self.model = EISegModel(model_path, param_path, use_gpu)\n                self.reset_predictor()  # 即刻生效\n            except KeyError as e:\n                return False, str(e)\n            logger.info(f\"Load model {model_path} took {time.time() - tic}\")\n            return True, \"模型设置成功\"\n\n    def setImage(self, image: np.array):\n        \"\"\"设置当前标注的图片\n\n        Parameters\n        ----------\n        image : np.array\n            当前标注的图片\n\n        \"\"\"\n        if self.model is not None:\n            self.image = image\n            self._result_mask = np.zeros(image.shape[:2], dtype=np.uint8)\n            self.resetLastObject()\n\n    # 标签操作\n    def setLabelList(self, labelList: json):\n        \"\"\"设置标签列表，会覆盖已有的标签列表\n\n        Parameters\n        ----------\n        labelList : json\n            标签列表格式为\n            {\n                {\n                    \"idx\" : int         (like 0 or 1 or 2)\n                    \"name\" : str        (like \"car\"　or \"airplan\")\n                    \"color\" : list      (like [255, 0, 0])\n                },\n                ...\n            }\n\n        Returns\n        -------\n        type\n            Description of returned object.\n\n        \"\"\"\n        self.labelList.clear()\n        labels = json.loads(labelList)\n        for lab in labels:\n            self.labelList.add(lab[\"id\"], lab[\"name\"], lab[\"color\"])\n\n    def addLabel(self, id: int, name: str, color: list):\n        self.labelList.add(id, name, color)\n\n    def delLabel(self, id: int):\n        self.labelList.remove(id)\n\n    def clearLabel(self):\n        self.labelList.clear()\n\n    def importLabel(self, path):\n        self.labelList.importLabel(path)\n\n    def exportLabel(self, path):\n        self.labelList.exportLabel(path)\n\n    # 点击操作\n    def addClick(self, x: int, y: int, is_positive: bool):\n        \"\"\"添加一个点并运行推理，保存历史用于undo\n\n        Parameters\n        ----------\n        x : int\n            点击的横坐标\n        y : int\n            点击的纵坐标\n        is_positive : bool\n            是否点的是正点\n\n        Returns\n        -------\n        bool, str\n            点击是否添加成功, 失败原因\n\n        \"\"\"\n\n        # 1. 确定可以点\n        if not self.inImage(x, y):\n            return False, \"点击越界\"\n        if not self.modelSet:\n            return False, \"未加载模型\"\n        if not self.imageSet:\n            return False, \"图像未设置\"\n\n        if len(self.states) == 0:  # 保存一个空状态\n            self.states.append(\n                {\n                    \"clicker\": self.clicker.get_state(),\n                    \"predictor\": self.predictor.get_states(),\n                }\n            )\n\n        # 2. 添加点击，跑推理\n        click = clicker.Click(is_positive=is_positive, coords=(y, x))\n        self.clicker.add_click(click)\n        pred = self.predictor.get_prediction(self.clicker)\n\n        # 3. 保存状态\n        self.states.append(\n            {\n                \"clicker\": self.clicker.get_state(),\n                \"predictor\": self.predictor.get_states(),\n            }\n        )\n        if self.probs_history:\n            self.probs_history.append((self.probs_history[-1][1], pred))\n        else:\n            self.probs_history.append((np.zeros_like(pred), pred))\n\n        # 点击之后就不能接着之前的历史redo了\n        self.undo_states = []\n        self.undo_probs_history = []\n        return True, \"点击添加成功\"\n\n    def undoClick(self):\n        \"\"\"\n        undo一步点击\n        \"\"\"\n        if len(self.states) <= 1:  # == 1就只剩下一个空状态了，不用再退\n            return\n        self.undo_states.append(self.states.pop())\n        self.clicker.set_state(self.states[-1][\"clicker\"])\n        self.predictor.set_states(self.states[-1][\"predictor\"])\n        self.undo_probs_history.append(self.probs_history.pop())\n        if not self.probs_history:\n            self.reset_init_mask()\n\n    def redoClick(self):\n        \"\"\"\n        redo一步点击\n        \"\"\"\n        if len(self.undo_states) == 0:  # 如果还没撤销过\n            return\n        if len(self.undo_probs_history) >= 1:\n            next_state = self.undo_states.pop()\n            self.states.append(next_state)\n            self.clicker.set_state(next_state[\"clicker\"])\n            self.predictor.set_states(next_state[\"predictor\"])\n            self.probs_history.append(self.undo_probs_history.pop())\n\n    def finishObject(self, building=False):\n        \"\"\"\n        结束当前物体标注，准备标下一个\n        \"\"\"\n        object_prob = self.current_object_prob\n        if object_prob is None:\n            return None, None\n        object_mask = object_prob > self.prob_thresh\n        if self.lccFilter:\n            object_mask = self.getLargestCC(object_mask)\n        polygon = util.get_polygon((object_mask.astype(np.uint8) * 255), \n                                    img_size=object_mask.shape,\n                                    building=building)\n        if polygon is not None:\n            self._result_mask[object_mask] = self.curr_label_number\n            self.resetLastObject()\n            self.polygons.append([self.curr_label_number, polygon])\n        return object_mask, polygon\n\n    # 多边形\n    def getPolygon(self):\n        return self.polygon\n\n    def setPolygon(self, polygon):\n        self.polygon = polygon\n\n    # mask\n    def getMask(self):\n        s = self.imgShape\n        img = np.zeros([s[0], s[1]])\n        for poly in self.polygons:\n            pts = np.int32([np.array(poly[1])])\n            cv2.fillPoly(img, pts=pts, color=poly[0])\n        return img\n\n    def setCurrLabelIdx(self, number):\n        if not isinstance(number, int):\n            return False\n        self.curr_label_number = number\n\n    def resetLastObject(self, update_image=True):\n        \"\"\"\n        重置控制器状态\n        Parameters\n            update_image(bool): 是否更新图像\n        \"\"\"\n        self.states = []\n        self.probs_history = []\n        self.undo_states = []\n        self.undo_probs_history = []\n        # self.current_object_prob = None\n        self.clicker.reset_clicks()\n        self.reset_predictor()\n        self.reset_init_mask()\n\n    def reset_predictor(self, predictor_params=None):\n        \"\"\"\n        重置推理器，可以换推理配置\n        Parameters\n            predictor_params(dict): 推理配置\n        \"\"\"\n        if predictor_params is not None:\n            self.predictor_params = predictor_params\n        if self.model.model:\n            self.predictor = get_predictor(self.model.model, **self.predictor_params)\n            if self.image is not None:\n                self.predictor.set_input_image(self.image)\n\n    def reset_init_mask(self):\n        self.clicker.click_indx_offset = 0\n\n    def getLargestCC(self, mask):\n        mask = label(mask)\n        if mask.max() == 0:\n            return mask\n        mask = mask == np.argmax(np.bincount(mask.flat)[1:]) + 1\n        return mask\n\n    def get_visualization(self, alpha_blend: float, click_radius: int):\n        if self.image is None:\n            return None\n        # 1. 正在标注的mask\n        # results_mask_for_vis = self.result_mask  # 加入之前标完的mask\n        results_mask_for_vis = np.zeros_like(self.result_mask)\n        results_mask_for_vis *= self.curr_label_number\n        if self.probs_history:\n            results_mask_for_vis[\n                self.current_object_prob > self.prob_thresh\n            ] = self.curr_label_number\n        if self.lccFilter:\n            results_mask_for_vis = (\n                self.getLargestCC(results_mask_for_vis) * self.curr_label_number\n            )\n        vis = draw_with_blend_and_clicks(\n            self.image,\n            mask=results_mask_for_vis,\n            alpha=alpha_blend,\n            clicks_list=self.clicker.clicks_list,\n            radius=click_radius,\n            palette=self.palette,\n        )\n        return vis\n\n    def inImage(self, x: int, y: int):\n        s = self.image.shape\n        if x < 0 or y < 0 or x >= s[1] or y >= s[0]:\n            return False\n        return True\n\n    @property\n    def result_mask(self):\n        result_mask = self._result_mask.copy()\n        return result_mask\n\n    @property\n    def palette(self):\n        if self.labelList:\n            colors = [ml.color for ml in self.labelList]\n            colors.insert(0, [0, 0, 0])\n        else:\n            colors = [[0, 0, 0]]\n        return colors\n\n    @property\n    def current_object_prob(self):\n        \"\"\"\n        获取当前推理标签\n        \"\"\"\n        if self.probs_history:\n            _, current_prob_additive = self.probs_history[-1]\n            return current_prob_additive\n        else:\n            return None\n\n    @property\n    def is_incomplete_mask(self):\n        \"\"\"\n        Returns\n            bool: 当前的物体是不是还没标完\n        \"\"\"\n        return len(self.probs_history) > 0\n\n    @property\n    def imgShape(self):\n        return self.image.shape  # [1::-1]\n\n    @property\n    def modelSet(self):\n        return self.model is not None\n\n    @property\n    def modelName(self):\n        return self.model.__name__\n\n    @property\n    def imageSet(self):\n        return self.image is not None\n"
  },
  {
    "path": "eiseg/exe.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport os.path as osp\nimport sys\n\nsys.path.append(osp.dirname(osp.dirname(osp.realpath(__file__))))\n\nfrom run import main\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "eiseg/inference/__init__.py",
    "content": ""
  },
  {
    "path": "eiseg/inference/clicker.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\nimport cv2\nimport numpy as np\nfrom copy import deepcopy\n\n\nclass Clicker(object):\n    def __init__(\n        self, gt_mask=None, init_clicks=None, ignore_label=-1, click_indx_offset=0\n    ):\n        self.click_indx_offset = click_indx_offset\n        if gt_mask is not None:\n            self.gt_mask = gt_mask == 1\n            self.not_ignore_mask = gt_mask != ignore_label\n        else:\n            self.gt_mask = None\n\n        self.reset_clicks()\n\n        if init_clicks is not None:\n            for click in init_clicks:\n                self.add_click(click)\n\n    def make_next_click(self, pred_mask):\n        assert self.gt_mask is not None\n        click = self._get_next_click(pred_mask)\n        self.add_click(click)\n\n    def get_clicks(self, clicks_limit=None):\n        return self.clicks_list[:clicks_limit]\n\n    def _get_next_click(self, pred_mask, padding=True):\n        fn_mask = np.logical_and(\n            np.logical_and(self.gt_mask, np.logical_not(pred_mask)),\n            self.not_ignore_mask,\n        )\n        fp_mask = np.logical_and(\n            np.logical_and(np.logical_not(self.gt_mask), pred_mask),\n            self.not_ignore_mask,\n        )\n\n        if padding:\n            fn_mask = np.pad(fn_mask, ((1, 1), (1, 1)), \"constant\")\n            fp_mask = np.pad(fp_mask, ((1, 1), (1, 1)), \"constant\")\n\n        fn_mask_dt = cv2.distanceTransform(fn_mask.astype(np.uint8), cv2.DIST_L2, 0)\n        fp_mask_dt = cv2.distanceTransform(fp_mask.astype(np.uint8), cv2.DIST_L2, 0)\n\n        if padding:\n            fn_mask_dt = fn_mask_dt[1:-1, 1:-1]\n            fp_mask_dt = fp_mask_dt[1:-1, 1:-1]\n\n        fn_mask_dt = fn_mask_dt * self.not_clicked_map\n        fp_mask_dt = fp_mask_dt * self.not_clicked_map\n\n        fn_max_dist = np.max(fn_mask_dt)\n        fp_max_dist = np.max(fp_mask_dt)\n\n        is_positive = fn_max_dist > fp_max_dist\n        if is_positive:\n            coords_y, coords_x = np.where(fn_mask_dt == fn_max_dist)  # coords is [y, x]\n        else:\n            coords_y, coords_x = np.where(fp_mask_dt == fp_max_dist)  # coords is [y, x]\n\n        return Click(is_positive=is_positive, coords=(coords_y[0], coords_x[0]))\n\n    def add_click(self, click):\n        coords = click.coords\n\n        click.indx = self.click_indx_offset + self.num_pos_clicks + self.num_neg_clicks\n        if click.is_positive:\n            self.num_pos_clicks += 1\n        else:\n            self.num_neg_clicks += 1\n\n        self.clicks_list.append(click)\n        if self.gt_mask is not None:\n            self.not_clicked_map[coords[0], coords[1]] = False\n\n    def _remove_last_click(self):\n        click = self.clicks_list.pop()\n        coords = click.coords\n\n        if click.is_positive:\n            self.num_pos_clicks -= 1\n        else:\n            self.num_neg_clicks -= 1\n\n        if self.gt_mask is not None:\n            self.not_clicked_map[coords[0], coords[1]] = True\n\n    def reset_clicks(self):\n        if self.gt_mask is not None:\n            self.not_clicked_map = np.ones_like(self.gt_mask, dtype=np.bool)\n\n        self.num_pos_clicks = 0\n        self.num_neg_clicks = 0\n\n        self.clicks_list = []\n\n    def get_state(self):\n        return deepcopy(self.clicks_list)\n\n    def set_state(self, state):\n        self.reset_clicks()\n        for click in state:\n            self.add_click(click)\n\n    def __len__(self):\n        return len(self.clicks_list)\n\n\nclass Click:\n    def __init__(self, is_positive, coords, indx=None):\n        self.is_positive = is_positive\n        self.coords = coords\n        self.indx = indx\n\n    @property\n    def coords_and_indx(self):\n        return (*self.coords, self.indx)\n\n    def copy(self, **kwargs):\n        self_copy = deepcopy(self)\n        for k, v in kwargs.items():\n            setattr(self_copy, k, v)\n        return self_copy\n"
  },
  {
    "path": "eiseg/inference/predictor/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\nfrom .base import BasePredictor\nfrom inference.transforms import ZoomIn\n\n\ndef get_predictor(\n    net, brs_mode, with_flip=False, zoom_in_params=dict(), predictor_params=None\n):\n\n    predictor_params_ = {\"optimize_after_n_clicks\": 1}\n\n    if zoom_in_params is not None:\n        zoom_in = ZoomIn(**zoom_in_params)\n    else:\n        zoom_in = None\n\n    if brs_mode == \"NoBRS\":\n\n        if predictor_params is not None:\n            predictor_params_.update(predictor_params)\n        predictor = BasePredictor(\n            net, zoom_in=zoom_in, with_flip=with_flip, **predictor_params_\n        )\n\n    else:\n        raise NotImplementedError(\"Just support NoBRS mode\")\n    return predictor\n"
  },
  {
    "path": "eiseg/inference/predictor/base.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\n\nimport paddle\nimport paddle.nn.functional as F\nimport numpy as np\n\nfrom inference.transforms import AddHorizontalFlip, SigmoidForPred, LimitLongestSide\nfrom .ops import DistMaps, ScaleLayer, BatchImageNormalize\n\n\nclass BasePredictor(object):\n    def __init__(\n        self,\n        model,\n        net_clicks_limit=None,\n        with_flip=False,\n        zoom_in=None,\n        max_size=None,\n        with_mask=True,\n        **kwargs\n    ):\n\n        self.with_flip = with_flip\n        self.net_clicks_limit = net_clicks_limit\n        self.original_image = None\n        self.zoom_in = zoom_in\n        self.prev_prediction = None\n        self.model_indx = 0\n        self.click_models = None\n        self.net_state_dict = None\n        self.with_prev_mask = with_mask\n        self.net = model\n\n        self.normalization = BatchImageNormalize(\n            [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]\n        )\n\n        self.transforms = [zoom_in] if zoom_in is not None else []\n        if max_size is not None:\n            self.transforms.append(LimitLongestSide(max_size=max_size))\n        self.transforms.append(SigmoidForPred())\n        if with_flip:\n            self.transforms.append(AddHorizontalFlip())\n        self.dist_maps = DistMaps(\n            norm_radius=5, spatial_scale=1.0, cpu_mode=False, use_disks=True\n        )\n\n    def to_tensor(self, x):\n        if isinstance(x, np.ndarray):\n            if x.ndim == 2:\n                x = x[:, :, None]\n        img = paddle.to_tensor(x.transpose([2, 0, 1])).astype(\"float32\") / 255\n        return img\n\n    def set_input_image(self, image):\n        image_nd = self.to_tensor(image)\n\n        for transform in self.transforms:\n            transform.reset()\n        self.original_image = image_nd\n        if len(self.original_image.shape) == 3:\n            self.original_image = self.original_image.unsqueeze(0)\n        self.prev_prediction = paddle.zeros_like(self.original_image[:, :1, :, :])\n        if not self.with_prev_mask:\n            self.prev_edge = paddle.zeros_like(self.original_image[:, :1, :, :])\n\n    def get_prediction(self, clicker, prev_mask=None):\n        clicks_list = clicker.get_clicks()\n\n        input_image = self.original_image\n        if prev_mask is None:\n            if not self.with_prev_mask:\n\n                prev_mask = self.prev_edge\n            else:\n                prev_mask = self.prev_prediction\n\n        input_image = paddle.concat([input_image, prev_mask], axis=1)\n\n        image_nd, clicks_lists, is_image_changed = self.apply_transforms(\n            input_image, [clicks_list]\n        )\n        pred_logits, pred_edges = self._get_prediction(\n            image_nd, clicks_lists, is_image_changed\n        )\n\n        pred_logits = paddle.to_tensor(pred_logits)\n\n        prediction = F.interpolate(\n            pred_logits, mode=\"bilinear\", align_corners=True, size=image_nd.shape[2:]\n        )\n        if pred_edges is not None:\n            pred_edge = paddle.to_tensor(pred_edges)\n            edge_prediction = F.interpolate(\n                pred_edge, mode=\"bilinear\", align_corners=True, size=image_nd.shape[2:]\n            )\n\n        for t in reversed(self.transforms):\n            if pred_edges is not None:\n                edge_prediction = t.inv_transform(edge_prediction)\n                self.prev_edge = edge_prediction\n            prediction = t.inv_transform(prediction)\n\n        if self.zoom_in is not None and self.zoom_in.check_possible_recalculation():\n            return self.get_prediction(clicker)\n\n        self.prev_prediction = prediction\n        return prediction.numpy()[0, 0]\n\n    def prepare_input(self, image):\n        prev_mask = None\n        prev_mask = image[:, 3:, :, :]\n        image = image[:, :3, :, :]\n        image = self.normalization(image)\n        return image, prev_mask\n\n    def get_coord_features(self, image, prev_mask, points):\n\n        coord_features = self.dist_maps(image, points)\n\n        if prev_mask is not None:\n            coord_features = paddle.concat((prev_mask, coord_features), axis=1)\n\n        return coord_features\n\n    def _get_prediction(self, image_nd, clicks_lists, is_image_changed):\n        input_names = self.net.get_input_names()\n        self.input_handle_1 = self.net.get_input_handle(input_names[0])\n        self.input_handle_2 = self.net.get_input_handle(input_names[1])\n        points_nd = self.get_points_nd(clicks_lists)\n\n        image, prev_mask = self.prepare_input(image_nd)\n        coord_features = self.get_coord_features(image, prev_mask, points_nd)\n        image = image.numpy().astype(\"float32\")\n        coord_features = coord_features.numpy().astype(\"float32\")\n\n        self.input_handle_1.copy_from_cpu(image)\n        self.input_handle_2.copy_from_cpu(coord_features)\n\n        self.net.run()\n\n        output_names = self.net.get_output_names()\n\n        output_handle = self.net.get_output_handle(output_names[0])\n        output_data = output_handle.copy_to_cpu()\n        if len(output_names) == 3:\n            edge_handle = self.net.get_output_handle(output_names[2])\n            edge_data = edge_handle.copy_to_cpu()\n            return output_data, edge_data\n        else:\n            return output_data, None\n\n    def _get_transform_states(self):\n        return [x.get_state() for x in self.transforms]\n\n    def _set_transform_states(self, states):\n        assert len(states) == len(self.transforms)\n        for state, transform in zip(states, self.transforms):\n            transform.set_state(state)\n\n    def apply_transforms(self, image_nd, clicks_lists):\n        is_image_changed = False\n        for t in self.transforms:\n            image_nd, clicks_lists = t.transform(image_nd, clicks_lists)\n            is_image_changed |= t.image_changed\n\n        return image_nd, clicks_lists, is_image_changed\n\n    def get_points_nd(self, clicks_lists):\n        total_clicks = []\n        num_pos_clicks = [\n            sum(x.is_positive for x in clicks_list) for clicks_list in clicks_lists\n        ]\n        num_neg_clicks = [\n            len(clicks_list) - num_pos\n            for clicks_list, num_pos in zip(clicks_lists, num_pos_clicks)\n        ]\n        num_max_points = max(num_pos_clicks + num_neg_clicks)\n        if self.net_clicks_limit is not None:\n            num_max_points = min(self.net_clicks_limit, num_max_points)\n        num_max_points = max(1, num_max_points)\n\n        for clicks_list in clicks_lists:\n            clicks_list = clicks_list[: self.net_clicks_limit]\n            pos_clicks = [\n                click.coords_and_indx for click in clicks_list if click.is_positive\n            ]\n            pos_clicks = pos_clicks + (num_max_points - len(pos_clicks)) * [\n                (-1, -1, -1)\n            ]\n\n            neg_clicks = [\n                click.coords_and_indx for click in clicks_list if not click.is_positive\n            ]\n            neg_clicks = neg_clicks + (num_max_points - len(neg_clicks)) * [\n                (-1, -1, -1)\n            ]\n            total_clicks.append(pos_clicks + neg_clicks)\n\n        return paddle.to_tensor(total_clicks)\n\n    def get_states(self):\n        return {\n            \"transform_states\": self._get_transform_states(),\n            \"prev_prediction\": self.prev_prediction,\n        }\n\n    def set_states(self, states):\n        self._set_transform_states(states[\"transform_states\"])\n        self.prev_prediction = states[\"prev_prediction\"]\n\n\ndef split_points_by_order(tpoints, groups):\n    points = tpoints.numpy()\n    num_groups = len(groups)\n    bs = points.shape[0]\n    num_points = points.shape[1] // 2\n\n    groups = [x if x > 0 else num_points for x in groups]\n    group_points = [np.full((bs, 2 * x, 3), -1, dtype=np.float32) for x in groups]\n\n    last_point_indx_group = np.zeros((bs, num_groups, 2), dtype=np.int)\n    for group_indx, group_size in enumerate(groups):\n        last_point_indx_group[:, group_indx, 1] = group_size\n\n    for bindx in range(bs):\n        for pindx in range(2 * num_points):\n            point = points[bindx, pindx, :]\n            group_id = int(point[2])\n            if group_id < 0:\n                continue\n\n            is_negative = int(pindx >= num_points)\n            if group_id >= num_groups or (\n                group_id == 0 and is_negative\n            ):  # disable negative first click\n                group_id = num_groups - 1\n\n            new_point_indx = last_point_indx_group[bindx, group_id, is_negative]\n            last_point_indx_group[bindx, group_id, is_negative] += 1\n\n            group_points[group_id][bindx, new_point_indx, :] = point\n\n    group_points = [paddle.to_tensor(x, dtype=tpoints.dtype) for x in group_points]\n\n    return group_points\n"
  },
  {
    "path": "eiseg/inference/predictor/ops.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\n\nimport paddle\nimport paddle.nn as nn\nimport numpy as np\n\n\nclass DistMaps(nn.Layer):\n    def __init__(self, norm_radius, spatial_scale=1.0, cpu_mode=True, use_disks=False):\n        super(DistMaps, self).__init__()\n        self.spatial_scale = spatial_scale\n        self.norm_radius = norm_radius\n        self.cpu_mode = cpu_mode\n        self.use_disks = use_disks\n\n        if self.cpu_mode:\n            from util.cython import get_dist_maps\n\n            self._get_dist_maps = get_dist_maps\n\n    def get_coord_features(self, points, batchsize, rows, cols):\n        if self.cpu_mode:\n            coords = []\n            for i in range(batchsize):\n                norm_delimeter = (\n                    1.0 if self.use_disks else self.spatial_scale * self.norm_radius\n                )\n                coords.append(\n                    self._get_dist_maps(\n                        points[i].numpy().astype(\"float32\"), rows, cols, norm_delimeter\n                    )\n                )\n            coords = paddle.to_tensor(np.stack(coords, axis=0)).astype(\"float32\")\n        else:\n            num_points = points.shape[1] // 2\n            points = points.reshape([-1, points.shape[2]])\n            points, points_order = paddle.split(points, [2, 1], axis=1)\n            invalid_points = paddle.max(points, axis=1, keepdim=False) < 0\n            row_array = paddle.arange(start=0, end=rows, step=1, dtype=\"float32\")\n            col_array = paddle.arange(start=0, end=cols, step=1, dtype=\"float32\")\n\n            coord_rows, coord_cols = paddle.meshgrid(row_array, col_array)\n            coords = paddle.unsqueeze(\n                paddle.stack([coord_rows, coord_cols], axis=0), axis=0\n            ).tile([points.shape[0], 1, 1, 1])\n\n            add_xy = (points * self.spatial_scale).reshape(\n                [points.shape[0], points.shape[1], 1, 1]\n            )\n            coords = coords - add_xy\n            if not self.use_disks:\n                coords = coords / (self.norm_radius * self.spatial_scale)\n\n            coords = coords * coords\n            coords[:, 0] += coords[:, 1]\n            coords = coords[:, :1]\n            invalid_points = invalid_points.numpy()\n\n            coords[invalid_points, :, :, :] = 1e6\n            coords = coords.reshape([-1, num_points, 1, rows, cols])\n            coords = paddle.min(coords, axis=1)\n            coords = coords.reshape([-1, 2, rows, cols])\n\n        if self.use_disks:\n            coords = (coords <= (self.norm_radius * self.spatial_scale) ** 2).astype(\n                \"float32\"\n            )\n        else:\n            coords = paddle.tanh(paddle.sqrt(coords) * 2)\n        return coords\n\n    def forward(self, x, coords):\n        return self.get_coord_features(coords, x.shape[0], x.shape[2], x.shape[3])\n\n\nclass ScaleLayer(nn.Layer):\n    def __init__(self, init_value=1.0, lr_mult=1):\n        super().__init__()\n        self.lr_mult = lr_mult\n        self.scale = self.create_parameter(\n            shape=[1],\n            dtype=\"float32\",\n            default_initializer=nn.initializer.Constant(init_value / lr_mult),\n        )\n\n    def forward(self, x):\n        scale = paddle.abs(self.scale * self.lr_mult)\n        return x * scale\n\n\nclass BatchImageNormalize:\n    def __init__(self, mean, std):\n        self.mean = paddle.to_tensor(\n            np.array(mean)[np.newaxis, :, np.newaxis, np.newaxis]\n        ).astype(\"float32\")\n        self.std = paddle.to_tensor(\n            np.array(std)[np.newaxis, :, np.newaxis, np.newaxis]\n        ).astype(\"float32\")\n\n    def __call__(self, tensor):\n        tensor = (tensor - self.mean) / self.std\n        return tensor\n"
  },
  {
    "path": "eiseg/inference/transforms/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom .base import SigmoidForPred\nfrom .flip import AddHorizontalFlip\nfrom .zoom_in import ZoomIn\nfrom .limit_longest_side import LimitLongestSide\nfrom .crops import Crops\n"
  },
  {
    "path": "eiseg/inference/transforms/base.py",
    "content": "import paddle.nn.functional as F\n\n\nclass BaseTransform(object):\n    def __init__(self):\n        self.image_changed = False\n\n    def transform(self, image_nd, clicks_lists):\n        raise NotImplementedError\n\n    def inv_transform(self, prob_map):\n        raise NotImplementedError\n\n    def reset(self):\n        raise NotImplementedError\n\n    def get_state(self):\n        raise NotImplementedError\n\n    def set_state(self, state):\n        raise NotImplementedError\n\n\nclass SigmoidForPred(BaseTransform):\n    def transform(self, image_nd, clicks_lists):\n        return image_nd, clicks_lists\n\n    def inv_transform(self, prob_map):\n        return F.sigmoid(prob_map)\n\n    def reset(self):\n        pass\n\n    def get_state(self):\n        return None\n\n    def set_state(self, state):\n        pass\n"
  },
  {
    "path": "eiseg/inference/transforms/crops.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\n\nimport math\n\nimport paddle\nimport numpy as np\n\nfrom inference.clicker import Click\nfrom .base import BaseTransform\n\n\nclass Crops(BaseTransform):\n    def __init__(self, crop_size=(320, 480), min_overlap=0.2):\n        super().__init__()\n        self.crop_height, self.crop_width = crop_size\n        self.min_overlap = min_overlap\n\n        self.x_offsets = None\n        self.y_offsets = None\n        self._counts = None\n\n    def transform(self, image_nd, clicks_lists):\n        assert image_nd.shape[0] == 1 and len(clicks_lists) == 1\n        image_height, image_width = image_nd.shape[2:4]\n        self._counts = None\n\n        if image_height < self.crop_height or image_width < self.crop_width:\n            return image_nd, clicks_lists\n\n        self.x_offsets = get_offsets(image_width, self.crop_width, self.min_overlap)\n        self.y_offsets = get_offsets(image_height, self.crop_height, self.min_overlap)\n        self._counts = np.zeros((image_height, image_width))\n\n        image_crops = []\n        for dy in self.y_offsets:\n            for dx in self.x_offsets:\n                self._counts[dy : dy + self.crop_height, dx : dx + self.crop_width] += 1\n                image_crop = image_nd[\n                    :, :, dy : dy + self.crop_height, dx : dx + self.crop_width\n                ]\n                image_crops.append(image_crop)\n        image_crops = paddle.concat(image_crops, axis=0)\n        self._counts = paddle.to_tensor(self._counts, dtype=\"float32\")\n\n        clicks_list = clicks_lists[0]\n        clicks_lists = []\n        for dy in self.y_offsets:\n            for dx in self.x_offsets:\n                crop_clicks = [\n                    x.copy(coords=(x.coords[0] - dy, x.coords[1] - dx))\n                    for x in clicks_list\n                ]\n                clicks_lists.append(crop_clicks)\n\n        return image_crops, clicks_lists\n\n    def inv_transform(self, prob_map):\n        if self._counts is None:\n            return prob_map\n\n        new_prob_map = paddle.zeros((1, 1, *self._counts.shape), dtype=prob_map.dtype)\n\n        crop_indx = 0\n        for dy in self.y_offsets:\n            for dx in self.x_offsets:\n                new_prob_map[\n                    0, 0, dy : dy + self.crop_height, dx : dx + self.crop_width\n                ] += prob_map[crop_indx, 0]\n                crop_indx += 1\n        new_prob_map = paddle.divide(new_prob_map, self._counts)\n\n        return new_prob_map\n\n    def get_state(self):\n        return self.x_offsets, self.y_offsets, self._counts\n\n    def set_state(self, state):\n        self.x_offsets, self.y_offsets, self._counts = state\n\n    def reset(self):\n        self.x_offsets = None\n        self.y_offsets = None\n        self._counts = None\n\n\ndef get_offsets(length, crop_size, min_overlap_ratio=0.2):\n    if length == crop_size:\n        return [0]\n\n    N = (length / crop_size - min_overlap_ratio) / (1 - min_overlap_ratio)\n    N = math.ceil(N)\n\n    overlap_ratio = (N - length / crop_size) / (N - 1)\n    overlap_width = int(crop_size * overlap_ratio)\n\n    offsets = [0]\n    for i in range(1, N):\n        new_offset = offsets[-1] + crop_size - overlap_width\n        if new_offset + crop_size > length:\n            new_offset = length - crop_size\n\n        offsets.append(new_offset)\n\n    return offsets\n"
  },
  {
    "path": "eiseg/inference/transforms/flip.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\nimport paddle\n\nfrom inference.clicker import Click\nfrom .base import BaseTransform\n\n\nclass AddHorizontalFlip(BaseTransform):\n    def transform(self, image_nd, clicks_lists):\n        assert len(image_nd.shape) == 4\n        image_nd = paddle.concat([image_nd, paddle.flip(image_nd, axis=[3])], axis=0)\n\n        image_width = image_nd.shape[3]\n        clicks_lists_flipped = []\n        for clicks_list in clicks_lists:\n            clicks_list_flipped = [\n                click.copy(coords=(click.coords[0], image_width - click.coords[1] - 1))\n                for click in clicks_list\n            ]\n            clicks_lists_flipped.append(clicks_list_flipped)\n        clicks_lists = clicks_lists + clicks_lists_flipped\n\n        return image_nd, clicks_lists\n\n    def inv_transform(self, prob_map):\n        assert len(prob_map.shape) == 4 and prob_map.shape[0] % 2 == 0\n        num_maps = prob_map.shape[0] // 2\n        prob_map, prob_map_flipped = prob_map[:num_maps], prob_map[num_maps:]\n\n        return 0.5 * (prob_map + paddle.flip(prob_map_flipped, axis=[3]))\n\n    def get_state(self):\n        return None\n\n    def set_state(self, state):\n        pass\n\n    def reset(self):\n        pass\n"
  },
  {
    "path": "eiseg/inference/transforms/limit_longest_side.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\nfrom .zoom_in import ZoomIn, get_roi_image_nd\n\n\nclass LimitLongestSide(ZoomIn):\n    def __init__(self, max_size=800):\n        super().__init__(target_size=max_size, skip_clicks=0)\n\n    def transform(self, image_nd, clicks_lists):\n        assert image_nd.shape[0] == 1 and len(clicks_lists) == 1\n        image_max_size = max(image_nd.shape[2:4])\n        self.image_changed = False\n\n        if image_max_size <= self.target_size:\n            return image_nd, clicks_lists\n        self._input_image = image_nd\n\n        self._object_roi = (0, image_nd.shape[2] - 1, 0, image_nd.shape[3] - 1)\n        self._roi_image = get_roi_image_nd(image_nd, self._object_roi, self.target_size)\n        self.image_changed = True\n\n        tclicks_lists = [self._transform_clicks(clicks_lists[0])]\n        return self._roi_image, tclicks_lists\n"
  },
  {
    "path": "eiseg/inference/transforms/zoom_in.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/saic-vul/ritm_interactive_segmentation\nThs copyright of saic-vul/ritm_interactive_segmentation is as follows:\nMIT License [see LICENSE for details]\n\"\"\"\n\nimport paddle\nimport numpy as np\nfrom inference.clicker import Click\nfrom util.misc import get_bbox_iou, get_bbox_from_mask, expand_bbox, clamp_bbox\nfrom .base import BaseTransform\n\n\nclass ZoomIn(BaseTransform):\n    def __init__(\n        self,\n        target_size=700,\n        skip_clicks=1,\n        expansion_ratio=1.4,\n        min_crop_size=480,\n        recompute_thresh_iou=0.5,\n        prob_thresh=0.50,\n    ):\n        super().__init__()\n        self.target_size = target_size\n        self.min_crop_size = min_crop_size\n        self.skip_clicks = skip_clicks\n        self.expansion_ratio = expansion_ratio\n        self.recompute_thresh_iou = recompute_thresh_iou\n        self.prob_thresh = prob_thresh\n\n        self._input_image_shape = None\n        self._prev_probs = None\n        self._object_roi = None\n        self._roi_image = None\n\n    def transform(self, image_nd, clicks_lists):\n        assert image_nd.shape[0] == 1 and len(clicks_lists) == 1\n        self.image_changed = False\n\n        clicks_list = clicks_lists[0]\n        if len(clicks_list) <= self.skip_clicks:\n            return image_nd, clicks_lists\n\n        self._input_image_shape = image_nd.shape\n\n        current_object_roi = None\n        if self._prev_probs is not None:\n            current_pred_mask = (self._prev_probs > self.prob_thresh)[0, 0]\n            if current_pred_mask.sum() > 0:\n                current_object_roi = get_object_roi(\n                    current_pred_mask,\n                    clicks_list,\n                    self.expansion_ratio,\n                    self.min_crop_size,\n                )\n\n        if current_object_roi is None:\n            if self.skip_clicks >= 0:\n                return image_nd, clicks_lists\n            else:\n                current_object_roi = 0, image_nd.shape[2] - 1, 0, image_nd.shape[3] - 1\n\n        update_object_roi = False\n        if self._object_roi is None:\n            update_object_roi = True\n        elif not check_object_roi(self._object_roi, clicks_list):\n            update_object_roi = True\n        elif (\n            get_bbox_iou(current_object_roi, self._object_roi)\n            < self.recompute_thresh_iou\n        ):\n            update_object_roi = True\n\n        if update_object_roi:\n            self._object_roi = current_object_roi\n            self.image_changed = True\n        self._roi_image = get_roi_image_nd(image_nd, self._object_roi, self.target_size)\n\n        tclicks_lists = [self._transform_clicks(clicks_list)]\n        return self._roi_image, tclicks_lists\n\n    def inv_transform(self, prob_map):\n        if self._object_roi is None:\n            self._prev_probs = prob_map.numpy()\n            return prob_map\n\n        assert prob_map.shape[0] == 1\n        rmin, rmax, cmin, cmax = self._object_roi\n        prob_map = paddle.nn.functional.interpolate(\n            prob_map,\n            size=(rmax - rmin + 1, cmax - cmin + 1),\n            mode=\"bilinear\",\n            align_corners=True,\n        )\n\n        if self._prev_probs is not None:\n            new_prob_map = paddle.zeros(\n                shape=self._prev_probs.shape, dtype=prob_map.dtype\n            )\n            new_prob_map[:, :, rmin : rmax + 1, cmin : cmax + 1] = prob_map\n        else:\n            new_prob_map = prob_map\n\n        self._prev_probs = new_prob_map.numpy()\n\n        return new_prob_map\n\n    def check_possible_recalculation(self):\n        if (\n            self._prev_probs is None\n            or self._object_roi is not None\n            or self.skip_clicks > 0\n        ):\n            return False\n\n        pred_mask = (self._prev_probs > self.prob_thresh)[0, 0]\n        if pred_mask.sum() > 0:\n            possible_object_roi = get_object_roi(\n                pred_mask, [], self.expansion_ratio, self.min_crop_size\n            )\n            image_roi = (\n                0,\n                self._input_image_shape[2] - 1,\n                0,\n                self._input_image_shape[3] - 1,\n            )\n            if get_bbox_iou(possible_object_roi, image_roi) < 0.50:\n                return True\n        return False\n\n    def get_state(self):\n        roi_image = self._roi_image if self._roi_image is not None else None\n        return (\n            self._input_image_shape,\n            self._object_roi,\n            self._prev_probs,\n            roi_image,\n            self.image_changed,\n        )\n\n    def set_state(self, state):\n        (\n            self._input_image_shape,\n            self._object_roi,\n            self._prev_probs,\n            self._roi_image,\n            self.image_changed,\n        ) = state\n\n    def reset(self):\n        self._input_image_shape = None\n        self._object_roi = None\n        self._prev_probs = None\n        self._roi_image = None\n        self.image_changed = False\n\n    def _transform_clicks(self, clicks_list):\n        if self._object_roi is None:\n            return clicks_list\n\n        rmin, rmax, cmin, cmax = self._object_roi\n        crop_height, crop_width = self._roi_image.shape[2:]\n\n        transformed_clicks = []\n        for click in clicks_list:\n            new_r = crop_height * (click.coords[0] - rmin) / (rmax - rmin + 1)\n            new_c = crop_width * (click.coords[1] - cmin) / (cmax - cmin + 1)\n            transformed_clicks.append(click.copy(coords=(new_r, new_c)))\n        return transformed_clicks\n\n\ndef get_object_roi(pred_mask, clicks_list, expansion_ratio, min_crop_size):\n    pred_mask = pred_mask.copy()\n\n    for click in clicks_list:\n        if click.is_positive:\n            pred_mask[int(click.coords[0]), int(click.coords[1])] = 1\n\n    bbox = get_bbox_from_mask(pred_mask)\n    bbox = expand_bbox(bbox, expansion_ratio, min_crop_size)\n    h, w = pred_mask.shape[0], pred_mask.shape[1]\n    bbox = clamp_bbox(bbox, 0, h - 1, 0, w - 1)\n\n    return bbox\n\n\ndef get_roi_image_nd(image_nd, object_roi, target_size):\n    rmin, rmax, cmin, cmax = object_roi\n\n    height = rmax - rmin + 1\n    width = cmax - cmin + 1\n\n    if isinstance(target_size, tuple):\n        new_height, new_width = target_size\n    else:\n        scale = target_size / max(height, width)\n        new_height = int(round(height * scale))\n        new_width = int(round(width * scale))\n\n    with paddle.no_grad():\n        roi_image_nd = image_nd[:, :, rmin : rmax + 1, cmin : cmax + 1]\n        roi_image_nd = paddle.nn.functional.interpolate(\n            roi_image_nd,\n            size=(new_height, new_width),\n            mode=\"bilinear\",\n            align_corners=True,\n        )\n\n    return roi_image_nd\n\n\ndef check_object_roi(object_roi, clicks_list):\n    for click in clicks_list:\n        if click.is_positive:\n            if click.coords[0] < object_roi[0] or click.coords[0] >= object_roi[1]:\n                return False\n            if click.coords[1] < object_roi[2] or click.coords[1] >= object_roi[3]:\n                return False\n\n    return True\n"
  },
  {
    "path": "eiseg/models.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport os.path as osp\nfrom abc import abstractmethod\n\n\nimport paddle.inference as paddle_infer\n\n\nhere = osp.dirname(osp.abspath(__file__))\n\n\nclass EISegModel:\n    @abstractmethod\n    def __init__(self, model_path, param_path, use_gpu=False):\n        model_path, param_path = self.check_param(model_path, param_path)\n        try:\n            config = paddle_infer.Config(model_path, param_path)\n        except:\n            ValueError(\" 模型和参数不匹配，请检查模型和参数是否加载错误\")\n        if not use_gpu:\n            config.enable_mkldnn()\n            # TODO: fluid要废弃了，研究判断方式\n            # if paddle.fluid.core.supports_bfloat16():\n            #     config.enable_mkldnn_bfloat16()\n            config.switch_ir_optim(True)\n            config.set_cpu_math_library_num_threads(10)\n        else:\n            config.enable_use_gpu(500, 0)\n            config.delete_pass(\"conv_elementwise_add_act_fuse_pass\")\n            config.delete_pass(\"conv_elementwise_add2_act_fuse_pass\")\n            config.delete_pass(\"conv_elementwise_add_fuse_pass\")\n            config.switch_ir_optim()\n            config.enable_memory_optim()\n            # use_tensoret = False  # TODO: 目前Linux和windows下使用TensorRT报错\n            # if use_tensoret:\n            #     config.enable_tensorrt_engine(\n            #         workspace_size=1 << 30,\n            #         precision_mode=paddle_infer.PrecisionType.Float32,\n            #         max_batch_size=1,\n            #         min_subgraph_size=5,\n            #         use_static=False,\n            #         use_calib_mode=False,\n            #     )\n        self.model = paddle_infer.create_predictor(config)\n\n    def check_param(self, model_path, param_path):\n        if model_path is None or not osp.exists(model_path):\n            raise Exception(f\"模型路径{model_path}不存在。请指定正确的模型路径\")\n        if param_path is None or not osp.exists(param_path):\n            raise Exception(f\"权重路径{param_path}不存在。请指定正确的权重路径\")\n        return model_path, param_path\n"
  },
  {
    "path": "eiseg/plugin/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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."
  },
  {
    "path": "eiseg/plugin/medical/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom .med import has_sitk, dcm_reader, windowlize\n"
  },
  {
    "path": "eiseg/plugin/medical/med.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport numpy as np\nimport cv2\n\nfrom eiseg import logger\n\n\ndef has_sitk():\n    try:\n        import SimpleITK\n\n        return True\n    except ImportError:\n        return False\n\n\nif has_sitk():\n    import SimpleITK as sitk\n\n\ndef dcm_reader(path):\n    logger.debug(f\"opening medical image {path}\")\n    reader = sitk.ImageSeriesReader()\n    reader.SetFileNames([path])\n    image = reader.Execute()\n    img = sitk.GetArrayFromImage(image)\n    logger.debug(f\"scan shape is {img.shape}\")\n    if len(img.shape) == 4:\n        img = img[0]\n    # WHC\n    img = np.transpose(img, [1, 2, 0])\n    return img.astype(np.int32)\n\n\ndef windowlize(scan, ww, wc):\n    wl = wc - ww / 2\n    wh = wc + ww / 2\n    res = scan.copy()\n    res = res.astype(np.float32)\n    res = np.clip(res, wl, wh)\n    res = (res - wl) / ww * 255\n    res = res.astype(np.uint8)\n    # print(\"++\", res.shape)\n    # for idx in range(res.shape[-1]):\n    # TODO: 支持3d或者改调用\n    res = cv2.cvtColor(res, cv2.COLOR_GRAY2BGR)\n\n    return res\n\n\n# def open_nii(niiimg_path):\n#     if IPT_SITK == True:\n#         sitk_image = sitk.ReadImage(niiimg_path)\n#         return _nii2arr(sitk_image)\n#     else:\n#         raise ImportError(\"can't import SimpleITK!\")\n\n\n#\n# def _nii2arr(sitk_image):\n#     if IPT_SITK == True:\n#         img = sitk.GetArrayFromImage(sitk_image).transpose((1, 2, 0))\n#         return img\n#     else:\n#         raise ImportError(\"can't import SimpleITK!\")\n#\n#\n# def slice_img(img, index):\n#     if index == 0:\n#         return sample_norm(\n#             cv2.merge(\n#                 [\n#                     np.uint16(img[:, :, index]),\n#                     np.uint16(img[:, :, index]),\n#                     np.uint16(img[:, :, index + 1]),\n#                 ]\n#             )\n#         )\n#     elif index == img.shape[2] - 1:\n#         return sample_norm(\n#             cv2.merge(\n#                 [\n#                     np.uint16(img[:, :, index - 1]),\n#                     np.uint16(img[:, :, index]),\n#                     np.uint16(img[:, :, index]),\n#                 ]\n#             )\n#         )\n#     else:\n#         return sample_norm(\n#             cv2.merge(\n#                 [\n#                     np.uint16(img[:, :, index - 1]),\n#                     np.uint16(img[:, :, index]),\n#                     np.uint16(img[:, :, index + 1]),\n#                 ]\n#             )\n#         )\n"
  },
  {
    "path": "eiseg/plugin/n2grid/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom .rs_grid import RSGrids\nfrom .grid import Grids, checkOpenGrid"
  },
  {
    "path": "eiseg/plugin/n2grid/grid.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nimport math\r\nimport numpy as np\r\nfrom PIL import Image\r\n\r\n\r\ndef checkOpenGrid(img, thumbnail_min):\r\n    H, W = img.shape[:2]\r\n    if max(H, W) <= thumbnail_min:\r\n        return False\r\n    else:\r\n        return True\r\n\r\n\r\nclass Grids:\r\n    def __init__(self, img, gridSize=(512, 512), overlap=(24, 24)):\r\n        self.clear()\r\n        self.detimg = img\r\n        self.gridSize = np.array(gridSize)\r\n        self.overlap = np.array(overlap)\r\n\r\n    def clear(self):\r\n        # 图像HWC格式\r\n        self.detimg = None  # 宫格初始图像\r\n        self.grid_init = False  # 是否初始化了宫格\r\n        # self.imagesGrid = []  # 图像宫格\r\n        self.mask_grids = []  # 标签宫格\r\n        self.grid_count = None  # (row count, col count)\r\n        self.curr_idx = None  # (current row, current col)\r\n\r\n    def createGrids(self):\r\n        # 计算宫格横纵向格数\r\n        imgSize = np.array(self.detimg.shape[:2])\r\n        grid_count = np.ceil((imgSize + self.overlap) / self.gridSize)\r\n        self.grid_count = grid_count = grid_count.astype(\"uint16\")\r\n        # ul = self.overlap - self.gridSize\r\n        # for row in range(grid_count[0]):\r\n        #     ul[0] = ul[0] + self.gridSize[0] - self.overlap[0]\r\n        #     for col in range(grid_count[1]):\r\n        #         ul[1] = ul[1] + self.gridSize[1] - self.overlap[1]\r\n        #         lr = ul + self.gridSize\r\n        #         # print(\"ul, lr\", ul, lr)\r\n        #         # 扩充\r\n        #         det_tmp = self.detimg[ul[0]: lr[0], ul[1]: lr[1]]\r\n        #         tmp = np.zeros((self.gridSize[0], self.gridSize[1], self.detimg.shape[-1]))\r\n        #         tmp[:det_tmp.shape[0], :det_tmp.shape[1], :] = det_tmp\r\n        #         self.imagesGrid.append(tmp)\r\n        # self.mask_grids = [[np.zeros(self.gridSize)] * grid_count[1]] * grid_count[0]  # 不能用浅拷贝\r\n        self.mask_grids = [\r\n            [np.zeros(self.gridSize) for _ in range(grid_count[1])]\r\n            for _ in range(grid_count[0])\r\n        ]\r\n        # print(len(self.mask_grids), len(self.mask_grids[0]))\r\n        self.grid_init = True\r\n        return list(grid_count)\r\n\r\n    def getGrid(self, row, col):\r\n        gridIdx = np.array([row, col])\r\n        ul = gridIdx * (self.gridSize - self.overlap)\r\n        lr = ul + self.gridSize\r\n        # print(\"ul, lr\", ul, lr)\r\n        img = self.detimg[ul[0]:lr[0], ul[1]:lr[1]]\r\n        mask = self.mask_grids[row][col]\r\n        self.curr_idx = (row, col)\r\n        return img, mask\r\n\r\n    def splicingList(self, save_path):\r\n        \"\"\"\r\n        将slide的out进行拼接，raw_size保证恢复到原状\r\n        \"\"\"\r\n        imgs = self.mask_grids\r\n        # print(len(imgs), len(imgs[0]))\r\n        raw_size = self.detimg.shape[:2]\r\n        # h, w = None, None\r\n        # for i in range(len(imgs)):\r\n        #     for j in range(len(imgs[i])):\r\n        #         im = imgs[i][j]\r\n        #         if im is not None:\r\n        #             h, w = im.shape[:2]\r\n        #             break\r\n        # if h is None and w is None:\r\n        #     return False\r\n        h, w = self.gridSize\r\n        row = math.ceil(raw_size[0] / h)\r\n        col = math.ceil(raw_size[1] / w)\r\n        # print('row, col:', row, col)\r\n        result_1 = np.zeros((h * row, w * col), dtype=np.uint8)\r\n        result_2 = result_1.copy()\r\n        # k = 0\r\n        for i in range(row):\r\n            for j in range(col):\r\n                # print('h, w:', h, w)\r\n                ih, iw = imgs[i][j].shape[:2]\r\n                im = np.zeros(self.gridSize)\r\n                im[:ih, :iw] = imgs[i][j]\r\n                start_h = (i * h) if i == 0 else (i * (h - self.overlap[0]))\r\n                end_h = start_h + h\r\n                start_w = (j * w) if j == 0 else (j * (w - self.overlap[1]))\r\n                end_w = start_w + w\r\n                # print(\"se: \", start_h, end_h, start_w, end_w)\r\n                # 单区自己，重叠取或\r\n                if (i + j) % 2 == 0:\r\n                    result_1[start_h:end_h, start_w:end_w] = im\r\n                else:\r\n                    result_2[start_h:end_h, start_w:end_w] = im\r\n                # k += 1\r\n                # print('r, c, k:', i_r, i_c, k)\r\n        result = np.where(result_2 != 0, result_2, result_1)\r\n        result = result[:raw_size[0], :raw_size[1]]\r\n        Image.fromarray(result).save(save_path, \"PNG\")\r\n        return result\r\n"
  },
  {
    "path": "eiseg/plugin/n2grid/rs_grid.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nimport numpy as np\r\nfrom typing import List, Tuple\r\nfrom eiseg.plugin.remotesensing.raster import Raster\r\n\r\n\r\nclass RSGrids:\r\n    def __init__(self, raset: Raster) -> None:\r\n        \"\"\" 在EISeg中用于处理遥感栅格数据的宫格类.\r\n\r\n        参数:\r\n            tif_path (str): GTiff数据的路径.\r\n            show_band (Union[List[int], Tuple[int]], optional): 用于RGB合成显示的波段. 默认为 [1, 1, 1].\r\n            grid_size (Union[List[int], Tuple[int]], optional): 切片大小. 默认为 [512, 512].\r\n            overlap (Union[List[int], Tuple[int]], optional): 重叠区域的大小. 默认为 [24, 24].\r\n        \"\"\"\r\n        super(RSGrids, self).__init__()\r\n        self.raster = raset\r\n        self.clear()\r\n\r\n    def clear(self) -> None:\r\n        self.mask_grids = []  # 标签宫格\r\n        self.grid_count = None  # (row count, col count)\r\n        self.curr_idx = None  # (current row, current col)\r\n\r\n    def createGrids(self) -> List[int]:\r\n        img_size = (self.raster.geoinfo.ysize, self.raster.geoinfo.xsize)\r\n        grid_count = np.ceil((img_size + self.raster.overlap) / self.raster.grid_size)\r\n        self.grid_count = grid_count = grid_count.astype(\"uint16\")\r\n        self.mask_grids = [[np.zeros(self.raster.grid_size) \\\r\n                            for _ in range(grid_count[1])] for _ in range(grid_count[0])]\r\n        return list(grid_count)\r\n\r\n    def getGrid(self, row: int, col: int) -> Tuple[np.ndarray]:\r\n        img, _ = self.raster.getGrid(row, col)\r\n        mask = self.mask_grids[row][col]\r\n        self.curr_idx = (row, col)\r\n        return img, mask\r\n\r\n    def splicingList(self, save_path: str) -> np.ndarray:\r\n        mask = self.raster.saveMaskbyGrids(self.mask_grids, \r\n                                           save_path,\r\n                                           self.raster.geoinfo)\r\n        return mask"
  },
  {
    "path": "eiseg/plugin/remotesensing/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nfrom .imgtools import *\r\nfrom .shape import *\r\nfrom .raster import *"
  },
  {
    "path": "eiseg/plugin/remotesensing/imgtools.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nimport numpy as np\r\nimport cv2\r\nfrom skimage import exposure\r\n\r\n\r\n# 2%线性拉伸\r\ndef two_percentLinear(image: np.ndarray, \r\n                      max_out: int=255, \r\n                      min_out: int=0) -> np.ndarray:\r\n    b, g, r = cv2.split(image)\r\n\r\n    def __gray_process(gray, maxout=max_out, minout=min_out):\r\n        high_value = np.percentile(gray, 98)  # 取得98%直方图处对应灰度\r\n        low_value = np.percentile(gray, 2)\r\n        truncated_gray = np.clip(gray, a_min=low_value, a_max=high_value)\r\n        processed_gray = ((truncated_gray - low_value) / (high_value - low_value)) * (\r\n            maxout - minout)\r\n        return processed_gray\r\n\r\n    r_p = __gray_process(r)\r\n    g_p = __gray_process(g)\r\n    b_p = __gray_process(b)\r\n    result = cv2.merge((b_p, g_p, r_p))\r\n    return np.uint8(result)\r\n\r\n\r\n# 简单图像标准化\r\ndef sample_norm(image: np.ndarray) -> np.ndarray:\r\n    stretches = []\r\n    if len(image.shape) == 3:\r\n        for b in range(image.shape[-1]):\r\n            stretched = exposure.equalize_hist(image[:, :, b])\r\n            stretched /= float(np.max(stretched))\r\n            stretches.append(stretched)\r\n        stretched_img = np.stack(stretches, axis=2)\r\n    else:  # if len(image.shape) == 2\r\n        stretched_img = exposure.equalize_hist(image)\r\n    return np.uint8(stretched_img * 255)\r\n\r\n\r\n# 计算缩略图\r\ndef get_thumbnail(image: np.ndarray, \r\n                  range: int=2000, \r\n                  max_size: int=1000) -> np.ndarray:\r\n    h, w = image.shape[:2]\r\n    if h >= range or w >= range:\r\n        if h >= w:\r\n            image = cv2.resize(image, (int(max_size / h * w), max_size))\r\n        else:\r\n            image = cv2.resize(image, (max_size, int(max_size / w * h)))\r\n    return image\r\n"
  },
  {
    "path": "eiseg/plugin/remotesensing/raster.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nimport os.path as osp\r\nimport numpy as np\r\nimport cv2\r\nimport math\r\nfrom typing import List, Dict, Tuple, Union\r\nfrom collections import defaultdict\r\nfrom easydict import EasyDict as edict\r\nfrom .imgtools import sample_norm, two_percentLinear, get_thumbnail\r\n\r\n\r\ndef check_rasterio() -> bool:\r\n    try:\r\n        import rasterio\r\n        return True\r\n    except:\r\n        return False\r\n\r\n\r\nIMPORT_STATE = False\r\nif check_rasterio():\r\n    import rasterio\r\n    from rasterio.windows import Window\r\n    IMPORT_STATE = True\r\n\r\n\r\nclass Raster:\r\n    def __init__(self, \r\n                 tif_path: str,\r\n                 show_band: Union[List[int], Tuple[int]]=[1, 1, 1], \r\n                 open_grid: bool=False,\r\n                 grid_size: Union[List[int], Tuple[int]]=[512, 512],\r\n                 overlap: Union[List[int], Tuple[int]]=[24, 24]) -> None:\r\n        \"\"\" 在EISeg中用于处理遥感栅格数据的类.\r\n\r\n        参数:\r\n            tif_path (str): GTiff数据的路径.\r\n            show_band (Union[List[int], Tuple[int]], optional): 用于RGB合成显示的波段. 默认为 [1, 1, 1].\r\n            open_grid (bool, optional): 是否打开了宫格切片功能. 默认为 False.\r\n            grid_size (Union[List[int], Tuple[int]], optional): 切片大小. 默认为 [512, 512].\r\n            overlap (Union[List[int], Tuple[int]], optional): 重叠区域的大小. 默认为 [24, 24].\r\n        \"\"\"\r\n        super(Raster, self).__init__()\r\n        if IMPORT_STATE is False:\r\n            raise(\"Can't import rasterio!\")\r\n        if osp.exists(tif_path):\r\n            self.src_data = rasterio.open(tif_path)\r\n            self.geoinfo = self.__getRasterInfo()\r\n            self.show_band = list(show_band)\r\n            self.grid_size = np.array(grid_size)\r\n            self.overlap = np.array(overlap)\r\n            self.open_grid = open_grid\r\n        else:\r\n            raise(\"{0} not exists!\".format(tif_path))\r\n        self.thumbnail_min = 2000\r\n\r\n    def __del__(self) -> None:\r\n        self.src_data.close()\r\n\r\n    def __getRasterInfo(self) -> Dict:\r\n        meta = self.src_data.meta\r\n        geoinfo = edict()\r\n        geoinfo.count = meta[\"count\"]\r\n        geoinfo.dtype = meta[\"dtype\"]\r\n        geoinfo.xsize = meta[\"width\"]\r\n        geoinfo.ysize = meta[\"height\"]\r\n        geoinfo.geotf = meta[\"transform\"]\r\n        geoinfo.crs = meta[\"crs\"]\r\n        if geoinfo.crs is not None:\r\n            geoinfo.crs_wkt = geoinfo.crs.wkt\r\n        else:\r\n            geoinfo.crs_wkt = None\r\n        return geoinfo\r\n\r\n    def checkOpenGrid(self, thumbnail_min: Union[int, None]) -> bool:\r\n        if isinstance(thumbnail_min, int):\r\n            self.thumbnail_min = thumbnail_min\r\n        if max(self.geoinfo.xsize, self.geoinfo.ysize) <= self.thumbnail_min:\r\n            self.open_grid = False\r\n        else:\r\n            self.open_grid = True\r\n        return self.open_grid\r\n\r\n    def setBand(self, bands: Union[List[int], Tuple[int]]) -> None:\r\n        self.show_band = list(bands)\r\n\r\n    # def __analysis_proj4(self) -> str:\r\n    #     proj4 = self.geoinfo.crs.wkt  # TODO: 解析为proj4\r\n    #     ap_dict = defaultdict(str)\r\n    #     dinf = proj4.split(\"+\")\r\n    #     for df in dinf:\r\n    #         kv = df.strip().split(\"=\")\r\n    #         if len(kv) == 2:\r\n    #             k, v = kv\r\n    #             ap_dict[k] = v\r\n    #     return str(\"● 投影：{0}\\n● 基准：{1}\\n● 单位：{2}\".format(\r\n    #             ap_dict[\"proj\"], ap_dict[\"datum\"], ap_dict[\"units\"])\r\n    #     )\r\n\r\n    def showGeoInfo(self) -> str:\r\n        # return str(\"● 波段数：{0}\\n● 数据类型：{1}\\n● 行数：{2}\\n● 列数：{3}\\n{4}\".format(\r\n        #     self.geoinfo.count, self.geoinfo.dtype, self.geoinfo.xsize,\r\n        #     self.geoinfo.ysize, self.__analysis_proj4())\r\n        # )\r\n        if self.geoinfo.crs is not None:\r\n            crs = str(self.geoinfo.crs.to_string().split(\":\")[-1])\r\n        else:\r\n            crs = \"None\"\r\n        return (str(self.geoinfo.count), str(self.geoinfo.dtype), str(self.geoinfo.xsize),\r\n                str(self.geoinfo.ysize), crs)\r\n\r\n    def getArray(self) -> Tuple[np.ndarray]:\r\n        rgb = []\r\n        if not self.open_grid:\r\n            for b in self.show_band:\r\n                rgb.append(self.src_data.read(b))\r\n            geotf = self.geoinfo.geotf\r\n        else:\r\n            for b in self.show_band:\r\n                rgb.append(get_thumbnail(self.src_data.read(b), self.thumbnail_min))\r\n            geotf = None\r\n        ima = np.stack(rgb, axis=2)  # cv2.merge(rgb)\r\n        if self.geoinfo[\"dtype\"] != \"uint8\":\r\n            ima = sample_norm(ima)\r\n        return two_percentLinear(ima), geotf\r\n\r\n    def getGrid(self, row: int, col: int) -> Tuple[np.ndarray]:\r\n        if self.open_grid is False:\r\n            return self.getArray()\r\n        grid_idx = np.array([row, col])\r\n        ul = grid_idx * (self.grid_size - self.overlap)\r\n        lr = ul + self.grid_size\r\n        # print(\"ul, lr\", ul, lr)\r\n        window = Window(ul[1], ul[0], (lr[1] - ul[1]), (lr[0] - ul[0]))\r\n        rgb = []\r\n        for b in self.show_band:\r\n            rgb.append(self.src_data.read(b, window=window))\r\n        win_tf = self.src_data.window_transform(window)\r\n        ima = cv2.merge([np.uint16(c) for c in rgb])\r\n        if self.geoinfo[\"dtype\"] == \"uint32\":\r\n            ima = sample_norm(ima)\r\n        return two_percentLinear(ima), win_tf\r\n\r\n    def saveMask(self, img: np.array, save_path: str, \r\n                 geoinfo: Union[Dict, None]=None, count: int=1) -> None:\r\n        if geoinfo is None:\r\n            geoinfo = self.geoinfo\r\n        new_meta = self.src_data.meta.copy()\r\n        new_meta.update({\r\n            \"driver\": \"GTiff\",\r\n            \"width\": geoinfo.xsize,\r\n            \"height\": geoinfo.ysize,\r\n            \"count\": count,\r\n            \"dtype\": geoinfo.dtype,\r\n            \"crs\": geoinfo.crs,\r\n            \"transform\": geoinfo.geotf[:6],\r\n            \"nodata\": 0\r\n            })\r\n        img = np.nan_to_num(img).astype(\"int16\")\r\n        with rasterio.open(save_path, \"w\", **new_meta) as tf:\r\n            if count == 1:\r\n                tf.write(img, indexes=1)\r\n            else:\r\n                for i in range(count):\r\n                    tf.write(img[:, :, i], indexes=(i + 1))\r\n\r\n    def saveMaskbyGrids(self, \r\n                        img_list: List[List[np.ndarray]], \r\n                        save_path: Union[str, None]=None,\r\n                        geoinfo: Union[Dict, None]=None) -> np.ndarray:\r\n        if geoinfo is None:\r\n            geoinfo = self.geoinfo\r\n        raw_size = (geoinfo.ysize, geoinfo.xsize)\r\n        h, w = self.grid_size\r\n        row = math.ceil(raw_size[0] / h)\r\n        col = math.ceil(raw_size[1] / w)\r\n        # print(\"row, col:\", row, col)\r\n        result_1 = np.zeros((h * row, w * col), dtype=np.uint8)\r\n        result_2 = result_1.copy()\r\n        for i in range(row):\r\n            for j in range(col):\r\n                # print(\"h, w:\", h, w)\r\n                ih, iw = img_list[i][j].shape[:2]\r\n                im = np.zeros(self.grid_size)\r\n                im[:ih, :iw] = img_list[i][j]\r\n                start_h = (i * h) if i == 0 else (i * (h - self.overlap[0]))\r\n                end_h = start_h + h\r\n                start_w = (j * w) if j == 0 else (j * (w - self.overlap[1]))\r\n                end_w = start_w + w\r\n                # print(\"se: \", start_h, end_h, start_w, end_w)\r\n                # 单区自己，重叠取或\r\n                if (i + j) % 2 == 0:\r\n                    result_1[start_h: end_h, start_w: end_w] = im\r\n                else:\r\n                    result_2[start_h: end_h, start_w: end_w] = im\r\n                # print(\"r, c, k:\", i_r, i_c, k)\r\n        result = np.where(result_2 != 0, result_2, result_1)\r\n        result = result[:raw_size[0], :raw_size[1]]\r\n        if save_path is not None:\r\n            self.saveMask(result, save_path, geoinfo)\r\n        return result"
  },
  {
    "path": "eiseg/plugin/remotesensing/shape.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nimport os\r\nimport os.path as osp\r\n\r\n\r\ndef check_gdal() -> bool:\r\n    try:\r\n        import gdal\r\n    except:\r\n        try:\r\n            from osgeo import gdal\r\n        except ImportError:\r\n            return False\r\n    return True\r\n\r\n\r\nIMPORT_STATE = False\r\nif check_gdal():\r\n    try:\r\n        import gdal\r\n        import osr\r\n        import ogr\r\n    except:\r\n        from osgeo import gdal, osr, ogr\r\n    IMPORT_STATE = True\r\n\r\n\r\n# 保存shp文件\r\ndef save_shp(shp_path: str, tif_path: str, ignore_index :int=0) -> str:\r\n    if IMPORT_STATE == True:\r\n        ds = gdal.Open(tif_path)\r\n        srcband = ds.GetRasterBand(1)\r\n        maskband = srcband.GetMaskBand()\r\n        gdal.SetConfigOption(\"GDAL_FILENAME_IS_UTF8\", \"YES\")\r\n        gdal.SetConfigOption(\"SHAPE_ENCODING\", \"UTF-8\")\r\n        ogr.RegisterAll()\r\n        drv = ogr.GetDriverByName(\"ESRI Shapefile\")\r\n        if osp.exists(shp_path):\r\n            os.remove(shp_path)\r\n        dst_ds = drv.CreateDataSource(shp_path)\r\n        prosrs = osr.SpatialReference(wkt=ds.GetProjection())\r\n        dst_layer = dst_ds.CreateLayer(\r\n            \"segmentation\", geom_type=ogr.wkbPolygon, srs=prosrs)\r\n        dst_fieldname = \"DN\"\r\n        fd = ogr.FieldDefn(dst_fieldname, ogr.OFTInteger)\r\n        dst_layer.CreateField(fd)\r\n        gdal.Polygonize(srcband, maskband, dst_layer, 0, [])\r\n        lyr = dst_ds.GetLayer()\r\n        lyr.SetAttributeFilter(\"DN = '{}'\".format(str(ignore_index)))\r\n        for holes in lyr:\r\n            lyr.DeleteFeature(holes.GetFID())\r\n        dst_ds.Destroy()\r\n        ds = None\r\n        return \"Dataset creation successfully!\"\r\n    else:\r\n        raise ImportError(\"can't import gdal, osr, ogr!\")"
  },
  {
    "path": "eiseg/run.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport sys\nimport os\nimport os.path as osp\nimport logging\nfrom datetime import datetime\n\nfrom qtpy.QtWidgets import QApplication  # 导入PyQt相关模块\nfrom qtpy import QtCore\n\nfrom eiseg import pjpath\nfrom app import APP_EISeg  # 导入带槽的界面\n\n\ndef main():\n    ## -- log --\n    settings = QtCore.QSettings(\n        osp.join(pjpath, \"config/setting.ini\"), QtCore.QSettings.IniFormat\n    )\n    #\n    # logFolder = settings.value(\"logFolder\")\n    # logLevel = settings.value(\"logLevel\")\n    # logDays = settings.value(\"logDays\")\n    #\n    # if logFolder is None or len(logFolder) == 0:\n    #     logFolder = osp.normcase(osp.join(pjpath, \"log\"))\n    # if not osp.exists(logFolder):\n    #     os.makedirs(logFolder)\n    #\n    # if logLevel:\n    #     logLevel = eval(logLevel)\n    # else:\n    #     logLevel = logging.DEBUG\n    # if logDays:\n    #     logDays = int(logDays)\n    # else:\n    #     logDays = 7\n    # # TODO: 删除大于logDays 的 log\n    #\n    # t = datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\n    # logging.basicConfig(\n    #     level=logging.DEBUG,\n    #     filename=osp.normcase(osp.join(logFolder, f\"eiseg-{t}.log\")),\n    #     format=\"%(levelname)s - %(asctime)s - %(filename)s - %(funcName)s - %(message)s\",\n    # )\n    # logger = logging.getLogger(\"EISeg Logger\")\n    # handler = logging.FileHandler(osp.normcase(osp.join(logFolder, f\"eiseg-{t}.log\")))\n    #\n    # handler.setFormatter(\n    #     logging.Formatter(\n    #         \"%(levelname)s - %(asctime)s - %(filename)s - %(funcName)s - %(message)s\"\n    #     )\n    # )\n    # logger.addHandler(handler)\n    # logger.info(\"test info\")\n    #\n    app = QApplication(sys.argv)\n    lang = settings.value(\"language\")\n    if lang != \"中文\":\n        trans = QtCore.QTranslator(app)\n        trans.load(osp.join(pjpath, f\"util/translate/{lang}\"))\n        app.installTranslator(trans)\n\n    window = APP_EISeg()  # 创建对象\n    window.currLanguage = lang\n    window.showMaximized()  # 全屏显示窗口\n    # 加载近期模型\n    QApplication.processEvents()\n    window.loadRecentModelParam()\n    sys.exit(app.exec())\n"
  },
  {
    "path": "eiseg/ui.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport os.path as osp\nfrom functools import partial\n\nfrom qtpy import QtCore, QtGui, QtWidgets\nfrom qtpy.QtGui import QIcon\nfrom qtpy.QtCore import Qt\n\nfrom eiseg import pjpath, __APPNAME__, __VERSION__, logger\nfrom eiseg.widget.create import creat_dock, create_button, create_slider, create_text\nfrom widget import AnnotationScene, AnnotationView\nfrom widget.create import *\nfrom widget.table import TableWidget\n\n# log = logging.getLogger(__name__ + \".ui\")\n\n\nclass Ui_EISeg(object):\n    def __init__(self):\n        super(Ui_EISeg, self).__init__()\n        self.tr = partial(QtCore.QCoreApplication.translate, \"APP_EISeg\")\n\n    def setupUi(self, MainWindow):\n        ## -- 主窗体设置 --\n        MainWindow.setObjectName(\"MainWindow\")\n        MainWindow.setMinimumSize(QtCore.QSize(1200, 700))  # 1366x768的屏幕显示不全\n        MainWindow.setWindowTitle(__APPNAME__ + \" \" + __VERSION__)\n        MainWindow.setWindowIcon(QIcon())  # TODO: 默认图标需要换一个吗，貌似不能不显示图标\n        CentralWidget = QtWidgets.QWidget(MainWindow)\n        CentralWidget.setObjectName(\"CentralWidget\")\n        MainWindow.setCentralWidget(CentralWidget)\n        ## -----\n        ## -- 工具栏 --\n        toolBar = QtWidgets.QToolBar(self)\n        sizePolicy = QtWidgets.QSizePolicy(\n            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum\n        )\n        sizePolicy.setHorizontalStretch(0)\n        sizePolicy.setVerticalStretch(0)\n        sizePolicy.setHeightForWidth(toolBar.sizePolicy().hasHeightForWidth())\n        toolBar.setSizePolicy(sizePolicy)\n        toolBar.setMinimumSize(QtCore.QSize(0, 33))\n        toolBar.setMovable(True)\n        toolBar.setAllowedAreas(QtCore.Qt.BottomToolBarArea | QtCore.Qt.TopToolBarArea)\n        toolBar.setObjectName(\"toolBar\")\n        self.toolBar = toolBar\n        MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)\n        ## -----\n        ## -- 状态栏 --\n        self.statusbar = QtWidgets.QStatusBar(MainWindow)\n        self.statusbar.setObjectName(\"statusbar\")\n        self.statusbar.setStyleSheet(\"QStatusBar::item {border: none;}\")\n        MainWindow.setStatusBar(self.statusbar)\n        self.statusbar.addPermanentWidget(\n            self.show_logo(osp.join(pjpath, \"resource/Paddle.png\"))\n        )\n        ## -----\n        ## -- 图形区域 --\n        ImageRegion = QtWidgets.QHBoxLayout(CentralWidget)\n        ImageRegion.setObjectName(\"ImageRegion\")\n        # 滑动区域\n        self.scrollArea = QtWidgets.QScrollArea(CentralWidget)\n        self.scrollArea.setWidgetResizable(True)\n        self.scrollArea.setObjectName(\"scrollArea\")\n        ImageRegion.addWidget(self.scrollArea)\n        # 图形显示\n        self.scene = AnnotationScene()\n        self.scene.addPixmap(QtGui.QPixmap())\n        self.canvas = AnnotationView(self.scene, self)\n        sizePolicy = QtWidgets.QSizePolicy(\n            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding\n        )\n        self.canvas.setSizePolicy(sizePolicy)\n        self.canvas.setAlignment(QtCore.Qt.AlignCenter)\n        self.canvas.setAutoFillBackground(False)\n        self.canvas.setStyleSheet(\"background-color: White\")\n        self.canvas.setObjectName(\"canvas\")\n        self.scrollArea.setWidget(self.canvas)\n        ## -----\n        ## -- 工作区 --\n        p_create_dock = partial(self.creat_dock, MainWindow)\n        p_create_button = partial(self.create_button, CentralWidget)\n        # 模型加载\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        ModelRegion = QtWidgets.QVBoxLayout()\n        ModelRegion.setObjectName(\"ModelRegion\")\n        # labShowSet = self.create_text(CentralWidget, \"labShowSet\", \"模型选择\")\n        # ModelRegion.addWidget(labShowSet)\n        # combo = QtWidgets.QComboBox(self)\n        # # combo.addItems([self.tr(ModelsNick[m.__name__][0]) for m in MODELS])\n        # combo.addItems([self.tr(ModelsNick[m][0]) for m in ModelsNick.keys()])\n        # self.comboModelSelect = combo\n        # ModelRegion.addWidget(self.comboModelSelect)\n        # 网络参数\n        self.btnParamsSelect = p_create_button(\n            \"btnParamsLoad\",\n            self.tr(\"加载网络参数\"),\n            osp.join(pjpath, \"resource/Model.png\"),\n            \"Ctrl+D\",\n        )\n        ModelRegion.addWidget(self.btnParamsSelect)  # 模型选择\n        self.cheWithMask = QtWidgets.QCheckBox(self)\n        self.cheWithMask.setText(self.tr(\"使用掩膜\"))\n        self.cheWithMask.setChecked(True)\n        ModelRegion.addWidget(self.cheWithMask)  # with_mask\n        horizontalLayout.addLayout(ModelRegion)\n        self.ModelDock = p_create_dock(\"ModelDock\", self.tr(\"模型选择\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.ModelDock)\n        # 数据列表\n        # TODO: 数据列表加一个搜索功能\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        ListRegion = QtWidgets.QVBoxLayout()\n        ListRegion.setObjectName(\"ListRegion\")\n        # labFiles = self.create_text(CentralWidget, \"labFiles\", \"数据列表\")\n        # ListRegion.addWidget(labFiles)\n        self.listFiles = QtWidgets.QListWidget(CentralWidget)\n        self.listFiles.setObjectName(\"ListFiles\")\n        ListRegion.addWidget(self.listFiles)\n\n        # ListRegion.addWidget(self.btnSave)\n        horizontalLayout.addLayout(ListRegion)\n        self.DataDock = p_create_dock(\"DataDock\", self.tr(\"数据列表\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.DataDock)\n        # 标签列表\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        LabelRegion = QtWidgets.QVBoxLayout()\n        LabelRegion.setObjectName(\"LabelRegion\")\n        self.labelListTable = TableWidget(\n            CentralWidget\n        )  # QtWidgets.QTableWidget(CentralWidget)\n        self.labelListTable.horizontalHeader().hide()\n        # 铺满\n        self.labelListTable.horizontalHeader().setSectionResizeMode(\n            QtWidgets.QHeaderView.Stretch\n        )\n        self.labelListTable.verticalHeader().hide()\n        self.labelListTable.setColumnWidth(0, 10)\n        # self.labelListTable.setMinimumWidth()\n        self.labelListTable.setObjectName(\"labelListTable\")\n        self.labelListTable.clearContents()\n        self.labelListTable.setRowCount(0)\n        self.labelListTable.setColumnCount(4)\n\n        LabelRegion.addWidget(self.labelListTable)\n        self.btnAddClass = p_create_button(\n            \"btnAddClass\", self.tr(\"添加标签\"), osp.join(pjpath, \"resource/Label.png\")\n        )\n        LabelRegion.addWidget(self.btnAddClass)\n        horizontalLayout.addLayout(LabelRegion)\n        self.LabelDock = p_create_dock(\"LabelDock\", self.tr(\"标签列表\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.LabelDock)\n        ## 滑块设置\n        # 分割阈值\n        p_create_slider = partial(self.create_slider, CentralWidget)\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        ShowSetRegion = QtWidgets.QVBoxLayout()\n        ShowSetRegion.setObjectName(\"ShowSetRegion\")\n        self.sldThresh, _, SegShowRegion = p_create_slider(\n            \"sldThresh\", \"labThresh\", self.tr(\"分割阈值：\")\n        )\n        ShowSetRegion.addLayout(SegShowRegion)\n        ShowSetRegion.addWidget(self.sldThresh)\n        # 透明度\n        self.sldOpacity, _, MaskShowRegion = p_create_slider(\n            \"sldOpacity\", \"labOpacity\", self.tr(\"标签透明度：\"), 75\n        )\n        ShowSetRegion.addLayout(MaskShowRegion)\n        ShowSetRegion.addWidget(self.sldOpacity)\n        # 点大小\n        self.sldClickRadius, _, PointShowRegion = p_create_slider(\n            \"sldClickRadius\", \"labClickRadius\", self.tr(\"点击可视化半径：\"), 3, 10, 0, 1\n        )\n        ShowSetRegion.addLayout(PointShowRegion)\n        ShowSetRegion.addWidget(self.sldClickRadius)\n        # 保存\n        self.btnSave = p_create_button(\n            \"btnSave\",\n            self.tr(\"保存\"),\n            osp.join(pjpath, \"resource/Save.png\"),\n            \"Ctrl+S\",\n        )\n        ShowSetRegion.addWidget(self.btnSave)\n        horizontalLayout.addLayout(ShowSetRegion)\n        self.SegSettingDock = p_create_dock(\"SegSettingDock\", self.tr(\"分割设置\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.SegSettingDock)\n        ## 专业功能区工作区\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        bandRegion = QtWidgets.QVBoxLayout()\n        bandRegion.setObjectName(\"bandRegion\")\n        bandSelection = create_text(CentralWidget, \"bandSelection\", self.tr(\"波段设置\"))\n        bandRegion.addWidget(bandSelection)\n        text_list = [\"R\", \"G\", \"B\"]\n        self.bandCombos = []\n        for txt in text_list:\n            lab = create_text(CentralWidget, \"band\" + txt, txt)\n            combo = QtWidgets.QComboBox()\n            combo.addItems([\"band_1\"])\n            self.bandCombos.append(combo)\n            hbandLayout = QtWidgets.QHBoxLayout()\n            hbandLayout.setObjectName(\"hbandLayout\")\n            hbandLayout.addWidget(lab)\n            hbandLayout.addWidget(combo)\n            hbandLayout.setStretch(1, 4)\n            bandRegion.addLayout(hbandLayout)\n        resultSave = create_text(CentralWidget, \"resultSave\", self.tr(\"保存设置\"))\n        bandRegion.addWidget(resultSave)\n        self.boundaryRegular = QtWidgets.QCheckBox(self.tr(\"建筑边界规范化\"))\n        self.boundaryRegular.setObjectName(\"boundaryRegular\")\n        bandRegion.addWidget(self.boundaryRegular)\n        self.shpSave = QtWidgets.QCheckBox(self.tr(\"另存为shapefile\"))\n        self.shpSave.setObjectName(\"shpSave\")\n        bandRegion.addWidget(self.shpSave)\n        horizontalLayout.addLayout(bandRegion)\n        showGeoInfo = create_text(CentralWidget, \"showGeoInfo\", self.tr(\"地理信息\"))\n        bandRegion.addWidget(showGeoInfo)\n        self.edtGeoinfo = QtWidgets.QTextEdit(self.tr(\"无\"))\n        self.edtGeoinfo.setObjectName(\"edtGeoinfo\")\n        self.edtGeoinfo.setReadOnly(True)\n        bandRegion.addWidget(self.edtGeoinfo)\n        self.RSDock = p_create_dock(\"RSDock\", self.tr(\"遥感设置\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.RSDock)\n\n        ## 医学影像设置\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        MIRegion = QtWidgets.QVBoxLayout()\n        MIRegion.setObjectName(\"MIRegion\")\n        # mi_text = create_text(CentralWidget, \"sliceSelection\", self.tr(\"切片选择\"))\n        # MIRegion.addWidget(mi_text)\n        # self.sldMISlide, _, slideRegion = p_create_slider(\n        #     \"sldMISlide\", \"labMISlide\", self.tr(\"切片选择：\"), 1, 1, 1\n        # )\n        # self.sldMISlide.setMinimum(1)\n        # MIRegion.addLayout(slideRegion)\n        # MIRegion.addWidget(self.sldMISlide)\n        self.sliderWw, self.textWw, WwRegion = p_create_slider(\n            \"sliderWw\", \"textWw\", self.tr(\"窗宽：\"), 200, 2048, -2048, 1, True\n        )\n        MIRegion.addLayout(WwRegion)\n        MIRegion.addWidget(self.sliderWw)\n        self.sliderWc, self.textWc, WcRegion = p_create_slider(\n            \"sliderWc\", \"textWc\", self.tr(\"窗位：\"), 0, 2048, -2048, 1, True\n        )\n        MIRegion.addLayout(WcRegion)\n        MIRegion.addWidget(self.sliderWc)\n        horizontalLayout.addLayout(MIRegion)\n        self.MedDock = p_create_dock(\"MedDock\", self.tr(\"医疗设置\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.MedDock)\n        ## 宫格区域\n        widget = QtWidgets.QWidget()\n        horizontalLayout = QtWidgets.QHBoxLayout(widget)\n        GridRegion = QtWidgets.QVBoxLayout()\n        GridRegion.setObjectName(\"GridRegion\")\n        # self.btnInitGrid = p_create_button(\n        #     \"btnInitGrid\",\n        #     self.tr(\"创建宫格\"),\n        #     osp.join(pjpath, \"resource/N2.png\"),\n        #     \"\",\n        # )\n        self.btnFinishedGrid = p_create_button(\n            \"btnFinishedGrid\",\n            self.tr(\"完成宫格\"),\n            osp.join(pjpath, \"resource/Save.png\"),\n            \"\",\n        )\n        hbandLayout = QtWidgets.QHBoxLayout()\n        hbandLayout.setObjectName(\"hbandLayout\")\n        # hbandLayout.addWidget(self.btnInitGrid)\n        hbandLayout.addWidget(self.btnFinishedGrid)\n        GridRegion.addLayout(hbandLayout)  # 创建宫格\n        self.cheSaveEvery = QtWidgets.QCheckBox(self)\n        self.cheSaveEvery.setText(self.tr(\"保存每个宫格的标签\"))\n        self.cheSaveEvery.setChecked(False)\n        GridRegion.addWidget(self.cheSaveEvery)\n        self.gridTable = QtWidgets.QTableWidget(CentralWidget)\n        self.gridTable.horizontalHeader().hide()\n        self.gridTable.verticalHeader().hide()\n        # 铺满\n        self.gridTable.horizontalHeader().setSectionResizeMode(\n            QtWidgets.QHeaderView.Stretch\n        )\n        self.gridTable.verticalHeader().setSectionResizeMode(\n            QtWidgets.QHeaderView.Stretch\n        )\n        self.gridTable.setObjectName(\"gridTable\")\n        self.gridTable.clearContents()\n        self.gridTable.setColumnCount(1)\n        self.gridTable.setRowCount(1)\n        GridRegion.addWidget(self.gridTable)\n        horizontalLayout.addLayout(GridRegion)\n        self.GridDock = p_create_dock(\"GridDock\", self.tr(\"宫格切换\"), widget)\n        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.GridDock)\n        ## -----\n        QtCore.QMetaObject.connectSlotsByName(MainWindow)\n\n        # log.debug(\"Set up UI finished\")\n\n    ## 创建文本\n    def create_text(self, parent, text_name=None, text_text=None):\n        return create_text(parent, text_name, text_text)\n\n    ## 创建按钮\n    def create_button(self, parent, btn_name, btn_text, ico_path=None, curt=None):\n        return create_button(parent, btn_name, btn_text, ico_path, curt)\n\n    ## 创建dock\n    def creat_dock(self, parent, name, text, layout):\n        return creat_dock(parent, name, text, layout)\n\n    ## 显示Logo\n    def show_logo(self, logo_path):\n        labLogo = QtWidgets.QLabel()\n        sizePolicy = QtWidgets.QSizePolicy(\n            QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum\n        )\n        labLogo.setSizePolicy(sizePolicy)\n        labLogo.setMaximumSize(QtCore.QSize(100, 33))\n        labLogo.setPixmap(QtGui.QPixmap(logo_path))\n        labLogo.setScaledContents(True)\n        labLogo.setObjectName(\"labLogo\")\n        return labLogo\n\n    ## 创建滑块区域\n    def create_slider(\n        self,\n        parent,\n        sld_name,\n        text_name,\n        text,\n        default_value=50,\n        max_value=100,\n        min_value=0,\n        text_rate=0.01,\n        edit=False\n    ):\n        return create_slider(\n            parent,\n            sld_name,\n            text_name,\n            text,\n            default_value,\n            max_value,\n            min_value,\n            text_rate,\n            edit\n        )\n"
  },
  {
    "path": "eiseg/util/__init__.py",
    "content": "from .qt import newAction, addActions, struct, newIcon\nfrom .config import parse_configs, save_configs\nfrom .colormap import colorMap\nfrom .polygon import get_polygon, Instructions\nfrom .manager import MODELS\nfrom .language import TransUI\nfrom .coco.coco import COCO\nfrom .label import LabelList\nfrom .opath import check_cn, normcase"
  },
  {
    "path": "eiseg/util/coco/__init__.py",
    "content": "__author__ = 'tylin'\n"
  },
  {
    "path": "eiseg/util/coco/_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": "eiseg/util/coco/coco.py",
    "content": "import json\nimport time\nimport matplotlib.pyplot as plt\nfrom matplotlib.collections import PatchCollection\nfrom matplotlib.patches import Polygon\nimport numpy as np\nimport copy\nimport itertools\nimport os\nimport os.path as osp\nfrom collections import defaultdict\nimport sys\nfrom datetime import datetime\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 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        # dataset, anns, cats, imgs, imgToAnns, catToImgs, imgNameToId, maxAnnId, maxImgId\n        self.dataset = {\n            \"categories\": [],\n            \"images\": [],\n            \"annotations\": [],\n            \"info\": \"\",\n            \"licenses\": [],\n        }  # the complete json\n        self.anns = dict()  # anns[annId]={}\n        self.cats = dict()  # cats[catId] = {}\n        self.imgs = dict()  # imgs[imgId] = {}\n        self.imgToAnns = defaultdict(list)  # imgToAnns[imgId] = [ann]\n        self.catToImgs = defaultdict(list)  # catToImgs[catId] = [imgId]\n        self.imgNameToId = defaultdict(list)  # imgNameToId[name] = imgId\n        self.maxAnnId = 0\n        self.maxImgId = 0\n        if annotation_file is not None and osp.exists(annotation_file):\n            print(\"loading annotations into memory...\")\n            tic = time.time()\n            dataset = json.load(open(annotation_file, \"r\"))\n            assert (\n                type(dataset) == dict\n            ), \"annotation file format {} not supported\".format(type(dataset))\n            print(\"Done (t={:0.2f}s)\".format(time.time() - tic))\n            self.dataset = dataset\n            self.createIndex()\n            print(\n                f\"load coco with {len(self.dataset['images'])} images and {len(self.dataset['annotations'])} annotations.\"\n            )\n\n    def hasImage(self, imageName):\n        imgId = self.imgNameToId.get(imageName, None)\n        return imgId is not None\n\n    def hasCat(self, catIdx):\n        res = self.cats.get(catIdx)\n        return res is not None\n\n    def createIndex(self):\n        # create index\n        print(\"creating index...\")\n        anns, cats, imgs = {}, {}, {}\n        imgNameToId, imgToAnns, catToImgs, imgNameToId = [\n            defaultdict(list) for _ in range(4)\n        ]\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                self.maxAnnId = max(self.maxAnnId, ann[\"id\"])\n\n        if \"images\" in self.dataset:\n            for img in self.dataset[\"images\"]:\n                imgs[img[\"id\"]] = img\n                imgNameToId[img[\"file_name\"]] = img[\"id\"]\n                try:\n                    imgId = int(img[\"id\"])\n                    self.maxImgId = max(self.maxImgId, imgId)\n                except:\n                    pass\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        # TODO: read license\n        print(\"index created!\")\n\n        self.anns = anns\n        self.imgToAnns = imgToAnns\n        self.catToImgs = catToImgs\n        self.imgNameToId = imgNameToId\n        self.imgs = imgs\n        self.cats = cats\n\n    def setInfo(\n        self,\n        year: int = \"\",\n        version: str = \"\",\n        description: str = \"\",\n        contributor: str = \"\",\n        url: str = \"\",\n        date_created: datetime = \"\",\n    ):\n        self.dataset[\"info\"] = {\n            \"year\": year,\n            \"version\": version,\n            \"description\": description,\n            \"contributor\": contributor,\n            \"url\": url,\n            \"date_created\": date_created,\n        }\n\n    def addCategory(\n        self,\n        id: int,\n        name: str,\n        color: list,\n        supercategory: str = \"\",\n    ):\n        cat = {\n            \"id\": id,\n            \"name\": name,\n            \"color\": color,\n            \"supercategory\": supercategory,\n        }\n        self.cats[id] = cat\n        self.dataset[\"categories\"].append(cat)\n\n    def updateCategory(\n        self,\n        id: int,\n        name: str,\n        color: list,\n        supercategory: str = \"\",\n    ):\n        cat = {\n            \"id\": id,\n            \"name\": name,\n            \"color\": color,\n            \"supercategory\": supercategory,\n        }\n        self.cats[id] = cat\n        for idx in range(len(self.dataset[\"categories\"])):\n            if self.dataset[\"categories\"][idx][\"id\"] == id:\n                self.dataset[\"categories\"][idx] = cat\n\n    def addImage(\n        self,\n        file_name: str,\n        width: int,\n        height: int,\n        id: int = None,\n        license: int = \"\",\n        flickr_url: str = \"\",\n        coco_url: str = \"\",\n        date_captured: datetime = \"\",\n    ):\n        if self.hasImage(file_name):\n            print(f\"{file_name}图片已存在\")\n            return\n        if not id:\n            self.maxImgId += 1\n            id = self.maxImgId\n        image = {\n            \"id\": id,\n            \"width\": width,\n            \"height\": height,\n            \"file_name\": file_name,\n            \"license\": license,\n            \"flickr_url\": flickr_url,\n            \"coco_url\": coco_url,\n            \"date_captured\": date_captured,\n        }\n        self.dataset[\"images\"].append(image)\n        self.imgs[id] = image\n        self.imgNameToId[file_name] = id\n        return id\n\n    def getBB(self, segmentation):\n        x = segmentation[::2]\n        y = segmentation[1::2]\n        maxx, minx, maxy, miny = max(x), min(x), max(y), min(y)\n        return [minx, miny, maxx - minx, maxy - miny]\n\n    def getArea(self, segmentation):\n        x = segmentation[::2]\n        y = segmentation[1::2]\n\n        return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))\n\n    def addAnnotation(\n        self,\n        image_id: int,\n        category_id: int,\n        segmentation: list,\n        area: float = None,\n        id: int = None,\n    ):\n        if id is not None and self.anns.get(id, None) is not None:\n            print(\"标签已经存在\")\n            return\n        if not id:\n            self.maxAnnId += 1\n            id = self.maxAnnId\n\n        ann = {\n            \"id\": id,\n            \"iscrowd\": 0,\n            \"image_id\": image_id,\n            \"category_id\": category_id,\n            \"segmentation\": [segmentation],\n            \"area\": self.getArea(segmentation),\n            \"bbox\": self.getBB(segmentation),\n        }\n\n        self.dataset[\"annotations\"].append(ann)\n        self.anns[id] = ann\n        self.imgToAnns[image_id].append(ann)\n        self.catToImgs[category_id].append(image_id)\n        return id\n\n    def delAnnotation(self, annId, imgId):\n        if \"annotations\" in self.dataset:\n            for idx, ann in enumerate(self.dataset[\"annotations\"]):\n                if ann[\"id\"] == annId:\n                    del self.dataset[\"annotations\"][idx]\n        if annId in self.anns.keys():\n            del self.anns[annId]\n\n        for idx, ann in enumerate(self.imgToAnns[imgId]):\n            if ann[\"id\"] == annId:\n                del self.imgToAnns[imgId][idx]\n\n    def updateAnnotation(self, id, imgId, segmentation):\n        self.anns[id][\"segmentation\"] = [segmentation]\n        self.anns[id][\"bbox\"] = self.getBB(segmentation)\n        self.anns[id][\"area\"] = self.getArea(segmentation)\n        for rec in self.dataset[\"annotations\"]:\n            if rec[\"id\"] == id:\n                rec = self.anns[id]\n                break\n\n        for rec in self.dataset[\"annotations\"]:\n            if rec[\"id\"] == id:\n                # @todo TODO move into debug codes or controls\n                print(\n                    \"record point : \",\n                    rec[\"segmentation\"][0][0],\n                    rec[\"segmentation\"][0][1],\n                )\n                break\n\n        for rec in self.imgToAnns[imgId]:\n            if rec[\"id\"] == id:\n                rec[\"segmentation\"] = [segmentation]\n                break\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 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 (e.g. [0 inf])\n               iscrowd (boolean)       : get anns for given crowd label (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 if imgId in self.imgToAnns\n                ]\n                anns = list(itertools.chain.from_iterable(lists))\n            else:\n                anns = self.dataset[\"annotations\"]\n            anns = (\n                anns\n                if len(catIds) == 0\n                else [ann for ann in anns if ann[\"category_id\"] in catIds]\n            )\n            anns = (\n                anns\n                if len(areaRng) == 0\n                else [\n                    ann\n                    for ann in anns\n                    if ann[\"area\"] > areaRng[0] and ann[\"area\"] < areaRng[1]\n                ]\n            )\n        if not iscrowd == 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 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 = (\n                cats\n                if len(catNms) == 0\n                else [cat for cat in cats if cat[\"name\"] in catNms]\n            )\n            cats = (\n                cats\n                if len(supNms) == 0\n                else [cat for cat in cats if cat[\"supercategory\"] in supNms]\n            )\n            cats = (\n                cats\n                if len(catIds) == 0\n                else [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 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 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    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    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    # 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((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(\n    #                             [ann[\"segmentation\"]], t[\"height\"], t[\"width\"]\n    #                         )\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(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(\n    #                     x[v > 0],\n    #                     y[v > 0],\n    #                     \"o\",\n    #                     markersize=8,\n    #                     markerfacecolor=c,\n    #                     markeredgecolor=\"k\",\n    #                     markeredgewidth=2,\n    #                 )\n    #                 plt.plot(\n    #                     x[v > 1],\n    #                     y[v > 1],\n    #                     \"o\",\n    #                     markersize=8,\n    #                     markerfacecolor=c,\n    #                     markeredgecolor=c,\n    #                     markeredgewidth=2,\n    #                 )\n    #\n    #             if draw_bbox:\n    #                 [bbox_x, bbox_y, bbox_w, bbox_h] = ann[\"bbox\"]\n    #                 poly = [\n    #                     [bbox_x, bbox_y],\n    #                     [bbox_x, bbox_y + bbox_h],\n    #                     [bbox_x + bbox_w, bbox_y + bbox_h],\n    #                     [bbox_x + bbox_w, bbox_y],\n    #                 ]\n    #                 np_poly = np.array(poly).reshape((4, 2))\n    #                 polygons.append(Polygon(np_poly))\n    #                 color.append(c)\n    #\n    #         p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.4)\n    #         ax.add_collection(p)\n    #         p = PatchCollection(\n    #             polygons, facecolor=\"none\", edgecolors=color, linewidths=2\n    #         )\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 or (PYTHON_VERSION == 2 and type(resFile) == unicode):\n    #         anns = json.load(open(resFile))\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) == (\n    #         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    #         )\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(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 not \"segmentation\" 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(self.dataset[\"categories\"])\n    #         for id, ann in enumerate(anns):\n    #             # now only support compressed RLE format as segmentation results\n    #             ann[\"area\"] = maskUtils.area(ann[\"segmentation\"])\n    #             if not \"bbox\" 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(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(\n                \"downloaded {}/{} images (t={:0.1f}s)\".format(i, N, time.time() - tic)\n            )\n\n    def loadNumpyAnnotations(self, data):\n        \"\"\"\n        Convert result data from a numpy array [Nx7] where each row contains {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                {\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            ]\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    # def annToMask(self, ann):\n    #     \"\"\"\n    #     Convert annotation which can be polygons, uncompressed RLE, or RLE to binary mask.\n    #     :return: binary mask (numpy 2D array)\n    #     \"\"\"\n    #     rle = self.annToRLE(ann)\n    #     m = maskUtils.decode(rle)\n    #     return m\n"
  },
  {
    "path": "eiseg/util/coco/cocoeval.py",
    "content": "__author__ = 'tsungyi'\n\nimport numpy as np\nimport datetime\nimport time\nfrom collections import defaultdict\nfrom . import mask as maskUtils\nimport copy\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(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 not cocoGt is None:\n            self.params.imgIds = sorted(cocoGt.getImgIds())\n            self.params.catIds = sorted(cocoGt.getCatIds())\n\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        p = self.params\n        if p.useCats:\n            gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))\n            dts=self.cocoDt.loadAnns(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(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 (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 not p.useSegm is None:\n            p.iouType = 'segm' if p.useSegm == 1 else 'bbox'\n            print('useSegm (deprecated) is not None. Running {} evaluation'.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\n                        for catId in catIds}\n\n        evaluateImg = self.evaluateImg\n        maxDet = p.maxDets[-1]\n        self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet)\n                 for catId in catIds\n                 for areaRng in p.areaRng\n                 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]; yg = g[1::3]; vg = g[2::3]\n            k1 = np.count_nonzero(vg > 0)\n            bb = gt['bbox']\n            x0 = bb[0] - bb[2]; x1 = bb[0] + bb[2] * 2\n            y0 = bb[1] - bb[3]; y1 = bb[1] + bb[3] * 2\n            for i, dt in enumerate(dts):\n                d = np.array(dt['keypoints'])\n                xd = d[0::3]; 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) & (x1,y1)\n                    z = np.zeros((k))\n                    dx = np.max((z, x0-xd),axis=0)+np.max((z, xd-x1),axis=0)\n                    dy = np.max((z, y0-yd),axis=0)+np.max((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(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 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] for d in dt]).reshape((1, len(dt)))\n        dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,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 self.eval\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((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 for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]\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 not e is None]\n                    if len(E) == 0:\n                        continue\n                    dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])\n\n                    # different sorting method generates slightly different results.\n                    # mergesort is used to be consistent as Matlab implementation.\n                    inds = np.argsort(-dtScores, kind='mergesort')\n                    dtScoresSorted = dtScores[inds]\n\n                    dtm  = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds]\n                    dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet]  for e in E], axis=1)[:,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), 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 accessing elements\n                        # use python array gets significant speed improvement\n                        pr = pr.tolist(); 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:\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 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}'\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 = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]\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(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))\n            return mean_s\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, iouThr=.75, maxDets=self.params.maxDets[2])\n            stats[3] = _summarize(1, areaRng='small', maxDets=self.params.maxDets[2])\n            stats[4] = _summarize(1, areaRng='medium', maxDets=self.params.maxDets[2])\n            stats[5] = _summarize(1, areaRng='large', 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, areaRng='small', maxDets=self.params.maxDets[2])\n            stats[10] = _summarize(0, areaRng='medium', maxDets=self.params.maxDets[2])\n            stats[11] = _summarize(0, areaRng='large', maxDets=self.params.maxDets[2])\n            return stats\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        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\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 larger than the true value\n        self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True)\n        self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, endpoint=True)\n        self.maxDets = [1, 10, 100]\n        self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [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 larger than the true value\n        self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True)\n        self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, 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([.26, .25, .25, .35, .35, .79, .79, .72, .72, .62,.62, 1.07, 1.07, .87, .87, .89, .89])/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": "eiseg/util/coco/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": "eiseg/util/coco/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": "eiseg/util/coco/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": "eiseg/util/coco/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": "eiseg/util/coco/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 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) in column-major order)\n#  iscrowd - [nx1] list of np.ndarray. 1 indicates corresponding gt image has 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\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\ndef decode(rleObjs):\n    if type(rleObjs) == list:\n        return _mask.decode(rleObjs)\n    else:\n        return _mask.decode([rleObjs])[:,:,0]\n\ndef area(rleObjs):\n    if type(rleObjs) == list:\n        return _mask.area(rleObjs)\n    else:\n        return _mask.area([rleObjs])[0]\n\ndef toBbox(rleObjs):\n    if type(rleObjs) == list:\n        return _mask.toBbox(rleObjs)\n    else:\n        return _mask.toBbox([rleObjs])[0]"
  },
  {
    "path": "eiseg/util/coco.py.bk",
    "content": "cocoDict = {\n    \"info\": info,\n    \"images\": [image],\n    \"annotations\": [annotation],\n    \"categories\": [\n        {\n            \"id\": int,\n            \"name\": str,\n            \"supercategory\": str,\n        }\n    ],\n    \"licenses\": [license],\n}\nlicense = {\n    \"id\": int,\n    \"name\": str,\n    \"url\": str,\n}\nimage = {\n    \"id\": int,\n    \"width\": int,\n    \"height\": int,\n    \"file_name\": str,\n    \"license\": int,\n    \"flickr_url\": str,\n    \"coco_url\": str,\n    \"date_captured\": datetime,\n}\nannotation = {\n    \"id\": int,\n    \"image_id\": int,\n    \"category_id\": int,\n    \"segmentation\": [polygon],\n    \"area\": float,\n    \"bbox\": [x, y, width, height],\n}\ninfo = {\n    \"year\": int,\n    \"version\": str,\n    \"description\": str,\n    \"contributor\": str,\n    \"url\": str,\n    \"date_created\": datetime,\n}\nimport datetime\n\n\nclass CoCoAnn:\n    def __init__(self, cocoFile=None):\n        self.dict = {\n            \"info\": {},\n            \"images\": [],\n            \"annotations\": [],\n            \"categories\": [],\n            \"licenses\": [],\n        }\n        self.annId = 0\n\n    def setInfo(\n        self,\n        year: int = \"\",\n        version=\"\",\n        description=\"\",\n        contributor=\"\",\n        url=\"\",\n        date_created=\"\",\n    ):\n        # if not year:\n        #     now = datetime.now()\n        #     year = now.strftime(\"%Y\")\n        # # TODO: datetime\n        # if not date_created:\n        #     pass\n        self.dict[\"info\"] = {\n            \"year\": year,\n            \"version\": version,\n            \"description\": description,\n            \"contributor\": contributor,\n            \"url\": url,\n            \"date_created\": date_created,\n        }\n\n    def setCategories(self, categories):\n        self.dict[\"categories\"] = categories\n\n    def addCategory(self, id, name, supercategory=\"\"):\n        cat = {\n            \"id\": int,\n            \"name\": str,\n            \"supercategory\": str,\n        }\n        self.dict[\"categories\"].append(cat)\n\n    def setLicenses(self, licenses):\n        self.licenses = licenses\n\n    def addLicense(self, id, name, url):\n        license = {\n            \"id\": int,\n            \"name\": str,\n            \"url\": str,\n        }\n        self.dict[\"licenses\"].append(license)\n\n    def addImage(\n        self,\n        id,\n        width,\n        height,\n        file_name,\n        license=\"\",\n        flickr_url=\"\",\n        coco_url=\"\",\n        date_captured=\"\",\n    ):\n        image = {\n            \"id\": id,\n            \"width\": width,\n            \"height\": height,\n            \"file_name\": file_name,\n            \"license\": license,\n            \"flickr_url\": flickr_url,\n            \"coco_url\": coco_url,\n            \"date_captured\": date_captured,\n        }\n        self.dict[\"images\"].append(image)\n\n    def addAnnotation(\n        self,\n        image_id,\n        category_id,\n        segmentation,\n        bbox,\n        area,\n        id,\n    ):\n        {\n            \"id\": int,\n            \"image_id\": int,\n            \"category_id\": int,\n            \"segmentation\": [polygon],\n            \"area\": float,\n            \"bbox\": [x, y, width, height],\n        }\n"
  },
  {
    "path": "eiseg/util/colormap.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\nimport os.path as osp\nimport random\n\nfrom eiseg import pjpath\n\n\nclass ColorMap(object):\n    def __init__(self, color_path, shuffle=False):\n        self.colors = []\n        self.index = 0\n        self.usedColors = []\n        with open(color_path, \"r\") as f:\n            colors = f.readlines()\n        if shuffle:\n            random.shuffle(colors)\n        self.colors = [[int(x) for x in c.strip().split(\",\")] for c in colors]\n\n    def get_color(self):\n        color = self.colors[self.index]\n        self.index = (self.index + 1) % len(self)\n        return color\n\n    def __len__(self):\n        return len(self.colors)\n\n\ncolorMap = ColorMap(osp.join(pjpath, \"config/colormap.txt\"))\n"
  },
  {
    "path": "eiseg/util/config.py",
    "content": "import yaml\nimport os.path as osp\nimport os\n\nfrom eiseg import pjpath\n\n\ndef parse_configs(path):\n    if not path or not osp.exists(path):\n        return\n    with open(path, \"r\", encoding=\"utf-8\") as f:\n        return yaml.load(f.read(), Loader=yaml.FullLoader)\n\n\ndef save_configs(path=None, config=None, actions=None):\n    if not path:\n        path = osp.join(pjpath, \"config/config.yaml\")\n    if not osp.exists(path):\n        # os.makedirs(osp.basename(path))\n        # windows无法使用mknod\n        f = open(path, \"w+\")\n        f.close()\n    if not config:\n        config = {}\n    if actions:\n        config[\"shortcut\"] = {}\n        for action in actions:\n            config[\"shortcut\"][action.data()] = action.shortcut().toString()\n    with open(path, \"w\", encoding=\"utf-8\") as f:\n        yaml.dump(config, f)\n\n\nclass cfgData(object):\n    def __init__(self, yaml_file):\n        with open(yaml_file, \"r\", encoding=\"utf-8\") as f:\n            fig_data = f.read()\n            self.dicts = yaml.load(fig_data)\n\n    def get(self, key):\n        if key in self.dicts.keys():\n            return self.dicts[key]\n        else:\n            raise ValueError(\"Not find this keyword.\")\n\n\nif __name__ == \"__main__\":\n    cfg = cfgData(\"EISeg/train/train_config.yaml\")\n    print(cfg.get(\"use_vdl\"))\n"
  },
  {
    "path": "eiseg/util/exp_imports/default.py",
    "content": "import paddle\nfrom functools import partial\nfrom easydict import EasyDict as edict\nfrom albumentations import *\n\nfrom data.datasets import *\nfrom model.losses import *\nfrom data.transforms import *\n#from isegm.engine.trainer import ISTrainer\nfrom model.metrics import AdaptiveIoU\nfrom data.points_sampler import MultiPointSampler\nfrom model.initializer import XavierGluon\n\nfrom model.is_hrnet_model import HRNetModel\nfrom model.is_deeplab_model import DeeplabModel"
  },
  {
    "path": "eiseg/util/label.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\nimport os\nimport os.path as osp\n\nfrom . import colorMap\n\n\nclass Label:\n    def __init__(self, idx=None, name=None, color=None):\n        self.idx = idx\n        self.name = name\n        self.color = color\n\n    def __repr__(self):\n        return f\"{self.idx} {self.name} {self.color}\"\n\n\nclass LabelList(object):\n    def __init__(self, labels: dict = None):\n        self.labelList = []\n        if labels is not None:\n            for lab in labels:\n                color = lab.get(\"color\", colorMap.get_color())\n                self.add(lab[\"id\"], lab[\"name\"], color)\n\n    def add(self, idx, name, color):\n        self.labelList.append(Label(idx, name, color))\n\n    def remove(self, index):\n        for idx, lab in enumerate(self.labelList):\n            if lab.idx == index:\n                del self.labelList[idx]\n                break\n        # del self.labelList[index]\n\n    def clear(self):\n        self.labelList = []\n\n    def toint(self, seq):\n        if isinstance(seq, list):\n            for i in range(len(seq)):\n                try:\n                    seq[i] = int(seq[i])\n                except ValueError:\n                    pass\n        else:\n            seq = int(seq)\n        return seq\n\n    def importLabel(self, path):\n        if not osp.exists(path):\n            return []\n        with open(path, \"r\", encoding=\"utf-8\") as f:\n            labels = f.readlines()\n        labelList = []\n        for lab in labels:\n            lab = lab.replace(\"\\n\", \"\").strip(\" \").split(\" \")\n            if len(lab) != 5:  # rm: and len(lab) != 2\n                print(f\"{lab} 标签不合法\")\n                continue\n            label = Label(self.toint(lab[0]), str(lab[1]), self.toint(lab[2:]))\n            labelList.append(label)\n        self.labelList = labelList\n\n    def exportLabel(self, path):\n        if not path or not osp.exists(osp.dirname(path)):\n            print(\"label path don't exist\")\n            return\n        with open(path, \"w\", encoding=\"utf-8\") as f:\n            for label in self.labelList:\n                print(label.idx, end=\" \", file=f)\n                print(label.name, end=\" \", file=f)\n                for idx in range(3):\n                    print(label.color[idx], end=\" \", file=f)\n                print(file=f)\n\n    def getLabelById(self, labelIdx):\n        for lab in self.labelList:\n            if lab.idx == labelIdx:\n                return lab\n\n    def __repr__(self):\n        return str(self.labelList)\n\n    def __getitem__(self, index):\n        return self.labelList[index]\n\n    def __len__(self):\n        return len(self.labelList)\n\n    @property\n    def colors(self):\n        cols = []\n        for lab in self.labelList:\n            cols.append(lab.color)\n        return cols\n"
  },
  {
    "path": "eiseg/util/language.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\nimport os.path as osp\r\nimport re\r\nfrom eiseg import pjpath\r\nfrom collections import defaultdict\r\nimport json\r\nfrom urllib import parse\r\nimport requests\r\n\r\n\r\nclass TransUI(object):\r\n    def __init__(self, is_trans=False):\r\n        super().__init__()\r\n        self.trans_dict = defaultdict(dict)\r\n        with open(osp.join(pjpath, \"config/zh_CN.EN\"), \"r\", encoding=\"utf-8\") as f:\r\n            texts = f.readlines()\r\n            for txt in texts:\r\n                strs = txt.split(\"@\")\r\n                self.trans_dict[strs[0].strip()] = strs[1].strip()\r\n        self.is_trans = is_trans\r\n        self.youdao_url = \"http://fanyi.youdao.com/translate?&doctype=json&type=AUTO&i=\"\r\n\r\n    def put(self, zh_CN):\r\n        if self.is_trans == False:\r\n            return zh_CN\r\n        else:\r\n            try:\r\n                return str(self.trans_dict[zh_CN])\r\n            except:\r\n                return zh_CN\r\n\r\n    # 联网动态翻译\r\n    def tr(self, zh_CN):\r\n        try:\r\n            tr_url = self.youdao_url + parse.quote(zh_CN)\r\n            response = requests.get(tr_url)\r\n            js = json.loads(response.text)\r\n            result_EN = js[\"translateResult\"][0][0][\"tgt\"]\r\n            return str(result_EN)\r\n        except:\r\n            return zh_CN\r\n"
  },
  {
    "path": "eiseg/util/manager.py",
    "content": "import inspect\nfrom collections.abc import Sequence\n\n\nclass ComponentManager:\n    def __init__(self, name=None):\n        self._components_dict = dict()\n        self._name = name\n\n    def __len__(self):\n        return len(self._components_dict)\n\n    def __repr__(self):\n        name_str = self._name if self._name else self.__class__.__name__\n        return \"{}:{}\".format(name_str, list(self._components_dict.keys()))\n\n    def __getitem__(self, item):\n        if isinstance(item, int):\n            if item >= len(self):\n                raise KeyError(f\"指定的下标 {item} 在长度为 {len(self)} 的 {self} 中越界\")\n            return list(self._components_dict.values())[item]\n        if item not in self._components_dict.keys():\n            raise KeyError(f\"{self} 中不存在 {item}\")\n        return self._components_dict[item]\n\n    def __iter__(self):\n        for val in self._components_dict.values():\n            yield val\n\n    def keys(self):\n        return list(self._components_dict.keys())\n\n    def idx(self, item):\n        for idx, val in enumerate(self.keys()):\n            if val == item:\n                return idx\n        raise KeyError(f\"{item} is not in {self}\")\n\n    @property\n    def components_dict(self):\n        return self._components_dict\n\n    @property\n    def name(self):\n        return self._name\n\n    def _add_single_component(self, component):\n        # Currently only support class or function type\n        if not (inspect.isclass(component) or inspect.isfunction(component)):\n            raise TypeError(\n                \"Expect class/function type, but received {}\".format(type(component))\n            )\n\n        # Obtain the internal name of the component\n        component_name = component.__name__\n\n        # Check whether the component was added already\n        if component_name in self._components_dict.keys():\n            raise KeyError(\"{} exists already!\".format(component_name))\n        else:\n            # Take the internal name of the component as its key\n            self._components_dict[component_name] = component\n\n    def add_component(self, components):\n        # Check whether the type is a sequence\n        if isinstance(components, Sequence):\n            for component in components:\n                self._add_single_component(component)\n        else:\n            component = components\n            self._add_single_component(component)\n\n        return components\n\n\nMODELS = ComponentManager(\"models\")\nACTIONS = ComponentManager(\"actions\")\n"
  },
  {
    "path": "eiseg/util/misc.py",
    "content": "import paddle\nimport numpy as np\nimport pickle\n\n\ndef get_dims_with_exclusion(dim, exclude=None):\n    dims = list(range(dim))\n    if exclude is not None:\n        dims.remove(exclude)\n\n    return dims\n\n\ndef save_checkpoint(\n    net, checkpoints_path, epoch=None, prefix=\"\", verbose=True, multi_gpu=False\n):\n    if epoch is None:\n        checkpoint_name = \"last_checkpoint.pdparams\"\n    else:\n        checkpoint_name = f\"{epoch:03d}.pdparams\"\n\n    if prefix:\n        checkpoint_name = f\"{prefix}_{checkpoint_name}\"\n\n    if not checkpoints_path.exists():\n        checkpoints_path.mkdir(parents=True)\n\n    checkpoint_path = checkpoints_path / checkpoint_name\n\n    net = net.module if multi_gpu else net\n\n    # model_state = {'state_dict': net.state_dict(),'config': net.__dict__}\n    paddle.save(net.state_dict(), checkpoint_path)\n\n\ndef get_bbox_from_mask(mask):\n    rows = np.any(mask, axis=1)\n    cols = np.any(mask, axis=0)\n    rmin, rmax = np.where(rows)[0][[0, -1]]\n    cmin, cmax = np.where(cols)[0][[0, -1]]\n\n    return rmin, rmax, cmin, cmax\n\n\ndef expand_bbox(bbox, expand_ratio, min_crop_size=None):\n    rmin, rmax, cmin, cmax = bbox\n    rcenter = 0.5 * (rmin + rmax)\n    ccenter = 0.5 * (cmin + cmax)\n    height = expand_ratio * (rmax - rmin + 1)\n    width = expand_ratio * (cmax - cmin + 1)\n    if min_crop_size is not None:\n        height = max(height, min_crop_size)\n        width = max(width, min_crop_size)\n\n    rmin = int(round(rcenter - 0.5 * height))\n    rmax = int(round(rcenter + 0.5 * height))\n    cmin = int(round(ccenter - 0.5 * width))\n    cmax = int(round(ccenter + 0.5 * width))\n\n    return rmin, rmax, cmin, cmax\n\n\ndef clamp_bbox(bbox, rmin, rmax, cmin, cmax):\n    return (\n        max(rmin, bbox[0]),\n        min(rmax, bbox[1]),\n        max(cmin, bbox[2]),\n        min(cmax, bbox[3]),\n    )\n\n\ndef get_bbox_iou(b1, b2):\n    h_iou = get_segments_iou(b1[:2], b2[:2])\n    w_iou = get_segments_iou(b1[2:4], b2[2:4])\n    return h_iou * w_iou\n\n\ndef get_segments_iou(s1, s2):\n    a, b = s1\n    c, d = s2\n    intersection = max(0, min(b, d) - max(a, c) + 1)\n    union = max(1e-6, max(b, d) - min(a, c) + 1)\n    return intersection / union\n\n\ndef get_labels_with_sizes(x):\n    obj_sizes = np.bincount(x.flatten())\n    labels = np.nonzero(obj_sizes)[0].tolist()\n    labels = [x for x in labels if x != 0]\n    return labels, obj_sizes[labels].tolist()\n"
  },
  {
    "path": "eiseg/util/opath.py",
    "content": "import re\r\n\r\n\r\n# 检查中文\r\ndef check_cn(path):\r\n    zh_model = re.compile(u'[\\u4e00-\\u9fa5]')\r\n    return zh_model.search(path)\r\n\r\n\r\n# 替换斜杠\r\ndef normcase(path):\r\n    return eval(repr(path).replace('\\\\\\\\', '/'))  "
  },
  {
    "path": "eiseg/util/polygon.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\nfrom enum import Enum\n\nimport cv2\nimport numpy as np\nimport math\nfrom .regularization import boundary_regularization\n\n\nclass Instructions(Enum):\n    No_Instruction = 0\n    Polygon_Instruction = 1\n\n\ndef get_polygon(label, sample=\"Dynamic\", img_size=None, building=False):\n    results = cv2.findContours(\n        image=label, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_TC89_KCOS\n    )  # 获取内外边界，用RETR_TREE更好表示\n    cv2_v = cv2.__version__.split(\".\")[0]\n    contours = results[1] if cv2_v == \"3\" else results[0]  # 边界\n    hierarchys = results[2] if cv2_v == \"3\" else results[1]  # 隶属信息\n    if len(contours) != 0:  # 可能出现没有边界的情况\n        polygons = []\n        relas = []\n        img_shape = label.shape\n        for idx, (contour, hierarchy) in enumerate(zip(contours, hierarchys[0])):\n            # print(hierarchy)\n            # opencv实现边界简化\n            epsilon = (\n                0.005 * cv2.arcLength(contour, True) if sample == \"Dynamic\" else sample\n            )\n            if not isinstance(epsilon, float) and not isinstance(epsilon, int):\n                epsilon = 0\n            # print(\"epsilon:\", epsilon)\n            if building is False:\n                # -- Douglas-Peucker算法边界简化\n                contour = cv2.approxPolyDP(contour, epsilon / 10, True)\n            else:\n                # -- 建筑边界简化（https://github.com/niecongchong/RS-building-regularization）\n                contour = boundary_regularization(contour, img_shape, epsilon)\n            # -- 自定义（角度和距离）边界简化\n            out = approx_poly_DIY(contour)\n            # 给出关系\n            rela = (\n                idx,  # own\n                hierarchy[-1] if hierarchy[-1] != -1 else None,\n            )  # parent\n            polygon = []\n            for p in out:\n                polygon.append(p[0])\n            polygons.append(polygon)  # 边界\n            relas.append(rela)  # 关系\n        for i in range(len(relas)):\n            if relas[i][1] != None:  # 有父圈\n                for j in range(len(relas)):\n                    if relas[j][0] == relas[i][1]:  # i的父圈就是j（i是j的子圈）\n                        if polygons[i] is not None and polygons[j] is not None:\n                            min_i, min_o = __find_min_point(polygons[i], polygons[j])\n                            # 改变顺序\n                            polygons[i] = __change_list(polygons[i], min_i)\n                            polygons[j] = __change_list(polygons[j], min_o)\n                            # 连接\n                            if min_i != -1 and len(polygons[i]) > 0:\n                                polygons[j].extend(polygons[i])  # 连接内圈\n                            polygons[i] = None\n        polygons = list(filter(None, polygons))  # 清除加到外圈的内圈多边形\n        if img_size is not None:\n            polygons = check_size_minmax(polygons, img_size)\n        return polygons\n    else:\n        print(\"没有标签范围，无法生成边界\")\n        return None\n\n\ndef __change_list(polygons, idx):\n    if idx == -1:\n        return polygons\n    s_p = polygons[:idx]\n    polygons = polygons[idx:]\n    polygons.extend(s_p)\n    polygons.append(polygons[0])  # 闭合圈\n    return polygons\n\n\ndef __find_min_point(i_list, o_list):\n    min_dis = 1e7\n    idx_i = -1\n    idx_o = -1\n    for i in range(len(i_list)):\n        for o in range(len(o_list)):\n            dis = math.sqrt(\n                (i_list[i][0] - o_list[o][0]) ** 2 + (i_list[i][1] - o_list[o][1]) ** 2\n            )\n            if dis <= min_dis:\n                min_dis = dis\n                idx_i = i\n                idx_o = o\n    return idx_i, idx_o\n\n\n# 根据三点坐标计算夹角\ndef __cal_ang(p1, p2, p3):\n    eps = 1e-12\n    a = math.sqrt((p2[0] - p3[0]) * (p2[0] - p3[0]) + (p2[1] - p3[1]) * (p2[1] - p3[1]))\n    b = math.sqrt((p1[0] - p3[0]) * (p1[0] - p3[0]) + (p1[1] - p3[1]) * (p1[1] - p3[1]))\n    c = math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]))\n    ang = math.degrees(\n        math.acos((b ** 2 - a ** 2 - c ** 2) / (-2 * a * c + eps))\n    )  # p2对应\n    return ang\n\n\n# 计算两点距离\ndef __cal_dist(p1, p2):\n    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)\n\n\n# 边界点简化\ndef approx_poly_DIY(contour, min_dist=10, ang_err=5):\n    # print(contour.shape)  # N, 1, 2\n    cs = [contour[i][0] for i in range(contour.shape[0])]\n    ## 1. 先删除两个相近点与前后两个点角度接近的点\n    i = 0\n    while i < len(cs):\n        try:\n            j = (i + 1) if (i != len(cs) - 1) else 0\n            if __cal_dist(cs[i], cs[j]) < min_dist:\n                last = (i - 1) if (i != 0) else (len(cs) - 1)\n                next = (j + 1) if (j != len(cs) - 1) else 0\n                ang_i = __cal_ang(cs[last], cs[i], cs[next])\n                ang_j = __cal_ang(cs[last], cs[j], cs[next])\n                # print(ang_i, ang_j)  # 角度值为-180到+180\n                if abs(ang_i - ang_j) < ang_err:\n                    # 删除距离两点小的\n                    dist_i = __cal_dist(cs[last], cs[i]) + __cal_dist(cs[i], cs[next])\n                    dist_j = __cal_dist(cs[last], cs[j]) + __cal_dist(cs[j], cs[next])\n                    if dist_j < dist_i:\n                        del cs[j]\n                    else:\n                        del cs[i]\n                else:\n                    i += 1\n            else:\n                i += 1\n        except:\n            i += 1\n    ## 2. 再删除夹角接近180度的点\n    i = 0\n    while i < len(cs):\n        try:\n            last = (i - 1) if (i != 0) else (len(cs) - 1)\n            next = (i + 1) if (i != len(cs) - 1) else 0\n            ang_i = __cal_ang(cs[last], cs[i], cs[next])\n            if abs(ang_i) > (180 - ang_err):\n                del cs[i]\n            else:\n                i += 1\n        except:\n            # i += 1\n            del cs[i]\n    res = np.array(cs).reshape([-1, 1, 2])\n    return res\n\n\ndef check_size_minmax(polygons, img_size):\n    h_max, w_max = img_size\n    for ps in polygons:\n        for j in range(len(ps)):\n            x, y = ps[j]\n            if x < 0:\n                x = 0\n            elif x > w_max:\n                x = w_max\n            if y < 0:\n                y = 0\n            elif y > h_max:\n                y = h_max\n            ps[j] = np.array([x, y])\n    return polygons"
  },
  {
    "path": "eiseg/util/qt.py",
    "content": "from math import sqrt\nimport os.path as osp\n\nimport numpy as np\n\nfrom eiseg import pjpath\nfrom qtpy import QtCore\nfrom qtpy import QtGui\nfrom qtpy import QtWidgets\nfrom .config import parse_configs\n\nshortcuts = parse_configs(osp.join(pjpath, \"config/config.yaml\"))[\"shortcut\"]\nhere = osp.dirname(osp.abspath(__file__))\n\n\ndef newIcon(icon):\n    if isinstance(icon, list) or isinstance(icon, tuple):\n        pixmap = QtGui.QPixmap(100, 100)\n        c = icon\n        pixmap.fill(QtGui.QColor(c[0], c[1], c[2]))\n        return QtGui.QIcon(pixmap)\n    icons_dir = osp.join(here, \"../resource\")\n    return QtGui.QIcon(osp.join(\":/\", icons_dir, f\"{icon}.png\"))\n\n\ndef newButton(text, icon=None, slot=None):\n    b = QtWidgets.QPushButton(text)\n    if icon is not None:\n        b.setIcon(newIcon(icon))\n    if slot is not None:\n        b.clicked.connect(slot)\n    return b\n\n\ndef newAction(\n    parent,\n    text,\n    slot=None,\n    shortcutName=None,\n    icon=None,\n    tip=None,\n    checkable=False,\n    enabled=True,\n    checked=False,\n):\n    \"\"\"Create a new action and assign callbacks, shortcuts, etc.\"\"\"\n    a = QtWidgets.QAction(text, parent)\n    a.setData(shortcutName)\n    # a = QtWidgets.QAction(\"\", parent)\n    if icon is not None:\n        a.setIconText(text.replace(\" \", \"\\n\"))\n        a.setIcon(newIcon(icon))\n    shortcut = shortcuts.get(shortcutName, None)\n    if shortcut is not None:\n        if isinstance(shortcut, (list, tuple)):\n            a.setShortcuts(shortcut)\n        else:\n            a.setShortcut(shortcut)\n    if tip is not None:\n        a.setToolTip(tip)\n        a.setStatusTip(tip)\n    if slot is not None:\n        a.triggered.connect(slot)\n    if checkable:\n        a.setCheckable(True)\n    a.setEnabled(enabled)\n    a.setChecked(checked)\n    return a\n\n\ndef addActions(widget, actions):\n    for action in actions:\n        if action is None:\n            widget.addSeparator()\n        elif isinstance(action, QtWidgets.QMenu):\n            widget.addMenu(action)\n        else:\n            widget.addAction(action)\n\n\ndef labelValidator():\n    return QtGui.QRegExpValidator(QtCore.QRegExp(r\"^[^ \\t].+\"), None)\n\n\nclass struct(object):\n    def __init__(self, **kwargs):\n        self.__dict__.update(kwargs)\n\n    def __len__(self):\n        return len(self.__dict__)\n\n    def append(self, action):\n        if isinstance(action, QtWidgets.QAction):\n            self.__dict__.update({action.data(): action})\n\n    def __iter__(self):\n        return list(self.__dict__.values()).__iter__()\n\n    def __getitem__(self, idx):\n        return list(self.__dict__.values())[idx]\n\n    def get(self, name):\n        return self.__dict__[name]\n\n\ndef fmtShortcut(text):\n    mod, key = text.split(\"+\", 1)\n    return \"<b>%s</b>+<b>%s</b>\" % (mod, key)\n"
  },
  {
    "path": "eiseg/util/regularization/__init__.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\"\"\"\r\nThis code is based on https://github.com/niecongchong/RS-building-regularization\r\nThs copyright of niecongchong/RS-building-regularization is as follows:\r\nApache License [see LICENSE for details]\r\n\"\"\"\r\n\r\n\r\nfrom .rs_regularization import boundary_regularization"
  },
  {
    "path": "eiseg/util/regularization/cal_line.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/niecongchong/RS-building-regularization\nThs copyright of niecongchong/RS-building-regularization is as follows:\nApache License [see LICENSE for details]\n\"\"\"\n\n\nimport numpy as np\n\n\n# 线生成函数\ndef line(p1, p2):\n    A = (p1[1] - p2[1])\n    B = (p2[0] - p1[0])\n    C = (p1[0] * p2[1] - p2[0] * p1[1])\n    return A, B, -C\n\n\n# 计算两条直线之间的交点\ndef intersection(L1, L2):\n    D  = L1[0] * L2[1] - L1[1] * L2[0]\n    Dx = L1[2] * L2[1] - L1[1] * L2[2]\n    Dy = L1[0] * L2[2] - L1[2] * L2[0]\n    if D != 0:\n        x = Dx / D\n        y = Dy / D\n        return x, y\n    else:\n        return False\n\n\n# 计算两个平行线之间的距离\ndef par_line_dist(L1, L2):\n    A1, B1, C1 = L1\n    A2, B2, C2 = L2\n    new_A1 = 1\n    new_B1 = B1 / A1\n    new_C1 = C1 / A1\n    new_A2 = 1\n    new_B2 = B2 / A2\n    new_C2 = C2 / A2\n    dist = (np.abs(new_C1 - new_C2)) / (np.sqrt(new_A2**2 + new_B2**2))\n    return dist\n\n\n# 计算点在直线的投影位置\ndef point_in_line(m, n, x1, y1, x2, y2):\n    x = (m * (x2 - x1) * (x2 - x1) + n * (y2 - y1) * (x2 - x1) + \n        (x1 * y2 - x2 * y1) * (y2 - y1)) / ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))\n    y = (m * (x2 - x1) * (y2 - y1) + n * (y2 - y1) * (y2 - y1) + \n        (x2 * y1 - x1 * y2) * (x2 - x1)) / ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))\n    return (x, y)\n\n\n\n"
  },
  {
    "path": "eiseg/util/regularization/cal_point.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/niecongchong/RS-building-regularization\nThs copyright of niecongchong/RS-building-regularization is as follows:\nApache License [see LICENSE for details]\n\"\"\"\n\n\nimport numpy as np\nimport math\n\n\n# 计算两点距离\ndef cal_dist(point_1, point_2):\n    dist = np.sqrt(np.sum(np.power((point_1-point_2), 2)))\n    return dist\n\n\n# 计算两条线的夹角\ndef cal_ang(point_1, point_2, point_3):\n\n    def _cal_pp(p_1, p_2):\n        return math.sqrt((p_1[0] - p_2[0]) * (p_1[0] - p_2[0]) + \n                         (p_1[1] - p_2[1]) * (p_1[1] - p_2[1]))\n\n    a = _cal_pp(point_2, point_3)\n    b = _cal_pp(point_1, point_3)\n    c = _cal_pp(point_1, point_2)\n    B = math.degrees(math.acos((b**2 - a**2 - c**2) / (-2 * a * c)))\n    return B\n\n\n# 计算线条的方位角\ndef cal_azimuth(point_0, point_1):\n    x1, y1 = point_0\n    x2, y2 = point_1\n    if x1 < x2:\n        if y1 < y2:\n            ang = math.atan((y2 - y1) / (x2 - x1))\n            ang = ang * 180 / math.pi\n            return ang\n        elif y1 > y2:\n            ang = math.atan((y1 - y2) / (x2 - x1))\n            ang = ang * 180 / math.pi\n            return 90 + (90 - ang)\n        elif y1==y2:\n            return 0\n    elif x1 > x2:\n        if y1 < y2:\n            ang = math.atan((y2-y1)/(x1-x2))\n            ang = ang*180/math.pi\n            return 90+(90-ang)\n        elif y1 > y2:\n            ang = math.atan((y1-y2)/(x1-x2))\n            ang = ang * 180 / math.pi\n            return ang\n        elif y1==y2:\n            return 0\n    elif x1==x2:\n        return 90"
  },
  {
    "path": "eiseg/util/regularization/rdp_alg.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/niecongchong/RS-building-regularization\nThs copyright of niecongchong/RS-building-regularization is as follows:\nApache License [see LICENSE for details]\n\"\"\"\n\n\n\"\"\"\nrdp\n~~~\nPure Python implementation of the Ramer-Douglas-Peucker algorithm.\n:copyright: (c) 2014 Fabian Hirschmann <fabian@hirschmann.email>\n:license: MIT, see LICENSE.txt for more details.\n\"\"\"\n\n\nimport numpy as np\n\n\ndef pldist(x0, x1, x2):\n    \"\"\"\n    Calculates the distance from the point ``x0`` to the line given\n    by the points ``x1`` and ``x2``.\n    :param x0: a point\n    :type x0: a 2x1 numpy array\n    :param x1: a point of the line\n    :type x1: 2x1 numpy array\n    :param x2: another point of the line\n    :type x2: 2x1 numpy array\n    \"\"\"\n    x0, x1, x2 = x0[:2], x1[:2], x2[:2]  # discard timestamp\n    if x1[0] == x2[0]:\n        return np.abs(x0[0] - x1[0])\n    return np.divide(np.linalg.norm(np.linalg.det([x2 - x1, x1 - x0])),\n                     np.linalg.norm(x2 - x1))\n\n\ndef _rdp(M, epsilon, dist):\n    \"\"\"\n    Simplifies a given array of points.\n    :param M: an array\n    :type M: Nx2 numpy array\n    :param epsilon: epsilon in the rdp algorithm\n    :type epsilon: float\n    :param dist: distance function\n    :type dist: function with signature ``f(x1, x2, x3)``\n    \"\"\"\n    dmax = 0.0\n    index = -1\n    for i in range(1, M.shape[0]):\n        d = dist(M[i], M[0], M[-1])\n        if d > dmax:\n            index = i\n            dmax = d\n    if dmax > epsilon:\n        r1 = _rdp(M[:index + 1], epsilon, dist)\n        r2 = _rdp(M[index:], epsilon, dist)\n        return np.vstack((r1[:-1], r2))\n    else:\n        return np.vstack((M[0], M[-1]))\n\n\ndef _rdp_nn(seq, epsilon, dist):\n    \"\"\"\n    Simplifies a given array of points.\n    :param seq: a series of points\n    :type seq: sequence of 2-tuples\n    :param epsilon: epsilon in the rdp algorithm\n    :type epsilon: float\n    :param dist: distance function\n    :type dist: function with signature ``f(x1, x2, x3)``\n    \"\"\"\n    return _rdp(np.array(seq), epsilon, dist).tolist()\n\n\ndef rdp(M, epsilon=0, dist=pldist):\n    \"\"\"\n    Simplifies a given array of points.\n    :param M: a series of points\n    :type M: either a Nx2 numpy array or sequence of 2-tuples\n    :param epsilon: epsilon in the rdp algorithm\n    :type epsilon: float\n    :param dist: distance function\n    :type dist: function with signature ``f(x1, x2, x3)``\n    \"\"\"\n    if \"numpy\" in str(type(M)):\n        return _rdp(M, epsilon, dist)\n    return _rdp_nn(M, epsilon, dist)"
  },
  {
    "path": "eiseg/util/regularization/rotate_ang.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/niecongchong/RS-building-regularization\nThs copyright of niecongchong/RS-building-regularization is as follows:\nApache License [see LICENSE for details]\n\"\"\"\n\n\nimport math\n\n\n# 顺时针旋转\ndef Nrotation_angle_get_coor_coordinates(point, center, angle):\n    src_x, src_y = point\n    center_x, center_y = center\n    radian = math.radians(angle)\n    dest_x = (src_x - center_x) * math.cos(radian) + \\\n             (src_y - center_y) * math.sin(radian) + center_x\n    dest_y = (src_y - center_y) * math.cos(radian) - \\\n             (src_x - center_x) * math.sin(radian) + center_y\n    # return (int(dest_x), int(dest_y))\n    return (dest_x, dest_y)\n\n\n# 逆时针旋转\ndef Srotation_angle_get_coor_coordinates(point, center, angle):\n    src_x, src_y = point\n    center_x, center_y = center\n    radian = math.radians(angle)\n    dest_x = (src_x - center_x) * math.cos(radian) - \\\n             (src_y - center_y) * math.sin(radian) + center_x\n    dest_y = (src_x - center_x) * math.sin(radian) + \\\n             (src_y - center_y) * math.cos(radian) + center_y\n    # return [int(dest_x), int(dest_y)]\n    return (dest_x, dest_y)"
  },
  {
    "path": "eiseg/util/regularization/rs_regularization.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\"\"\"\nThis code is based on https://github.com/niecongchong/RS-building-regularization\nThs copyright of niecongchong/RS-building-regularization is as follows:\nApache License [see LICENSE for details]\n\"\"\"\n\n\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom .rdp_alg import rdp\nfrom .cal_point import cal_ang, cal_dist, cal_azimuth\nfrom .rotate_ang import Nrotation_angle_get_coor_coordinates, Srotation_angle_get_coor_coordinates\nfrom .cal_line import line, intersection, par_line_dist, point_in_line\n\n\ndef boundary_regularization(contours, img_shape, epsilon=6):\n    h, w = img_shape[0:2]\n    # 轮廓定位\n    contours = np.squeeze(contours)\n    # 轮廓精简DP\n    contours = rdp(contours, epsilon=epsilon)\n    contours[:, 1] = h - contours[:, 1]\n    # 轮廓规则化\n    dists = []\n    azis = []\n    azis_index = []\n    # 获取每条边的长度和方位角\n    for i in range(contours.shape[0]):\n        cur_index = i\n        next_index = i + 1 if i < contours.shape[0] - 1 else 0\n        prev_index = i - 1\n        cur_point = contours[cur_index]\n        nest_point = contours[next_index]\n        prev_point = contours[prev_index]\n        dist = cal_dist(cur_point, nest_point)\n        azi = cal_azimuth(cur_point, nest_point)\n        dists.append(dist)\n        azis.append(azi)\n        azis_index.append([cur_index, next_index])\n    # 以最长的边的方向作为主方向\n    longest_edge_idex = np.argmax(dists)\n    main_direction = azis[longest_edge_idex]\n    # 方向纠正，绕中心点旋转到与主方向垂直或者平行\n    correct_points = []\n    para_vetr_idxs = []  # 0平行 1垂直\n    for i, (azi, (point_0_index, point_1_index)) in enumerate(zip(azis, azis_index)):\n        if i == longest_edge_idex:\n            correct_points.append([contours[point_0_index], contours[point_1_index]])\n            para_vetr_idxs.append(0)\n        else:\n            # 确定旋转角度\n            rotate_ang = main_direction - azi\n            if np.abs(rotate_ang) < 180 / 4:\n                rotate_ang = rotate_ang\n                para_vetr_idxs.append(0)\n            elif np.abs(rotate_ang) >= 90 - 180 / 4:\n                rotate_ang = rotate_ang + 90\n                para_vetr_idxs.append(1)\n            # 执行旋转任务\n            point_0 = contours[point_0_index]\n            point_1 = contours[point_1_index]\n            point_middle = (point_0 + point_1) / 2\n            if rotate_ang > 0:\n                rotate_point_0 = Srotation_angle_get_coor_coordinates(\n                    point_0, point_middle, np.abs(rotate_ang))\n                rotate_point_1 = Srotation_angle_get_coor_coordinates(\n                    point_1, point_middle, np.abs(rotate_ang))\n            elif rotate_ang < 0:\n                rotate_point_0 = Nrotation_angle_get_coor_coordinates(\n                    point_0, point_middle, np.abs(rotate_ang))\n                rotate_point_1 = Nrotation_angle_get_coor_coordinates(\n                    point_1, point_middle, np.abs(rotate_ang))\n            else:\n                rotate_point_0 = point_0\n                rotate_point_1 = point_1\n            correct_points.append([rotate_point_0, rotate_point_1])\n    correct_points = np.array(correct_points)\n    # 相邻边校正，垂直取交点，平行平移短边或者加线\n    final_points = []\n    final_points.append(correct_points[0][0])\n    for i in range(correct_points.shape[0] - 1):\n        cur_index = i\n        next_index = i + 1 if i < correct_points.shape[0] - 1 else 0\n        cur_edge_point_0 = correct_points[cur_index][0]\n        cur_edge_point_1 = correct_points[cur_index][1]\n        next_edge_point_0 = correct_points[next_index][0]\n        next_edge_point_1 = correct_points[next_index][1]\n        cur_para_vetr_idx = para_vetr_idxs[cur_index]\n        next_para_vetr_idx = para_vetr_idxs[next_index]\n        if cur_para_vetr_idx != next_para_vetr_idx:\n            # 垂直取交点\n            L1 = line(cur_edge_point_0, cur_edge_point_1)\n            L2 = line(next_edge_point_0, next_edge_point_1)\n            point_intersection = intersection(L1, L2)\n            final_points.append(point_intersection)\n        elif cur_para_vetr_idx == next_para_vetr_idx:\n            # 平行分两种，一种加短线，一种平移，取决于距离阈值\n            L1 = line(cur_edge_point_0, cur_edge_point_1)\n            L2 = line(next_edge_point_0, next_edge_point_1)\n            marg = par_line_dist(L1, L2)\n            if marg < 3:\n                # 平移\n                point_move = point_in_line(next_edge_point_0[0], next_edge_point_0[1], \n                                           cur_edge_point_0[0], cur_edge_point_0[1], \n                                           cur_edge_point_1[0], cur_edge_point_1[1])\n                final_points.append(point_move)\n                # 更新平移之后的下一条边\n                correct_points[next_index][0] = point_move\n                correct_points[next_index][1] = point_in_line(\n                    next_edge_point_1[0], next_edge_point_1[1], \n                    cur_edge_point_0[0], cur_edge_point_0[1], \n                    cur_edge_point_1[0], cur_edge_point_1[1])\n            else:\n                # 加线\n                add_mid_point = (cur_edge_point_1 + next_edge_point_0) / 2\n                add_point_1 = point_in_line(add_mid_point[0], add_mid_point[1], \n                                            cur_edge_point_0[0], cur_edge_point_0[1], \n                                            cur_edge_point_1[0], cur_edge_point_1[1])\n                add_point_2 = point_in_line(add_mid_point[0], add_mid_point[1], \n                                            next_edge_point_0[0], next_edge_point_0[1], \n                                            next_edge_point_1[0], next_edge_point_1[1])\n                final_points.append(add_point_1)\n                final_points.append(add_point_2)\n    final_points.append(final_points[0])\n    final_points = np.array(final_points)\n    final_points[:, 1] = h - final_points[:, 1]\n    final_points = final_points[np.newaxis, :].transpose((1, 0, 2))\n    return final_points\n\n\n# def rs_build_re(mask):\n#     # 中值滤波，去噪\n#     ori_img = cv2.medianBlur(mask, 5)\n#     ori_img = cv2.cvtColor(ori_img, cv2.COLOR_BGR2GRAY)\n#     ret, ori_img = cv2.threshold(ori_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)\n#     # 连通域分析\n#     num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(ori_img, connectivity=8)\n#     # 遍历联通域\n#     for i in range(1, num_labels):\n#         img = np.zeros_like(labels)\n#         index = np.where(labels==i)\n#         img[index] = 255\n#         img = np.array(img, dtype=np.uint8)\n#         regularization_contour = boundary_regularization(img).astype(np.int32)\n#         cv2.polylines(img=mask, pts=[regularization_contour], isClosed=True, color=(255, 0, 0), thickness=5)\n#         single_out = np.zeros_like(mask)\n#         cv2.polylines(img=single_out, pts=[regularization_contour], isClosed=True, color=(255, 0, 0), thickness=5)\n#         cv2.imwrite('single_out_{}.jpg'.format(i), single_out)"
  },
  {
    "path": "eiseg/util/serialization.py",
    "content": "from functools import wraps\nfrom copy import deepcopy\nimport inspect\nimport paddle.nn as nn\n\n\ndef serialize(init):\n    parameters = list(inspect.signature(init).parameters)\n\n    @wraps(init)\n    def new_init(self, *args, **kwargs):\n        params = deepcopy(kwargs)\n        for pname, value in zip(parameters[1:], args):\n            params[pname] = value\n\n        config = {\"class\": get_classname(self.__class__), \"params\": dict()}\n        specified_params = set(params.keys())\n\n        for pname, param in get_default_params(self.__class__).items():\n            if pname not in params:\n                params[pname] = param.default\n\n        for name, value in list(params.items()):\n            param_type = \"builtin\"\n            if inspect.isclass(value):\n                param_type = \"class\"\n                value = get_classname(value)\n\n            config[\"params\"][name] = {\n                \"type\": param_type,\n                \"value\": value,\n                \"specified\": name in specified_params,\n            }\n\n        setattr(self, \"_config\", config)\n        init(self, *args, **kwargs)\n\n    return new_init\n\n\ndef load_model(config, **kwargs):\n    model_class = get_class_from_str(config[\"class\"])\n    model_default_params = get_default_params(model_class)\n\n    model_args = dict()\n    for pname, param in config[\"params\"].items():\n        value = param[\"value\"]\n        if param[\"type\"] == \"class\":\n            value = get_class_from_str(value)\n\n        if pname not in model_default_params and not param[\"specified\"]:\n            continue\n\n        assert pname in model_default_params\n        if not param[\"specified\"] and model_default_params[pname].default == value:\n            continue\n        model_args[pname] = value\n\n    model_args.update(kwargs)\n\n    return model_class(**model_args)\n\n\ndef get_config_repr(config):\n    config_str = f'Model: {config[\"class\"]}\\n'\n    for pname, param in config[\"params\"].items():\n        value = param[\"value\"]\n        if param[\"type\"] == \"class\":\n            value = value.split(\".\")[-1]\n        param_str = f\"{pname:<22} = {str(value):<12}\"\n        if not param[\"specified\"]:\n            param_str += \" (default)\"\n        config_str += param_str + \"\\n\"\n    return config_str\n\n\ndef get_default_params(some_class):\n    params = dict()\n    for mclass in some_class.mro():\n        if mclass is nn.Layer or mclass is object:\n            continue\n\n        mclass_params = inspect.signature(mclass.__init__).parameters\n        for pname, param in mclass_params.items():\n            if param.default != param.empty and pname not in params:\n                params[pname] = param\n\n    return params\n\n\ndef get_classname(cls):\n    module = cls.__module__\n    name = cls.__qualname__\n    if module is not None and module != \"__builtin__\":\n        name = module + \".\" + name\n    return name\n\n\ndef get_class_from_str(class_str):\n    components = class_str.split(\".\")\n    mod = __import__(\".\".join(components[:-1]))\n    for comp in components[1:]:\n        mod = getattr(mod, comp)\n    return mod\n"
  },
  {
    "path": "eiseg/util/vis.py",
    "content": "from functools import lru_cache\n\nimport cv2\nimport numpy as np\n\n\ndef visualize_instances(\n    imask, bg_color=255, boundaries_color=None, boundaries_width=1, boundaries_alpha=0.8\n):\n    num_objects = imask.max() + 1\n    palette = get_palette(num_objects)\n    if bg_color is not None:\n        palette[0] = bg_color\n\n    result = palette[imask].astype(np.uint8)\n    if boundaries_color is not None:\n        boundaries_mask = get_boundaries(imask, boundaries_width=boundaries_width)\n        tresult = result.astype(np.float32)\n        tresult[boundaries_mask] = boundaries_color\n        tresult = tresult * boundaries_alpha + (1 - boundaries_alpha) * result\n        result = tresult.astype(np.uint8)\n\n    return result\n\n\n@lru_cache(maxsize=16)\ndef get_palette(num_cls):\n    return np.array([[0, 0, 0], [128, 0, 0], [0, 128, 0], [0, 0, 128]])\n\n\ndef visualize_mask(mask, num_cls):\n    palette = get_palette(num_cls)\n    mask[mask == -1] = 0\n\n    return palette[mask].astype(np.uint8)\n\n\ndef visualize_proposals(proposals_info, point_color=(255, 0, 0), point_radius=1):\n    proposal_map, colors, candidates = proposals_info\n\n    proposal_map = draw_probmap(proposal_map)\n    for x, y in candidates:\n        proposal_map = cv2.circle(proposal_map, (y, x), point_radius, point_color, -1)\n\n    return proposal_map\n\n\ndef draw_probmap(x):\n    return cv2.applyColorMap((x * 255).astype(np.uint8), cv2.COLORMAP_HOT)\n\n\ndef draw_points(image, points, color, radius=3):\n    image = image.copy()\n    for p in points:\n        image = cv2.circle(image, (int(p[1]), int(p[0])), radius, color, -1)\n\n    return image\n\n\ndef draw_instance_map(x, palette=None):\n    num_colors = x.max() + 1\n    if palette is None:\n        palette = get_palette(num_colors)\n\n    return palette[x].astype(np.uint8)\n\n\ndef blend_mask(image, mask, alpha=0.6):\n    if mask.min() == -1:\n        mask = mask.copy() + 1\n\n    imap = draw_instance_map(mask)\n    result = (image * (1 - alpha) + alpha * imap).astype(np.uint8)\n    return result\n\n\ndef get_boundaries(instances_masks, boundaries_width=1):\n    boundaries = np.zeros(\n        (instances_masks.shape[0], instances_masks.shape[1]), dtype=np.bool\n    )\n\n    for obj_id in np.unique(instances_masks.flatten()):\n        if obj_id == 0:\n            continue\n\n        obj_mask = instances_masks == obj_id\n        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))\n        inner_mask = cv2.erode(\n            obj_mask.astype(np.uint8), kernel, iterations=boundaries_width\n        ).astype(np.bool)\n\n        obj_boundary = np.logical_xor(obj_mask, np.logical_and(inner_mask, obj_mask))\n        boundaries = np.logical_or(boundaries, obj_boundary)\n    return boundaries\n\n\ndef draw_with_blend_and_clicks(\n    img,\n    mask=None,\n    alpha=0.6,\n    clicks_list=None,\n    pos_color=(0, 255, 0),\n    neg_color=(255, 0, 0),\n    radius=4,\n    palette=None,\n):\n    result = img.copy()\n\n    if mask is not None:\n        if not palette:\n            palette = get_palette(np.max(mask) + 1)\n        palette = np.array(palette)\n        rgb_mask = palette[mask.astype(np.uint8)]\n\n        mask_region = (mask > 0).astype(np.uint8)\n        result = (\n            result * (1 - mask_region[:, :, np.newaxis])\n            + (1 - alpha) * mask_region[:, :, np.newaxis] * result\n            + alpha * rgb_mask\n        )\n        result = result.astype(np.uint8)\n\n    if clicks_list is not None and len(clicks_list) > 0:\n        pos_points = [click.coords for click in clicks_list if click.is_positive]\n        neg_points = [click.coords for click in clicks_list if not click.is_positive]\n\n        result = draw_points(result, pos_points, pos_color, radius=radius)\n        result = draw_points(result, neg_points, neg_color, radius=radius)\n\n    return result\n"
  },
  {
    "path": "eiseg/widget/__init__.py",
    "content": "from .shortcut import ShortcutWidget\nfrom .loading import LoadingWidget\nfrom .line import LineItem\nfrom .grip import GripItem\nfrom .bbox import BBoxAnnotation\nfrom .polygon import PolygonAnnotation\nfrom .scene import AnnotationScene\nfrom .view import AnnotationView\nfrom .create import (\n    create_text, create_button, create_slider, DockWidget, creat_dock\n)\nfrom .table import TableWidget\n"
  },
  {
    "path": "eiseg/widget/bbox.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom qtpy import QtWidgets, QtGui, QtCore\nfrom qtpy.QtCore import Qt\n\n\n# Note, bbox annotation is more convenient than the default boundingBox generated by QGrpaphicItem\nclass BBoxAnnotation(QtWidgets.QGraphicsPathItem):\n    def __init__(\n        self,\n        labelIndex,\n        polyline,\n        borderColor=[0, 0, 255],\n        cocoIndex=None,\n        parent=None,\n    ):\n        super(BBoxAnnotation, self).__init__(parent)\n        self.polyline = polyline\n        self.corner_points = []\n        self.upper_right = QtCore.QPointF()\n        self.bottom_left = QtCore.QPointF()\n        self.w = -1.0\n        self.h = -1.0\n\n        self.parent = parent\n        self.is_added = False\n        if self.parent is not None:\n            self.is_added = True\n        self.labelIndex = labelIndex\n        self.coco_id = cocoIndex\n        self.bbox_hovering = True\n\n        # set rendering attributes\n        self.setZValue(10)\n\n        # b = borderColor\n        # self.borderColor = QtGui.QColor(b[0], b[1], b[2])\n        self.borderColor = QtGui.QColor(128, 128, 128)\n        self.borderColor.setAlphaF(0.8)\n        pen = QtGui.QPen(self.borderColor, 1.2)\n        pen.setStyle(Qt.DashDotLine)\n        self.setPen(pen)\n\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, False)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)\n        self.setAcceptHoverEvents(False)\n        # self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))\n\n    @property\n    def scnenePoints(self):\n        # return 4 corner points\n        raise Exception(\"Not Implemented Yet!\")\n\n    def setAnning(self, isAnning=True):\n        raise Exception(\"Not Implemented Yet!\")\n\n    def remove(self):\n        raise Exception(\"Not Implemented Yet!\")\n\n    # ===== generate geometry info\n\n    def create_corners(self):\n        bbox_rect_geo = self.polyline.boundingRect()\n        self.bottom_left = bbox_rect_geo.bottomLeft()\n        self.upper_right = bbox_rect_geo.topRight()\n        self.corner_points.clear()\n        self.corner_points.extend(\n            [\n                self.bottom_left,\n                bbox_rect_geo.topLeft(),\n                self.upper_right,\n                bbox_rect_geo.bottomRight(),\n            ]\n        )\n        self.w = self.corner_points[3].x() - self.corner_points[1].x()\n        self.h = self.corner_points[3].y() - self.corner_points[1].y()\n\n        if self.corner_points[1].x() > 512 or self.corner_points[1].x() + self.w > 512:\n            pass\n        if self.corner_points[1].y() > 512 or self.corner_points[1].y() + self.h > 512:\n            pass\n        return self.corner_points\n\n    def create_lines(self):\n        pass\n\n    # ===== graphic interface to update in scene tree\n\n    def update(self):\n        l = len(self.polyline.points)\n        # print(\"up L:\", l, \" is_added:\", self.is_added)\n        if l < 3:\n            if self.is_added:\n                self.remove_from_scene()\n        else:  # 大于三个点就可以更新，小于三个点删除多边形\n            if self.is_added:\n                self.add_to_scene()\n            else:\n                path_geo = QtGui.QPainterPath()\n                self.create_corners()\n                path_geo.moveTo(self.corner_points[0])\n                for i in range(4):\n                    path_geo.lineTo(self.corner_points[(i + 1) % 4])\n                self.setPath(QtGui.QPainterPath(path_geo))\n                pass\n            pass\n        pass\n\n    def add_to_scene(self):\n        # self.parentItem().scene().addItem(self)\n        self.setParentItem(self.parent)\n        self.is_added = True\n\n    def remove_from_scene(self):\n        # self.parentItem().scene().removeItem(self)\n        self.setParentItem(None)\n        self.is_added = False\n\n    # ===== annotation info\n\n    # @return : [x, y, w, h]\n    def to_array(self):\n        np_array = [\n            self._round(self.corner_points[1].x()),\n            self._round(self.corner_points[1].y()),  # topLeft\n            self._round(self.w),\n            self._round(self.h),\n        ]\n        return np_array\n\n    def _round(self, number, ind=0):\n        nint, ndec = str(number).split(\".\")\n        res = float(nint + \".\" + ndec[:ind])\n        if res <= 0:\n            res = 0.0\n        return res\n\n    def __del__(self):\n        self.corner_points.clear()\n"
  },
  {
    "path": "eiseg/widget/create.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nfrom qtpy.QtWidgets import QDockWidget\r\nfrom qtpy import QtCore, QtGui, QtWidgets\r\nfrom qtpy.QtCore import Qt\r\n\r\n\r\n## 创建文本\r\ndef create_text(parent, text_name=None, text_text=None):\r\n    text = QtWidgets.QLabel(parent)\r\n    if text_name is not None:\r\n        text.setObjectName(text_name)\r\n    if text_text is not None:\r\n        text.setText(text_text)\r\n    return text\r\n\r\n\r\n## 创建可编辑文本\r\ndef create_edit(parent, text_name=None, text_text=None):\r\n    edit = QtWidgets.QLineEdit(parent)\r\n    if text_name is not None:\r\n        edit.setObjectName(text_name)\r\n    if text_text is not None:\r\n        edit.setText(text_text)\r\n    edit.setValidator(QtGui.QIntValidator())\r\n    edit.setMaxLength(5)\r\n    return edit\r\n\r\n\r\n## 创建按钮\r\ndef create_button(parent, btn_name, btn_text, ico_path=None, curt=None):\r\n    # 创建和设置按钮\r\n    sizePolicy = QtWidgets.QSizePolicy(\r\n        QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed\r\n    )\r\n    min_size = QtCore.QSize(0, 40)\r\n    sizePolicy.setHorizontalStretch(0)\r\n    sizePolicy.setVerticalStretch(0)\r\n    btn = QtWidgets.QPushButton(parent)\r\n    sizePolicy.setHeightForWidth(btn.sizePolicy().hasHeightForWidth())\r\n    btn.setSizePolicy(sizePolicy)\r\n    btn.setMinimumSize(min_size)\r\n    btn.setObjectName(btn_name)\r\n    if ico_path is not None:\r\n        btn.setIcon(QtGui.QIcon(ico_path))\r\n    btn.setText(btn_text)\r\n    if curt is not None:\r\n        btn.setShortcut(curt)\r\n    return btn\r\n\r\n\r\n## 创建滑块区域\r\ndef create_slider(\r\n    parent,\r\n    sld_name,\r\n    text_name,\r\n    text,\r\n    default_value=50,\r\n    max_value=100,\r\n    min_value=0,\r\n    text_rate=0.01,\r\n    edit=False\r\n):\r\n    Region = QtWidgets.QHBoxLayout()\r\n    lab = create_text(parent, None, text)\r\n    Region.addWidget(lab)\r\n    if edit is False:\r\n        labShow = create_text(parent, text_name, str(default_value * text_rate))\r\n    else:\r\n        labShow = create_edit(parent, text_name, str(default_value * text_rate))\r\n    Region.addWidget(labShow)\r\n    Region.setStretch(0, 1)\r\n    Region.setStretch(1, 10)\r\n    sld = QtWidgets.QSlider(parent)\r\n    sld.setMaximum(max_value)  # 好像只能整数的，这里是扩大了10倍，1 . 10\r\n    sld.setMinimum(min_value)\r\n    sld.setProperty(\"value\", default_value)\r\n    sld.setOrientation(QtCore.Qt.Horizontal)\r\n    sld.setObjectName(sld_name)\r\n    sld.setStyleSheet(\r\n        \"\"\"\r\n        QSlider::sub-page:horizontal {\r\n            background: #9999F1\r\n        }\r\n        QSlider::handle:horizontal\r\n        {\r\n            background: #3334E3;\r\n            width: 12px;\r\n            border-radius: 4px;\r\n        }\r\n        \"\"\"\r\n    )\r\n    sld.textLab = labShow\r\n    return sld, labShow, Region\r\n\r\n\r\nclass DockWidget(QDockWidget):\r\n    def __init__(self, parent, name, text):\r\n        super().__init__(parent=parent)\r\n        self.setObjectName(name)\r\n        self.setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea)\r\n        # 感觉不给关闭好点。可以在显示里面取消显示。有关闭的话显示里面的enable还能判断修改，累了\r\n        self.setFeatures(\r\n            QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable\r\n        )\r\n        sizePolicy = QtWidgets.QSizePolicy(\r\n            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred\r\n        )\r\n        sizePolicy.setHorizontalStretch(0)\r\n        sizePolicy.setVerticalStretch(0)\r\n        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())\r\n        self.setSizePolicy(sizePolicy)\r\n        self.setMinimumWidth(230)\r\n        self.setWindowTitle(text)\r\n        self.setStyleSheet(\"QDockWidget { background-color:rgb(204,204,248); }\")\r\n        self.topLevelChanged.connect(self.changeBackColor)\r\n\r\n    def changeBackColor(self, isFloating):\r\n        if isFloating:\r\n            self.setStyleSheet(\"QDockWidget { background-color:rgb(255,255,255); }\")\r\n        else:\r\n            self.setStyleSheet(\"QDockWidget { background-color:rgb(204,204,248); }\")\r\n\r\n\r\n## 创建dock\r\ndef creat_dock(parent, name, text, widget):\r\n    dock = DockWidget(parent, name, text)\r\n    dock.setMinimumWidth(300)  # Uniform size\r\n    dock.setWidget(widget)\r\n    return dock\r\n"
  },
  {
    "path": "eiseg/widget/grip.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom PyQt5.QtCore import QPointF\nfrom qtpy import QtWidgets, QtGui, QtCore\n\n\nclass GripItem(QtWidgets.QGraphicsPathItem):\n    maxSize = 1.5\n    minSize = 0.8\n\n    def __init__(self, annotation_item, index, color, img_size):\n        super(GripItem, self).__init__()\n        self.m_annotation_item = annotation_item\n        self.hovering = False\n        self.m_index = index\n        self.anning = True\n        color.setAlphaF(1)\n        self.color = color\n        self.img_size = img_size\n\n        self.updateSize()\n        self.setPath(self.circle)\n        self.setBrush(self.color)\n        self.setPen(QtGui.QPen(self.color, 1))\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True)\n        self.setAcceptHoverEvents(True)\n        self.setZValue(12)\n        self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))\n\n    def setColor(self, color):\n        self.setBrush(color)\n        self.setPen(QtGui.QPen(color, 1))\n        self.color = color\n\n    def setAnning(self, anning=True):\n        self.anning = anning\n        self.setEnabled(anning)\n\n    # BUG: Scaling causes a crash\n    @property\n    def size(self):\n        return GripItem.minSize\n        # if not self.scene():\n        #     return GripItem.minSize\n        # else:\n        #     maxi, mini = GripItem.maxSize, GripItem.minSize\n        #     exp = 1 - mini / maxi\n        #     size = maxi * (1 - exp ** self.scene().scale)\n        #     if size > GripItem.maxSize:\n        #         size = GripItem.maxSize\n        #     if size < GripItem.minSize:\n        #         size = GripItem.minSize\n        #     return size\n\n    def updateSize(self, s=2):\n        size = self.size\n        self.circle = QtGui.QPainterPath()\n        self.circle.addEllipse(QtCore.QRectF(-size, -size, size * s, size * s))\n        self.square = QtGui.QPainterPath()\n        self.square.addRect(QtCore.QRectF(-size, -size, size * s, size * s))\n        self.setPath(self.square if self.hovering else self.circle)\n\n    def hoverEnterEvent(self, ev):\n        self.setPath(self.square)\n        self.setBrush(QtGui.QColor(0, 0, 0, 0))\n        self.m_annotation_item.item_hovering = True\n        self.hovring = True\n        super(GripItem, self).hoverEnterEvent(ev)\n\n    def hoverLeaveEvent(self, ev):\n        self.setPath(self.circle)\n        self.setBrush(self.color)\n        self.m_annotation_item.item_hovering = False\n        self.hovring = False\n        super(GripItem, self).hoverLeaveEvent(ev)\n\n    def mouseReleaseEvent(self, ev):\n        self.setSelected(False)\n        super(GripItem, self).mouseReleaseEvent(ev)\n\n    def itemChange(self, change, value):\n        tmp_val = value\n        if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():\n            if value.x() > self.img_size[1]:\n                x = self.img_size[1]\n            elif value.x() < 0:\n                x = 0\n            else:\n                x = value.x()\n            if value.y() > self.img_size[0]:\n                y = self.img_size[0]\n            elif value.y() < 0:\n                y = 0\n            else:\n                y = value.y()\n            tmp_val = QPointF(x, y)\n            self.m_annotation_item.movePoint(self.m_index, tmp_val)\n            self.m_annotation_item.setDirty(True)\n        return super(GripItem, self).itemChange(change, tmp_val)\n\n    def shape(self):\n        path = QtGui.QPainterPath()\n        p = self.mapFromScene(self.pos())\n        x, y = p.x(), p.y()\n        s = self.size\n        path.addEllipse(p, s + GripItem.minSize, s + GripItem.minSize)\n        return path\n\n    def mouseDoubleClickEvent(self, ev):\n        self.m_annotation_item.removeFocusPoint()\n"
  },
  {
    "path": "eiseg/widget/line.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom qtpy import QtWidgets, QtGui, QtCore\n\n\nclass LineItem(QtWidgets.QGraphicsLineItem):\n    maxWidth = 1\n    minWidth = 0.5\n\n    def __init__(self, annotation_item, idx, color):\n        super(LineItem, self).__init__()\n        self.polygon_item = annotation_item\n        self.idx = idx\n        self.color = color\n        self.anning = True\n        self.setPen(QtGui.QPen(color, self.width))\n\n        self.setZValue(11)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True)\n        # self.setFlag(QtWidgets.QGraphicsItem.ItemClipsToShape, True)\n        self.setAcceptHoverEvents(True)\n        self.setBoundingRegionGranularity(0.5)\n        self.updateWidth()\n\n    def setColor(self, color):\n        self.setPen(QtGui.QPen(color, self.width))\n        self.color = color\n\n    def setAnning(self, anning=True):\n        self.anning = anning\n        self.setEnabled(anning)\n        self.updateWidth()\n\n    # BUG: Scaling causes a crash\n    @property\n    def width(self):\n        return LineItem.minWidth\n        # if not self.scene():\n        #     width = LineItem.minWidth\n        # else:\n        #     maxi, mini = LineItem.maxWidth, LineItem.minWidth\n        #     exp = 1 - mini / maxi\n        #     width = maxi * (1 - exp ** self.scene().scale)\n        #     if width > LineItem.maxWidth:\n        #         width = LineItem.maxWidth\n        #     if width < LineItem.minWidth:\n        #         width = LineItem.minWidth\n        # return width\n\n    def updateWidth(self):\n        self.setPen(QtGui.QPen(self.color, self.width))\n\n    def hoverEnterEvent(self, ev):\n        self.boundingPolygon(True)\n        print(\"hover in\")\n        if self.anning:\n            self.polygon_item.line_hovering = True\n            self.setPen(QtGui.QPen(self.color, self.width * 1.4))\n        super(LineItem, self).hoverEnterEvent(ev)\n\n    def hoverLeaveEvent(self, ev):\n        self.polygon_item.line_hovering = False\n        self.setPen(QtGui.QPen(self.color, self.width))\n        super(LineItem, self).hoverLeaveEvent(ev)\n\n    def mouseDoubleClickEvent(self, ev):\n        print(\"anning\", self.anning)\n        if self.anning:\n            self.setPen(QtGui.QPen(self.color, self.width))\n            self.polygon_item.addPointMiddle(self.idx, ev.pos())\n        super(LineItem, self).mouseDoubleClickEvent(ev)\n\n    def shape(self):\n        path = QtGui.QPainterPath()\n        path.addPolygon(self.boundingPolygon(False))\n        return path\n\n    # def shape(self):\n    #     path = QtGui.QPainterPath()\n    #     path.moveTo(self.line().p1())\n    #     path.lineTo(self.line().p2())\n    #     path.setPen(QtGui.QPen(self.color, self.width * 3))\n    #     return path\n\n    def boundingPolygon(self, debug):\n        w = self.width * 1.5\n        w = min(w, 2)\n        s, e = self.line().p1(), self.line().p2()\n        dir = s - e\n        dx, dy = -dir.y(), dir.x()\n        norm = (dx ** 2 + dy ** 2) ** (1 / 2)\n        if debug:\n            print(\n                self.width,\n                w,\n                s.x(),\n                s.y(),\n                e.x(),\n                e.y(),\n                dir.x(),\n                dir.y(),\n                dx,\n                dy,\n                norm,\n            )\n        dx /= (norm + 1e-16)\n        dy /= (norm + 1e-16)\n        if debug:\n            print(\"dir\", dx, dy)\n        p = QtCore.QPointF(dx * w, dy * w)\n        poly = QtGui.QPolygonF([s - p, s + p, e + p, e - p])\n        return poly\n"
  },
  {
    "path": "eiseg/widget/loading.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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.\nimport os.path as osp\n\nfrom qtpy.QtWidgets import QWidget, QLabel, QHBoxLayout\nfrom qtpy.QtGui import QIcon, QMovie\nfrom qtpy import QtCore\n\nfrom eiseg import pjpath\n\n\nclass LoadingWidget(QWidget):\n    def __init__(self):\n        super().__init__()\n        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)\n        layout = QHBoxLayout(self)\n        self.label = QLabel()\n        layout.addWidget(self.label)\n        self.setLayout(layout)\n        self.movie = QMovie(osp.join(pjpath, \"resource\", \"loading.gif\"))\n        self.label.setMovie(self.movie)\n        self.movie.start()\n"
  },
  {
    "path": "eiseg/widget/polygon.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom qtpy import QtWidgets, QtGui, QtCore\n\nfrom . import GripItem, LineItem, BBoxAnnotation\n\n\nclass PolygonAnnotation(QtWidgets.QGraphicsPolygonItem):\n    def __init__(\n        self,\n        labelIndex,\n        shape,\n        delPolygon,\n        setDirty,\n        insideColor=[255, 0, 0],\n        borderColor=[0, 255, 0],\n        opacity=0.5,\n        cocoIndex=None,\n        parent=None,\n    ):\n        super(PolygonAnnotation, self).__init__(parent)\n        self.points = []\n        self.m_items = []\n        self.m_lines = []\n        self.coco_id = cocoIndex\n        self.height, self.width = shape[:2]\n        self.delPolygon = delPolygon\n        self.setDirty = setDirty\n\n        self.labelIndex = labelIndex\n        self.item_hovering = False\n        self.polygon_hovering = False\n        self.anning = False  # 是否标注模式\n        self.line_hovering = False\n        self.noMove = False\n        self.last_focse = False  # 之前是不是焦点在\n\n        self.setZValue(10)\n        self.opacity = opacity\n        i = insideColor\n        self.insideColor = QtGui.QColor(i[0], i[1], i[2])\n        self.insideColor.setAlphaF(opacity)\n        self.halfInsideColor = QtGui.QColor(i[0], i[1], i[2])\n        self.halfInsideColor.setAlphaF(opacity / 2)\n        self.setBrush(self.halfInsideColor)\n        b = borderColor\n        self.borderColor = QtGui.QColor(b[0], b[1], b[2])\n        self.borderColor.setAlphaF(0.8)\n        self.setPen(QtGui.QPen(self.borderColor))\n        self.setAcceptHoverEvents(True)\n\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)\n        self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True)\n\n        self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))\n\n        # persistent this bbox instance and update when needed\n        self.bbox = BBoxAnnotation(labelIndex, self, cocoIndex, self)\n        self.bbox.setParentItem(self)\n\n    @property\n    def scnenePoints(self):\n        points = []\n        for p in self.points:\n            p = self.mapToScene(p)\n            points.append([p.x(), p.y()])\n        return points\n\n    def setAnning(self, isAnning=True):\n        if isAnning:\n            self.setAcceptHoverEvents(False)\n            self.last_focse = self.polygon_hovering\n            self.polygon_hovering = False\n            self.anning = True\n            self.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))\n            self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, False)\n            # self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)\n            self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, False)\n            self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)\n            self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))\n            for line in self.m_lines:\n                line.setAnning(False)\n            for grip in self.m_items:\n                grip.setAnning(False)\n        else:\n            self.setAcceptHoverEvents(True)\n            self.anning = False\n            if self.last_focse:\n                self.polygon_hovering = True\n                self.setBrush(self.insideColor)\n            else:\n                self.setBrush(self.halfInsideColor)\n            self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)\n            # self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)\n            self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)\n            self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True)\n            self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))\n            for line in self.m_lines:\n                line.setAnning(True)\n            for grip in self.m_items:\n                grip.setAnning(True)\n\n    def addPointMiddle(self, lineIdx, point):\n        gripItem = GripItem(self, lineIdx + 1, self.borderColor, (self.height, self.width))\n        gripItem.setEnabled(False)\n        gripItem.setPos(point)\n        self.scene().addItem(gripItem)\n        gripItem.updateSize()\n        gripItem.setEnabled(True)\n        for grip in self.m_items[lineIdx + 1 :]:\n            grip.m_index += 1\n        self.m_items.insert(lineIdx + 1, gripItem)\n        self.points.insert(lineIdx + 1, self.mapFromScene(point))\n        self.setPolygon(QtGui.QPolygonF(self.points))\n        self.bbox.update()\n        for line in self.m_lines[lineIdx + 1 :]:\n            line.idx += 1\n        line = QtCore.QLineF(self.mapToScene(self.points[lineIdx]), point)\n        self.m_lines[lineIdx].setLine(line)\n        lineItem = LineItem(self, lineIdx + 1, self.borderColor)\n        line = QtCore.QLineF(\n            point,\n            self.mapToScene(self.points[(lineIdx + 2) % len(self)]),\n        )\n        lineItem.setLine(line)\n        self.m_lines.insert(lineIdx + 1, lineItem)\n        self.scene().addItem(lineItem)\n        lineItem.updateWidth()\n\n    def addPointLast(self, p):\n        grip = GripItem(self, len(self), self.borderColor, (self.height, self.width))\n        self.scene().addItem(grip)\n        self.m_items.append(grip)\n        grip.updateSize()\n        grip.setPos(p)\n        if len(self) == 0:\n            line = LineItem(self, len(self), self.borderColor)\n            self.scene().addItem(line)\n            self.m_lines.append(line)\n            line.setLine(QtCore.QLineF(p, p))\n        else:\n            self.m_lines[-1].setLine(QtCore.QLineF(self.points[-1], p))\n            line = LineItem(self, len(self), self.borderColor)\n            self.scene().addItem(line)\n            self.m_lines.append(line)\n            line.setLine(QtCore.QLineF(p, self.points[0]))\n\n        self.points.append(p)\n        self.setPolygon(QtGui.QPolygonF(self.points))\n        self.bbox.update()\n\n    def remove(self):\n        for grip in self.m_items:\n            self.scene().removeItem(grip)\n        for line in self.m_lines:\n            self.scene().removeItem(line)\n        while len(self.m_items) != 0:\n            self.m_items.pop()\n        while len(self.m_lines) != 0:\n            self.m_lines.pop()\n        self.scene().polygon_items.remove(self)\n        self.scene().removeItem(self)\n        self.bbox.remove_from_scene()\n        del self.bbox\n        del self\n\n    def removeFocusPoint(self):\n        focusIdx = None\n        for idx, item in enumerate(self.m_items):\n            if item.hasFocus():\n                focusIdx = idx\n                break\n        if focusIdx is not None:\n            if len(self) <= 3:\n                self.delPolygon(self)  # 调用app的删除多边形，为了同时删除coco标签\n                return\n            del self.points[focusIdx]\n            self.setPolygon(QtGui.QPolygonF(self.points))\n            self.bbox.update()\n            self.scene().removeItem(self.m_items[focusIdx])\n            del self.m_items[focusIdx]\n            for grip in self.m_items[focusIdx:]:\n                grip.m_index -= 1\n\n            self.scene().removeItem(self.m_lines[focusIdx])\n            del self.m_lines[focusIdx]\n            line = QtCore.QLineF(\n                self.mapToScene(self.points[(focusIdx - 1) % len(self)]),\n                self.mapToScene(self.points[focusIdx % len(self)]),\n            )\n            # print((focusIdx - 1) % len(self), len(self.m_lines), len(self))\n            self.m_lines[(focusIdx - 1) % len(self)].setLine(line)\n            for line in self.m_lines[focusIdx:]:\n                line.idx -= 1\n\n    def removeLastPoint(self):\n        # TODO: 创建的时候用到，需要删line\n        if len(self.points) == 0:\n            self.points.pop()\n            self.setPolygon(QtGui.QPolygonF(self.points))\n            self.bbox.update()\n            it = self.m_items.pop()\n            self.scene().removeItem(it)\n            del it\n\n    def movePoint(self, i, p):\n        # print(\"Move point\", i, p)\n        if 0 <= i < len(self.points):\n            p = self.mapFromScene(p)\n            self.points[i] = p\n            self.setPolygon(QtGui.QPolygonF(self.points))\n            self.bbox.update()\n            self.moveLine(i)\n\n    def moveLine(self, i):\n        # print(\"Moving line: \", i, self.noMove)\n        if self.noMove:\n            return\n        points = self.points\n        # line[i]\n        line = QtCore.QLineF(\n            self.mapToScene(points[i]), self.mapToScene(points[(i + 1) % len(self)])\n        )\n        self.m_lines[i].setLine(line)\n        # line[i-1]\n        line = QtCore.QLineF(\n            self.mapToScene(points[(i - 1) % len(self)]), self.mapToScene(points[i])\n        )\n        # print((i - 1) % len(self), len(self.m_lines), len(self))\n        self.m_lines[(i - 1) % len(self)].setLine(line)\n\n    def move_item(self, i, pos):\n        if 0 <= i < len(self.m_items):\n            item = self.m_items[i]\n            item.setEnabled(False)\n            item.setPos(pos)\n            item.setEnabled(True)\n            self.moveLine(i)\n\n    def itemChange(self, change, value):\n        if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:\n            for i, point in enumerate(self.points):\n                self.move_item(i, self.mapToScene(point))\n        return super(PolygonAnnotation, self).itemChange(change, value)\n\n    def hoverEnterEvent(self, ev):\n        self.polygon_hovering = True\n        self.setBrush(self.insideColor)\n        super(PolygonAnnotation, self).hoverEnterEvent(ev)\n\n    def hoverLeaveEvent(self, ev):\n        self.polygon_hovering = False\n        if not self.hasFocus():\n            self.setBrush(self.halfInsideColor)\n        super(PolygonAnnotation, self).hoverLeaveEvent(ev)\n\n    def focusInEvent(self, ev):\n        if not self.anning:\n            self.setBrush(self.insideColor)\n\n    def focusOutEvent(self, ev):\n        if not self.polygon_hovering and not self.anning:\n            self.setBrush(self.halfInsideColor)\n\n    def setColor(self, insideColor, borderColor):\n        i = insideColor\n        self.insideColor = QtGui.QColor(i[0], i[1], i[2])\n        self.insideColor.setAlphaF(self.opacity)\n        self.halfInsideColor = QtGui.QColor(i[0], i[1], i[2])\n        self.halfInsideColor.setAlphaF(self.opacity / 2)\n        self.setBrush(self.halfInsideColor)\n        b = borderColor\n        self.borderColor = QtGui.QColor(b[0], b[1], b[2])\n        self.borderColor.setAlphaF(0.8)\n        self.setPen(QtGui.QPen(self.borderColor))\n        for grip in self.m_items:\n            grip.setColor(self.borderColor)\n        for line in self.m_lines:\n            line.setColor(self.borderColor)\n\n    def __len__(self):\n        return len(self.points)\n"
  },
  {
    "path": "eiseg/widget/scene.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom PyQt5.QtCore import QPointF\nfrom qtpy import QtWidgets, QtCore\nfrom qtpy.QtCore import Qt\nfrom qtpy.QtGui import QPen, QColor\n\n\nclass AnnotationScene(QtWidgets.QGraphicsScene):\n    clickRequest = QtCore.Signal(int, int, bool)\n\n    def __init__(self, parent=None):\n        super(AnnotationScene, self).__init__(parent)\n        self.creating = False\n        self.polygon_items = []\n        # draw cross\n        self.coords = None\n        self.pen = QPen()\n        self.pen.setWidth(1)\n        self.pen.setColor(QColor(0, 0, 0, 127))\n\n    def setPenColor(self, color_list):\n        R, G, B, A = color_list\n        self.pen.setColor(QColor(R, G, B, A))\n\n    def updatePolygonSize(self):\n        for poly in self.polygon_items:\n            for grip in poly.m_items:\n                grip.updateSize()\n            for line in poly.m_lines:\n                line.updateWidth()\n\n    def setCreating(self, creating=True):\n        self.creating = creating\n\n    def mousePressEvent(self, ev):\n        pos = ev.scenePos()\n        if not self.creating and not self.hovering:\n            if ev.buttons() in [Qt.LeftButton, Qt.RightButton]:\n                self.clickRequest.emit(\n                    int(pos.x()), int(pos.y()), ev.buttons() == Qt.LeftButton\n                )\n        elif self.creating:\n            self.polygon_item.removeLastPoint()\n            self.polygon_item.addPointLast(ev.scenePos())\n            # movable element\n            self.polygon_item.addPointLast(ev.scenePos())\n        super(AnnotationScene, self).mousePressEvent(ev)\n\n    def mouseMoveEvent(self, ev):\n        if self.creating:\n            self.polygon_item.movePoint(\n                # self.polygon_item.number_of_points() - 1, ev.scenePos()\n                len(self.polygon_item) - 1,\n                ev.scenePos(),\n            )\n        super(AnnotationScene, self).mouseMoveEvent(ev)\n\n    def drawForeground(self, painter, rect):\n        if self.coords is not None and self.coords != QPointF(-1, -1):\n            painter.setClipRect(rect)\n            painter.setPen(self.pen)\n            painter.drawLine(int(self.coords.x()), int(rect.top()), \n                             int(self.coords.x()), int(rect.bottom()))\n            painter.drawLine(int(rect.left()), int(self.coords.y()), \n                             int(rect.right()), int(self.coords.y()))\n\n    def onMouseChanged(self, pointf):\n        self.coords = pointf\n        self.invalidate()\n\n    @property\n    def item_hovering(self):\n        for poly in self.polygon_items:\n            if poly.item_hovering:\n                return True\n        return False\n\n    @property\n    def polygon_hovering(self):\n        for poly in self.polygon_items:\n            if poly.polygon_hovering:\n                return True\n        return False\n\n    @property\n    def line_hovering(self):\n        for poly in self.polygon_items:\n            if poly.line_hovering:\n                return True\n        return False\n\n    @property\n    def hovering(self):\n        return self.item_hovering or self.polygon_hovering or self.line_hovering\n"
  },
  {
    "path": "eiseg/widget/shortcut.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport os.path as osp\nimport math\nfrom functools import partial\n\nfrom PyQt5.QtCore import QPoint\nfrom PyQt5.QtWidgets import QDesktopWidget\n\nfrom qtpy import QtCore, QtWidgets\nfrom qtpy.QtWidgets import (\n    QWidget,\n    QLabel,\n    QPushButton,\n    QGridLayout,\n    QKeySequenceEdit,\n    QMessageBox,\n)\nfrom qtpy.QtGui import QIcon\nfrom qtpy import QtCore\nfrom qtpy.QtCore import Qt\n\nfrom util import save_configs\n\n\nclass RecordShortcutWidget(QKeySequenceEdit):\n    def __init__(self, finishCallback, location):\n        super().__init__()\n        self.finishCallback = finishCallback\n        # 隐藏界面\n        self.setWindowFlags(Qt.FramelessWindowHint)\n        self.move(location)\n        self.show()\n        self.editingFinished.connect(lambda: finishCallback(self.keySequence()))\n\n    def keyReleaseEvent(self, ev):\n        self.finishCallback(self.keySequence())\n\n\nclass ShortcutWidget(QWidget):\n    def __init__(self, actions, pjpath):\n        super().__init__()\n        self.tr = partial(QtCore.QCoreApplication.translate, \"ShortcutWidget\")\n        self.setWindowTitle(self.tr(\"编辑快捷键\"))\n        self.setWindowIcon(QIcon(osp.join(pjpath, \"resource/Shortcut.png\")))\n        # self.setFixedSize(self.width(), self.height())\n        self.actions = actions\n        self.recorder = None\n        self.initUI()\n\n    def initUI(self):\n        grid = QGridLayout()\n        self.setLayout(grid)\n\n        actions = self.actions\n        for idx, action in enumerate(actions):\n            # 2列英文看不清\n            grid.addWidget(QLabel(action.iconText()[1:]), idx // 3, idx % 3 * 3)\n            shortcut = action.shortcut().toString()\n            if len(shortcut) == 0:\n                shortcut = self.tr(\"-\")\n            button = QPushButton(shortcut)\n            button.setFixedWidth(150)\n            button.setFixedHeight(30)\n            button.clicked.connect(partial(self.recordShortcut, action))\n            grid.addWidget(\n                button,\n                idx // 3,\n                idx % 3 * 3 + 1,\n            )\n\n    def refreshUi(self):\n        actions = self.actions\n        for idx, action in enumerate(actions):\n            shortcut = action.shortcut().toString()\n            if len(shortcut) == 0:\n                shortcut = self.tr(\"-\")\n            self.layout().itemAtPosition(\n                idx // 3,\n                idx % 3 * 3 + 1,\n            ).widget().setText(shortcut)\n\n    def recordShortcut(self, action):\n        # 打开快捷键设置的窗口时，如果之前的还在就先关闭\n        if self.recorder is not None:\n            self.recorder.close()\n        rect = self.geometry()\n        x = rect.x()\n        y = rect.y() + rect.height()\n        self.recorder = RecordShortcutWidget(self.setShortcut, QPoint(x, y))\n        self.currentAction = action\n\n    def setShortcut(self, key):\n        self.recorder.close()\n\n        for a in self.actions:\n            if a.shortcut() == key:\n                key = key.toString()\n                msg = QMessageBox()\n                msg.setIcon(QMessageBox.Warning)\n                msg.setWindowTitle(key + \" \" + self.tr(\"快捷键冲突\"))\n                msg.setText(\n                    key\n                    + \" \"\n                    + self.tr(\"快捷键已被\")\n                    + \" \"\n                    + a.data()\n                    + \" \"\n                    + self.tr(\"使用，请设置其他快捷键或先修改\")\n                    + \" \"\n                    + a.data()\n                    + \" \"\n                    + self.tr(\"的快捷键\")\n                )\n                msg.setStandardButtons(QMessageBox.Ok)\n                msg.exec_()\n                return\n        key = \"\" if key.toString() == \"Esc\" else key  # ESC不设置快捷键\n        self.currentAction.setShortcut(key)\n        self.refreshUi()\n        save_configs(None, None, self.actions)\n\n    def center(self):\n        qr = self.frameGeometry()\n        cp = QDesktopWidget().availableGeometry().center()\n        qr.moveCenter(cp)\n        self.move(qr.topLeft())\n\n    # 快捷键设置跟随移动\n    def moveEvent(self, event):\n        p = self.geometry()\n        x = p.x()\n        y = p.y() + p.height()\n        if self.recorder is not None:\n            self.recorder.move(x, y)\n\n    def closeEvent(self, event):\n        # 关闭时也退出快捷键设置\n        if self.recorder is not None:\n            self.recorder.close()\n"
  },
  {
    "path": "eiseg/widget/table.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nfrom qtpy import QtWidgets\r\nfrom qtpy.QtCore import Qt\r\n\r\n\r\nclass TableWidget(QtWidgets.QTableWidget):\r\n    def __init__(self, *args, **kwargs):\r\n        super().__init__(*args, **kwargs)\r\n        self.setDragEnabled(True)\r\n        self.setAcceptDrops(True)\r\n        self.viewport().setAcceptDrops(True)\r\n        self.setDragDropOverwriteMode(False)\r\n        self.setDropIndicatorShown(True)\r\n        self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)\r\n        self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)\r\n        self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)\r\n\r\n    def dropEvent(self, event):\r\n        if event.source() == self:\r\n            rows = set([mi.row() for mi in self.selectedIndexes()])\r\n            targetRow = self.indexAt(event.pos()).row()\r\n            rows.discard(targetRow)\r\n            rows = sorted(rows)\r\n            if not rows:\r\n                return\r\n            if targetRow == -1:\r\n                targetRow = self.rowCount()\r\n            for _ in range(len(rows)):\r\n                self.insertRow(targetRow)\r\n            rowMapping = dict()  # Src row to target row.\r\n            for idx, row in enumerate(rows):\r\n                if row < targetRow:\r\n                    rowMapping[row] = targetRow + idx\r\n                else:\r\n                    rowMapping[row + len(rows)] = targetRow + idx\r\n            colCount = self.columnCount()\r\n            for srcRow, tgtRow in sorted(rowMapping.items()):\r\n                for col in range(0, colCount):\r\n                    self.setItem(tgtRow, col, self.takeItem(srcRow, col))\r\n            for row in reversed(sorted(rowMapping.keys())):\r\n                self.removeRow(row)\r\n            event.accept()\r\n            return\r\n\r\n    def drop_on(self, event):\r\n        index = self.indexAt(event.pos())\r\n        if not index.isValid():\r\n            return self.rowCount()\r\n\r\n        return index.row() + 1 if self.is_below(event.pos(), index) else index.row()\r\n\r\n    def is_below(self, pos, index):\r\n        rect = self.visualRect(index)\r\n        margin = 2\r\n        if pos.y() - rect.top() < margin:\r\n            return False\r\n        elif rect.bottom() - pos.y() < margin:\r\n            return True\r\n        # noinspection PyTypeChecker\r\n        return (\r\n            rect.contains(pos, True)\r\n            and not (int(self.model().flags(index)) & Qt.ItemIsDropEnabled)\r\n            and pos.y() >= rect.center().y()\r\n        )\r\n"
  },
  {
    "path": "eiseg/widget/view.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nfrom qtpy import QtWidgets, QtCore, QtGui\nfrom qtpy.QtCore import Qt, QPointF\n\n\nclass AnnotationView(QtWidgets.QGraphicsView):\n    zoomRequest = QtCore.Signal(float)\n    mousePosChanged = QtCore.Signal(QPointF)\n\n    def __init__(self, *args):\n        super(AnnotationView, self).__init__(*args)\n        self.setRenderHints(\n            QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform\n        )\n        self.setMouseTracking(True)\n        self.setTransformationAnchor(QtWidgets.QGraphicsView.NoAnchor)\n        self.setResizeAnchor(QtWidgets.QGraphicsView.NoAnchor)\n        self.point = QtCore.QPoint(0, 0)\n        self.middle_click = False\n        self.zoom_all = 1\n        # hint mouse\n        self.setCursor(Qt.BlankCursor)\n\n    def wheelEvent(self, ev):\n        if ev.modifiers() & QtCore.Qt.ControlModifier:\n            zoom = 1 + ev.angleDelta().y() / 2880\n            self.zoom_all *= zoom\n            oldPos = self.mapToScene(ev.pos())\n            if self.zoom_all >= 0.02 and self.zoom_all <= 50:  # 限制缩放的倍数\n                self.scale(zoom, zoom)\n            newPos = self.mapToScene(ev.pos())\n            delta = newPos - oldPos\n            self.translate(delta.x(), delta.y())\n            ev.ignore()\n            self.zoomRequest.emit(self.zoom_all)\n        else:\n            super(AnnotationView, self).wheelEvent(ev)\n\n    def mouseMoveEvent(self, ev):\n        mouse_pos = QPointF(self.mapToScene(ev.pos()))\n        self.mousePosChanged.emit(mouse_pos.toPoint())\n        if self.middle_click and (\n            self.horizontalScrollBar().isVisible()\n            or self.verticalScrollBar().isVisible()\n        ):\n            # 放大到出现滚动条才允许拖动，避免出现抖动\n            self._endPos = ev.pos() / self.zoom_all - self._startPos / self.zoom_all\n            # 这儿不写为先减后除，这样会造成速度不一致\n            self.point = self.point + self._endPos\n            self._startPos = ev.pos()\n            self.translate(self._endPos.x(), self._endPos.y())\n        super(AnnotationView, self).mouseMoveEvent(ev)\n\n    def mousePressEvent(self, ev):\n        if ev.buttons() == Qt.MiddleButton:\n            self.middle_click = True\n            self._startPos = ev.pos()\n        super(AnnotationView, self).mousePressEvent(ev)\n\n    def mouseReleaseEvent(self, ev):\n        if ev.button() == Qt.MiddleButton:\n            self.middle_click = False\n        super(AnnotationView, self).mouseReleaseEvent(ev)\n\n    def leaveEvent(self, ev):\n        self.mousePosChanged.emit(QPointF(-1, -1))\n        return super(AnnotationView, self).leaveEvent(ev)"
  },
  {
    "path": "init.sh",
    "content": "#!/bin/bash\n\nROOT=`cd \"$(dirname ${BASH_SOURCE[0]})\" && pwd`\n\necho \"ROOT : $ROOT\"\n\nexport PYTHONPATH=$PYTHONPATH:$ROOT/eiseg\n"
  },
  {
    "path": "requirements-med.txt",
    "content": "SimpleITK\n"
  },
  {
    "path": "requirements-rs.txt",
    "content": "GDAL>=3.3.0\nrasterio>=1.2.4\n"
  },
  {
    "path": "requirements.txt",
    "content": "pyqt5\nqtpy\nopencv-python\nscipy\npaddleseg\nalbumentations\ncython\npyyaml\nwget\nrequests\neasydict\nscikit-image"
  },
  {
    "path": "setup.py",
    "content": "import pathlib\r\nfrom setuptools import setup, find_packages, Extension\r\n\r\nimport numpy as np\r\n\r\nfrom eiseg import __APPNAME__, __VERSION__\r\n\r\n\r\n# from Cython.Build import cythonize\r\n\r\nHERE = pathlib.Path(__file__).parent\r\n\r\nREADME = (HERE / \"README.md\").read_text(encoding=\"utf-8\")\r\n\r\nwith open(\"requirements.txt\") as fin:\r\n    REQUIRED_PACKAGES = fin.read()\r\n\r\next_modules = [\r\n    Extension(\r\n        \"pycocotools._mask\",\r\n        sources=[\r\n            \"./eiseg/util/coco/common/maskApi.c\",\r\n            \"./eiseg/util/coco/pycocotools/_mask.pyx\",\r\n        ],\r\n        include_dirs=[np.get_include(), \"./eiseg/util/coco/common\"],\r\n        extra_compile_args=[\"-Wno-cpp\", \"-Wno-unused-function\", \"-std=c99\"],\r\n    )\r\n]\r\n\r\nsetup(\r\n    name=__APPNAME__,\r\n    version=__VERSION__,\r\n    description=\"交互式标注软件\",\r\n    long_description=README,\r\n    long_description_content_type=\"text/markdown\",\r\n    url=\"https://github.com/PaddleCV-SIG/EISeg\",\r\n    author=\"PaddleCV-SIG\",\r\n    author_email=\"linhandev@qq.com\",\r\n    license=\"Apache Software License\",\r\n    classifiers=[\r\n        \"License :: OSI Approved :: Apache Software License\",\r\n        \"Programming Language :: Python :: 3\",\r\n        \"Programming Language :: Python :: 3.8\",\r\n    ],\r\n    packages=find_packages(exclude=(\"test\",)),\r\n    # packages=[\"EISeg\"],\r\n    include_package_data=True,\r\n    install_requires=REQUIRED_PACKAGES,\r\n    entry_points={\r\n        \"console_scripts\": [\r\n            \"eiseg=eiseg.run:main\",\r\n        ]\r\n    },\r\n)\r\n"
  },
  {
    "path": "tool/baidu_translate.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport json\nimport random\nimport hashlib\nfrom urllib import parse\nimport http.client\nfrom tqdm import tqdm\nfrom collections import defaultdict\n\nfrom bs4 import BeautifulSoup as bs\n\n\nclass BaiduTranslate:\n    def __init__(self, fromLang, toLang):\n        self.url = \"/api/trans/vip/translate\"\n        self.appid = \"20200311000396156\"\n        self.secretKey = \"s6c3ZeYTI9lhrwQVugnM\"\n        self.fromLang = fromLang\n        self.toLang = toLang\n        self.salt = random.randint(32768, 65536)\n\n    def trans(self, text):\n        sign = self.appid + text + str(self.salt) + self.secretKey\n        md = hashlib.md5()\n        md.update(sign.encode(encoding=\"utf-8\"))\n        sign = md.hexdigest()\n        myurl = (\n            self.url\n            + \"?appid=\"\n            + self.appid\n            + \"&q=\"\n            + parse.quote(text)\n            + \"&from=\"\n            + self.fromLang\n            + \"&to=\"\n            + self.toLang\n            + \"&salt=\"\n            + str(self.salt)\n            + \"&sign=\"\n            + sign\n        )\n        try:\n            httpClient = http.client.HTTPConnection(\"api.fanyi.baidu.com\")\n            httpClient.request(\"GET\", myurl)\n            response = httpClient.getresponse()\n            html = response.read().decode(\"utf-8\")\n            html = json.loads(html)\n            dst = html[\"trans_result\"][0][\"dst\"]\n            return True, dst\n        except Exception as e:\n            return False, e\n\ndef read_ts(ts_path):\n    xml = open(ts_path, \"r\", encoding=\"utf-8\").read()\n    xml = bs(xml, \"xml\")\n    return xml\n\n\npre_ts_path = \"tool/ts/English.ts\"  # Russia\nts_path = \"tool/ts/out.ts\"\npre_xml = read_ts(pre_ts_path)\nxml = read_ts(ts_path)\npre_messages = pre_xml.find_all(\"message\")\nmessages = xml.find_all(\"message\")\nbd_trans = BaiduTranslate(\"auto\", \"en\")  # ru\ntrans = bd_trans.trans\n\ntranslated = 0\nfailed = 0\nfor msg in messages:\n    type = msg.translation.get(\"type\", None)\n    source = msg.source.string\n    trans = msg.translation.string\n    if type == \"unfinished\" and trans is None and source is not None:\n        in_pre = False\n        for pmsg in pre_messages:\n            if pmsg.source.string == source:\n                try:\n                    msg.translation.string = pmsg.translation.string\n                    translated += 1\n                    print(\n                        f\"{translated + failed} / {len(messages)}:{source} \\t {msg.translation.string}\"\n                    )\n                    in_pre = True\n                except:\n                    pass\n                break\n        if in_pre is False:\n            res = bd_trans.trans(source)\n            if res[0]:\n                msg.translation.string = res[1]\n                translated += 1\n            else:\n                failed += 1\n            print(\n                f\"{translated + failed} / {len(messages)}:{source} \\t {msg.translation.string}\"\n            )\n\nfor name in xml.find_all(\"name\"):\n    name.string = \"APP_EISeg\"\n\nprint(f\"Totally {len(messages)} , translated {translated}, failed {failed}\")\nopen(ts_path, \"w\", encoding=\"utf-8\").write(str(xml))"
  },
  {
    "path": "tool/pypi.sh",
    "content": "rm dist/*\npython setup.py sdist bdist_wheel\ntwine upload --repository-url https://test.pypi.org/legacy/  dist/* --verbose\n# https://upload.pypi.org/legacy/\n\nconda create -n test python=3.9\nconda activate test\npip install --upgrade eiseg\npip install paddlepaddle\neiseg\n"
  },
  {
    "path": "tool/semantic2instance.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n# you may not use this file except in compliance with the License.\r\n# You may obtain a copy of the License at\r\n#\r\n#     http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n\r\nimport os\r\nimport os.path as osp\r\nimport argparse\r\nfrom tqdm import tqdm\r\nimport numpy as np\r\nimport cv2\r\nfrom PIL import Image\r\n\r\n\r\ndef _savePalette(label, save_path):\r\n    bin_colormap = np.random.randint(0, 255, (256, 3))  # 可视化的颜色\r\n    bin_colormap[0, :] = [0, 0, 0]\r\n    bin_colormap = bin_colormap.astype(np.uint8)\r\n    visualimg  = Image.fromarray(label, \"P\")\r\n    palette = bin_colormap  # long palette of 768 items\r\n    visualimg.putpalette(palette) \r\n    visualimg.save(save_path, format='PNG')\r\n\r\ndef _segMaskB2I(mask_path, save_path):\r\n    img = np.asarray(Image.open(mask_path))\r\n    mask = np.zeros_like(img)\r\n    results = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)\r\n    cv2_v = cv2.__version__.split(\".\")[0]\r\n    contours = results[1] if cv2_v == \"3\" else results[0]  # 边界\r\n    hierarchys = results[2] if cv2_v == \"3\" else results[1]  # 隶属信息\r\n    areas = {}  # 面积\r\n    for i in range(len(contours)):\r\n        areas[i] = cv2.contourArea(contours[i])\r\n    sorted(areas.items(), key = lambda kv:(kv[1], kv[0]), reverse=True)  # 面积升序\r\n    # 开始填充\r\n    color = 1\r\n    for idx in areas.keys():\r\n        contour = contours[idx]\r\n        hierarchy = hierarchys[0][idx]\r\n        # print(hierarchy)\r\n        if hierarchy[-1] == -1:  # 输入子轮廓\r\n            cv2.fillPoly(mask, [contour], color)\r\n            color += 1\r\n        else:\r\n            cv2.fillPoly(mask, [contour], 0)\r\n    # 显示\r\n    # cv2.drawContours(mask, contours, -1, (125,125,125), 1)\r\n    # cv2.imshow('src',mask)\r\n    # cv2.waitKey()\r\n    _savePalette(mask, save_path)\r\n\r\n\r\nparser = argparse.ArgumentParser(description='Label path and save path')\r\nparser.add_argument('--label_path', '-o', help='读取语义分割标签文件夹路径，必要参数', required=True)\r\nparser.add_argument('--save_path', '-d', help='实例分割标签保存文件夹路径，必要参数', required=True)\r\nargs = parser.parse_args()\r\n\r\nif __name__ == \"__main__\":\r\n    label_path = args.label_path\r\n    save_path = args.save_path\r\n    names = os.listdir(label_path)\r\n    for name in tqdm(names):\r\n        label = osp.join(label_path, name)\r\n        saver = osp.join(save_path, name)\r\n        _segMaskB2I(label, saver)"
  },
  {
    "path": "tool/translate.pro",
    "content": "CODECFORTR = UTF-8\nSOURCES = ../eiseg/app.py ../eiseg/ui.py ../eiseg/widget/shortcut.py\nTRANSLATIONS = ./ts/out.ts\n"
  },
  {
    "path": "tool/translateUI.py",
    "content": "import os\r\nimport os.path as osp\r\nimport json\r\nimport random\r\nimport hashlib\r\nfrom urllib import parse\r\nimport http.client\r\nfrom tqdm import tqdm\r\nfrom collections import defaultdict\r\n\r\n\r\nclass BaiduTranslate:\r\n    def __init__(self, fromLang, toLang):\r\n        self.url = \"/api/trans/vip/translate\"\r\n        self.appid = \"20200311000396156\"\r\n        self.secretKey = \"s6c3ZeYTI9lhrwQVugnM\"\r\n        self.fromLang = fromLang\r\n        self.toLang = toLang\r\n        self.salt = random.randint(32768, 65536)\r\n\r\n    def BdTrans(self, text):\r\n        sign = self.appid + text + str(self.salt) + self.secretKey\r\n        md = hashlib.md5()\r\n        md.update(sign.encode(encoding=\"utf-8\"))\r\n        sign = md.hexdigest()\r\n        myurl = self.url + \\\r\n                \"?appid=\" + self.appid + \\\r\n                \"&q=\" + parse.quote(text) + \\\r\n                \"&from=\" + self.fromLang + \\\r\n                \"&to=\" + self.toLang + \\\r\n                \"&salt=\" + str(self.salt) + \\\r\n                \"&sign=\" + sign\r\n        try:\r\n            httpClient = http.client.HTTPConnection(\"api.fanyi.baidu.com\")\r\n            httpClient.request(\"GET\", myurl)\r\n            response = httpClient.getresponse()\r\n            html = response.read().decode(\"utf-8\")\r\n            html = json.loads(html)\r\n            dst = html[\"trans_result\"][0][\"dst\"]\r\n            return True, dst\r\n        except Exception as e:\r\n            return False , e\r\n\r\n\r\n# 获取所有可能带有ui的py文件\r\nui_files = []\r\nwidget_path = \"eiseg/widget\"\r\nwidget_names = os.listdir(widget_path)\r\nfor widget_name in widget_names:\r\n    if widget_name != \"__init__.py\" and widget_name != \"__pycache__\":\r\n        ui_files.append(osp.join(widget_path, widget_name))\r\nui_files.append(\"eiseg/ui.py\")\r\nui_files.append(\"eiseg/app.py\")\r\n\r\n# 查找\r\nchinese = []\r\nkeys = \"trans.put(\\\"\"\r\nfor ui_file in ui_files:\r\n    with open(ui_file, \"r\", encoding=\"utf-8\") as f:\r\n        codes = f.read()\r\n        sp_codes = codes.split(keys)\r\n        if len(sp_codes) == 1:\r\n            continue\r\n        else:\r\n            sp_codes.pop(0)\r\n            for sp_code in sp_codes:\r\n                chinese.append(sp_code.split(\"\\\")\")[0])\r\nchinese = list(set(chinese))\r\n# print(len(chinese))\r\n# print(chinese)\r\n\r\n# 比对（以前有的不重新机翻）\r\nsave_path = \"eiseg/config/zh_CN.EN\"\r\nnow_words = defaultdict(dict)\r\nwith open(save_path, \"r\", encoding=\"utf-8\") as f:\r\n    datas = f.readlines()\r\n    for data in datas:\r\n        words = data.strip().split(\"@\")\r\n        now_words[words[0]] = words[1]\r\n\r\n# 翻译\r\ndef firstCharUpper(s):\r\n    return s[:1].upper() + s[1:]\r\n\r\ntranslate = []\r\nbaidu_trans = BaiduTranslate(\"zh\", \"en\")\r\nfor cn in tqdm(chinese):\r\n    if cn not in now_words.keys():\r\n        en = baidu_trans.BdTrans(cn)\r\n        tr = cn + \"@\" + firstCharUpper(en[-1])  # 首字母大写\r\n    else:\r\n        tr = cn + \"@\" + now_words[cn]\r\n    translate.append(tr)\r\n\r\n# 保存翻译内容\r\nwith open(save_path, \"w\", encoding=\"utf-8\") as f:\r\n    for language in translate:\r\n        f.write(language + \"\\n\")\r\n\r\nprint(\"trans OK!\")"
  },
  {
    "path": "tool/ts/Arabic.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ar\" sourcelanguage=\"zh\">\n<context>\n    <name>APP_EISeg</name>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"250\"/>\n        <source>&amp;打开图像</source>\n        <translation>&amp; فتح الصورة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"254\"/>\n        <source>打开一张图像进行标注</source>\n        <translation>فتح صورة الشرح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"257\"/>\n        <source>&amp;打开文件夹</source>\n        <translation>&amp; فتح مجلد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"261\"/>\n        <source>打开一个文件夹下所有的图像进行标注</source>\n        <translation>فتح مجلد تحت كل صورة الشرح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"264\"/>\n        <source>&amp;改变标签保存路径</source>\n        <translation>&amp; تغيير مسار حفظ التسمية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"268\"/>\n        <source>改变标签保存的文件夹路径</source>\n        <translation>تغيير مسار المجلد حفظ التسمية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"271\"/>\n        <source>&amp;加载模型参数</source>\n        <translation>&amp; تحميل معالم النموذج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"275\"/>\n        <source>加载一个模型参数</source>\n        <translation>تحميل نموذج المعلمة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"278\"/>\n        <source>&amp;保存</source>\n        <translation>حفظ</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"282\"/>\n        <source>保存图像标签</source>\n        <translation>حفظ صورة العلامة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"285\"/>\n        <source>&amp;另存为</source>\n        <translation>&amp; حفظ باسم</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"289\"/>\n        <source>在指定位置另存为标签</source>\n        <translation>حفظ التسمية في الموقع المحدد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"292\"/>\n        <source>&amp;自动保存</source>\n        <translation>&amp; حفظ السيارات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"296\"/>\n        <source>翻页同时自动保存</source>\n        <translation>حفظ الصفحة تلقائيا في نفس الوقت</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"303\"/>\n        <source>&amp;上一张</source>\n        <translation>و آخر واحد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"307\"/>\n        <source>翻到上一张图片</source>\n        <translation>انتقل إلى الصورة السابقة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"310\"/>\n        <source>&amp;下一张</source>\n        <translation>الصورة التالية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"314\"/>\n        <source>翻到下一张图片</source>\n        <translation>انتقل إلى الصورة التالية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"317\"/>\n        <source>&amp;完成当前目标</source>\n        <translation>&amp; تحقيق الهدف الحالي</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"321\"/>\n        <source>完成当前目标的标注</source>\n        <translation>الانتهاء من وضع العلامات على الهدف الحالي</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"324\"/>\n        <source>&amp;清除所有标注</source>\n        <translation>&amp; مسح جميع الشروح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"328\"/>\n        <source>清除所有标注信息</source>\n        <translation>مسح جميع معلومات الشرح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"331\"/>\n        <source>&amp;撤销</source>\n        <translation>إلغاء</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"335\"/>\n        <source>撤销一次点击</source>\n        <translation>إلغاء بنقرة واحدة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"338\"/>\n        <source>&amp;重做</source>\n        <translation>و إعادة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"342\"/>\n        <source>重做一次点击</source>\n        <translation>انقر فوق إعادة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"345\"/>\n        <source>&amp;删除多边形</source>\n        <translation>&amp; حذف المضلعات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"349\"/>\n        <source>删除当前选中的多边形</source>\n        <translation>حذف المضلع المحدد حاليا</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"352\"/>\n        <source>&amp;删除所有多边形</source>\n        <translation>&amp; حذف جميع المضلعات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"356\"/>\n        <source>删除所有的多边形</source>\n        <translation>حذف جميع المضلعات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"359\"/>\n        <source>&amp;保留最大连通块</source>\n        <translation>&amp; الحفاظ على معظم كتل داليان تونغ</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"363\"/>\n        <source>保留最大的连通块</source>\n        <translation>الحفاظ على أقصى قدر من كتلة متصلة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"367\"/>\n        <source>&amp;标签和图像使用相同拓展名</source>\n        <translation>&amp; العلامات والصور باستخدام نفس التمديد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"371\"/>\n        <source>标签和图像使用相同拓展名，用于图像中有文件名相同但拓展名不同的情况，防止标签覆盖</source>\n        <translation>التسمية والصورة استخدام نفس اسم التمديد ، في صورة لها نفس اسم الملف ولكن تمديد اسم مختلف ، ومنع التسمية من الكتابة .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"375\"/>\n        <source>&amp;伪彩色保存</source>\n        <translation>&amp; حفظ الألوان الزائفة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"379\"/>\n        <source>保存为伪彩色图像</source>\n        <translation>حفظ الصورة الملونة الزائفة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"384\"/>\n        <source>&amp;灰度保存</source>\n        <translation>&amp; حفظ رمادي</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"388\"/>\n        <source>保存为灰度图像，像素的灰度为对应类型的标签</source>\n        <translation>حفظ الصورة الرمادية ، رمادي بكسل هو نوع من التسمية المقابلة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"393\"/>\n        <source>&amp;JSON保存</source>\n        <translation>جسون حفظ</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"397\"/>\n        <source>保存为JSON格式</source>\n        <translation>حفظ جسون</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"402\"/>\n        <source>&amp;COCO保存</source>\n        <translation>حفظ كوكو</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"406\"/>\n        <source>保存为COCO格式</source>\n        <translation>حفظ كوكو</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"411\"/>\n        <source>&amp;抠图保存</source>\n        <translation>&amp; حفظ حصيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"415\"/>\n        <source>只保留前景，背景设置为背景色</source>\n        <translation>الحفاظ على الصدارة فقط ، تعيين الخلفية إلى لون الخلفية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"420\"/>\n        <source>&amp;设置抠图背景色</source>\n        <translation>&amp; تعيين لون الخلفية حصيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"424\"/>\n        <source>抠图后背景像素的颜色</source>\n        <translation>لون الخلفية بكسل بعد حصيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"427\"/>\n        <source>&amp;关闭</source>\n        <translation>إغلاق</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"431\"/>\n        <source>关闭当前图像</source>\n        <translation>إغلاق الصورة الحالية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"434\"/>\n        <source>&amp;退出</source>\n        <translation>خروج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"438\"/>\n        <source>退出软件</source>\n        <translation>الخروج من البرنامج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"441\"/>\n        <source>&amp;导出标签列表</source>\n        <translation>&amp; تصدير قائمة العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"445\"/>\n        <source>将标签列表导出成标签配置文件</source>\n        <translation>تصدير قائمة التسمية إلى تسمية ملف التكوين</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"448\"/>\n        <source>&amp;载入标签列表</source>\n        <translation>&amp; تحميل قائمة العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"452\"/>\n        <source>从标签配置文件载入标签列表</source>\n        <translation>تحميل قائمة من العلامات العلامات من ملف التكوين</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"455\"/>\n        <source>&amp;清空标签列表</source>\n        <translation>&amp; مسح قائمة التبويب</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"459\"/>\n        <source>清空所有的标签</source>\n        <translation>مسح جميع العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"462\"/>\n        <source>&amp;清除近期文件记录</source>\n        <translation>&amp; مسح الملفات الأخيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"466\"/>\n        <source>清除近期标注文件记录</source>\n        <translation>مسح سجل ملف الشرح الأخيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"469\"/>\n        <source>&amp;模型选择</source>\n        <translation>&amp; اختيار النموذج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"473\"/>\n        <source>隐藏/展示模型选择面板</source>\n        <translation>إخفاء / عرض نموذج اختيار الفريق</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"477\"/>\n        <source>&amp;数据列表</source>\n        <translation>&amp; قائمة البيانات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"481\"/>\n        <source>隐藏/展示数据列表面板</source>\n        <translation>إخفاء / عرض لوحة قائمة البيانات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"485\"/>\n        <source>&amp;标签列表</source>\n        <translation>&amp; قائمة العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"489\"/>\n        <source>隐藏/展示标签列表面板</source>\n        <translation>إخفاء / عرض قائمة التبويب لوحة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"493\"/>\n        <source>&amp;分割设置</source>\n        <translation>&amp; إعدادات التقسيم</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"497\"/>\n        <source>隐藏/展示分割设置面板</source>\n        <translation>إخفاء / عرض تقسيم لوحة إعدادات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"501\"/>\n        <source>&amp;遥感设置</source>\n        <translation>&amp; إعدادات الاستشعار عن بعد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"505\"/>\n        <source>隐藏/展示遥感设置面板</source>\n        <translation>إخفاء / عرض إعدادات الاستشعار عن بعد لوحة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"509\"/>\n        <source>&amp;医疗设置</source>\n        <translation>&amp; إعدادات طبية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"513\"/>\n        <source>隐藏/展示医疗设置面板</source>\n        <translation>إخفاء / عرض لوحة الإعدادات الطبية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"517\"/>\n        <source>&amp;N2宫格标注</source>\n        <translation>و N2</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"521\"/>\n        <source>隐藏/展示N^2宫格细粒度标注面板</source>\n        <translation>إخفاء / عرض ن ^ 2 غرامة الحبيبات لوحة الشرح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"525\"/>\n        <source>&amp;快速入门</source>\n        <translation>&amp; بداية سريعة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"529\"/>\n        <source>主要功能使用介绍</source>\n        <translation>الوظيفة الرئيسية مقدمة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"532\"/>\n        <source>&amp;反馈问题</source>\n        <translation>&amp; أسئلة التغذية المرتدة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"536\"/>\n        <source>通过Github Issue反馈使用过程中遇到的问题。我们会尽快进行修复</source>\n        <translation>المشاكل التي واجهتها في استخدام ردود الفعل من خلال github issue . . . . . . . ونحن سوف إصلاح ذلك في أقرب وقت ممكن .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"539\"/>\n        <source>&amp;编辑快捷键</source>\n        <translation>&amp; تحرير اختصارات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"543\"/>\n        <source>编辑软件快捷键</source>\n        <translation>تحرير اختصارات البرامج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"546\"/>\n        <source>&amp;调试日志</source>\n        <translation>&amp; سجل التصحيح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"550\"/>\n        <source>用于观察软件执行过程和进行debug。我们不会自动收集任何日志，可能会希望您在反馈问题时间打开此功能，帮助我们定位问题。</source>\n        <translation>تستخدم لمراقبة تنفيذ البرامج وإجراء التصحيحات . نحن لا تلقائيا جمع أي سجلات ، قد ترغب في فتح هذه الميزة في الوقت المناسب للحصول على التغذية المرتدة من الأسئلة لمساعدتنا في تحديد المشكلة .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"653\"/>\n        <source>文件</source>\n        <translation>الوثائق</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"654\"/>\n        <source>标注</source>\n        <translation>الشرح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"655\"/>\n        <source>功能</source>\n        <translation>وظيفة .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"656\"/>\n        <source>显示</source>\n        <translation>عرض .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"657\"/>\n        <source>帮助</source>\n        <translation>ساعد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"566\"/>\n        <source>近期文件</source>\n        <translation>الوثائق الأخيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"567\"/>\n        <source>近期模型及参数</source>\n        <translation>نموذج المعلمة الأخيرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"697\"/>\n        <source>切换语言</source>\n        <translation>تبديل اللغة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"697\"/>\n        <source>切换语言需要重启软件才能生效</source>\n        <translation>لغة التبديل يتطلب إعادة تشغيل البرنامج قبل أن يصبح نافذا</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"713\"/>\n        <source>无近期文件</source>\n        <translation>لا توجد وثائق حديثة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"729\"/>\n        <source>已清除最近打开文件</source>\n        <translation>تم مسح الملفات المفتوحة مؤخرا</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"749\"/>\n        <source>无近期模型记录</source>\n        <translation>لا يوجد سجل النموذج الحالي</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"761\"/>\n        <source>Paddle静态模型权重文件(*.pdiparams)</source>\n        <translation>بادل نموذج ثابت الوزن الملف (*.pdiparams)</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"768\"/>\n        <source>选择模型参数</source>\n        <translation>اختيار نموذج المعلمة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"777\"/>\n        <source>参数路径存在中文</source>\n        <translation>المعلمة المسار موجود في الصينية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"777\"/>\n        <source>请修改参数路径为非中文路径！</source>\n        <translation>يرجى تعديل مسار المعلمة غير الصينية المسار !</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"800\"/>\n        <source> 模型加载成功</source>\n        <translation>تحميل النموذج بنجاح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"813\"/>\n        <source>掩膜已启用</source>\n        <translation>قناع تمكين</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"815\"/>\n        <source>掩膜已关闭</source>\n        <translation>قناع مغلق</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"819\"/>\n        <source>没有最近使用模型信息，请加载模型</source>\n        <translation>لا يوجد نموذج المعلومات المستخدمة مؤخرا ، يرجى تحميل النموذج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"828\"/>\n        <location filename=\"../../eiseg/app.py\" line=\"847\"/>\n        <source>标签配置文件</source>\n        <translation>تسمية ملف التكوين</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"831\"/>\n        <source>选择标签配置文件路径</source>\n        <translation>حدد مسار ملف التسمية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"844\"/>\n        <source>没有需要保存的标签</source>\n        <translation>لا تحتاج إلى حفظ العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"844\"/>\n        <source>请先添加标签之后再进行保存！</source>\n        <translation>يرجى إضافة علامة قبل حفظ !</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"848\"/>\n        <source>保存标签配置文件</source>\n        <translation>حفظ العلامة الشخصية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"853\"/>\n        <source>选择保存标签配置文件路径</source>\n        <translation>حدد مسار حفظ العلامة الشخصية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"896\"/>\n        <source>清空标签列表?</source>\n        <translation>مسح قائمة العلامات ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"897\"/>\n        <source>请确认是否要清空标签列表</source>\n        <translation>تأكد من أن كنت تريد إفراغ قائمة العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1012\"/>\n        <source>确认删除？</source>\n        <translation>تأكيد حذف ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1013\"/>\n        <source>确认要删除当前选中多边形标注？</source>\n        <translation>هل أنت متأكد أنك تريد حذف المضلع المحدد حاليا الشرح ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1078\"/>\n        <source>选择待标注图片</source>\n        <translation>حدد الصورة إلى علامة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1105\"/>\n        <source>选择待标注图片文件夹</source>\n        <translation>حدد مجلد الصور إلى علامة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1181\"/>\n        <source>未启用医疗组件</source>\n        <translation>العنصر الطبي لا يمكن</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1182\"/>\n        <source>加载医疗影像需启用医疗组件，是否立即启用？</source>\n        <translation>المكونات الطبية اللازمة لتحميل الصور الطبية . هل تريد تمكين الآن ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1202\"/>\n        <source>未打开遥感组件</source>\n        <translation>الاستشعار عن بعد عنصر لم تفتح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1203\"/>\n        <source>打开遥感图像需启用遥感组件，是否立即启用？</source>\n        <translation>فتح صور الاستشعار عن بعد يتطلب تمكين الاستشعار عن بعد عنصر . هل تريد تمكين الآن ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1213\"/>\n        <source>图像过大</source>\n        <translation>صورة كبيرة جدا</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1213\"/>\n        <source>图像过大，将启用宫格功能！</source>\n        <translation>الصورة هي كبيرة جدا ، وسوف تمكين وظيفة الشبكة !</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1230\"/>\n        <source>无</source>\n        <translation>لا</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1347\"/>\n        <source>模型未加载</source>\n        <translation>نموذج لا تحميل</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1347\"/>\n        <source>尚未加载模型，请先加载模型！</source>\n        <translation>لم يتم تحميل النموذج ، يرجى تحميل النموذج الأول !</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1387\"/>\n        <source>完成最后一个目标？</source>\n        <translation>تحقيق الهدف النهائي ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1388\"/>\n        <source>是否完成最后一个目标的标注，不完成不会进行保存。</source>\n        <translation>إذا كان الهدف النهائي هو الانتهاء من الشرح ، لا يتم حفظها .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1408\"/>\n        <source>保存标签？</source>\n        <translation>حفظ العلامات ؟</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1409\"/>\n        <source>标签尚未保存，是否保存标签</source>\n        <translation>التسمية لم يتم حفظها ، حفظ التسمية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1552\"/>\n        <source>标签成功保存至</source>\n        <translation>حفظ التسمية بنجاح</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1562\"/>\n        <source>保存标签文件路径</source>\n        <translation>حفظ مسار ملف التسمية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1572\"/>\n        <source>选择标签文件保存路径</source>\n        <translation>حدد مسار حفظ ملف التسمية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1592\"/>\n        <source>选择标签保存路径</source>\n        <translation>اختر علامة حفظ المسار</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1688\"/>\n        <source>未选择模型</source>\n        <translation>لا اختيار النموذج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1688\"/>\n        <source>尚未选择模型，请先在右上角选择模型</source>\n        <translation>لم يتم اختيار النموذج ، حدد النموذج الأول في الزاوية اليمنى العليا</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1695\"/>\n        <source>未选择当前标签</source>\n        <translation>التسمية الحالية لم يتم اختياره</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1695\"/>\n        <source>请先在标签列表中单击点选标签</source>\n        <translation>انقر فوق علامة التبويب في قائمة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1803\"/>\n        <source>无法导入GDAL</source>\n        <translation>غير قادر على استيراد gdal</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1804\"/>\n        <source>使用遥感工具需要安装GDAL！</source>\n        <translation>استخدام أدوات الاستشعار عن بعد تحتاج إلى تثبيت GDAL !</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1807\"/>\n        <source>打开遥感工具失败，请安装GDAL库</source>\n        <translation>فشل في فتح أداة الاستشعار عن بعد ، يرجى تثبيت مكتبة جدال</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1814\"/>\n        <source>无法导入SimpleITK</source>\n        <translation>لا يمكن استيراد simpleitk</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1815\"/>\n        <source>使用医疗工具需要安装SimpleITK！</source>\n        <translation>استخدام الأدوات الطبية تحتاج إلى تثبيت simpleitk !</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1818\"/>\n        <source>打开医疗工具失败，请安装SimpleITK</source>\n        <translation>فشل في فتح الأداة الطبية ، الرجاء تثبيت simpleitk</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"2052\"/>\n        <source>图像过大，已显示缩略图</source>\n        <translation>الصورة كبيرة جدا ، وقد تم عرض الصور المصغرة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"2159\"/>\n        <source>功能尚在开发</source>\n        <translation>وظيفة لا تزال قيد التطوير</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"57\"/>\n        <source>编辑快捷键</source>\n        <translation>تحرير اختصارات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"114\"/>\n        <source>快捷键冲突</source>\n        <translation>اختصارات الصراع</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"119\"/>\n        <source>快捷键已被</source>\n        <translation>اختصارات تم</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"123\"/>\n        <source>使用，请设置其他快捷键或先修改</source>\n        <translation>استخدام مجموعة أخرى من مفاتيح الاختصار أو تعديل</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"127\"/>\n        <source>的快捷键</source>\n        <translation>مفاتيح الاختصار</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"112\"/>\n        <source>加载网络参数</source>\n        <translation>تحميل معلمات الشبكة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"118\"/>\n        <source>使用掩膜</source>\n        <translation>استخدام قناع</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"122\"/>\n        <source>模型选择</source>\n        <translation>اختيار النموذج</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"138\"/>\n        <source>数据列表</source>\n        <translation>قائمة البيانات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"163\"/>\n        <source>添加标签</source>\n        <translation>إضافة علامة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"167\"/>\n        <source>标签列表</source>\n        <translation>قائمة العلامات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"178\"/>\n        <source>分割阈值：</source>\n        <translation>تجزئة العتبة :</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"183\"/>\n        <source>标签透明度：</source>\n        <translation>علامة الشفافية :</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"189\"/>\n        <source>点击可视化半径：</source>\n        <translation>انقر فوق دائرة نصف قطرها البصرية :</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"196\"/>\n        <source>保存</source>\n        <translation>حفظ .</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"202\"/>\n        <source>分割设置</source>\n        <translation>تقسيم إعدادات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"209\"/>\n        <source>波段设置</source>\n        <translation>وضع الفرقة</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"224\"/>\n        <source>保存设置</source>\n        <translation>حفظ الإعدادات</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"226\"/>\n        <source>使用建筑边界简化</source>\n        <translation>تبسيط استخدام الحدود المعمارية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"229\"/>\n        <source>额外保存为ESRI Shapefile</source>\n        <translation>حفظ إضافية كما esri shapefile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"233\"/>\n        <source>地理信息</source>\n        <translation>معلومات جغرافية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"239\"/>\n        <source>遥感设置</source>\n        <translation>إعدادات الاستشعار عن بعد</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"272\"/>\n        <source>医疗设置</source>\n        <translation>الإعداد الطبي</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"287\"/>\n        <source>完成宫格</source>\n        <translation>الانتهاء من قصر شعرية</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"316\"/>\n        <source>宫格切换</source>\n        <translation>قصر التبديل</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "tool/ts/English.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"en\" sourcelanguage=\"zh\">\n<context>\n    <name>APP_EISeg</name>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"250\"/>\n        <source>&amp;打开图像</source>\n        <translation>&amp;Open Image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"254\"/>\n        <source>打开一张图像进行标注</source>\n        <translation>Open an image for annotation</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"257\"/>\n        <source>&amp;打开文件夹</source>\n        <translation>&amp;Open Dir</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"261\"/>\n        <source>打开一个文件夹下所有的图像进行标注</source>\n        <translation>Open all images in a folder for annotation</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"264\"/>\n        <source>&amp;改变标签保存路径</source>\n        <translation>&amp;Change Output Dir</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"268\"/>\n        <source>改变标签保存的文件夹路径</source>\n        <translation>Change the folder where labels are saved</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"271\"/>\n        <source>&amp;加载模型参数</source>\n        <translation>&amp;Load Model Parameters</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"275\"/>\n        <source>加载一个模型参数</source>\n        <translation>Load a model parameter</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"278\"/>\n        <source>&amp;保存</source>\n        <translation>&amp;Save</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"282\"/>\n        <source>保存图像标签</source>\n        <translation>Save image label</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"285\"/>\n        <source>&amp;另存为</source>\n        <translation>&amp;Save as</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"289\"/>\n        <source>在指定位置另存为标签</source>\n        <translation>Save as label at the specified location</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"292\"/>\n        <source>&amp;自动保存</source>\n        <translation>&amp;Auto Save</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"296\"/>\n        <source>翻页同时自动保存</source>\n        <translation>Save automatically while turning image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"303\"/>\n        <source>&amp;上一张</source>\n        <translation>&amp;Prev Image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"307\"/>\n        <source>翻到上一张图片</source>\n        <translation>Filp to previous image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"310\"/>\n        <source>&amp;下一张</source>\n        <translation>&amp;Next Image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"314\"/>\n        <source>翻到下一张图片</source>\n        <translation>Flip to the next image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"317\"/>\n        <source>&amp;完成当前目标</source>\n        <translation>&amp;Finish Current Target</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"321\"/>\n        <source>完成当前目标的标注</source>\n        <translation>Finish labeling the current object</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"324\"/>\n        <source>&amp;清除所有标注</source>\n        <translation>&amp;Clear All Labels</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"328\"/>\n        <source>清除所有标注信息</source>\n        <translation>Clear all labels in the image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"331\"/>\n        <source>&amp;撤销</source>\n        <translation>&amp;Undo</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"335\"/>\n        <source>撤销一次点击</source>\n        <translation>Undo one click</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"338\"/>\n        <source>&amp;重做</source>\n        <translation>&amp;Redo</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"342\"/>\n        <source>重做一次点击</source>\n        <translation>Redo one click</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"345\"/>\n        <source>&amp;删除多边形</source>\n        <translation>&amp;Delete Polygon</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"349\"/>\n        <source>删除当前选中的多边形</source>\n        <translation>Deletes the currently selected polygon</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"352\"/>\n        <source>&amp;删除所有多边形</source>\n        <translation>&amp;Delete all polygons</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"356\"/>\n        <source>删除所有的多边形</source>\n        <translation>Delete all polygons</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"359\"/>\n        <source>&amp;保留最大连通块</source>\n        <translation>&amp;Filter LCC</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"363\"/>\n        <source>保留最大的连通块</source>\n        <translation>Keep the largest connected component only</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"367\"/>\n        <source>&amp;标签和图像使用相同拓展名</source>\n        <translation>&amp;Use same extension name</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"371\"/>\n        <source>标签和图像使用相同拓展名，用于图像中有文件名相同但拓展名不同的情况，防止标签覆盖</source>\n        <translation>The label and image use the same extension name, which is used to prevent the label from being overwritten when the file name in the image is the same but the extension name is different</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"375\"/>\n        <source>&amp;伪彩色保存</source>\n        <translation>&amp;Pseudo Color Format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"379\"/>\n        <source>保存为伪彩色图像</source>\n        <translation>Save label in pseudo color format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"384\"/>\n        <source>&amp;灰度保存</source>\n        <translation>&amp;Grayscale Format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"388\"/>\n        <source>保存为灰度图像，像素的灰度为对应类型的标签</source>\n        <translation>Save label in grayscale format, the value of each pixel is the id for the label category of the pixel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"393\"/>\n        <source>&amp;JSON保存</source>\n        <translation>&amp;JSON Format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"397\"/>\n        <source>保存为JSON格式</source>\n        <translation>Save polygon information in JSON format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"402\"/>\n        <source>&amp;COCO保存</source>\n        <translation>&amp;Coco Format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"406\"/>\n        <source>保存为COCO格式</source>\n        <translation>Save polygon information in coco format</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"409\"/>\n        <source>&amp;显示遥感多边形</source>\n        <translation>&amp;Display RS polygons</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"409\"/>\n        <source>显示遥感大图多边形</source>\n        <translation>Display RS large polygon</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"411\"/>\n        <source>&amp;抠图保存</source>\n        <translation>&amp;Save Matting</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"415\"/>\n        <source>只保留前景，背景设置为背景色</source>\n        <translation>Only keep foreground pixels, set all background pixels to background color</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"420\"/>\n        <source>&amp;设置抠图背景色</source>\n        <translation>&amp;Set matting background color</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"424\"/>\n        <source>抠图后背景像素的颜色</source>\n        <translation>The color to use for all background pixels</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"442\"/>\n        <source>&amp;设置十字丝颜色</source>\n        <translation>&amp;Set cross wire color</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"447\"/>\n        <source>十字丝的显示颜色</source>\n        <translation>The color of crosshair</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"427\"/>\n        <source>&amp;关闭</source>\n        <translation>&amp;Close</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"431\"/>\n        <source>关闭当前图像</source>\n        <translation>Close current image</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"434\"/>\n        <source>&amp;退出</source>\n        <translation>&amp;Exit</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"438\"/>\n        <source>退出软件</source>\n        <translation>Exit software</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"441\"/>\n        <source>&amp;导出标签列表</source>\n        <translation>&amp;Export label list</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"445\"/>\n        <source>将标签列表导出成标签配置文件</source>\n        <translation>Export label list to label profile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"448\"/>\n        <source>&amp;载入标签列表</source>\n        <translation>&amp;Load label list</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"452\"/>\n        <source>从标签配置文件载入标签列表</source>\n        <translation>Load label list from label profile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"455\"/>\n        <source>&amp;清空标签列表</source>\n        <translation>&amp;Clear Label List</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"459\"/>\n        <source>清空所有的标签</source>\n        <translation>Clear all labels</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"462\"/>\n        <source>&amp;清除近期文件记录</source>\n        <translation>&amp;Clear recent file records</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"466\"/>\n        <source>清除近期标注文件记录</source>\n        <translation>Clear recent annotation file records</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"469\"/>\n        <source>&amp;模型选择</source>\n        <translation>&amp;Model Selection</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"473\"/>\n        <source>隐藏/展示模型选择面板</source>\n        <translation>Hide / show model selection panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"477\"/>\n        <source>&amp;数据列表</source>\n        <translation>&amp;Image List</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"481\"/>\n        <source>隐藏/展示数据列表面板</source>\n        <translation>Hide / show data list panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"485\"/>\n        <source>&amp;标签列表</source>\n        <translation>&amp;Label List</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"489\"/>\n        <source>隐藏/展示标签列表面板</source>\n        <translation>Hide / show label list panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"493\"/>\n        <source>&amp;分割设置</source>\n        <translation>&amp;Segmentation Setting</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"497\"/>\n        <source>隐藏/展示分割设置面板</source>\n        <translation>Hide / show split settings panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"501\"/>\n        <source>&amp;遥感设置</source>\n        <translation>&amp;Remote sensing settings</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"505\"/>\n        <source>隐藏/展示遥感设置面板</source>\n        <translation>Hide / show remote sensing settings panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"509\"/>\n        <source>&amp;医疗设置</source>\n        <translation>&amp;Medical settings</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"513\"/>\n        <source>隐藏/展示医疗设置面板</source>\n        <translation>Hide / show medical settings panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"517\"/>\n        <source>&amp;N2宫格标注</source>\n        <translation>&amp;N2 grid label</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"521\"/>\n        <source>隐藏/展示N^2宫格细粒度标注面板</source>\n        <translation>Hide / show n ^ 2 grid fine-grained dimension panel</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"525\"/>\n        <source>&amp;快速入门</source>\n        <translation>&amp;Quick start</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"529\"/>\n        <source>主要功能使用介绍</source>\n        <translation>Introduction to main functions</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"532\"/>\n        <source>&amp;反馈问题</source>\n        <translation>&amp;Feedback questions</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"536\"/>\n        <source>通过Github Issue反馈使用过程中遇到的问题。我们会尽快进行修复</source>\n        <translation>Feed back the problems encountered during use through GitHub issue. We will repair it as soon as possible</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"539\"/>\n        <source>&amp;编辑快捷键</source>\n        <translation>&amp;Edit Shortcuts</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"543\"/>\n        <source>编辑软件快捷键</source>\n        <translation>Edit software shortcuts</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"546\"/>\n        <source>&amp;调试日志</source>\n        <translation>&amp;Debug log</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"550\"/>\n        <source>用于观察软件执行过程和进行debug。我们不会自动收集任何日志，可能会希望您在反馈问题时间打开此功能，帮助我们定位问题。</source>\n        <translation>It is used to observe the software execution process and debug. We don&apos;t automatically collect any logs. We may want you to turn on this function when you feed back the problem to help us locate the problem.</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"551\"/>\n        <source>&amp;使用QT文件窗口</source>\n        <translation>Use QT file window</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"551\"/>\n        <source>如果使用文件选择窗口时遇到问题可以选择使用Qt窗口</source>\n        <translation>If you encounter problems using the file selection window, you can choose to use the QT window.</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"578\"/>\n        <source>语言</source>\n        <translation>Language</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"653\"/>\n        <source>文件</source>\n        <translation>File</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"654\"/>\n        <source>标注</source>\n        <translation>Annotation</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"655\"/>\n        <source>功能</source>\n        <translation>Functions</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"656\"/>\n        <source>显示</source>\n        <translation>Display</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"657\"/>\n        <source>帮助</source>\n        <translation>Help</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"566\"/>\n        <source>近期文件</source>\n        <translation>Recent documents</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"567\"/>\n        <source>近期模型及参数</source>\n        <translation>Recent models and parameters</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"697\"/>\n        <source>切换语言</source>\n        <translation>Changing language</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"697\"/>\n        <source>切换语言需要重启软件才能生效</source>\n        <translation>Changing language only takes effect after restarting the app</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"713\"/>\n        <source>无近期文件</source>\n        <translation>No recent documents</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"729\"/>\n        <source>已清除最近打开文件</source>\n        <translation>Recently opened files have been cleared</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"749\"/>\n        <source>无近期模型记录</source>\n        <translation>No recent model parameters</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"761\"/>\n        <source>Paddle静态模型权重文件(*.pdiparams)</source>\n        <translation>Paddle static model weight files (*.pdiparams)</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"768\"/>\n        <source>选择模型参数</source>\n        <translation>Select model parameters</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"777\"/>\n        <source>参数路径存在中文</source>\n        <translation>Parameter path exists in Chinese</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"777\"/>\n        <source>请修改参数路径为非中文路径！</source>\n        <translation>Please change the parameter path to non Chinese path!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"800\"/>\n        <source> 模型加载成功</source>\n        <translation>Model loaded successfully</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"813\"/>\n        <source>掩膜已启用</source>\n        <translation>Mask enabled</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"815\"/>\n        <source>掩膜已关闭</source>\n        <translation>Mask closed</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"819\"/>\n        <source>没有最近使用模型信息，请加载模型</source>\n        <translation>There is no recently used model information, please load the model</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"828\"/>\n        <location filename=\"../../eiseg/app.py\" line=\"847\"/>\n        <source>标签配置文件</source>\n        <translation>Label profile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"831\"/>\n        <source>选择标签配置文件路径</source>\n        <translation>Select label profile path</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"844\"/>\n        <source>没有需要保存的标签</source>\n        <translation>There are no labels to save</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"844\"/>\n        <source>请先添加标签之后再进行保存！</source>\n        <translation>Please add a label before saving!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"848\"/>\n        <source>保存标签配置文件</source>\n        <translation>Save label profile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"853\"/>\n        <source>选择保存标签配置文件路径</source>\n        <translation>Select the path to save the label profile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"896\"/>\n        <source>清空标签列表?</source>\n        <translation>Clear label list?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"897\"/>\n        <source>请确认是否要清空标签列表</source>\n        <translation>Please confirm you want to clear the label list</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1012\"/>\n        <source>确认删除？</source>\n        <translation>Confirm deletion?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1013\"/>\n        <source>确认要删除当前选中多边形标注？</source>\n        <translation>Are you sure you want to delete the currently selected polygon dimension?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1078\"/>\n        <source>选择待标注图片</source>\n        <translation>Select the image to be labeled</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1105\"/>\n        <source>选择待标注图片文件夹</source>\n        <translation>Select the image folder to label</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1181\"/>\n        <source>未启用医疗组件</source>\n        <translation>Medical components are not enabled</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1182\"/>\n        <source>加载医疗影像需启用医疗组件，是否立即启用？</source>\n        <translation>Loading medical images requires enabling medical components. Do you want to enable them now?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1202\"/>\n        <source>未打开遥感组件</source>\n        <translation>Remote sensing component not open</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1203\"/>\n        <source>打开遥感图像需启用遥感组件，是否立即启用？</source>\n        <translation>The remote sensing component needs to be enabled to open the remote sensing image. Do you want to enable it now?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1238\"/>\n        <source>● 波段数：</source>\n        <translation>● Number of bands: </translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1238\"/>\n        <source>● 数据类型：</source>\n        <translation>● Data type: </translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1238\"/>\n        <source>● 行数：</source>\n        <translation>● Number of rows: </translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1238\"/>\n        <source>● 列数：</source>\n        <translation>● Number of columns: </translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1213\"/>\n        <source>图像过大</source>\n        <translation>The image is too large</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1213\"/>\n        <source>图像过大，将启用宫格功能！</source>\n        <translation>If the image is too large, the grid function will be enabled!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1230\"/>\n        <source>无</source>\n        <translation>Na</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1347\"/>\n        <source>模型未加载</source>\n        <translation>Model not loaded</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1347\"/>\n        <source>尚未加载模型，请先加载模型！</source>\n        <translation>The model has not been loaded, please load the model first!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1387\"/>\n        <source>完成最后一个目标？</source>\n        <translation>Finish the last goal?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1388\"/>\n        <source>是否完成最后一个目标的标注，不完成不会进行保存。</source>\n        <translation>Whether to complete the annotation of the last target. If not, it will not be saved.</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1408\"/>\n        <source>保存标签？</source>\n        <translation>Save label?</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1409\"/>\n        <source>标签尚未保存，是否保存标签</source>\n        <translation>The label has not been saved. Do you want to save the label</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1552\"/>\n        <source>标签成功保存至</source>\n        <translation>Label successfully saved to</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1562\"/>\n        <source>保存标签文件路径</source>\n        <translation>Save label file path</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1572\"/>\n        <source>选择标签文件保存路径</source>\n        <translation>Select the path to save the label file</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1592\"/>\n        <source>选择标签保存路径</source>\n        <translation>Select the folder to save labels</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1688\"/>\n        <source>未选择模型</source>\n        <translation>Model not selected</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1688\"/>\n        <source>尚未选择模型，请先在右上角选择模型</source>\n        <translation>model not selected. Please select the model in the upper right corner first</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1695\"/>\n        <source>未选择当前标签</source>\n        <translation>The current label is not selected</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1695\"/>\n        <source>请先在标签列表中单击点选标签</source>\n        <translation>Please click the label in the label list first</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1803\"/>\n        <source>无法导入GDAL</source>\n        <translation>Unable to import GDAL</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1804\"/>\n        <source>使用遥感工具需要安装GDAL！</source>\n        <translation>GDAL needs to be installed to use remote sensing tools!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1807\"/>\n        <source>打开遥感工具失败，请安装GDAL库</source>\n        <translation>Failed to open remote sensing tool. Please install GDAL library</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1814\"/>\n        <source>无法导入SimpleITK</source>\n        <translation>Cannot import simpleitk</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1815\"/>\n        <source>使用医疗工具需要安装SimpleITK！</source>\n        <translation>Simpleitk needs to be installed to use medical tools!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"1818\"/>\n        <source>打开医疗工具失败，请安装SimpleITK</source>\n        <translation>Failed to open medical tool, please install simpleitk</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"2052\"/>\n        <source>图像过大，已显示缩略图</source>\n        <translation>The image is too large and thumbnails are displayed</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/app.py\" line=\"2159\"/>\n        <source>功能尚在开发</source>\n        <translation>The function is still under development</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"57\"/>\n        <source>编辑快捷键</source>\n        <translation>Edit Keyboard Shortcuts</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"114\"/>\n        <source>快捷键冲突</source>\n        <translation>Shortcut key conflict</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"119\"/>\n        <source>快捷键已被</source>\n        <translation>shortcut has been used by</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"123\"/>\n        <source>使用，请设置其他快捷键或先修改</source>\n        <translation>. Please set another key sequence or modify the keyboard shotcut of</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/widget/shortcut.py\" line=\"127\"/>\n        <source>的快捷键</source>\n        <translation>first!</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"112\"/>\n        <source>加载网络参数</source>\n        <translation>Load Model Parameter</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"118\"/>\n        <source>使用掩膜</source>\n        <translation>Use mask</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"122\"/>\n        <source>模型选择</source>\n        <translation>Model selection</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"138\"/>\n        <source>数据列表</source>\n        <translation>Image List</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"163\"/>\n        <source>添加标签</source>\n        <translation>Add Label</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"167\"/>\n        <source>标签列表</source>\n        <translation>Label List</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"178\"/>\n        <source>分割阈值：</source>\n        <translation>Segmentation Threshold:</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"183\"/>\n        <source>标签透明度：</source>\n        <translation>Label Transparency:</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"189\"/>\n        <source>点击可视化半径：</source>\n        <translation>Click Visualization Radius:</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"196\"/>\n        <source>保存</source>\n        <translation>Save</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"202\"/>\n        <source>分割设置</source>\n        <translation>Segmentation Setting</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"209\"/>\n        <source>波段设置</source>\n        <translation>Band setting</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"224\"/>\n        <source>保存设置</source>\n        <translation>Save settings</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"226\"/>\n        <source>建筑边界规范化</source>\n        <translation>building boundary normalization</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"229\"/>\n        <source>另存为shapefile</source>\n        <translation>Save extra as shapefile</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"233\"/>\n        <source>地理信息</source>\n        <translation>geographic information</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"239\"/>\n        <source>遥感设置</source>\n        <translation>Remote sensing settings</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"272\"/>\n        <source>医疗设置</source>\n        <translation>Medical settings</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"287\"/>\n        <source>完成宫格</source>\n        <translation>Complete the grid</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"291\"/>\n        <source>保存每个宫格的标签</source>\n        <translation>Save all of grids</translation>\n    </message>\n    <message>\n        <location filename=\"../../eiseg/ui.py\" line=\"316\"/>\n        <source>宫格切换</source>\n        <translation>Palace grid switching</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "tool/update_md5.py",
    "content": "# Copyright (c) 2021 PaddlePaddle Authors. 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\n\nimport hashlib\nfrom pathlib import Path\n\nmodels_dir = Path()\next = \".pdparams\"\nfor model_path in models_dir.glob(\"*/*\" + ext):\n    md5 = hashlib.md5(model_path.read_bytes()).hexdigest()\n    md5_path = str(model_path)[: -len(ext)] + \".md5\"\n    Path(md5_path).write_text(md5)\n"
  }
]