Full Code of PaddleCV-SIG/EISeg for AI

develop bbb42e97ccbe cached
98 files
461.0 KB
124.3k tokens
558 symbols
1 requests
Download .txt
Showing preview only (510K chars total). Download the full file or copy to clipboard to get everything.
Repository: PaddleCV-SIG/EISeg
Branch: develop
Commit: bbb42e97ccbe
Files: 98
Total size: 461.0 KB

Directory structure:
gitextract_x7oiqy62/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── --bug.md
│       └── feature_request.md
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── README_EN.md
├── docs/
│   ├── medical.md
│   ├── medical_en.md
│   ├── remote_sensing.md
│   ├── remote_sensing_en.md
│   └── tools.md
├── eiseg/
│   ├── __init__.py
│   ├── __main__.py
│   ├── app.py
│   ├── config/
│   │   ├── colormap.txt
│   │   └── config.yaml
│   ├── controller.py
│   ├── exe.py
│   ├── inference/
│   │   ├── __init__.py
│   │   ├── clicker.py
│   │   ├── predictor/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── ops.py
│   │   └── transforms/
│   │       ├── __init__.py
│   │       ├── base.py
│   │       ├── crops.py
│   │       ├── flip.py
│   │       ├── limit_longest_side.py
│   │       └── zoom_in.py
│   ├── models.py
│   ├── plugin/
│   │   ├── __init__.py
│   │   ├── medical/
│   │   │   ├── __init__.py
│   │   │   └── med.py
│   │   ├── n2grid/
│   │   │   ├── __init__.py
│   │   │   ├── grid.py
│   │   │   └── rs_grid.py
│   │   └── remotesensing/
│   │       ├── __init__.py
│   │       ├── imgtools.py
│   │       ├── raster.py
│   │       └── shape.py
│   ├── run.py
│   ├── ui.py
│   ├── util/
│   │   ├── __init__.py
│   │   ├── coco/
│   │   │   ├── __init__.py
│   │   │   ├── _mask.pyx
│   │   │   ├── coco.py
│   │   │   ├── cocoeval.py
│   │   │   ├── common/
│   │   │   │   ├── gason.cpp
│   │   │   │   ├── gason.h
│   │   │   │   ├── maskApi.c
│   │   │   │   └── maskApi.h
│   │   │   └── mask.py
│   │   ├── coco.py.bk
│   │   ├── colormap.py
│   │   ├── config.py
│   │   ├── exp_imports/
│   │   │   └── default.py
│   │   ├── label.py
│   │   ├── language.py
│   │   ├── manager.py
│   │   ├── misc.py
│   │   ├── opath.py
│   │   ├── polygon.py
│   │   ├── qt.py
│   │   ├── regularization/
│   │   │   ├── __init__.py
│   │   │   ├── cal_line.py
│   │   │   ├── cal_point.py
│   │   │   ├── rdp_alg.py
│   │   │   ├── rotate_ang.py
│   │   │   └── rs_regularization.py
│   │   ├── serialization.py
│   │   ├── translate/
│   │   │   ├── Arabic.qm
│   │   │   └── English.qm
│   │   └── vis.py
│   └── widget/
│       ├── __init__.py
│       ├── bbox.py
│       ├── create.py
│       ├── grip.py
│       ├── line.py
│       ├── loading.py
│       ├── polygon.py
│       ├── scene.py
│       ├── shortcut.py
│       ├── table.py
│       └── view.py
├── init.sh
├── requirements-med.txt
├── requirements-rs.txt
├── requirements.txt
├── setup.py
└── tool/
    ├── baidu_translate.py
    ├── pypi.sh
    ├── semantic2instance.py
    ├── translate.pro
    ├── translateUI.py
    ├── ts/
    │   ├── Arabic.ts
    │   └── English.ts
    └── update_md5.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/--bug.md
================================================
---
name: 反馈bug
about: 反馈使用过程中出现的各种错误
title: ''
labels: bug
assignees: ''

---

**bug描述**
请大致描述出错的现象,在什么情况下或操作过程中遇到,在上述条件下是否总是出现等。

**复现方法**
我们可以如何操作重现这个bug
如:
1. 启动软件
2. 点击....
3. 打开....
4. 出现问题 ....

**截屏**
如果条件允许可以添加针对问题的截屏,可以帮助我们理解问题。

**运行环境(请尽量填写,这可以帮助我们定位问题):**
 - 系统: [e.g. Windows/Mac os/Linux]
 - 安装方式:[e.g. 克隆代码/pip/Windows exe]
 - 软件版本:[运行时显示在软件最上方]


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
test/
__pycache__/
*.py[cod]
*$py.class
dzq*
.vscode
.vscode/
vis_temp.py
test.txt
*.pdparams
output/
temp*
temp/


# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# vscode
.vscode/*

# pycharm
.idea/*

# exe
out/
eiseg/requirements_with_opt.txt

# test
test/
test_output/
*.npy

# qsetting
*.ini

# mask_sm
tool/mask.png

# static_weights
*.pdiparams
*.pdiparams.info
*.pdmodel

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: MANIFEST.in
================================================
include eiseg/config/*
include eiseg/resource/*
include eiseg/util/translate/*

================================================
FILE: README.md
================================================
# EISeg

[![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)
<!-- [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/PaddleCV-SIG/iseg/releases) -->

简体中文 | [English](README_EN.md)

## 最新动向

- [2022.7.20] 为了减少重复工作以集中精力做好后续的开发和维护工作,**后续EISeg将仅在飞桨官方仓库[PaddlePaddle/PaddeSeg](https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.6/EISeg)下进行更新,现有代码仓库将不再更新**。非常感谢大家的一路相伴与支持,EISeg将竭诚为大家提供更多更好用的标注功能,欢迎大家去新的地址体验,也希望大家继续支持。


================================================
FILE: README_EN.md
================================================
# EISeg

[![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)
<!-- [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/PaddleCV-SIG/iseg/releases) -->
[Chinese (Simplified)](README.md) | English

## Latest Developments

- [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.

================================================
FILE: docs/medical.md
================================================
# 医疗相关

以下内容为EISeg中医疗垂类相关的文档,主要包括环境配置和功能介绍。

## 1 环境配置

使用医疗组件需要额外安装SimpleITK包用于读取医学影像,安装方式如下:

```shell
pip install SimpleITK
```

## 2 功能介绍

目前EISeg支持打开**单层的Dicom格式图像**,对Nitfi格式和多张Dicom的支持正在开发中。EISeg通过图像拓展名判断图像格式。打开单张图像时需要在右下角类型下拉菜单中选择医疗图像,如下图所示

打开文件夹时和自然图像过程相同。打开 .dcm 后缀的图像后会询问是否开启医疗组件。

![med-prompt](https://linhandev.github.io/assets/img/post/Med/med-prompt.png)

点击确定后会出现图像窗宽窗位设置面板

![med-widget](https://linhandev.github.io/assets/img/post/Med/med-widget.png)

窗宽窗位的作用是聚焦一定的强度区间,方便观察CT扫描。CT扫描中每个像素点存储的数值代表人体在该位置的密度,密度越高数值越大,图像的数据范围通常为-1024~1024。不过查看扫描时人眼无法分辨2048个灰度,因此通常选择一个更小的强度范围,将这一区间内图像的灰度差异拉大,从而方便观察。具体的操作是取扫描中强度范围在 窗位-窗宽/2~窗位+窗宽/2 的部分,将这一部分数据放入256灰度的图片中展示给用户。

推理方面,目前EISeg针对医疗场景提供[肝脏分割预训练模型](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18s_ocr48_lits.zip),推荐窗宽窗位400, 0。该模型用于肝脏分割效果最佳,也可以用于其他组织或器官的分割。


================================================
FILE: docs/medical_en.md
================================================
# Medical Treatment

This part presents documents related to medical treatment in EISeg, including its environment configuration and functions.

## 1 Environment Configuration

The SimpleITK package should be additionally installed for image reading, please try the following:

```
pip install SimpleITK
```

## 2 Functions

EISeg 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.

The 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.

[![med-prompt](https://camo.githubusercontent.com/ba9ab11d3e602ae61769d2926bd6774d1dfa633346cc483ab04bf4c89e65d2d0/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d70726f6d70742e706e67)](https://camo.githubusercontent.com/ba9ab11d3e602ae61769d2926bd6774d1dfa633346cc483ab04bf4c89e65d2d0/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d70726f6d70742e706e67)



Click Yes and there appears the setting panel of the image window width and position.

[![med-widget](https://camo.githubusercontent.com/05e9c84842f9b18ad94d5a9d7610642607f569d3ef6a9d97fd445a60df9ece46/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d7769646765742e706e67)](https://camo.githubusercontent.com/05e9c84842f9b18ad94d5a9d7610642607f569d3ef6a9d97fd445a60df9ece46/68747470733a2f2f6c696e68616e6465762e6769746875622e696f2f6173736574732f696d672f706f73742f4d65642f6d65642d7769646765742e706e67)

The 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.

For 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. 



================================================
FILE: docs/remote_sensing.md
================================================
# 遥感相关

以下内容为EISeg中遥感垂类相关的文档,主要包括环境配置和功能介绍两大方面。

## 1 环境配置

EISeg中对遥感数据的支持来自GDAL/OGR,GDAL是一个在X/MIT许可协议下的开源栅格空间数据转换库,OGR与其功能类似但主要提供对矢量数据的支持。

### 1.1 依赖安装

关于GDAL的安装,可参考如下安装方式:

#### 1.1.1 Windows

Windows用户可以通过[这里](https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal)下载对应Python和系统版本的二进制文件(*.whl)到本地,以GDAL‑3.3.3‑cp39‑cp39‑win_amd64.whl为例,进入下载目录:

```shell
cd download
```

安装依赖:

```shell
pip install GDAL‑3.3.3‑cp39‑cp39‑win_amd64.whl
```

#### 1.1.2 Linux/Mac安装

Mac用户建议利用conda安装,如下:

```shell script
conda install gdal
```


## 2 功能介绍

目前EISeg中的遥感垂类功能建设还比较简单,基本完成了GTiff类数据加载、大幅遥感影像切片与合并、地理栅格/矢量数据(GTiff/ESRI Shapefile)导出。并基于各类建筑提取数据集40余万张数据训练了一个建筑分割的交互式模型。

### 2.1 数据加载

目前EISeg仅支持了*.tif/tiff图像后缀的的遥感影像读取,由于训练数据都是来自于RGB三通道的遥感图像切片,因此交互分割也仅在RGB三通道上完成,也就表示EISeg支持多波段数据的波段选择。

当使用EISeg打开GTiff图像时,会获取当前波段数,可通过波段设置的下拉列表进行设置。默认为[b1, b1, b1]。下例展示的是天宫一号多光谱数据设置真彩色:

![yd6fa-hqvvb](https://user-images.githubusercontent.com/71769312/141137443-a327309e-0987-4b2a-88fd-f698e08d3294.gif)

### 2.2 大幅数据切片

目前EISeg对于大幅遥感图像(目前最大尝试为900M,17000*10000大小三通道图像),支持切片预测后合并,其中切片的重叠区域overlap为24。

![140916007-86076366-62ce-49ba-b1d9-18239baafc90](https://user-images.githubusercontent.com/71769312/141139282-854dcb4f-bcab-4ccc-aa3c-577cc52ca385.png)


下面是一副来自谷歌地球的重庆部分地区的切片演示:

![7kevx-q90hv](https://user-images.githubusercontent.com/71769312/141137447-60b305b1-a8ef-4b06-a45e-6db0b1ef2516.gif)

### 2.3 地理数据保存

当打开标注的GTiff图像带有地理参考,可设置EISeg保存时保存为带有地理参考的GTiff和ESRI Shapefile。

- GTiff:已成为GIS和卫星遥感应用的行业图像标准文件。
- ESRI Shapefile:是最常见的的矢量数据格式,Shapefile文件是美国环境系统研究所(ESRI)所研制的GIS文件系统格式文件,是工业标准的矢量数据文件。 所有的商业和开源GIS软件都支持。无处不在的它已成为行业标准。

![82jlu-no59o](https://user-images.githubusercontent.com/71769312/141137726-76457454-5e9c-4ad0-85d6-d03f658ee63c.gif)

### 2.4 遥感标注模型选择

建筑物标注建议使用[static_hrnet18_ocr48_rsbuilding_instance](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18_ocr48_rsbuilding_instance.zip)


================================================
FILE: docs/remote_sensing_en.md
================================================
# Remote Sensing

以下内容为EISeg中遥感垂类相关的文档,主要包括环境配置和功能介绍两大方面。

This part presents documents related to remote sensing in EISeg, including its environment configuration and functions.

## 1 Environment Configuration

EISeg 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.

### 1.1 Install Dependencies

GDAL can be installed as follows:

#### 1.1.1 Windows

Windows 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:

```
cd download
```

Install the dependencies:

```
pip install GDAL‑3.3.3‑cp39‑cp39‑win_amd64.whl
```

#### 1.1.2 Linux/Mac

Mac users are recommended to install with conda:

```
conda install gdal
```

## 2 Functions

At 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.

### 2.1 Data Loading

For 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.

When 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.

[![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)

### 2.2 large Image Slicing

EISeg 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.

[![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)

The following demonstrates the slicing of some districts in Chongqing from Google Earth:

[![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)

### 2.3 Geographic Data Saving

When the GTiff images to be labeled are accompanied by georeferencing, you can set EISeg to save them as GTiff with georeferencing or ESRI Shapefile.

- GTiff: A standard image file for industries of GIS and satellite remote sensing.
- 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.

[![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)

### 2.4 Labeling Model for Remote Sensing

[static_hrnet18_ocr48_rsbuilding_instance](https://paddleseg.bj.bcebos.com/eiseg/0.4/static_hrnet18_ocr48_rsbuilding_instance.zip) are recommended for building labeling.



================================================
FILE: docs/tools.md
================================================
# 脚本工具相关

以下内容为EISeg中的相关工具使用。位置位于EISeg/tool

## 语义标签转实例标签

语义分割标签转实例分割标签(原标签为0/255),结果为单通道图像采用调色板调色。通过`tool`中的`semantic2instance`,可以将EISeg标注好的语义分割数据转为实例分割数据。使用以下方法:

``` shell
python semantic2instance.py -o label_path -d save_path
```

其中:

- `label_path`: 语义标签存放路径,必填
- `save_path`: 实例标签保存路径,必填

![68747470733a2f2f73332e626d702e6f76682f696d67732f323032312f30392f303038633562373638623765343737612e706e67](https://user-images.githubusercontent.com/71769312/141392781-d99ec177-f445-4336-9ab2-0ba7ae75d664.png)



================================================
FILE: eiseg/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import sys
import os
import os.path as osp
import logging
from datetime import datetime

from qtpy import QtCore
import cv2

__APPNAME__ = "EISeg"
__VERSION__ = "0.5.0"


pjpath = osp.dirname(osp.realpath(__file__))
sys.path.append(pjpath)

for k, v in os.environ.items():
    if k.startswith("QT_") and "cv2" in v:
        del os.environ[k]

# log
settings = QtCore.QSettings(
    osp.join(pjpath, "config/setting.ini"), QtCore.QSettings.IniFormat
)

logFolder = settings.value("logFolder")
logLevel = bool(settings.value("log"))
logDays = settings.value("logDays")

if logFolder is None or len(logFolder) == 0:
    logFolder = osp.normcase(osp.join(pjpath, "log"))
if not osp.exists(logFolder):
    os.makedirs(logFolder)

if logLevel:
    logLevel = logging.DEBUG
else:
    logLevel = logging.CRITICAL
if logDays:
    logDays = int(logDays)
else:
    logDays = 7
# TODO: 删除大于logDays 的 log

t = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
logger = logging.getLogger("EISeg Logger")
handler = logging.FileHandler(osp.normcase(osp.join(logFolder, f"eiseg-{t}.log")))
handler.setFormatter(
    logging.Formatter(
        "%(levelname)s - %(asctime)s - %(filename)s - %(funcName)s - %(message)s"
    )
)
logger.setLevel(logLevel)
logger.addHandler(handler)


================================================
FILE: eiseg/__main__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from run import main


if __name__ == "__main__":
    main()


================================================
FILE: eiseg/app.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import logging
import os
import os.path as osp
from functools import partial
import json
from distutils.util import strtobool
import webbrowser
from easydict import EasyDict as edict

from qtpy import QtGui, QtCore, QtWidgets
from qtpy.QtWidgets import QMainWindow, QMessageBox, QTableWidgetItem
from qtpy.QtGui import QImage, QPixmap
from qtpy.QtCore import Qt, QByteArray, QVariant, QCoreApplication, QThread, Signal
import cv2
import numpy as np

from eiseg import pjpath, __APPNAME__, logger
from widget import ShortcutWidget, PolygonAnnotation
from controller import InteractiveController
from ui import Ui_EISeg
import util
from util import COCO
from util import check_cn, normcase

import plugin.remotesensing as rs
from plugin.medical import med
from plugin.remotesensing import Raster
from plugin.n2grid import RSGrids, Grids, checkOpenGrid


# TODO: 研究paddle子线程
class ModelThread(QThread):
    _signal = Signal(dict)

    def __init__(self, controller, param_path):
        super().__init__()
        self.controller = controller
        self.param_path = param_path

    def run(self):
        success, res = self.controller.setModel(self.param_path, False)
        self._signal.emit(
            {"success": success, "res": res, "param_path": self.param_path}
        )


class APP_EISeg(QMainWindow, Ui_EISeg):
    IDILE, ANNING, EDITING = 0, 1, 2
    # IDILE:网络,权重,图像三者任一没有加载
    # EDITING:多边形编辑,可以交互式,但是多边形内部不能点
    # ANNING:交互式标注,只能交互式,不能编辑多边形,多边形不接hover

    # 宫格标注背景颜色
    GRID_COLOR = {
        "idle": QtGui.QColor(255, 255, 255),
        "current": QtGui.QColor(192, 220, 243),
        "finised": QtGui.QColor(185, 185, 225),
        "overlying": QtGui.QColor(51, 52, 227),
    }

    def __init__(self, parent=None):
        super(APP_EISeg, self).__init__(parent)

        self.settings = QtCore.QSettings(
            osp.join(pjpath, "config/setting.ini"), QtCore.QSettings.IniFormat
        )
        currentLang = self.settings.value("language")
        layoutdir = Qt.RightToLeft if currentLang == "Arabic" else Qt.LeftToRight
        self.setLayoutDirection(layoutdir)

        # 初始化界面
        self.setupUi(self)

        # app变量
        self._anning = False  # self.status替代
        self.isDirty = False  # 是否需要保存
        self.image = None  # 可能先加载图片后加载模型,只用于暂存图片
        self.predictor_params = {
            "brs_mode": "NoBRS",
            "with_flip": False,
            "zoom_in_params": {
                "skip_clicks": -1,
                "target_size": (400, 400),
                "expansion_ratio": 1.4,
            },
            "predictor_params": {
                "net_clicks_limit": None,
                "max_size": 800,
                "with_mask": True,
            },
        }
        self.controller = InteractiveController(
            predictor_params=self.predictor_params,
            prob_thresh=self.segThresh,
        )
        # self.controller.labelList = util.LabelList()  # 标签列表
        self.save_status = {
            "gray_scale": True,
            "pseudo_color": True,
            "json": False,
            "coco": True,
            "cutout": True,
        }  # 是否保存这几个格式
        self.outputDir = None  # 标签保存路径
        self.labelPaths = []  # 所有outputdir中的标签文件路径
        self.imagePaths = []  # 文件夹下所有待标注图片路径
        self.currIdx = 0  # 文件夹标注当前图片下标
        self.origExt = False  # 是否使用图片本身拓展名,防止重名覆盖
        if self.save_status["coco"]:
            self.coco = COCO()
        else:
            self.coco = None
        self.colorMap = util.colorMap

        if self.settings.value("cutout_background"):
            self.cutoutBackground = [
                int(c) for c in self.settings.value("cutout_background")
            ]
            if len(self.cutoutBackground) == 3:
                self.cutoutBackground += tuple([255])
        else:
            self.cutoutBackground = [0, 0, 128, 255]

        if self.settings.value("cross_color"):
            self.crossColor = [
                int(c) for c in self.settings.value("cross_color")
            ]
        else:
            self.crossColor = [0, 0, 0, 127]
        self.scene.setPenColor(self.crossColor)

        # widget
        self.dockWidgets = {
            "model": self.ModelDock,
            "data": self.DataDock,
            "label": self.LabelDock,
            "seg": self.SegSettingDock,
            "rs": self.RSDock,
            "med": self.MedDock,
            "grid": self.GridDock,
        }
        # self.display_dockwidget = [True, True, True, True, False, False, False]
        self.dockStatus = self.settings.value(
            "dock_status", QVariant([]), type=list
        )  # 所有widget是否展示
        if len(self.dockStatus) != len(self.dockWidgets):
            self.dockStatus = [True] * 4 + [False] * (len(self.dockWidgets) - 4)
            self.settings.setValue("dock_status", self.dockStatus)
        else:
            self.dockStatus = [strtobool(s) for s in self.dockStatus]

        self.layoutStatus = self.settings.value("layout_status", QByteArray())  # 界面元素位置

        self.recentModels = self.settings.value(
            "recent_models", QVariant([]), type=list
        )
        self.recentFiles = self.settings.value("recent_files", QVariant([]), type=list)

        self.config = util.parse_configs(osp.join(pjpath, "config/config.yaml"))

        # 支持的图像格式
        rs_ext = [".tif", ".tiff"]
        img_ext = []
        for fmt in QtGui.QImageReader.supportedImageFormats():
            fmt = ".{}".format(fmt.data().decode())
            if fmt not in rs_ext:
                img_ext.append(fmt)
        self.formats = [
            img_ext,  # 自然图像
            [".dcm"],  # 医学影像
            rs_ext,  # 遥感影像
        ]

        # 遥感
        self.raster = None
        self.grid = None
        self.rsRGB = [1, 1, 1]  # 遥感索引

        # 医疗参数
        self.midx = 0  # 医疗切片索引

        # 大图限制
        self.thumbnail_min = 2000

        # 初始化action
        self.initActions()

        # 更新近期记录
        self.loadLayout()  # 放前面
        self.toggleWidget("all", warn=False)
        self.updateModelMenu()
        self.updateRecentFile()

        # 窗口
        ## 快捷键
        self.ShortcutWidget = ShortcutWidget(self.actions, pjpath)

        ## 画布
        self.scene.clickRequest.connect(self.canvasClick)
        self.canvas.zoomRequest.connect(self.viewZoomed)
        self.canvas.mousePosChanged.connect(self.scene.onMouseChanged)
        self.annImage = QtWidgets.QGraphicsPixmapItem()
        self.scene.addItem(self.annImage)

        ## 按钮点击
        self.btnSave.clicked.connect(self.exportLabel)  # 保存
        self.listFiles.itemDoubleClicked.connect(self.imageListClicked)  # 标签列表点击

        self.btnAddClass.clicked.connect(self.addLabel)
        self.btnParamsSelect.clicked.connect(self.changeParam)  # 模型参数选择
        self.cheWithMask.stateChanged.connect(self.chooseMode)  # with_mask

        ## 滑动
        self.sldOpacity.valueChanged.connect(self.maskOpacityChanged)
        self.sldClickRadius.valueChanged.connect(self.clickRadiusChanged)
        self.sldThresh.valueChanged.connect(self.threshChanged)
        self.sliderWw.valueChanged.connect(self.swwChanged)
        self.sliderWc.valueChanged.connect(self.swcChanged)
        self.textWw.returnPressed.connect(self.twwChanged)
        self.textWc.returnPressed.connect(self.twcChanged)

        ## 标签列表点击
        self.labelListTable.cellDoubleClicked.connect(self.labelListDoubleClick)
        self.labelListTable.cellClicked.connect(self.labelListClicked)
        self.labelListTable.cellChanged.connect(self.labelListItemChanged)

        ## 功能区选择
        # self.rsShow.currentIndexChanged.connect(self.rsShowModeChange)  # 显示模型
        for bandCombo in self.bandCombos:
            bandCombo.currentIndexChanged.connect(self.rsBandSet)  # 设置波段
        # self.btnInitGrid.clicked.connect(self.initGrid)  # 打开宫格
        self.btnFinishedGrid.clicked.connect(self.saveGridLabel)

    def initActions(self):
        tr = partial(QtCore.QCoreApplication.translate, "APP_EISeg")
        action = partial(util.newAction, self)
        start = dir()

        # 打开/加载/保存
        open_image = action(
            tr("&打开图像"),
            self.openImage,
            "open_image",
            "OpenImage",
            tr("打开一张图像进行标注"),
        )
        open_folder = action(
            tr("&打开文件夹"),
            self.openFolder,
            "open_folder",
            "OpenFolder",
            tr("打开一个文件夹下所有的图像进行标注"),
        )
        change_output_dir = action(
            tr("&改变标签保存路径"),
            partial(self.changeOutputDir, None),
            "change_output_dir",
            "ChangeOutputDir",
            tr("改变标签保存的文件夹路径"),
        )
        load_param = action(
            tr("&加载模型参数"),
            self.changeParam,
            "load_param",
            "Model",
            tr("加载一个模型参数"),
        )
        save = action(
            tr("&保存"),
            self.exportLabel,
            "save",
            "Save",
            tr("保存图像标签"),
        )
        save_as = action(
            tr("&另存为"),
            partial(self.exportLabel, saveAs=True),
            "save_as",
            "SaveAs",
            tr("在指定位置另存为标签"),
        )
        auto_save = action(
            tr("&自动保存"),
            self.toggleAutoSave,
            "auto_save",
            "AutoSave",
            tr("翻页同时自动保存"),
            checkable=True,
        )
        # auto_save.setChecked(self.config.get("auto_save", False))

        # 标注
        turn_prev = action(
            tr("&上一张"),
            partial(self.turnImg, -1),
            "turn_prev",
            "Prev",
            tr("翻到上一张图片"),
        )
        turn_next = action(
            tr("&下一张"),
            partial(self.turnImg, 1),
            "turn_next",
            "Next",
            tr("翻到下一张图片"),
        )
        finish_object = action(
            tr("&完成当前目标"),
            self.finishObject,
            "finish_object",
            "Ok",
            tr("完成当前目标的标注"),
        )
        clear = action(
            tr("&清除所有标注"),
            self.clearAll,
            "clear",
            "Clear",
            tr("清除所有标注信息"),
        )
        undo = action(
            tr("&撤销"),
            self.undoClick,
            "undo",
            "Undo",
            tr("撤销一次点击"),
        )
        redo = action(
            tr("&重做"),
            self.redoClick,
            "redo",
            "Redo",
            tr("重做一次点击"),
        )
        del_active_polygon = action(
            tr("&删除多边形"),
            self.delActivePolygon,
            "del_active_polygon",
            "DeletePolygon",
            tr("删除当前选中的多边形"),
        )
        del_all_polygon = action(
            tr("&删除所有多边形"),
            self.delAllPolygon,
            "del_all_polygon",
            "DeleteAllPolygon",
            tr("删除所有的多边形"),
        )
        largest_component = action(
            tr("&保留最大连通块"),
            self.toggleLargestCC,
            "largest_component",
            "SaveLargestCC",
            tr("保留最大的连通块"),
            checkable=True,
        )
        origional_extension = action(
            tr("&标签和图像使用相同拓展名"),
            self.toggleOrigExt,
            "origional_extension",
            "Same",
            tr("标签和图像使用相同拓展名,用于图像中有文件名相同但拓展名不同的情况,防止标签覆盖"),
            checkable=True,
        )
        save_pseudo = action(
            tr("&伪彩色保存"),
            partial(self.toggleSave, "pseudo_color"),
            "save_pseudo",
            "SavePseudoColor",
            tr("保存为伪彩色图像"),
            checkable=True,
        )
        save_pseudo.setChecked(self.save_status["pseudo_color"])
        save_grayscale = action(
            tr("&灰度保存"),
            partial(self.toggleSave, "gray_scale"),
            "save_grayscale",
            "SaveGrayScale",
            tr("保存为灰度图像,像素的灰度为对应类型的标签"),
            checkable=True,
        )
        save_grayscale.setChecked(self.save_status["gray_scale"])
        save_json = action(
            tr("&JSON保存"),
            partial(self.toggleSave, "json"),
            "save_json",
            "SaveJson",
            tr("保存为JSON格式"),
            checkable=True,
        )
        save_json.setChecked(self.save_status["json"])
        save_coco = action(
            tr("&COCO保存"),
            partial(self.toggleSave, "coco"),
            "save_coco",
            "SaveCOCO",
            tr("保存为COCO格式"),
            checkable=True,
        )
        save_coco.setChecked(self.save_status["coco"])
        # test func
        self.show_rs_poly = action(
            tr("&显示遥感多边形"),
            None,
            "show_rs_poly",
            "Show",
            tr("显示遥感大图的多边形结果"),
            checkable=True,
        )
        self.show_rs_poly.setChecked(False)
        self.grid_message = action(
            tr("&启用宫格检测"),
            None,
            "grid_message",
            "Show",
            tr("针对每张图片启用宫格检测"),
            checkable=True,
        )
        self.grid_message.setChecked(True)
        save_cutout = action(
            tr("&抠图保存"),
            partial(self.toggleSave, "cutout"),
            "save_cutout",
            "SaveCutout",
            tr("只保留前景,背景设置为背景色"),
            checkable=True,
        )
        save_cutout.setChecked(self.save_status["cutout"])
        set_cutout_background = action(
            tr("&设置抠图背景色"),
            self.setCutoutBackground,
            "set_cutout_background",
            self.cutoutBackground,
            tr("抠图后背景像素的颜色"),
        )
        set_cross_color = action(
            tr("&设置十字丝颜色"),
            self.setCrossColor,
            "set_cross_color",
            self.crossColor,
            tr("十字丝的显示颜色"),
        )
        close = action(
            tr("&关闭"),
            partial(self.saveImage, True),
            "close",
            "Close",
            tr("关闭当前图像"),
        )
        quit = action(
            tr("&退出"),
            self.close,
            "quit",
            "Quit",
            tr("退出软件"),
        )
        export_label_list = action(
            tr("&导出标签列表"),
            partial(self.exportLabelList, None),
            "export_label_list",
            "ExportLabel",
            tr("将标签列表导出成标签配置文件"),
        )
        import_label_list = action(
            tr("&载入标签列表"),
            partial(self.importLabelList, None),
            "import_label_list",
            "ImportLabel",
            tr("从标签配置文件载入标签列表"),
        )
        clear_label_list = action(
            tr("&清空标签列表"),
            self.clearLabelList,
            "clear_label_list",
            "ClearLabel",
            tr("清空所有的标签"),
        )
        clear_recent = action(
            tr("&清除近期文件记录"),
            self.clearRecentFile,
            "clear_recent",
            "ClearRecent",
            tr("清除近期标注文件记录"),
        )
        model_widget = action(
            tr("&模型选择"),
            partial(self.toggleWidget, 0),
            "model_widget",
            "Net",
            tr("隐藏/展示模型选择面板"),
            checkable=True,
        )
        data_widget = action(
            tr("&数据列表"),
            partial(self.toggleWidget, 1),
            "data_widget",
            "Data",
            tr("隐藏/展示数据列表面板"),
            checkable=True,
        )
        label_widget = action(
            tr("&标签列表"),
            partial(self.toggleWidget, 2),
            "label_widget",
            "Label",
            tr("隐藏/展示标签列表面板"),
            checkable=True,
        )
        segmentation_widget = action(
            tr("&分割设置"),
            partial(self.toggleWidget, 3),
            "segmentation_widget",
            "Setting",
            tr("隐藏/展示分割设置面板"),
            checkable=True,
        )
        rs_widget = action(
            tr("&遥感设置"),
            partial(self.toggleWidget, 4),
            "rs_widget",
            "RemoteSensing",
            tr("隐藏/展示遥感设置面板"),
            checkable=True,
        )
        mi_widget = action(
            tr("&医疗设置"),
            partial(self.toggleWidget, 5),
            "mi_widget",
            "MedicalImaging",
            tr("隐藏/展示医疗设置面板"),
            checkable=True,
        )
        grid_ann_widget = action(
            tr("&N2宫格标注"),
            partial(self.toggleWidget, 6),
            "grid_ann_widget",
            "N2",
            tr("隐藏/展示N^2宫格细粒度标注面板"),
            checkable=True,
        )
        quick_start = action(
            tr("&快速入门"),
            self.quickStart,
            "quick_start",
            "Use",
            tr("主要功能使用介绍"),
        )
        report_bug = action(
            tr("&反馈问题"),
            self.reportBug,
            "report_bug",
            "ReportBug",
            tr("通过Github Issue反馈使用过程中遇到的问题。我们会尽快进行修复"),
        )
        edit_shortcuts = action(
            tr("&编辑快捷键"),
            self.editShortcut,
            "edit_shortcuts",
            "Shortcut",
            tr("编辑软件快捷键"),
        )
        toggle_logging = action(
            tr("&调试日志"),
            self.toggleLogging,
            "toggle_logging",
            "Log",
            tr("用于观察软件执行过程和进行debug。我们不会自动收集任何日志,可能会希望您在反馈问题时间打开此功能,帮助我们定位问题。"),
            checkable=True,
        )
        toggle_logging.setChecked(bool(self.settings.value("log", False)))
        use_qt_widget = action(
            tr("&使用QT文件窗口"),
            self.useQtWidget,
            "use_qt_widget",
            "Qt",
            tr("如果使用文件选择窗口时遇到问题可以选择使用Qt窗口"),
            checkable=True,
        )
        # print(
        #     "use_qt_widget",
        #     self.settings.value("use_qt_widget", type=bool),
        # )
        use_qt_widget.setChecked(self.settings.value("use_qt_widget", False, type=bool))

        self.actions = util.struct()
        for name in dir():
            if name not in start:
                self.actions.append(eval(name))

        def newWidget(text, icon, showAction):
            widget = QtWidgets.QMenu(tr(text))
            widget.setIcon(util.newIcon(icon))
            widget.aboutToShow.connect(showAction)
            return widget

        recent_files = newWidget(self.tr("近期文件"), "Data", self.updateRecentFile)
        recent_params = newWidget(self.tr("近期模型及参数"), "Net", self.updateModelMenu)
        languages = newWidget(self.tr("语言"), "Language", self.updateLanguage)

        self.menus = util.struct(
            recent_files=recent_files,
            recent_params=recent_params,
            languages=languages,
            fileMenu=(
                open_image,
                open_folder,
                change_output_dir,
                load_param,
                clear_recent,
                recent_files,
                recent_params,
                None,
                save,
                save_as,
                auto_save,
                None,
                turn_next,
                turn_prev,
                close,
                None,
                quit,
            ),
            labelMenu=(
                export_label_list,
                import_label_list,
                clear_label_list,
            ),
            functionMenu=(
                largest_component,
                del_active_polygon,
                del_all_polygon,
                None,
                origional_extension,
                save_pseudo,
                save_grayscale,
                save_cutout,
                set_cutout_background,
                None,
                set_cross_color,
                None,
                save_json,
                save_coco,
                None,
                # test
                self.show_rs_poly,
                None,
                self.grid_message,
            ),
            showMenu=(
                model_widget,
                data_widget,
                label_widget,
                segmentation_widget,
                rs_widget,
                mi_widget,
                grid_ann_widget,
            ),
            helpMenu=(
                languages,
                use_qt_widget,
                quick_start,
                report_bug,
                edit_shortcuts,
                toggle_logging,
            ),
            toolBar=(
                finish_object,
                clear,
                undo,
                redo,
                turn_prev,
                turn_next,
                None,
                save_pseudo,
                save_grayscale,
                save_cutout,
                save_json,
                save_coco,
                origional_extension,
                None,
                largest_component,
            ),
        )

        def menu(title, actions=None):
            menu = self.menuBar().addMenu(title)
            if actions:
                util.addActions(menu, actions)
            return menu

        menu(tr("文件"), self.menus.fileMenu)
        menu(tr("标注"), self.menus.labelMenu)
        menu(tr("功能"), self.menus.functionMenu)
        menu(tr("显示"), self.menus.showMenu)
        menu(tr("帮助"), self.menus.helpMenu)
        util.addActions(self.toolBar, self.menus.toolBar)

    def __setColor(self, action, setting_name):
        c = action
        color = QtWidgets.QColorDialog.getColor(
            QtGui.QColor(*c),
            self,
            options=QtWidgets.QColorDialog.ShowAlphaChannel,
        )
        action = color.getRgb()
        self.settings.setValue(
            setting_name, [int(c) for c in action]
        )
        return action

    def setCutoutBackground(self):
        self.cutoutBackground = self.__setColor(self.cutoutBackground, "cutout_background")
        self.actions.set_cutout_background.setIcon(util.newIcon(self.cutoutBackground))

    def setCrossColor(self):
        self.crossColor = self.__setColor(self.crossColor, "cross_color")
        self.actions.set_cross_color.setIcon(util.newIcon(self.crossColor))
        self.scene.setPenColor(self.crossColor)

    def editShortcut(self):
        self.ShortcutWidget.center()
        self.ShortcutWidget.show()

    # 多语言
    def updateLanguage(self):
        self.menus.languages.clear()
        langs = os.listdir(osp.join(pjpath, "util/translate"))
        langs = [n.split(".")[0] for n in langs if n.endswith("qm")]
        langs.append("中文")
        for lang in langs:
            if lang == self.currLanguage:
                continue
            entry = util.newAction(
                self,
                lang,
                partial(self.changeLanguage, lang),
                None,
                lang if lang != "Arabic" else "Egypt",
            )
            self.menus.languages.addAction(entry)

    def changeLanguage(self, lang):
        self.settings.setValue("language", lang)
        self.warn(self.tr("切换语言"), self.tr("切换语言需要重启软件才能生效"))

    # 近期图像
    def updateRecentFile(self):
        menu = self.menus.recent_files
        menu.clear()
        recentFiles = self.settings.value("recent_files", QVariant([]), type=list)
        files = [f for f in recentFiles if osp.exists(f)]
        for i, f in enumerate(files):
            icon = util.newIcon("File")
            action = QtWidgets.QAction(
                icon, "&【%d】 %s" % (i + 1, QtCore.QFileInfo(f).fileName()), self
            )
            action.triggered.connect(partial(self.openRecentImage, f))
            menu.addAction(action)
        if len(files) == 0:
            menu.addAction(self.tr("无近期文件"))
        self.settings.setValue("recent_files", files)

    def addRecentFile(self, path):
        if not osp.exists(path):
            return
        paths = self.settings.value("recent_files", QVariant([]), type=list)
        if path not in paths:
            paths.append(path)
        if len(paths) > 15:
            del paths[0]
        self.settings.setValue("recent_files", paths)
        self.updateRecentFile()

    def clearRecentFile(self):
        self.settings.remove("recent_files")
        self.statusbar.showMessage(self.tr("已清除最近打开文件"), 10000)

    # 模型加载
    def updateModelMenu(self):
        menu = self.menus.recent_params
        menu.clear()

        self.recentModels = [
            m for m in self.recentModels if osp.exists(m["param_path"])
        ]
        for idx, m in enumerate(self.recentModels):
            icon = util.newIcon("Model")
            action = QtWidgets.QAction(
                icon,
                f"{osp.basename(m['param_path'])}",
                self,
            )
            action.triggered.connect(partial(self.setModelParam, m["param_path"]))
            menu.addAction(action)
        if len(self.recentModels) == 0:
            menu.addAction(self.tr("无近期模型记录"))
        self.settings.setValue("recent_params", self.recentModels)

    def setModelParam(self, paramPath):
        res = self.changeParam(paramPath)
        if res:
            return True
        return False

    def changeParam(self, param_path: str = None):
        if not param_path:
            filters = self.tr("Paddle静态模型权重文件(*.pdiparams)")
            start_path = (
                "."
                if len(self.recentModels) == 0
                else osp.dirname(self.recentModels[-1]["param_path"])
            )
            if self.settings.value("use_qt_widget", False, type=bool):
                options = QtWidgets.QFileDialog.DontUseNativeDialog
            else:
                options = QtWidgets.QFileDialog.ReadOnly
            param_path, _ = QtWidgets.QFileDialog.getOpenFileName(
                self,
                self.tr("选择模型参数") + " - " + __APPNAME__,
                start_path,
                filters,
                options=options,
            )
            # QtWidgets.QFileDialog.DontUseNativeDialog
        if not param_path:
            return False

        # 中文路径打不开
        if check_cn(param_path):
            self.warn(self.tr("参数路径存在中文"), self.tr("请修改参数路径为非中文路径!"))
            return False

        # success, res = self.controller.setModel(param_path)
        self.load_thread = ModelThread(self.controller, param_path)
        self.load_thread._signal.connect(self.__change_model_callback)
        self.load_thread.start()

    def __change_model_callback(self, signal_dict: dict):
        success = signal_dict["success"]
        res = signal_dict["res"]
        param_path = signal_dict["param_path"]
        if success:
            model_dict = {"param_path": param_path}
            if model_dict not in self.recentModels:
                self.recentModels.insert(0, model_dict)
                if len(self.recentModels) > 10:
                    del self.recentModels[-1]
            else:  # 如果存在移动位置,确保加载最近模型的正确
                self.recentModels.remove(model_dict)
                self.recentModels.insert(0, model_dict)
            self.settings.setValue("recent_models", self.recentModels)
            self.statusbar.showMessage(
                osp.basename(param_path) + self.tr(" 模型加载成功"), 10000
            )
            return True
        else:
            self.warnException(res)
            return False

    def chooseMode(self):
        self.predictor_params["predictor_params"][
            "with_mask"
        ] = self.cheWithMask.isChecked()
        self.controller.reset_predictor(predictor_params=self.predictor_params)
        if self.cheWithMask.isChecked():
            self.statusbar.showMessage(self.tr("掩膜已启用"), 10000)
        else:
            self.statusbar.showMessage(self.tr("掩膜已关闭"), 10000)

    def loadRecentModelParam(self):
        if len(self.recentModels) == 0:
            self.statusbar.showMessage(self.tr("没有最近使用模型信息,请加载模型"), 10000)
            return
        m = self.recentModels[0]
        param_path = m["param_path"]
        self.setModelParam(param_path)

    # 标签列表
    def importLabelList(self, filePath=None):
        if filePath is None:
            if self.settings.value("use_qt_widget", False, type=bool):
                options = QtWidgets.QFileDialog.DontUseNativeDialog
            else:
                options = QtWidgets.QFileDialog.ReadOnly
            filters = self.tr("标签配置文件") + " (*.txt)"
            filePath, _ = QtWidgets.QFileDialog.getOpenFileName(
                self,
                self.tr("选择标签配置文件路径") + " - " + __APPNAME__,
                ".",
                filters,
                options=options,
            )
        filePath = normcase(filePath)
        if not osp.exists(filePath):
            return
        self.controller.importLabel(filePath)
        logger.info(f"Loaded label list: {self.controller.labelList.labelList}")
        self.refreshLabelList()

    def exportLabelList(self, savePath: str = None):
        if len(self.controller.labelList) == 0:
            self.warn(self.tr("没有需要保存的标签"), self.tr("请先添加标签之后再进行保存!"))
            return
        if savePath is None:
            filters = self.tr("标签配置文件") + "(*.txt)"
            dlg = QtWidgets.QFileDialog(
                self,
                self.tr("保存标签配置文件"),
                ".",
                filters,
            )
            dlg.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)
            if self.settings.value("use_qt_widget", False, type=bool):
                options = QtWidgets.QFileDialog.DontUseNativeDialog
            else:
                options = QtWidgets.QFileDialog.DontUseCustomDirectoryIcons
            dlg.setDefaultSuffix("txt")
            dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
            savePath, _ = dlg.getSaveFileName(
                self,
                self.tr("选择保存标签配置文件路径") + " - " + __APPNAME__,
                ".",
                filters,
                options=options,
            )
        self.controller.exportLabel(savePath)

    def addLabel(self):
        c = self.colorMap.get_color()
        table = self.labelListTable
        idx = table.rowCount()
        table.insertRow(table.rowCount())
        self.controller.addLabel(idx + 1, "", c)
        numberItem = QTableWidgetItem(str(idx + 1))
        numberItem.setFlags(QtCore.Qt.ItemIsEnabled)
        table.setItem(idx, 0, numberItem)
        table.setItem(idx, 1, QTableWidgetItem())
        colorItem = QTableWidgetItem()
        colorItem.setBackground(QtGui.QColor(c[0], c[1], c[2]))
        colorItem.setFlags(QtCore.Qt.ItemIsEnabled)
        table.setItem(idx, 2, colorItem)
        delItem = QTableWidgetItem()
        delItem.setIcon(util.newIcon("Clear"))
        delItem.setTextAlignment(Qt.AlignCenter)
        delItem.setFlags(QtCore.Qt.ItemIsEnabled)
        table.setItem(idx, 3, delItem)
        self.adjustTableSize()
        self.labelListClicked(self.labelListTable.rowCount() - 1, 0)

    def adjustTableSize(self):
        self.labelListTable.horizontalHeader().setDefaultSectionSize(25)
        self.labelListTable.horizontalHeader().setSectionResizeMode(
            0, QtWidgets.QHeaderView.Fixed
        )
        self.labelListTable.horizontalHeader().setSectionResizeMode(
            3, QtWidgets.QHeaderView.Fixed
        )
        self.labelListTable.horizontalHeader().setSectionResizeMode(
            2, QtWidgets.QHeaderView.Fixed
        )
        self.labelListTable.setColumnWidth(2, 50)

    def clearLabelList(self):
        if len(self.controller.labelList) == 0:
            return True
        res = self.warn(
            self.tr("清空标签列表?"),
            self.tr("请确认是否要清空标签列表"),
            QMessageBox.Yes | QMessageBox.Cancel,
        )
        if res == QMessageBox.Cancel:
            return False
        self.controller.labelList.clear()
        if self.controller:
            self.controller.label_list = []
            self.controller.curr_label_number = 0
        self.labelListTable.clear()
        self.labelListTable.setRowCount(0)
        return True

    def refreshLabelList(self):
        table = self.labelListTable
        table.clearContents()
        table.setRowCount(len(self.controller.labelList))
        table.setColumnCount(4)
        for idx, lab in enumerate(self.controller.labelList):
            numberItem = QTableWidgetItem(str(lab.idx))
            numberItem.setFlags(QtCore.Qt.ItemIsEnabled)
            table.setItem(idx, 0, numberItem)
            table.setItem(idx, 1, QTableWidgetItem(lab.name))
            c = lab.color
            colorItem = QTableWidgetItem()
            colorItem.setBackground(QtGui.QColor(c[0], c[1], c[2]))
            colorItem.setFlags(QtCore.Qt.ItemIsEnabled)
            table.setItem(idx, 2, colorItem)
            delItem = QTableWidgetItem()
            delItem.setIcon(util.newIcon("Clear"))
            delItem.setTextAlignment(Qt.AlignCenter)
            delItem.setFlags(QtCore.Qt.ItemIsEnabled)
            table.setItem(idx, 3, delItem)
            self.adjustTableSize()

        cols = [0, 1, 3]
        for idx in cols:
            table.resizeColumnToContents(idx)
        self.adjustTableSize()

    def labelListDoubleClick(self, row, col):
        if col != 2:
            return
        table = self.labelListTable
        color = QtWidgets.QColorDialog.getColor()
        if color.getRgb() == (0, 0, 0, 255):
            return
        table.item(row, col).setBackground(color)
        self.controller.labelList[row].color = color.getRgb()[:3]
        if self.controller:
            self.controller.label_list = self.controller.labelList
        for p in self.scene.polygon_items:
            idlab = self.controller.labelList.getLabelById(p.labelIndex)
            if idlab is not None:
                color = idlab.color
                p.setColor(color, color)
        self.labelListClicked(row, 0)

    @property
    def currLabelIdx(self):
        return self.controller.curr_label_number - 1

    def labelListClicked(self, row, col):
        table = self.labelListTable
        if col == 3:
            labelIdx = int(table.item(row, 0).text())
            self.controller.labelList.remove(labelIdx)
            table.removeRow(row)

        if col == 0 or col == 1:
            for cl in range(2):
                for idx in range(len(self.controller.labelList)):
                    table.item(idx, cl).setBackground(QtGui.QColor(255, 255, 255))
                table.item(row, cl).setBackground(QtGui.QColor(48, 140, 198))
                table.item(row, 0).setSelected(True)
            if self.controller:
                self.controller.setCurrLabelIdx(int(table.item(row, 0).text()))
                self.controller.label_list = self.controller.labelList

    def labelListItemChanged(self, row, col):
        self.colorMap.usedColors = self.controller.labelList.colors
        try:
            if col == 1:
                name = self.labelListTable.item(row, col).text()
                self.controller.labelList[row].name = name
        except:
            pass

    # 多边形标注
    def createPoly(self, curr_polygon, color):
        if curr_polygon is None:
            return
        for points in curr_polygon:
            if len(points) < 3:
                continue
            poly = PolygonAnnotation(
                self.controller.labelList[self.currLabelIdx].idx,
                self.controller.image.shape,
                self.delPolygon,
                self.setDirty,
                color,
                color,
                self.opacity,
            )
            poly.labelIndex = self.controller.labelList[self.currLabelIdx].idx
            self.scene.addItem(poly)
            self.scene.polygon_items.append(poly)
            for p in points:
                poly.addPointLast(QtCore.QPointF(p[0], p[1]))
            self.setDirty(True)

    def delActivePolygon(self):
        for idx, polygon in enumerate(self.scene.polygon_items):
            if polygon.hasFocus():
                res = self.warn(
                    self.tr("确认删除?"),
                    self.tr("确认要删除当前选中多边形标注?"),
                    QMessageBox.Yes | QMessageBox.Cancel,
                )
                if res == QMessageBox.Yes:
                    self.delPolygon(polygon)

    def delPolygon(self, polygon):
        polygon.remove()
        if self.save_status["coco"]:
            if polygon.coco_id:
                self.coco.delAnnotation(
                    polygon.coco_id,
                    self.coco.imgNameToId[osp.basename(self.imagePath)],
                )
        self.setDirty(True)

    def delAllPolygon(self):
        for p in self.scene.polygon_items[::-1]:  # 删除所有多边形
            self.delPolygon(p)

    def delActivePoint(self):
        for polygon in self.scene.polygon_items:
            polygon.removeFocusPoint()

    # 图片/标签 io
    def getMask(self):
        if not self.controller or self.controller.image is None:
            return
        s = self.controller.imgShape
        pesudo = np.zeros([s[0], s[1]])
        # 覆盖顺序,从上往下
        # TODO: 是标签数值大的会覆盖小的吗?
        # A: 是列表中上面的覆盖下面的,由于标签可以移动,不一定是大小按顺序覆盖
        # RE: 我们做医学的时候覆盖比较多,感觉一般是数值大的标签覆盖数值小的标签。按照上面覆盖下面的话可能跟常见的情况正好是反过来的,感觉可能从下往上覆盖会比较好
        len_lab = self.labelListTable.rowCount()
        for i in range(len_lab - 1, -1, -1):
            idx = int(self.labelListTable.item(len_lab - i - 1, 0).text())
            for poly in self.scene.polygon_items:
                if poly.labelIndex == idx:
                    pts = np.int32([np.array(poly.scnenePoints)])
                    cv2.fillPoly(pesudo, pts=pts, color=idx)
        return pesudo

    def openRecentImage(self, file_path):
        self.queueEvent(partial(self.loadImage, file_path))
        self.listFiles.addItems([file_path.replace("\\", "/")])
        self.currIdx = self.listFiles.count() - 1
        self.listFiles.setCurrentRow(self.currIdx)  # 移动位置
        self.imagePaths.append(file_path)

    def openImage(self, filePath: str = None):
        # 在triggered.connect中使用不管默认filePath为什么返回值都为False
        if not isinstance(filePath, str) or filePath is False:
            prompts = ["图片", "医学影像", "遥感影像"]
            filters = ""
            for fmts, p in zip(self.formats, prompts):
                filters += f"{p} ({' '.join(['*' + f for f in fmts])}) ;; "
            filters = filters[:-3]
            recentPath = self.settings.value("recent_files", [])
            if len(recentPath) == 0:
                recentPath = "."
            else:
                recentPath = osp.dirname(recentPath[0])
            if self.settings.value("use_qt_widget", False, type=bool):
                options = QtWidgets.QFileDialog.DontUseNativeDialog
            else:
                options = QtWidgets.QFileDialog.ReadOnly
            filePath, _ = QtWidgets.QFileDialog.getOpenFileName(
                self,
                self.tr("选择待标注图片") + " - " + __APPNAME__,
                recentPath,
                filters,
                options=options,
            )
            if len(filePath) == 0:  # 用户没选就直接关闭窗口
                return
        filePath = normcase(filePath)
        if not self.loadImage(filePath):
            return False

        # 3. 添加记录
        self.listFiles.addItems([filePath])
        self.currIdx = self.listFiles.count() - 1
        self.listFiles.setCurrentRow(self.currIdx)  # 移动位置
        self.imagePaths.append(filePath)
        return True

    def openFolder(self, inputDir: str = None):
        # 1. 如果没传文件夹,弹框让用户选
        if not isinstance(inputDir, str):
            recentPath = self.settings.value("recent_files", [])
            if len(recentPath) == 0:
                recentPath = "."
            else:
                recentPath = osp.dirname(recentPath[-1])
            options = (
                QtWidgets.QFileDialog.ShowDirsOnly
                | QtWidgets.QFileDialog.DontResolveSymlinks
            )
            if self.settings.value("use_qt_widget", False, type=bool):
                options = options | QtWidgets.QFileDialog.DontUseNativeDialog
            inputDir = QtWidgets.QFileDialog.getExistingDirectory(
                self,
                self.tr("选择待标注图片文件夹") + " - " + __APPNAME__,
                recentPath,
                options,
            )
            if not osp.exists(inputDir):
                return

        # 2. 关闭当前图片,清空文件列表
        self.saveImage(close=True)
        self.imagePaths = []
        self.listFiles.clear()

        # 3. 扫描文件夹下所有图片
        # 3.1 获取所有文件名
        imagePaths = os.listdir(inputDir)
        exts = tuple(f for fmts in self.formats for f in fmts)
        imagePaths = [n for n in imagePaths if n.lower().endswith(exts)]
        imagePaths = [n for n in imagePaths if not n[0] == "."]
        imagePaths.sort()
        if len(imagePaths) == 0:
            return
        # 3.2 设置默认输出路径
        if self.outputDir is None:
            # 没设置为文件夹下的 label 文件夹
            self.outputDir = osp.join(inputDir, "label")
        if not osp.exists(self.outputDir):
            os.makedirs(self.outputDir)
        # 3.3 有重名图片,标签保留原来拓展名
        names = []
        for name in imagePaths:
            name = osp.splitext(name)[0]
            if name not in names:
                names.append(name)
            else:
                self.toggleOrigExt(True)
                break
        imagePaths = [osp.join(inputDir, n) for n in imagePaths]
        for p in imagePaths:
            p = normcase(p)
            self.imagePaths.append(p)
            self.listFiles.addItem(p)

        # 3.4 加载已有的标注
        if self.outputDir is not None and osp.exists(self.outputDir):
            self.changeOutputDir(self.outputDir)
        if len(self.imagePaths) != 0:
            self.currIdx = 0
            self.turnImg(0)
        self.inputDir = inputDir

    def loadImage(self, path):
        if self.controller.model is None:
            self.warn("未检测到模型", "请先加载模型参数")
            return
        # 1. 拒绝None和不存在的路径,关闭当前图像
        if not path:
            return
        path = normcase(path)
        if not osp.exists(path):
            return
        self.saveImage(True)  # 关闭当前图像
        self.eximgsInit()  # TODO: 将grid的部分整合到saveImage里

        # 2. 判断图像类型,打开
        # TODO: 加用户指定类型的功能
        image = None

        # 直接if会报错,因为打开遥感图像后多波段不存在,现在把遥感图像的单独抽出来了
        # 自然图像
        if path.lower().endswith(tuple(self.formats[0])):
            image = cv2.imdecode(np.fromfile(path, dtype=np.uint8), 1)
            image = image[:, :, ::-1]  # BGR转RGB
            if self.grid_message.isChecked():
                if checkOpenGrid(image, self.thumbnail_min):
                    if self.loadGrid(image, False):
                        image, _ = self.grid.getGrid(0, 0)
            else:
                if self.dockWidgets["grid"].isVisible() is True:
                    self.grid = Grids(image)
                    self.initGrid()
                    image, _ = self.grid.getGrid(0, 0)

        # 医学影像
        if path.lower().endswith(tuple(self.formats[1])):
            if not self.dockStatus[5]:
                res = self.warn(
                    self.tr("未启用医疗组件"),
                    self.tr("加载医疗影像需启用医疗组件,是否立即启用?"),
                    QMessageBox.Yes | QMessageBox.Cancel,
                )
                if res == QMessageBox.Cancel:
                    return False
                self.toggleWidget(5)
                if not self.dockStatus[5]:
                    return False
            image = med.dcm_reader(path)  # TODO: 添加多层支持
            if image.shape[-1] != 1:
                self.warn("医学影像打开错误", "暂不支持打开多层医学影像")
                return False

            maxValue = np.max(image)  # 根据数据模态自适应窗宽窗位
            minValue = np.min(image)
            if minValue == 0:
                ww = maxValue
                wc = int(maxValue / 2)
            else:
                ww = maxValue + int(abs(minValue))
                wc = int((minValue + maxValue) / 2)
            self.sliderWw.setValue(int(ww))
            self.textWw.setText(str(ww))
            self.sliderWc.setValue(int(wc))
            self.textWc.setText(str(wc))

            self.controller.rawImage = self.image = image
            image = med.windowlize(image, self.ww, self.wc)

        # 遥感图像
        if path.lower().endswith(
            tuple(self.formats[2])
        ):  # imghdr.what(path) == "tiff":
            if not self.dockStatus[4]:
                res = self.warn(
                    self.tr("未打开遥感组件"),
                    self.tr("打开遥感图像需启用遥感组件,是否立即启用?"),
                    QMessageBox.Yes | QMessageBox.Cancel,
                )
                if res == QMessageBox.Cancel:
                    return False
                self.toggleWidget(4)
                if not self.dockStatus[4]:
                    return False
            self.raster = Raster(path)
            gi = self.raster.showGeoInfo()
            self.edtGeoinfo.setText(self.tr("● 波段数:") + gi[0] + "\n" + 
                                    self.tr("● 数据类型:") + gi[1] + "\n" + 
                                    self.tr("● 行数:") + gi[2] + "\n" + 
                                    self.tr("● 列数:") + gi[3] + "\n" + 
                                    "● EPSG:" + gi[4])
            if max(self.rsRGB) > self.raster.geoinfo.count:
                self.rsRGB = [1, 1, 1]
            self.raster.setBand(self.rsRGB)
            if self.grid_message.isChecked():
                if self.raster.checkOpenGrid(self.thumbnail_min):
                    if self.loadGrid(self.raster):
                        image, _ = self.raster.getGrid(0, 0)
                    else:
                        image, _ = self.raster.getArray()
                else:
                    image, _ = self.raster.getArray()
            else:
                if self.dockWidgets["grid"].isVisible() is True:
                    self.grid = RSGrids(self.raster)
                    self.raster.open_grid = True
                    self.initGrid()
                    image, _ = self.raster.getGrid(0, 0)
                else:
                    image, _ = self.raster.getArray()
            self.updateBandList()
            # self.updateSlideSld(True)
        else:
            self.edtGeoinfo.setText(self.tr("无"))

        # 如果没找到图片的reader
        if image is None:
            self.warn("打开图像失败", f"未找到{path}文件对应的读取程序")
            return

        self.image = image
        self.controller.setImage(image)
        self.updateImage(True)

        # 2. 加载标签
        self.loadLabel(path)
        self.addRecentFile(path)
        self.imagePath = path
        return True

    def loadLabel(self, imgPath):
        if imgPath == "":
            return None

        # 1. 读取json格式标签
        if self.save_status["json"]:

            def getName(path):
                return osp.splitext(osp.basename(path))[0]

            imgName = getName(imgPath)
            labelPath = None
            for path in self.labelPaths:
                if not path.endswith(".json"):
                    continue
                if self.origExt:
                    if getName(path) == osp.basename(imgPath):
                        labelPath = path
                        break
                else:
                    if getName(path) == imgName:
                        labelPath = path
                        break
            if not labelPath:
                return

            labels = json.loads(open(labelPath, "r").read())

            for label in labels:
                color = label["color"]
                labelIdx = label["labelIdx"]
                points = label["points"]
                poly = PolygonAnnotation(
                    labelIdx,
                    self.controller.image.shape,
                    self.delPolygon,
                    self.setDirty,
                    color,
                    color,
                    self.opacity,
                )
                self.scene.addItem(poly)
                self.scene.polygon_items.append(poly)
                for p in points:
                    poly.addPointLast(QtCore.QPointF(p[0], p[1]))

        # 2. 读取coco格式标签
        if self.save_status["coco"]:
            imgId = self.coco.imgNameToId.get(osp.basename(imgPath), None)
            if imgId is None:
                return
            anns = self.coco.imgToAnns[imgId]
            for ann in anns:
                xys = ann["segmentation"][0]
                points = []
                for idx in range(0, len(xys), 2):
                    points.append([xys[idx], xys[idx + 1]])
                labelIdx = ann["category_id"]
                idlab = self.controller.labelList.getLabelById(labelIdx)
                if idlab is not None:
                    color = idlab.color
                    poly = PolygonAnnotation(
                        ann["category_id"],
                        self.controller.image.shape,
                        self.delPolygon,
                        self.setDirty,
                        color,
                        color,
                        self.opacity,
                        ann["id"],
                    )
                    self.scene.addItem(poly)
                    self.scene.polygon_items.append(poly)
                    for p in points:
                        poly.addPointLast(QtCore.QPointF(p[0], p[1]))

    def turnImg(self, delta, list_click=False):
        if (self.grid is None or self.grid.curr_idx is None) or list_click:
            # 1. 检查是否有图可翻,保存标签
            self.currIdx += delta
            if self.currIdx >= len(self.imagePaths) or self.currIdx < 0:
                self.currIdx -= delta
                if delta == 1:
                    self.statusbar.showMessage(self.tr(f"没有后一张图片"))
                else:
                    self.statusbar.showMessage(self.tr(f"没有前一张图片"))
                self.saveImage(False)
                return
            else:
                self.saveImage(True)

            # 2. 打开新图
            self.loadImage(self.imagePaths[self.currIdx])
            self.listFiles.setCurrentRow(self.currIdx)
        else:
            self.turnGrid(delta)
        self.setDirty(False)

    def imageListClicked(self):
        if not self.controller:
            self.warn(self.tr("模型未加载"), self.tr("尚未加载模型,请先加载模型!"))
            self.changeParam()
            if not self.controller:
                return
        if self.controller.is_incomplete_mask:
            self.exportLabel()
        toRow = self.listFiles.currentRow()
        delta = toRow - self.currIdx
        self.turnImg(delta, True)

    def finishObject(self):
        if not self.controller or self.image is None:
            return
        current_mask, curr_polygon = self.controller.finishObject(
            building=self.boundaryRegular.isChecked()
        )
        if curr_polygon is not None:
            self.updateImage()
            if current_mask is not None:
                # current_mask = current_mask.astype(np.uint8) * 255
                # polygon = util.get_polygon(current_mask)
                color = self.controller.labelList[self.currLabelIdx].color
                self.createPoly(curr_polygon, color)
        # 状态改变
        if self.status == self.EDITING:
            self.status = self.ANNING
            for p in self.scene.polygon_items:
                p.setAnning(isAnning=True)
        else:
            self.status = self.EDITING
            for p in self.scene.polygon_items:
                p.setAnning(isAnning=False)
        self.getMask()

    def completeLastMask(self):
        # 返回最后一个标签是否完成,false就是还有带点的
        if not self.controller or self.controller.image is None:
            return True
        if not self.controller.is_incomplete_mask:
            return True
        res = self.warn(
            self.tr("完成最后一个目标?"),
            self.tr("是否完成最后一个目标的标注,不完成不会进行保存。"),
            QMessageBox.Yes | QMessageBox.Cancel,
        )
        if res == QMessageBox.Yes:
            self.finishObject()
            self.exportLabel()
            self.setDirty(False)
            return True
        return False

    def saveImage(self, close=False):
        if self.controller and self.controller.image is not None:
            # 1. 完成正在交互式标注的标签
            self.completeLastMask()
            # 2. 进行保存
            if self.isDirty:
                if self.actions.auto_save.isChecked():
                    self.exportLabel()
                else:
                    res = self.warn(
                        self.tr("保存标签?"),
                        self.tr("标签尚未保存,是否保存标签"),
                        QMessageBox.Yes | QMessageBox.Cancel,
                    )
                    if res == QMessageBox.Yes:
                        self.exportLabel()
                self.setDirty(False)
            if close:
                # 3. 清空多边形标注,删掉图片
                for p in self.scene.polygon_items[::-1]:
                    p.remove()
                self.scene.polygon_items = []
                self.controller.resetLastObject()
                self.updateImage()
                self.controller.image = None
        if close:
            self.annImage.setPixmap(QPixmap())

    def exportLabel(self, saveAs=False, savePath=None, lab_input=None):
        # 1. 需要处于标注状态
        if not self.controller or self.controller.image is None:
            return
        # 2. 完成正在交互式标注的标签
        self.completeLastMask()
        # 3. 确定保存路径
        # 3.1 如果参数指定了保存路径直接存到savePath
        if not savePath:
            if not saveAs and self.outputDir is not None:
                # 3.2 指定了标签文件夹,而且不是另存为:根据标签文件夹和文件名出保存路径
                name, ext = osp.splitext(osp.basename(self.imagePath))
                if not self.origExt:
                    ext = ".png"
                savePath = osp.join(
                    self.outputDir,
                    name + ext,
                )
            else:
                # 3.3 没有指定标签存到哪,或者是另存为:弹框让用户选
                savePath = self.chooseSavePath()
        if savePath is None or not osp.exists(osp.dirname(savePath)):
            return

        if savePath not in self.labelPaths:
            self.labelPaths.append(savePath)

        if lab_input is None:
            mask_output = self.getMask()
            s = self.controller.imgShape
        else:
            mask_output = lab_input
            s = lab_input.shape

        # BUG: 如果用了多边形标注从多边形生成mask
        # 4.1 保存灰度图
        if self.save_status["gray_scale"]:
            if self.raster is not None:
                # FIXME: when big map saved, self.raster is None, 
                #        so adjust polygon can't saved in tif's mask.
                pathHead, _ = osp.splitext(savePath)
                # if self.rsSave.isChecked():
                tifPath = pathHead + "_mask.tif"
                self.raster.saveMask(mask_output, tifPath)
                if self.shpSave.isChecked():
                    shpPath = pathHead + ".shp"
                    # geocode_list = self.mask2poly(mask_output, False)
                    print(rs.save_shp(shpPath, tifPath))
            else:
                ext = osp.splitext(savePath)[1]
                cv2.imencode(ext, mask_output)[1].tofile(savePath)
                # self.labelPaths.append(savePath)

        # 4.2 保存伪彩色
        if self.save_status["pseudo_color"]:
            if self.raster is None:
                pseudoPath, ext = osp.splitext(savePath)
                pseudoPath = pseudoPath + "_pseudo" + ext
                pseudo = np.zeros([s[0], s[1], 3])
                # mask = self.controller.result_mask
                mask = mask_output
                # print(pseudo.shape, mask.shape)
                for lab in self.controller.labelList:
                    pseudo[mask == lab.idx, :] = lab.color[::-1]
                cv2.imencode(ext, pseudo)[1].tofile(pseudoPath)

        # 4.3 保存前景抠图
        if self.save_status["cutout"]:
            if self.raster is None:
                mattingPath, ext = osp.splitext(savePath)
                mattingPath = mattingPath + "_cutout" + ext
                img = np.ones([s[0], s[1], 4], dtype="uint8") * 255
                img[:, :, :3] = self.controller.image.copy()
                img[mask_output == 0] = self.cutoutBackground
                img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
                cv2.imencode(ext, img)[1].tofile(mattingPath)

        # 4.4 保存json
        if self.save_status["json"]:
            polygons = self.scene.polygon_items
            labels = []
            for polygon in polygons:
                l = self.controller.labelList[polygon.labelIndex - 1]
                label = {
                    "name": l.name,
                    "labelIdx": l.idx,
                    "color": l.color,
                    "points": [],
                }
                for p in polygon.scnenePoints:
                    label["points"].append(p)
                labels.append(label)
            if self.origExt:
                jsonPath = savePath + ".json"
            else:
                jsonPath = osp.splitext(savePath)[0] + ".json"
            open(jsonPath, "w", encoding="utf-8").write(json.dumps(labels))
            self.labelPaths.append(jsonPath)

        # 4.5 保存coco
        if self.save_status["coco"]:
            if not self.coco.hasImage(osp.basename(self.imagePath)):
                imgId = self.coco.addImage(osp.basename(self.imagePath), s[1], s[0])
            else:
                imgId = self.coco.imgNameToId[osp.basename(self.imagePath)]
            for polygon in self.scene.polygon_items:
                points = []
                for p in polygon.scnenePoints:
                    for val in p:
                        points.append(val)

                if not polygon.coco_id:
                    annId = self.coco.addAnnotation(imgId, polygon.labelIndex, points)
                    polygon.coco_id = annId
                else:
                    self.coco.updateAnnotation(polygon.coco_id, imgId, points)
            for lab in self.controller.labelList:
                if self.coco.hasCat(lab.idx):
                    self.coco.updateCategory(lab.idx, lab.name, lab.color)
                else:
                    self.coco.addCategory(lab.idx, lab.name, lab.color)
            saveDir = (
                self.outputDir if self.outputDir is not None else osp.dirname(savePath)
            )
            cocoPath = osp.join(saveDir, "annotations.json")
            open(cocoPath, "w", encoding="utf-8").write(json.dumps(self.coco.dataset))

        self.setDirty(False)
        self.statusbar.showMessage(self.tr("标签成功保存至") + " " + savePath, 5000)

    def chooseSavePath(self):
        formats = [
            "*.{}".format(fmt.data().decode())
            for fmt in QtGui.QImageReader.supportedImageFormats()
        ]
        filters = "Label file (%s)" % " ".join(formats)
        dlg = QtWidgets.QFileDialog(
            self,
            self.tr("保存标签文件路径"),
            osp.dirname(self.imagePath),
            filters,
        )
        dlg.setDefaultSuffix("png")
        dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
        dlg.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)
        dlg.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, False)
        savePath, _ = dlg.getSaveFileName(
            self,
            self.tr("选择标签文件保存路径"),
            osp.splitext(osp.basename(self.imagePath))[0] + ".png",
        )
        return savePath

    def eximgsInit(self):
        self.gridTable.setRowCount(0)
        self.gridTable.clearContents()
        # 清零
        self.raster = None
        self.grid = None

    def setDirty(self, isDirty):
        self.isDirty = isDirty

    def changeOutputDir(self, outputDir=None):
        # 1. 弹框选择标签路径
        if outputDir is None:
            options = (
                QtWidgets.QFileDialog.ShowDirsOnly
                | QtWidgets.QFileDialog.DontResolveSymlinks
            )
            if self.settings.value("use_qt_widget", False, type=bool):
                options = options | QtWidgets.QFileDialog.DontUseNativeDialog
            outputDir = QtWidgets.QFileDialog.getExistingDirectory(
                self,
                self.tr("选择标签保存路径") + " - " + __APPNAME__,
                self.settings.value("output_dir", "."),
                options,
            )
        if not osp.exists(outputDir):
            return False
        self.settings.setValue("output_dir", outputDir)
        self.outputDir = outputDir

        # 2. 加载标签
        # 2.1 如果保存coco格式,加载coco标签
        if self.save_status["coco"]:
            defaultPath = osp.join(self.outputDir, "annotations.json")
            if osp.exists(defaultPath):
                self.initCoco(defaultPath)

        # 2.2 如果保存json格式,获取所有json文件名
        if self.save_status["json"]:
            labelPaths = os.listdir(outputDir)
            labelPaths = [n for n in labelPaths if n.endswith(".json")]
            labelPaths = [osp.join(outputDir, n) for n in labelPaths]
            self.labelPaths = labelPaths

            # 加载对应的标签列表
            lab_auto_save = osp.join(self.outputDir, "autosave_label.txt")
            if osp.exists(lab_auto_save) == False:
                lab_auto_save = osp.join(self.outputDir, "label/autosave_label.txt")
            if osp.exists(lab_auto_save):
                try:
                    self.importLabelList(lab_auto_save)
                except:
                    pass
        return True

    def maskOpacityChanged(self):
        self.sldOpacity.textLab.setText(str(self.opacity))
        if not self.controller or self.controller.image is None:
            return
        for polygon in self.scene.polygon_items:
            polygon.setOpacity(self.opacity)
        self.updateImage()

    def clickRadiusChanged(self):
        self.sldClickRadius.textLab.setText(str(self.clickRadius))
        if not self.controller or self.controller.image is None:
            return
        self.updateImage()

    def threshChanged(self):
        self.sldThresh.textLab.setText(str(self.segThresh))
        if not self.controller or self.controller.image is None:
            return
        self.controller.prob_thresh = self.segThresh
        self.updateImage()

    # def slideChanged(self):
    #     self.sldMISlide.textLab.setText(str(self.slideMi))
    #     if not self.controller or self.controller.image is None:
    #         return
    #     self.midx = int(self.slideMi) - 1
    #     self.miSlideSet()
    #     self.updateImage()

    def undoClick(self):
        if self.image is None:
            return
        if not self.controller:
            return
        self.controller.undoClick()
        self.updateImage()
        if not self.controller.is_incomplete_mask:
            self.setDirty(False)

    def clearAll(self):
        if not self.controller or self.controller.image is None:
            return
        self.controller.resetLastObject()
        self.updateImage()
        self.setDirty(False)

    def redoClick(self):
        if self.image is None:
            return
        if not self.controller:
            return
        self.controller.redoClick()
        self.updateImage()

    def canvasClick(self, x, y, isLeft):
        c = self.controller
        if c.image is None:
            return
        if not c.inImage(x, y):
            return
        if not c.modelSet:
            self.warn(self.tr("未选择模型", self.tr("尚未选择模型,请先在右上角选择模型")))
            return

        if self.status == self.IDILE:
            return
        currLabel = self.controller.curr_label_number
        if not currLabel or currLabel == 0:
            self.warn(self.tr("未选择当前标签"), self.tr("请先在标签列表中单击点选标签"))
            return

        self.controller.addClick(x, y, isLeft)
        self.updateImage()
        self.status = self.ANNING

    def updateImage(self, reset_canvas=False):
        if not self.controller:
            return
        image = self.controller.get_visualization(
            alpha_blend=self.opacity,
            click_radius=self.clickRadius,
        )
        height, width, _ = image.shape
        bytesPerLine = 3 * width
        image = QImage(image.data, width, height, bytesPerLine, QImage.Format_RGB888)
        if reset_canvas:
            self.resetZoom(width, height)
        self.annImage.setPixmap(QPixmap(image))

    def viewZoomed(self, scale):
        self.scene.scale = scale
        self.scene.updatePolygonSize()

    # 界面缩放重置
    def resetZoom(self, width, height):
        # 每次加载图像前设定下当前的显示框,解决图像缩小后不在中心的问题
        self.scene.setSceneRect(0, 0, width, height)
        # 缩放清除
        self.canvas.scale(1 / self.canvas.zoom_all, 1 / self.canvas.zoom_all)  # 重置缩放
        self.canvas.zoom_all = 1
        # 最佳缩放
        s_eps = 0.98
        scr_cont = [
            (self.scrollArea.width() * s_eps) / width,
            (self.scrollArea.height() * s_eps) / height,
        ]
        if scr_cont[0] * height > self.scrollArea.height():
            self.canvas.zoom_all = scr_cont[1]
        else:
            self.canvas.zoom_all = scr_cont[0]
        self.canvas.scale(self.canvas.zoom_all, self.canvas.zoom_all)
        self.scene.scale = self.canvas.zoom_all

    def keyReleaseEvent(self, event):
        # print(event.key(), Qt.Key_Control)
        # 释放ctrl的时候刷新图像,对应自适应点大小在缩放后刷新
        if not self.controller or self.controller.image is None:
            return
        if event.key() == Qt.Key_Control:
            self.updateImage()

    def queueEvent(self, function):
        QtCore.QTimer.singleShot(0, function)

    def toggleOrigExt(self, dst=None):
        if dst:
            self.origExt = dst
        else:
            self.origExt = not self.origExt
        self.actions.origional_extension.setChecked(self.origExt)

    def toggleAutoSave(self, save):
        if save and not self.outputDir:
            self.changeOutputDir(None)
        if save and not self.outputDir:
            save = False
        self.actions.auto_save.setChecked(save)
        self.settings.setValue("auto_save", save)

    def toggleSave(self, type):
        self.save_status[type] = not self.save_status[type]
        if type == "coco" and self.save_status["coco"]:
            self.initCoco()
        if type == "coco":
            self.save_status["json"] = not self.save_status["coco"]
            self.actions.save_json.setChecked(self.save_status["json"])
        if type == "json":
            self.save_status["coco"] = not self.save_status["json"]
            self.actions.save_coco.setChecked(self.save_status["coco"])

    def initCoco(self, coco_path: str = None):
        if not coco_path:
            if not self.outputDir or not osp.exists(self.outputDir):
                coco_path = None
            else:
                coco_path = osp.join(self.outputDir, "annotations.json")
        else:
            if not osp.exists(coco_path):
                coco_path = None
        self.coco = COCO(coco_path)
        if self.clearLabelList():
            self.controller.labelList = util.LabelList(self.coco.dataset["categories"])
            self.refreshLabelList()

    def toggleWidget(self, index=None, warn=True):
        # TODO: 输入从数字改成名字

        # 1. 改变
        if isinstance(index, int):
            self.dockStatus[index] = not self.dockStatus[index]

        # 2. 判断widget是否可以开启
        # 2.1 遥感
        if self.dockStatus[4] and not (rs.check_gdal() and rs.check_rasterio()):
            if warn:
                self.warn(
                    self.tr("无法导入GDAL"),
                    self.tr("使用遥感工具需要安装GDAL!"),
                    QMessageBox.Yes,
                )
            self.statusbar.showMessage(self.tr("打开遥感工具失败,请安装GDAL库"))
            self.dockStatus[4] = False

        # 2.2 医疗
        if self.dockStatus[5] and not med.has_sitk():
            if warn:
                self.warn(
                    self.tr("无法导入SimpleITK"),
                    self.tr("使用医疗工具需要安装SimpleITK!"),
                    QMessageBox.Yes,
                )
            self.statusbar.showMessage(self.tr("打开医疗工具失败,请安装SimpleITK"))
            self.dockStatus[5] = False
        widgets = list(self.dockWidgets.values())

        for idx, s in enumerate(self.dockStatus):
            self.menus.showMenu[idx].setChecked(s)
            if s:
                widgets[idx].show()
            else:
                widgets[idx].hide()

        self.settings.setValue("dock_status", self.dockStatus)
        # self.display_dockwidget[index] = bool(self.display_dockwidget[index] - 1)
        # self.toggleDockWidgets()
        self.saveLayout()

    # def toggleDockWidgets(self, is_init=False):
    #     if is_init == True:
    #         if self.dockStatus != []:
    #             if len(self.dockStatus) != len(self.menus.showMenu):
    #                 self.settings.remove("dock_status")
    #             else:
    #                 self.display_dockwidget = [strtobool(w) for w in self.dockStatus]
    #         for i in range(len(self.menus.showMenu)):
    #             self.menus.showMenu[i].setChecked(bool(self.display_dockwidget[i]))
    #     else:
    #         self.settings.setValue("dock_status", self.display_dockwidget)
    #     for t, w in zip(self.display_dockwidget, self.dockWidgets.values()):
    #         if t == True:
    #             w.show()
    #         else:
    #             w.hide()

    def rsBandSet(self, idx):
        if self.raster is None:
            return
        for i in range(len(self.bandCombos)):
            self.rsRGB[i] = self.bandCombos[i].currentIndex() + 1  # 从1开始
        self.raster.setBand(self.rsRGB)
        if self.grid is not None:
            if isinstance(self.grid.curr_idx, (list, tuple)):
                row, col = self.grid.curr_idx
                image, _ = self.raster.getGrid(row, col)
            else:
                image, _ = self.raster.getArray()
        else:
            image, _ = self.raster.getArray()
        self.image = image
        self.controller.image = image
        self.updateImage()

    # def miSlideSet(self):
    #     image = rs.slice_img(self.controller.rawImage, self.midx)
    #     self.test_show(image)

    # def changeWorkerShow(self, index):
    #     self.display_dockwidget[index] = bool(self.display_dockwidget[index] - 1)
    #     self.toggleDockWidgets()

    def updateBandList(self, clean=False):
        if clean:
            for i in range(len(self.bandCombos)):
                try:  # 避免打开jpg后再打开tif报错
                    self.bandCombos[i].currentIndexChanged.disconnect()
                except TypeError:
                    pass
                self.bandCombos[i].clear()
                self.bandCombos[i].addItems(["band_1"])
            return
        bands = self.raster.geoinfo.count
        for i in range(len(self.bandCombos)):
            try:  # 避免打开jpg后再打开tif报错
                self.bandCombos[i].currentIndexChanged.disconnect()
            except TypeError:
                pass
            self.bandCombos[i].clear()
            self.bandCombos[i].addItems([("band_" + str(j + 1)) for j in range(bands)])
            try:
                self.bandCombos[i].setCurrentIndex(self.rsRGB[i] - 1)
            except IndexError:
                pass
        for bandCombo in self.bandCombos:
            bandCombo.currentIndexChanged.connect(self.rsBandSet)  # 设置波段

    # def updateSlideSld(self, clean=False):
    #     if clean:
    #         self.sldMISlide.setMaximum(1)
    #         return
    #     C = self.controller.rawImage.shape[-1] if len(self.controller.rawImage.shape) == 3 else 1
    #     self.sldMISlide.setMaximum(C)

    def toggleLargestCC(self, on):
        try:
            self.controller.filterLargestCC(on)
        except:
            pass

    # 宫格标注
    def initGrid(self):
        self.delAllPolygon()
        grid_row_count, grid_col_count = self.grid.createGrids()
        self.gridTable.setRowCount(grid_row_count)
        self.gridTable.setColumnCount(grid_col_count)
        for r in range(grid_row_count):
            for c in range(grid_col_count):
                self.gridTable.setItem(r, c, QtWidgets.QTableWidgetItem())
                self.gridTable.item(r, c).setBackground(self.GRID_COLOR["idle"])
                self.gridTable.item(r, c).setFlags(Qt.ItemIsSelectable)  # 无法高亮选择
        # 初始显示第一个
        self.grid.curr_idx = (0, 0)
        self.gridTable.item(0, 0).setBackground(self.GRID_COLOR["overlying"])
        # 事件注册
        self.gridTable.cellClicked.connect(self.changeGrid)

    def changeGrid(self, row, col):
        # 清除未保存的切换
        # TODO: 这块应该通过dirty判断?
        if self.grid.curr_idx is not None:
            self.saveGrid()  # 切换时自动保存上一块
            last_r, last_c = self.grid.curr_idx
            if self.grid.mask_grids[last_r][last_c] is None:
                self.gridTable.item(last_r, last_c).setBackground(
                    self.GRID_COLOR["idle"]
                )
            else:
                self.gridTable.item(last_r, last_c).setBackground(
                    self.GRID_COLOR["finised"]
                )
        self.delAllPolygon()
        image, mask = self.grid.getGrid(row, col)
        self.controller.setImage(image)
        self.grid.curr_idx = (row, col)
        if mask is None:
            self.gridTable.item(row, col).setBackground(self.GRID_COLOR["current"])
        else:
            self.gridTable.item(row, col).setBackground(self.GRID_COLOR["overlying"])
            self.mask2poly(mask)
        # 刷新
        self.updateImage(True)

    def mask2poly(self, mask, show=True):
        labs = np.unique(mask)[1:]
        colors = []
        for i in range(len(labs)):
            idx = int(labs[i]) - 1
            if idx < len(self.controller.labelList):
                c = self.controller.labelList[idx].color
            else:
                if self.currLabelIdx != -1:
                    c = self.controller.labelList[self.currLabelIdx].color
                else:
                    c = None
            colors.append(c)
        geocode_list = []
        for idx, (l, c) in enumerate(zip(labs, colors)):
            if c is not None:
                curr_polygon = util.get_polygon(
                    ((mask == l).astype(np.uint8) * 255),
                    building=self.boundaryRegular.isChecked(),
                )
                if show == True:
                    self.createPoly(curr_polygon, c)
                    for p in self.scene.polygon_items:
                        p.setAnning(isAnning=False)
                else:
                    for g in curr_polygon:
                        points = [gi.tolist() for gi in g]
                        geocode_list.append(
                            {
                                "name": self.controller.labelList[idx].name,
                                "points": points,
                            }
                        )
        return geocode_list

    def saveGrid(self):
        row, col = self.grid.curr_idx
        if self.grid.curr_idx is None:
            return
        self.gridTable.item(row, col).setBackground(self.GRID_COLOR["overlying"])
        # if len(np.unique(self.grid.mask_grids[row][col])) == 1:
        self.grid.mask_grids[row][col] = np.array(self.getMask())
        if self.cheSaveEvery.isChecked():
            if self.outputDir is None:
                self.changeOutputDir()
            _, fullflname = osp.split(self.listFiles.currentItem().text())
            fname, _ = os.path.splitext(fullflname)
            path = osp.join(
                self.outputDir, (fname + "_data_" + str(row) + "_" + str(col) + ".tif")
            )
            im, tf = self.raster.getGrid(row, col)
            h, w = im.shape[:2]
            geoinfo = edict()
            geoinfo.xsize = w
            geoinfo.ysize = h
            geoinfo.dtype = self.raster.geoinfo.dtype
            geoinfo.crs = self.raster.geoinfo.crs
            geoinfo.geotf = tf
            self.raster.saveMask(
                self.grid.mask_grids[row][col], path.replace("data", "mask"), geoinfo
            )  # 保存mask
            self.raster.saveMask(im, path, geoinfo, 3)  # 保存图像

    def turnGrid(self, delta):
        # 切换下一个宫格
        r, c = self.grid.curr_idx if self.grid.curr_idx is not None else (0, -1)
        c += delta
        if c >= self.grid.grid_count[1]:
            c = 0
            r += 1
            if r >= self.grid.grid_count[0]:
                r = 0
        if c < 0:
            c = self.grid.grid_count[1] - 1
            r -= 1
            if r < 0:
                r = self.grid.grid_count[0] - 1
        self.changeGrid(r, c)

    def closeGrid(self):
        self.grid = None
        self.gridTable.setRowCount(0)
        self.gridTable.clearContents()

    def saveGridLabel(self):
        if self.outputDir is not None:
            name, ext = osp.splitext(osp.basename(self.imagePath))
            if not self.origExt:
                ext = ".png"
            save_path = osp.join(self.outputDir, name + ext)
        else:
            save_path = self.chooseSavePath()
            if save_path == "":
                return
        try:
            self.finishObject()
            self.saveGrid()  # 先保存当前
        except:
            pass
        self.delAllPolygon()  # 清理
        mask = self.grid.splicingList(save_path)
        if self.grid.__class__.__name__ == "RSGrids":
            self.image, is_big = self.raster.getArray()
        else:
            self.image = self.grid.detimg
            is_big = checkOpenGrid(self.image, self.thumbnail_min)
        if is_big is None:
            self.statusbar.showMessage(self.tr("图像过大,已显示缩略图"))
        self.controller.image = self.image
        self.controller._result_mask = mask
        self.exportLabel(savePath=save_path, lab_input=mask)
        # -- RS Show polygon demo --
        if self.show_rs_poly.isChecked():
            h, w = self.image.shape[:2]
            th_mask = cv2.resize(mask, dsize=(w, h), interpolation=cv2.INTER_NEAREST)
            indexs = np.unique(th_mask)[1:]
            for i in indexs:
                i_mask = np.zeros_like(th_mask, dtype="uint8")
                i_mask[th_mask == i] = 255
                curr_polygon = util.get_polygon(i_mask)
                color = self.controller.labelList[i - 1].color
                self.createPoly(curr_polygon, color)
                for p in self.scene.polygon_items:
                    p.setAnning(isAnning=False)
        # -- RS Show polygon demo --
        # 刷新
        grid_row_count = self.gridTable.rowCount()
        grid_col_count = self.gridTable.colorCount()
        for r in range(grid_row_count):
            for c in range(grid_col_count):
                try:
                    self.gridTable.item(r, c).setBackground(self.GRID_COLOR["idle"])
                except:
                    pass
        self.raster = None
        self.closeGrid()
        self.updateBandList(True)
        self.controller.setImage(self.image)
        self.updateImage(True)
        self.setDirty(False)

    @property
    def opacity(self):
        return self.sldOpacity.value() / 100

    @property
    def clickRadius(self):
        return self.sldClickRadius.value()

    @property
    def segThresh(self):
        return self.sldThresh.value() / 100

    # @property
    # def slideMi(self):
    #     return self.sldMISlide.value()

    def warnException(self, e):
        e = str(e)
        title = e.split("。")[0]
        self.warn(title, e)

    def warn(self, title, text, buttons=QMessageBox.Yes):
        msg = QMessageBox()
        # msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(title)
        msg.setText(text)
        msg.setStandardButtons(buttons)
        return msg.exec_()

    @property
    def status(self):
        # TODO: 图片,模型
        if not self.controller:
            return self.IDILE
        c = self.controller
        if c.model is None or c.image is None:
            return self.IDILE
        if self._anning:
            return self.ANNING
        return self.EDITING

    @status.setter
    def status(self, status):
        if status not in [self.ANNING, self.EDITING]:
            return
        if status == self.ANNING:
            self._anning = True
        else:
            self._anning = False

    def loadGrid(self, img, is_rs=True):
        res = self.warn(self.tr("图像过大"), self.tr("图像过大,将启用宫格功能!"), \
                        buttons=QMessageBox.Yes | QMessageBox.No)
        if res == QMessageBox.Yes:
            # 打开宫格功能
            if self.dockWidgets["grid"].isVisible() is False:
                # TODO: 改成self.dockStatus
                self.menus.showMenu[-1].setChecked(True)
                # self.display_dockwidget[-1] = True
                self.dockWidgets["grid"].show()
            self.grid = RSGrids(img) if is_rs else Grids(img)
            self.initGrid()
            return True
        return False

    # 界面布局
    def loadLayout(self):
        self.restoreState(self.layoutStatus)
        # TODO: 这里检查环境,判断是不是开医疗和遥感widget

    def saveLayout(self):
        # 保存界面
        self.settings.setValue("layout_status", QByteArray(self.saveState()))
        self.settings.setValue(
            "save_status", [(k, self.save_status[k]) for k in self.save_status.keys()]
        )
        # # 如果设置了保存路径,把标签也保存下
        # if self.outputDir is not None and len(self.controller.labelList) != 0:
        #     self.exportLabelList(osp.join(self.outputDir, "autosave_label.txt"))

    def closeEvent(self, event):
        self.saveImage()
        self.saveLayout()
        QCoreApplication.quit()
        # sys.exit(0)

    def reportBug(self):
        webbrowser.open("https://github.com/PaddleCV-SIG/EISeg/issues/new/choose")

    def quickStart(self):
        # self.saveImage(True)
        # self.canvas.setStyleSheet(self.note_style)
        webbrowser.open("https://github.com/PaddleCV-SIG/EISeg/tree/release/0.4.0")

    def toggleLogging(self, s):
        if s:
            logger.setLevel(logging.DEBUG)
        else:
            logger.setLevel(logging.CRITICAL)
        self.settings.setValue("log", s)

    def toBeImplemented(self):
        self.statusbar.showMessage(self.tr("功能尚在开发"))

    # 医疗
    def wwChanged(self):
        if not self.controller or self.image is None:
            return
        try:  # 那种jpg什么格式的医疗图像调整窗宽等会造成崩溃
            self.textWw.selectAll()
            self.controller.image = med.windowlize(
                self.controller.rawImage, self.ww, self.wc
            )
            self.updateImage()
        except:
            pass

    def wcChanged(self):
        if not self.controller or self.image is None:
            return
        try:
            self.textWc.selectAll()
            self.controller.image = med.windowlize(
                self.controller.rawImage, self.ww, self.wc
            )
            self.updateImage()
        except:
            pass

    @property
    def ww(self):
        return int(self.textWw.text())

    @property
    def wc(self):
        return int(self.textWc.text())

    def twwChanged(self):
        if self.ww > self.sliderWw.maximum():
            self.textWw.setText(str(self.sliderWw.maximum()))
        if self.ww < self.sliderWw.minimum():
            self.textWw.setText(str(self.sliderWw.minimum()))
        self.sliderWw.setProperty("value", self.ww)
        self.wwChanged()

    def swwChanged(self):
        self.textWw.setText(str(self.sliderWw.value()))
        self.wwChanged()

    def twcChanged(self):
        if self.wc > self.sliderWc.maximum():
            self.textWc.setText(str(self.sliderWc.maximum()))
        if self.wc < self.sliderWc.minimum():
            self.textWc.setText(str(self.sliderWc.minimum()))
        self.sliderWc.setProperty("value", self.wc)
        self.wcChanged()

    def swcChanged(self):
        self.textWc.setText(str(self.sliderWc.value()))
        self.wcChanged()

    def useQtWidget(self, s):
        print("checked", s)
        self.settings.setValue("use_qt_widget", s)


================================================
FILE: eiseg/config/colormap.txt
================================================
53,119,181
245,128,6
67,159,36
204,43,41
145,104,190
135,86,75
219,120,195
127,127,127
187,189,18
72,190,207
178,199,233
248,187,118
160,222,135
247,153,150
195,176,214
192,156,148
241,183,211
199,199,199
218,219,139
166,218,229

================================================
FILE: eiseg/config/config.yaml
================================================
shortcut:
  about: Q
  auto_save: X
  change_output_dir: Shift+Z
  clear: Ctrl+Shift+Z
  clear_label: ''
  clear_recent: ''
  close: Ctrl+W
  data_worker: ''
  del_active_polygon: Backspace
  edit_shortcuts: E
  finish_object: Space
  grid_ann: ''
  label_worker: ''
  largest_component: ''
  load_label: ''
  load_param: Ctrl+M
  medical_worker: ''
  model_worker: ''
  open_folder: Shift+A
  open_image: Ctrl+A
  origional_extension: ''
  quick_start: ''
  quit: ''
  redo: Ctrl+Y
  remote_worker: ''
  save: ''
  save_as: ''
  save_coco: ''
  save_json: ''
  save_label: ''
  save_pseudo: ''
  set_worker: ''
  turn_next: F
  turn_prev: S
  undo: Ctrl+Z


================================================
FILE: eiseg/controller.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os.path as osp
import time
import json
import logging

import cv2
import numpy as np
from skimage.measure import label
import paddle

from eiseg import logger
from inference import clicker
from inference.predictor import get_predictor
import util
from util.vis import draw_with_blend_and_clicks
from models import EISegModel
from util import LabelList


class InteractiveController:
    def __init__(
        self,
        predictor_params: dict = None,
        prob_thresh: float = 0.5,
    ):
        """初始化控制器.

        Parameters
        ----------
        predictor_params : dict
            推理器配置
        prob_thresh : float
            区分前景和背景结果的阈值

        """
        self.predictor_params = predictor_params
        self.prob_thresh = prob_thresh
        self.model = None
        self.image = None
        self.rawImage = None
        self.predictor = None
        self.clicker = clicker.Clicker()
        self.states = []
        self.probs_history = []
        self.polygons = []

        # 用于redo
        self.undo_states = []
        self.undo_probs_history = []

        self.curr_label_number = 0
        self._result_mask = None
        self.labelList = LabelList()
        self.lccFilter = False
        self.log = logging.getLogger(__name__)

    def filterLargestCC(self, do_filter: bool):
        """设置是否只保留推理结果中的最大联通块

        Parameters
        ----------
        do_filter : bool
            是否只保存推理结果中的最大联通块
        """
        if not isinstance(do_filter, bool):
            return
        self.lccFilter = do_filter

    def setModel(self, param_path=None, use_gpu=None):
        """设置推理其模型.

        Parameters
        ----------
        params_path : str
            模型路径

        use_gpu : bool
            None:检测,根据paddle版本判断
            bool:按照指定是否开启GPU

        Returns
        -------
        bool, str
            是否成功设置模型, 失败原因

        """
        if param_path is not None:
            model_path = param_path.replace(".pdiparams", ".pdmodel")
            if not osp.exists(model_path):
                raise Exception(f"未在 {model_path} 找到模型文件")
            if use_gpu is None:
                if paddle.device.is_compiled_with_cuda():  # TODO: 可以使用GPU却返回False
                    use_gpu = True
                else:
                    use_gpu = False
            logger.info(f"User paddle compiled with gpu: use_gpu {use_gpu}")
            tic = time.time()
            try:
                self.model = EISegModel(model_path, param_path, use_gpu)
                self.reset_predictor()  # 即刻生效
            except KeyError as e:
                return False, str(e)
            logger.info(f"Load model {model_path} took {time.time() - tic}")
            return True, "模型设置成功"

    def setImage(self, image: np.array):
        """设置当前标注的图片

        Parameters
        ----------
        image : np.array
            当前标注的图片

        """
        if self.model is not None:
            self.image = image
            self._result_mask = np.zeros(image.shape[:2], dtype=np.uint8)
            self.resetLastObject()

    # 标签操作
    def setLabelList(self, labelList: json):
        """设置标签列表,会覆盖已有的标签列表

        Parameters
        ----------
        labelList : json
            标签列表格式为
            {
                {
                    "idx" : int         (like 0 or 1 or 2)
                    "name" : str        (like "car" or "airplan")
                    "color" : list      (like [255, 0, 0])
                },
                ...
            }

        Returns
        -------
        type
            Description of returned object.

        """
        self.labelList.clear()
        labels = json.loads(labelList)
        for lab in labels:
            self.labelList.add(lab["id"], lab["name"], lab["color"])

    def addLabel(self, id: int, name: str, color: list):
        self.labelList.add(id, name, color)

    def delLabel(self, id: int):
        self.labelList.remove(id)

    def clearLabel(self):
        self.labelList.clear()

    def importLabel(self, path):
        self.labelList.importLabel(path)

    def exportLabel(self, path):
        self.labelList.exportLabel(path)

    # 点击操作
    def addClick(self, x: int, y: int, is_positive: bool):
        """添加一个点并运行推理,保存历史用于undo

        Parameters
        ----------
        x : int
            点击的横坐标
        y : int
            点击的纵坐标
        is_positive : bool
            是否点的是正点

        Returns
        -------
        bool, str
            点击是否添加成功, 失败原因

        """

        # 1. 确定可以点
        if not self.inImage(x, y):
            return False, "点击越界"
        if not self.modelSet:
            return False, "未加载模型"
        if not self.imageSet:
            return False, "图像未设置"

        if len(self.states) == 0:  # 保存一个空状态
            self.states.append(
                {
                    "clicker": self.clicker.get_state(),
                    "predictor": self.predictor.get_states(),
                }
            )

        # 2. 添加点击,跑推理
        click = clicker.Click(is_positive=is_positive, coords=(y, x))
        self.clicker.add_click(click)
        pred = self.predictor.get_prediction(self.clicker)

        # 3. 保存状态
        self.states.append(
            {
                "clicker": self.clicker.get_state(),
                "predictor": self.predictor.get_states(),
            }
        )
        if self.probs_history:
            self.probs_history.append((self.probs_history[-1][1], pred))
        else:
            self.probs_history.append((np.zeros_like(pred), pred))

        # 点击之后就不能接着之前的历史redo了
        self.undo_states = []
        self.undo_probs_history = []
        return True, "点击添加成功"

    def undoClick(self):
        """
        undo一步点击
        """
        if len(self.states) <= 1:  # == 1就只剩下一个空状态了,不用再退
            return
        self.undo_states.append(self.states.pop())
        self.clicker.set_state(self.states[-1]["clicker"])
        self.predictor.set_states(self.states[-1]["predictor"])
        self.undo_probs_history.append(self.probs_history.pop())
        if not self.probs_history:
            self.reset_init_mask()

    def redoClick(self):
        """
        redo一步点击
        """
        if len(self.undo_states) == 0:  # 如果还没撤销过
            return
        if len(self.undo_probs_history) >= 1:
            next_state = self.undo_states.pop()
            self.states.append(next_state)
            self.clicker.set_state(next_state["clicker"])
            self.predictor.set_states(next_state["predictor"])
            self.probs_history.append(self.undo_probs_history.pop())

    def finishObject(self, building=False):
        """
        结束当前物体标注,准备标下一个
        """
        object_prob = self.current_object_prob
        if object_prob is None:
            return None, None
        object_mask = object_prob > self.prob_thresh
        if self.lccFilter:
            object_mask = self.getLargestCC(object_mask)
        polygon = util.get_polygon((object_mask.astype(np.uint8) * 255), 
                                    img_size=object_mask.shape,
                                    building=building)
        if polygon is not None:
            self._result_mask[object_mask] = self.curr_label_number
            self.resetLastObject()
            self.polygons.append([self.curr_label_number, polygon])
        return object_mask, polygon

    # 多边形
    def getPolygon(self):
        return self.polygon

    def setPolygon(self, polygon):
        self.polygon = polygon

    # mask
    def getMask(self):
        s = self.imgShape
        img = np.zeros([s[0], s[1]])
        for poly in self.polygons:
            pts = np.int32([np.array(poly[1])])
            cv2.fillPoly(img, pts=pts, color=poly[0])
        return img

    def setCurrLabelIdx(self, number):
        if not isinstance(number, int):
            return False
        self.curr_label_number = number

    def resetLastObject(self, update_image=True):
        """
        重置控制器状态
        Parameters
            update_image(bool): 是否更新图像
        """
        self.states = []
        self.probs_history = []
        self.undo_states = []
        self.undo_probs_history = []
        # self.current_object_prob = None
        self.clicker.reset_clicks()
        self.reset_predictor()
        self.reset_init_mask()

    def reset_predictor(self, predictor_params=None):
        """
        重置推理器,可以换推理配置
        Parameters
            predictor_params(dict): 推理配置
        """
        if predictor_params is not None:
            self.predictor_params = predictor_params
        if self.model.model:
            self.predictor = get_predictor(self.model.model, **self.predictor_params)
            if self.image is not None:
                self.predictor.set_input_image(self.image)

    def reset_init_mask(self):
        self.clicker.click_indx_offset = 0

    def getLargestCC(self, mask):
        mask = label(mask)
        if mask.max() == 0:
            return mask
        mask = mask == np.argmax(np.bincount(mask.flat)[1:]) + 1
        return mask

    def get_visualization(self, alpha_blend: float, click_radius: int):
        if self.image is None:
            return None
        # 1. 正在标注的mask
        # results_mask_for_vis = self.result_mask  # 加入之前标完的mask
        results_mask_for_vis = np.zeros_like(self.result_mask)
        results_mask_for_vis *= self.curr_label_number
        if self.probs_history:
            results_mask_for_vis[
                self.current_object_prob > self.prob_thresh
            ] = self.curr_label_number
        if self.lccFilter:
            results_mask_for_vis = (
                self.getLargestCC(results_mask_for_vis) * self.curr_label_number
            )
        vis = draw_with_blend_and_clicks(
            self.image,
            mask=results_mask_for_vis,
            alpha=alpha_blend,
            clicks_list=self.clicker.clicks_list,
            radius=click_radius,
            palette=self.palette,
        )
        return vis

    def inImage(self, x: int, y: int):
        s = self.image.shape
        if x < 0 or y < 0 or x >= s[1] or y >= s[0]:
            return False
        return True

    @property
    def result_mask(self):
        result_mask = self._result_mask.copy()
        return result_mask

    @property
    def palette(self):
        if self.labelList:
            colors = [ml.color for ml in self.labelList]
            colors.insert(0, [0, 0, 0])
        else:
            colors = [[0, 0, 0]]
        return colors

    @property
    def current_object_prob(self):
        """
        获取当前推理标签
        """
        if self.probs_history:
            _, current_prob_additive = self.probs_history[-1]
            return current_prob_additive
        else:
            return None

    @property
    def is_incomplete_mask(self):
        """
        Returns
            bool: 当前的物体是不是还没标完
        """
        return len(self.probs_history) > 0

    @property
    def imgShape(self):
        return self.image.shape  # [1::-1]

    @property
    def modelSet(self):
        return self.model is not None

    @property
    def modelName(self):
        return self.model.__name__

    @property
    def imageSet(self):
        return self.image is not None


================================================
FILE: eiseg/exe.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os.path as osp
import sys

sys.path.append(osp.dirname(osp.dirname(osp.realpath(__file__))))

from run import main


if __name__ == "__main__":
    main()


================================================
FILE: eiseg/inference/__init__.py
================================================


================================================
FILE: eiseg/inference/clicker.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""

import cv2
import numpy as np
from copy import deepcopy


class Clicker(object):
    def __init__(
        self, gt_mask=None, init_clicks=None, ignore_label=-1, click_indx_offset=0
    ):
        self.click_indx_offset = click_indx_offset
        if gt_mask is not None:
            self.gt_mask = gt_mask == 1
            self.not_ignore_mask = gt_mask != ignore_label
        else:
            self.gt_mask = None

        self.reset_clicks()

        if init_clicks is not None:
            for click in init_clicks:
                self.add_click(click)

    def make_next_click(self, pred_mask):
        assert self.gt_mask is not None
        click = self._get_next_click(pred_mask)
        self.add_click(click)

    def get_clicks(self, clicks_limit=None):
        return self.clicks_list[:clicks_limit]

    def _get_next_click(self, pred_mask, padding=True):
        fn_mask = np.logical_and(
            np.logical_and(self.gt_mask, np.logical_not(pred_mask)),
            self.not_ignore_mask,
        )
        fp_mask = np.logical_and(
            np.logical_and(np.logical_not(self.gt_mask), pred_mask),
            self.not_ignore_mask,
        )

        if padding:
            fn_mask = np.pad(fn_mask, ((1, 1), (1, 1)), "constant")
            fp_mask = np.pad(fp_mask, ((1, 1), (1, 1)), "constant")

        fn_mask_dt = cv2.distanceTransform(fn_mask.astype(np.uint8), cv2.DIST_L2, 0)
        fp_mask_dt = cv2.distanceTransform(fp_mask.astype(np.uint8), cv2.DIST_L2, 0)

        if padding:
            fn_mask_dt = fn_mask_dt[1:-1, 1:-1]
            fp_mask_dt = fp_mask_dt[1:-1, 1:-1]

        fn_mask_dt = fn_mask_dt * self.not_clicked_map
        fp_mask_dt = fp_mask_dt * self.not_clicked_map

        fn_max_dist = np.max(fn_mask_dt)
        fp_max_dist = np.max(fp_mask_dt)

        is_positive = fn_max_dist > fp_max_dist
        if is_positive:
            coords_y, coords_x = np.where(fn_mask_dt == fn_max_dist)  # coords is [y, x]
        else:
            coords_y, coords_x = np.where(fp_mask_dt == fp_max_dist)  # coords is [y, x]

        return Click(is_positive=is_positive, coords=(coords_y[0], coords_x[0]))

    def add_click(self, click):
        coords = click.coords

        click.indx = self.click_indx_offset + self.num_pos_clicks + self.num_neg_clicks
        if click.is_positive:
            self.num_pos_clicks += 1
        else:
            self.num_neg_clicks += 1

        self.clicks_list.append(click)
        if self.gt_mask is not None:
            self.not_clicked_map[coords[0], coords[1]] = False

    def _remove_last_click(self):
        click = self.clicks_list.pop()
        coords = click.coords

        if click.is_positive:
            self.num_pos_clicks -= 1
        else:
            self.num_neg_clicks -= 1

        if self.gt_mask is not None:
            self.not_clicked_map[coords[0], coords[1]] = True

    def reset_clicks(self):
        if self.gt_mask is not None:
            self.not_clicked_map = np.ones_like(self.gt_mask, dtype=np.bool)

        self.num_pos_clicks = 0
        self.num_neg_clicks = 0

        self.clicks_list = []

    def get_state(self):
        return deepcopy(self.clicks_list)

    def set_state(self, state):
        self.reset_clicks()
        for click in state:
            self.add_click(click)

    def __len__(self):
        return len(self.clicks_list)


class Click:
    def __init__(self, is_positive, coords, indx=None):
        self.is_positive = is_positive
        self.coords = coords
        self.indx = indx

    @property
    def coords_and_indx(self):
        return (*self.coords, self.indx)

    def copy(self, **kwargs):
        self_copy = deepcopy(self)
        for k, v in kwargs.items():
            setattr(self_copy, k, v)
        return self_copy


================================================
FILE: eiseg/inference/predictor/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""

from .base import BasePredictor
from inference.transforms import ZoomIn


def get_predictor(
    net, brs_mode, with_flip=False, zoom_in_params=dict(), predictor_params=None
):

    predictor_params_ = {"optimize_after_n_clicks": 1}

    if zoom_in_params is not None:
        zoom_in = ZoomIn(**zoom_in_params)
    else:
        zoom_in = None

    if brs_mode == "NoBRS":

        if predictor_params is not None:
            predictor_params_.update(predictor_params)
        predictor = BasePredictor(
            net, zoom_in=zoom_in, with_flip=with_flip, **predictor_params_
        )

    else:
        raise NotImplementedError("Just support NoBRS mode")
    return predictor


================================================
FILE: eiseg/inference/predictor/base.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""


import paddle
import paddle.nn.functional as F
import numpy as np

from inference.transforms import AddHorizontalFlip, SigmoidForPred, LimitLongestSide
from .ops import DistMaps, ScaleLayer, BatchImageNormalize


class BasePredictor(object):
    def __init__(
        self,
        model,
        net_clicks_limit=None,
        with_flip=False,
        zoom_in=None,
        max_size=None,
        with_mask=True,
        **kwargs
    ):

        self.with_flip = with_flip
        self.net_clicks_limit = net_clicks_limit
        self.original_image = None
        self.zoom_in = zoom_in
        self.prev_prediction = None
        self.model_indx = 0
        self.click_models = None
        self.net_state_dict = None
        self.with_prev_mask = with_mask
        self.net = model

        self.normalization = BatchImageNormalize(
            [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
        )

        self.transforms = [zoom_in] if zoom_in is not None else []
        if max_size is not None:
            self.transforms.append(LimitLongestSide(max_size=max_size))
        self.transforms.append(SigmoidForPred())
        if with_flip:
            self.transforms.append(AddHorizontalFlip())
        self.dist_maps = DistMaps(
            norm_radius=5, spatial_scale=1.0, cpu_mode=False, use_disks=True
        )

    def to_tensor(self, x):
        if isinstance(x, np.ndarray):
            if x.ndim == 2:
                x = x[:, :, None]
        img = paddle.to_tensor(x.transpose([2, 0, 1])).astype("float32") / 255
        return img

    def set_input_image(self, image):
        image_nd = self.to_tensor(image)

        for transform in self.transforms:
            transform.reset()
        self.original_image = image_nd
        if len(self.original_image.shape) == 3:
            self.original_image = self.original_image.unsqueeze(0)
        self.prev_prediction = paddle.zeros_like(self.original_image[:, :1, :, :])
        if not self.with_prev_mask:
            self.prev_edge = paddle.zeros_like(self.original_image[:, :1, :, :])

    def get_prediction(self, clicker, prev_mask=None):
        clicks_list = clicker.get_clicks()

        input_image = self.original_image
        if prev_mask is None:
            if not self.with_prev_mask:

                prev_mask = self.prev_edge
            else:
                prev_mask = self.prev_prediction

        input_image = paddle.concat([input_image, prev_mask], axis=1)

        image_nd, clicks_lists, is_image_changed = self.apply_transforms(
            input_image, [clicks_list]
        )
        pred_logits, pred_edges = self._get_prediction(
            image_nd, clicks_lists, is_image_changed
        )

        pred_logits = paddle.to_tensor(pred_logits)

        prediction = F.interpolate(
            pred_logits, mode="bilinear", align_corners=True, size=image_nd.shape[2:]
        )
        if pred_edges is not None:
            pred_edge = paddle.to_tensor(pred_edges)
            edge_prediction = F.interpolate(
                pred_edge, mode="bilinear", align_corners=True, size=image_nd.shape[2:]
            )

        for t in reversed(self.transforms):
            if pred_edges is not None:
                edge_prediction = t.inv_transform(edge_prediction)
                self.prev_edge = edge_prediction
            prediction = t.inv_transform(prediction)

        if self.zoom_in is not None and self.zoom_in.check_possible_recalculation():
            return self.get_prediction(clicker)

        self.prev_prediction = prediction
        return prediction.numpy()[0, 0]

    def prepare_input(self, image):
        prev_mask = None
        prev_mask = image[:, 3:, :, :]
        image = image[:, :3, :, :]
        image = self.normalization(image)
        return image, prev_mask

    def get_coord_features(self, image, prev_mask, points):

        coord_features = self.dist_maps(image, points)

        if prev_mask is not None:
            coord_features = paddle.concat((prev_mask, coord_features), axis=1)

        return coord_features

    def _get_prediction(self, image_nd, clicks_lists, is_image_changed):
        input_names = self.net.get_input_names()
        self.input_handle_1 = self.net.get_input_handle(input_names[0])
        self.input_handle_2 = self.net.get_input_handle(input_names[1])
        points_nd = self.get_points_nd(clicks_lists)

        image, prev_mask = self.prepare_input(image_nd)
        coord_features = self.get_coord_features(image, prev_mask, points_nd)
        image = image.numpy().astype("float32")
        coord_features = coord_features.numpy().astype("float32")

        self.input_handle_1.copy_from_cpu(image)
        self.input_handle_2.copy_from_cpu(coord_features)

        self.net.run()

        output_names = self.net.get_output_names()

        output_handle = self.net.get_output_handle(output_names[0])
        output_data = output_handle.copy_to_cpu()
        if len(output_names) == 3:
            edge_handle = self.net.get_output_handle(output_names[2])
            edge_data = edge_handle.copy_to_cpu()
            return output_data, edge_data
        else:
            return output_data, None

    def _get_transform_states(self):
        return [x.get_state() for x in self.transforms]

    def _set_transform_states(self, states):
        assert len(states) == len(self.transforms)
        for state, transform in zip(states, self.transforms):
            transform.set_state(state)

    def apply_transforms(self, image_nd, clicks_lists):
        is_image_changed = False
        for t in self.transforms:
            image_nd, clicks_lists = t.transform(image_nd, clicks_lists)
            is_image_changed |= t.image_changed

        return image_nd, clicks_lists, is_image_changed

    def get_points_nd(self, clicks_lists):
        total_clicks = []
        num_pos_clicks = [
            sum(x.is_positive for x in clicks_list) for clicks_list in clicks_lists
        ]
        num_neg_clicks = [
            len(clicks_list) - num_pos
            for clicks_list, num_pos in zip(clicks_lists, num_pos_clicks)
        ]
        num_max_points = max(num_pos_clicks + num_neg_clicks)
        if self.net_clicks_limit is not None:
            num_max_points = min(self.net_clicks_limit, num_max_points)
        num_max_points = max(1, num_max_points)

        for clicks_list in clicks_lists:
            clicks_list = clicks_list[: self.net_clicks_limit]
            pos_clicks = [
                click.coords_and_indx for click in clicks_list if click.is_positive
            ]
            pos_clicks = pos_clicks + (num_max_points - len(pos_clicks)) * [
                (-1, -1, -1)
            ]

            neg_clicks = [
                click.coords_and_indx for click in clicks_list if not click.is_positive
            ]
            neg_clicks = neg_clicks + (num_max_points - len(neg_clicks)) * [
                (-1, -1, -1)
            ]
            total_clicks.append(pos_clicks + neg_clicks)

        return paddle.to_tensor(total_clicks)

    def get_states(self):
        return {
            "transform_states": self._get_transform_states(),
            "prev_prediction": self.prev_prediction,
        }

    def set_states(self, states):
        self._set_transform_states(states["transform_states"])
        self.prev_prediction = states["prev_prediction"]


def split_points_by_order(tpoints, groups):
    points = tpoints.numpy()
    num_groups = len(groups)
    bs = points.shape[0]
    num_points = points.shape[1] // 2

    groups = [x if x > 0 else num_points for x in groups]
    group_points = [np.full((bs, 2 * x, 3), -1, dtype=np.float32) for x in groups]

    last_point_indx_group = np.zeros((bs, num_groups, 2), dtype=np.int)
    for group_indx, group_size in enumerate(groups):
        last_point_indx_group[:, group_indx, 1] = group_size

    for bindx in range(bs):
        for pindx in range(2 * num_points):
            point = points[bindx, pindx, :]
            group_id = int(point[2])
            if group_id < 0:
                continue

            is_negative = int(pindx >= num_points)
            if group_id >= num_groups or (
                group_id == 0 and is_negative
            ):  # disable negative first click
                group_id = num_groups - 1

            new_point_indx = last_point_indx_group[bindx, group_id, is_negative]
            last_point_indx_group[bindx, group_id, is_negative] += 1

            group_points[group_id][bindx, new_point_indx, :] = point

    group_points = [paddle.to_tensor(x, dtype=tpoints.dtype) for x in group_points]

    return group_points


================================================
FILE: eiseg/inference/predictor/ops.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""


import paddle
import paddle.nn as nn
import numpy as np


class DistMaps(nn.Layer):
    def __init__(self, norm_radius, spatial_scale=1.0, cpu_mode=True, use_disks=False):
        super(DistMaps, self).__init__()
        self.spatial_scale = spatial_scale
        self.norm_radius = norm_radius
        self.cpu_mode = cpu_mode
        self.use_disks = use_disks

        if self.cpu_mode:
            from util.cython import get_dist_maps

            self._get_dist_maps = get_dist_maps

    def get_coord_features(self, points, batchsize, rows, cols):
        if self.cpu_mode:
            coords = []
            for i in range(batchsize):
                norm_delimeter = (
                    1.0 if self.use_disks else self.spatial_scale * self.norm_radius
                )
                coords.append(
                    self._get_dist_maps(
                        points[i].numpy().astype("float32"), rows, cols, norm_delimeter
                    )
                )
            coords = paddle.to_tensor(np.stack(coords, axis=0)).astype("float32")
        else:
            num_points = points.shape[1] // 2
            points = points.reshape([-1, points.shape[2]])
            points, points_order = paddle.split(points, [2, 1], axis=1)
            invalid_points = paddle.max(points, axis=1, keepdim=False) < 0
            row_array = paddle.arange(start=0, end=rows, step=1, dtype="float32")
            col_array = paddle.arange(start=0, end=cols, step=1, dtype="float32")

            coord_rows, coord_cols = paddle.meshgrid(row_array, col_array)
            coords = paddle.unsqueeze(
                paddle.stack([coord_rows, coord_cols], axis=0), axis=0
            ).tile([points.shape[0], 1, 1, 1])

            add_xy = (points * self.spatial_scale).reshape(
                [points.shape[0], points.shape[1], 1, 1]
            )
            coords = coords - add_xy
            if not self.use_disks:
                coords = coords / (self.norm_radius * self.spatial_scale)

            coords = coords * coords
            coords[:, 0] += coords[:, 1]
            coords = coords[:, :1]
            invalid_points = invalid_points.numpy()

            coords[invalid_points, :, :, :] = 1e6
            coords = coords.reshape([-1, num_points, 1, rows, cols])
            coords = paddle.min(coords, axis=1)
            coords = coords.reshape([-1, 2, rows, cols])

        if self.use_disks:
            coords = (coords <= (self.norm_radius * self.spatial_scale) ** 2).astype(
                "float32"
            )
        else:
            coords = paddle.tanh(paddle.sqrt(coords) * 2)
        return coords

    def forward(self, x, coords):
        return self.get_coord_features(coords, x.shape[0], x.shape[2], x.shape[3])


class ScaleLayer(nn.Layer):
    def __init__(self, init_value=1.0, lr_mult=1):
        super().__init__()
        self.lr_mult = lr_mult
        self.scale = self.create_parameter(
            shape=[1],
            dtype="float32",
            default_initializer=nn.initializer.Constant(init_value / lr_mult),
        )

    def forward(self, x):
        scale = paddle.abs(self.scale * self.lr_mult)
        return x * scale


class BatchImageNormalize:
    def __init__(self, mean, std):
        self.mean = paddle.to_tensor(
            np.array(mean)[np.newaxis, :, np.newaxis, np.newaxis]
        ).astype("float32")
        self.std = paddle.to_tensor(
            np.array(std)[np.newaxis, :, np.newaxis, np.newaxis]
        ).astype("float32")

    def __call__(self, tensor):
        tensor = (tensor - self.mean) / self.std
        return tensor


================================================
FILE: eiseg/inference/transforms/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from .base import SigmoidForPred
from .flip import AddHorizontalFlip
from .zoom_in import ZoomIn
from .limit_longest_side import LimitLongestSide
from .crops import Crops


================================================
FILE: eiseg/inference/transforms/base.py
================================================
import paddle.nn.functional as F


class BaseTransform(object):
    def __init__(self):
        self.image_changed = False

    def transform(self, image_nd, clicks_lists):
        raise NotImplementedError

    def inv_transform(self, prob_map):
        raise NotImplementedError

    def reset(self):
        raise NotImplementedError

    def get_state(self):
        raise NotImplementedError

    def set_state(self, state):
        raise NotImplementedError


class SigmoidForPred(BaseTransform):
    def transform(self, image_nd, clicks_lists):
        return image_nd, clicks_lists

    def inv_transform(self, prob_map):
        return F.sigmoid(prob_map)

    def reset(self):
        pass

    def get_state(self):
        return None

    def set_state(self, state):
        pass


================================================
FILE: eiseg/inference/transforms/crops.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""


import math

import paddle
import numpy as np

from inference.clicker import Click
from .base import BaseTransform


class Crops(BaseTransform):
    def __init__(self, crop_size=(320, 480), min_overlap=0.2):
        super().__init__()
        self.crop_height, self.crop_width = crop_size
        self.min_overlap = min_overlap

        self.x_offsets = None
        self.y_offsets = None
        self._counts = None

    def transform(self, image_nd, clicks_lists):
        assert image_nd.shape[0] == 1 and len(clicks_lists) == 1
        image_height, image_width = image_nd.shape[2:4]
        self._counts = None

        if image_height < self.crop_height or image_width < self.crop_width:
            return image_nd, clicks_lists

        self.x_offsets = get_offsets(image_width, self.crop_width, self.min_overlap)
        self.y_offsets = get_offsets(image_height, self.crop_height, self.min_overlap)
        self._counts = np.zeros((image_height, image_width))

        image_crops = []
        for dy in self.y_offsets:
            for dx in self.x_offsets:
                self._counts[dy : dy + self.crop_height, dx : dx + self.crop_width] += 1
                image_crop = image_nd[
                    :, :, dy : dy + self.crop_height, dx : dx + self.crop_width
                ]
                image_crops.append(image_crop)
        image_crops = paddle.concat(image_crops, axis=0)
        self._counts = paddle.to_tensor(self._counts, dtype="float32")

        clicks_list = clicks_lists[0]
        clicks_lists = []
        for dy in self.y_offsets:
            for dx in self.x_offsets:
                crop_clicks = [
                    x.copy(coords=(x.coords[0] - dy, x.coords[1] - dx))
                    for x in clicks_list
                ]
                clicks_lists.append(crop_clicks)

        return image_crops, clicks_lists

    def inv_transform(self, prob_map):
        if self._counts is None:
            return prob_map

        new_prob_map = paddle.zeros((1, 1, *self._counts.shape), dtype=prob_map.dtype)

        crop_indx = 0
        for dy in self.y_offsets:
            for dx in self.x_offsets:
                new_prob_map[
                    0, 0, dy : dy + self.crop_height, dx : dx + self.crop_width
                ] += prob_map[crop_indx, 0]
                crop_indx += 1
        new_prob_map = paddle.divide(new_prob_map, self._counts)

        return new_prob_map

    def get_state(self):
        return self.x_offsets, self.y_offsets, self._counts

    def set_state(self, state):
        self.x_offsets, self.y_offsets, self._counts = state

    def reset(self):
        self.x_offsets = None
        self.y_offsets = None
        self._counts = None


def get_offsets(length, crop_size, min_overlap_ratio=0.2):
    if length == crop_size:
        return [0]

    N = (length / crop_size - min_overlap_ratio) / (1 - min_overlap_ratio)
    N = math.ceil(N)

    overlap_ratio = (N - length / crop_size) / (N - 1)
    overlap_width = int(crop_size * overlap_ratio)

    offsets = [0]
    for i in range(1, N):
        new_offset = offsets[-1] + crop_size - overlap_width
        if new_offset + crop_size > length:
            new_offset = length - crop_size

        offsets.append(new_offset)

    return offsets


================================================
FILE: eiseg/inference/transforms/flip.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""

import paddle

from inference.clicker import Click
from .base import BaseTransform


class AddHorizontalFlip(BaseTransform):
    def transform(self, image_nd, clicks_lists):
        assert len(image_nd.shape) == 4
        image_nd = paddle.concat([image_nd, paddle.flip(image_nd, axis=[3])], axis=0)

        image_width = image_nd.shape[3]
        clicks_lists_flipped = []
        for clicks_list in clicks_lists:
            clicks_list_flipped = [
                click.copy(coords=(click.coords[0], image_width - click.coords[1] - 1))
                for click in clicks_list
            ]
            clicks_lists_flipped.append(clicks_list_flipped)
        clicks_lists = clicks_lists + clicks_lists_flipped

        return image_nd, clicks_lists

    def inv_transform(self, prob_map):
        assert len(prob_map.shape) == 4 and prob_map.shape[0] % 2 == 0
        num_maps = prob_map.shape[0] // 2
        prob_map, prob_map_flipped = prob_map[:num_maps], prob_map[num_maps:]

        return 0.5 * (prob_map + paddle.flip(prob_map_flipped, axis=[3]))

    def get_state(self):
        return None

    def set_state(self, state):
        pass

    def reset(self):
        pass


================================================
FILE: eiseg/inference/transforms/limit_longest_side.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""

from .zoom_in import ZoomIn, get_roi_image_nd


class LimitLongestSide(ZoomIn):
    def __init__(self, max_size=800):
        super().__init__(target_size=max_size, skip_clicks=0)

    def transform(self, image_nd, clicks_lists):
        assert image_nd.shape[0] == 1 and len(clicks_lists) == 1
        image_max_size = max(image_nd.shape[2:4])
        self.image_changed = False

        if image_max_size <= self.target_size:
            return image_nd, clicks_lists
        self._input_image = image_nd

        self._object_roi = (0, image_nd.shape[2] - 1, 0, image_nd.shape[3] - 1)
        self._roi_image = get_roi_image_nd(image_nd, self._object_roi, self.target_size)
        self.image_changed = True

        tclicks_lists = [self._transform_clicks(clicks_lists[0])]
        return self._roi_image, tclicks_lists


================================================
FILE: eiseg/inference/transforms/zoom_in.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This code is based on https://github.com/saic-vul/ritm_interactive_segmentation
Ths copyright of saic-vul/ritm_interactive_segmentation is as follows:
MIT License [see LICENSE for details]
"""

import paddle
import numpy as np
from inference.clicker import Click
from util.misc import get_bbox_iou, get_bbox_from_mask, expand_bbox, clamp_bbox
from .base import BaseTransform


class ZoomIn(BaseTransform):
    def __init__(
        self,
        target_size=700,
        skip_clicks=1,
        expansion_ratio=1.4,
        min_crop_size=480,
        recompute_thresh_iou=0.5,
        prob_thresh=0.50,
    ):
        super().__init__()
        self.target_size = target_size
        self.min_crop_size = min_crop_size
        self.skip_clicks = skip_clicks
        self.expansion_ratio = expansion_ratio
        self.recompute_thresh_iou = recompute_thresh_iou
        self.prob_thresh = prob_thresh

        self._input_image_shape = None
        self._prev_probs = None
        self._object_roi = None
        self._roi_image = None

    def transform(self, image_nd, clicks_lists):
        assert image_nd.shape[0] == 1 and len(clicks_lists) == 1
        self.image_changed = False

        clicks_list = clicks_lists[0]
        if len(clicks_list) <= self.skip_clicks:
            return image_nd, clicks_lists

        self._input_image_shape = image_nd.shape

        current_object_roi = None
        if self._prev_probs is not None:
            current_pred_mask = (self._prev_probs > self.prob_thresh)[0, 0]
            if current_pred_mask.sum() > 0:
                current_object_roi = get_object_roi(
                    current_pred_mask,
                    clicks_list,
                    self.expansion_ratio,
                    self.min_crop_size,
                )

        if current_object_roi is None:
            if self.skip_clicks >= 0:
                return image_nd, clicks_lists
            else:
                current_object_roi = 0, image_nd.shape[2] - 1, 0, image_nd.shape[3] - 1

        update_object_roi = False
        if self._object_roi is None:
            update_object_roi = True
        elif not check_object_roi(self._object_roi, clicks_list):
            update_object_roi = True
        elif (
            get_bbox_iou(current_object_roi, self._object_roi)
            < self.recompute_thresh_iou
        ):
            update_object_roi = True

        if update_object_roi:
            self._object_roi = current_object_roi
            self.image_changed = True
        self._roi_image = get_roi_image_nd(image_nd, self._object_roi, self.target_size)

        tclicks_lists = [self._transform_clicks(clicks_list)]
        return self._roi_image, tclicks_lists

    def inv_transform(self, prob_map):
        if self._object_roi is None:
            self._prev_probs = prob_map.numpy()
            return prob_map

        assert prob_map.shape[0] == 1
        rmin, rmax, cmin, cmax = self._object_roi
        prob_map = paddle.nn.functional.interpolate(
            prob_map,
            size=(rmax - rmin + 1, cmax - cmin + 1),
            mode="bilinear",
            align_corners=True,
        )

        if self._prev_probs is not None:
            new_prob_map = paddle.zeros(
                shape=self._prev_probs.shape, dtype=prob_map.dtype
            )
            new_prob_map[:, :, rmin : rmax + 1, cmin : cmax + 1] = prob_map
        else:
            new_prob_map = prob_map

        self._prev_probs = new_prob_map.numpy()

        return new_prob_map

    def check_possible_recalculation(self):
        if (
            self._prev_probs is None
            or self._object_roi is not None
            or self.skip_clicks > 0
        ):
            return False

        pred_mask = (self._prev_probs > self.prob_thresh)[0, 0]
        if pred_mask.sum() > 0:
            possible_object_roi = get_object_roi(
                pred_mask, [], self.expansion_ratio, self.min_crop_size
            )
            image_roi = (
                0,
                self._input_image_shape[2] - 1,
                0,
                self._input_image_shape[3] - 1,
            )
            if get_bbox_iou(possible_object_roi, image_roi) < 0.50:
                return True
        return False

    def get_state(self):
        roi_image = self._roi_image if self._roi_image is not None else None
        return (
            self._input_image_shape,
            self._object_roi,
            self._prev_probs,
            roi_image,
            self.image_changed,
        )

    def set_state(self, state):
        (
            self._input_image_shape,
            self._object_roi,
            self._prev_probs,
            self._roi_image,
            self.image_changed,
        ) = state

    def reset(self):
        self._input_image_shape = None
        self._object_roi = None
        self._prev_probs = None
        self._roi_image = None
        self.image_changed = False

    def _transform_clicks(self, clicks_list):
        if self._object_roi is None:
            return clicks_list

        rmin, rmax, cmin, cmax = self._object_roi
        crop_height, crop_width = self._roi_image.shape[2:]

        transformed_clicks = []
        for click in clicks_list:
            new_r = crop_height * (click.coords[0] - rmin) / (rmax - rmin + 1)
            new_c = crop_width * (click.coords[1] - cmin) / (cmax - cmin + 1)
            transformed_clicks.append(click.copy(coords=(new_r, new_c)))
        return transformed_clicks


def get_object_roi(pred_mask, clicks_list, expansion_ratio, min_crop_size):
    pred_mask = pred_mask.copy()

    for click in clicks_list:
        if click.is_positive:
            pred_mask[int(click.coords[0]), int(click.coords[1])] = 1

    bbox = get_bbox_from_mask(pred_mask)
    bbox = expand_bbox(bbox, expansion_ratio, min_crop_size)
    h, w = pred_mask.shape[0], pred_mask.shape[1]
    bbox = clamp_bbox(bbox, 0, h - 1, 0, w - 1)

    return bbox


def get_roi_image_nd(image_nd, object_roi, target_size):
    rmin, rmax, cmin, cmax = object_roi

    height = rmax - rmin + 1
    width = cmax - cmin + 1

    if isinstance(target_size, tuple):
        new_height, new_width = target_size
    else:
        scale = target_size / max(height, width)
        new_height = int(round(height * scale))
        new_width = int(round(width * scale))

    with paddle.no_grad():
        roi_image_nd = image_nd[:, :, rmin : rmax + 1, cmin : cmax + 1]
        roi_image_nd = paddle.nn.functional.interpolate(
            roi_image_nd,
            size=(new_height, new_width),
            mode="bilinear",
            align_corners=True,
        )

    return roi_image_nd


def check_object_roi(object_roi, clicks_list):
    for click in clicks_list:
        if click.is_positive:
            if click.coords[0] < object_roi[0] or click.coords[0] >= object_roi[1]:
                return False
            if click.coords[1] < object_roi[2] or click.coords[1] >= object_roi[3]:
                return False

    return True


================================================
FILE: eiseg/models.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os.path as osp
from abc import abstractmethod


import paddle.inference as paddle_infer


here = osp.dirname(osp.abspath(__file__))


class EISegModel:
    @abstractmethod
    def __init__(self, model_path, param_path, use_gpu=False):
        model_path, param_path = self.check_param(model_path, param_path)
        try:
            config = paddle_infer.Config(model_path, param_path)
        except:
            ValueError(" 模型和参数不匹配,请检查模型和参数是否加载错误")
        if not use_gpu:
            config.enable_mkldnn()
            # TODO: fluid要废弃了,研究判断方式
            # if paddle.fluid.core.supports_bfloat16():
            #     config.enable_mkldnn_bfloat16()
            config.switch_ir_optim(True)
            config.set_cpu_math_library_num_threads(10)
        else:
            config.enable_use_gpu(500, 0)
            config.delete_pass("conv_elementwise_add_act_fuse_pass")
            config.delete_pass("conv_elementwise_add2_act_fuse_pass")
            config.delete_pass("conv_elementwise_add_fuse_pass")
            config.switch_ir_optim()
            config.enable_memory_optim()
            # use_tensoret = False  # TODO: 目前Linux和windows下使用TensorRT报错
            # if use_tensoret:
            #     config.enable_tensorrt_engine(
            #         workspace_size=1 << 30,
            #         precision_mode=paddle_infer.PrecisionType.Float32,
            #         max_batch_size=1,
            #         min_subgraph_size=5,
            #         use_static=False,
            #         use_calib_mode=False,
            #     )
        self.model = paddle_infer.create_predictor(config)

    def check_param(self, model_path, param_path):
        if model_path is None or not osp.exists(model_path):
            raise Exception(f"模型路径{model_path}不存在。请指定正确的模型路径")
        if param_path is None or not osp.exists(param_path):
            raise Exception(f"权重路径{param_path}不存在。请指定正确的权重路径")
        return model_path, param_path


================================================
FILE: eiseg/plugin/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

================================================
FILE: eiseg/plugin/medical/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from .med import has_sitk, dcm_reader, windowlize


================================================
FILE: eiseg/plugin/medical/med.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import numpy as np
import cv2

from eiseg import logger


def has_sitk():
    try:
        import SimpleITK

        return True
    except ImportError:
        return False


if has_sitk():
    import SimpleITK as sitk


def dcm_reader(path):
    logger.debug(f"opening medical image {path}")
    reader = sitk.ImageSeriesReader()
    reader.SetFileNames([path])
    image = reader.Execute()
    img = sitk.GetArrayFromImage(image)
    logger.debug(f"scan shape is {img.shape}")
    if len(img.shape) == 4:
        img = img[0]
    # WHC
    img = np.transpose(img, [1, 2, 0])
    return img.astype(np.int32)


def windowlize(scan, ww, wc):
    wl = wc - ww / 2
    wh = wc + ww / 2
    res = scan.copy()
    res = res.astype(np.float32)
    res = np.clip(res, wl, wh)
    res = (res - wl) / ww * 255
    res = res.astype(np.uint8)
    # print("++", res.shape)
    # for idx in range(res.shape[-1]):
    # TODO: 支持3d或者改调用
    res = cv2.cvtColor(res, cv2.COLOR_GRAY2BGR)

    return res


# def open_nii(niiimg_path):
#     if IPT_SITK == True:
#         sitk_image = sitk.ReadImage(niiimg_path)
#         return _nii2arr(sitk_image)
#     else:
#         raise ImportError("can't import SimpleITK!")


#
# def _nii2arr(sitk_image):
#     if IPT_SITK == True:
#         img = sitk.GetArrayFromImage(sitk_image).transpose((1, 2, 0))
#         return img
#     else:
#         raise ImportError("can't import SimpleITK!")
#
#
# def slice_img(img, index):
#     if index == 0:
#         return sample_norm(
#             cv2.merge(
#                 [
#                     np.uint16(img[:, :, index]),
#                     np.uint16(img[:, :, index]),
#                     np.uint16(img[:, :, index + 1]),
#                 ]
#             )
#         )
#     elif index == img.shape[2] - 1:
#         return sample_norm(
#             cv2.merge(
#                 [
#                     np.uint16(img[:, :, index - 1]),
#                     np.uint16(img[:, :, index]),
#                     np.uint16(img[:, :, index]),
#                 ]
#             )
#         )
#     else:
#         return sample_norm(
#             cv2.merge(
#                 [
#                     np.uint16(img[:, :, index - 1]),
#                     np.uint16(img[:, :, index]),
#                     np.uint16(img[:, :, index + 1]),
#                 ]
#             )
#         )


================================================
FILE: eiseg/plugin/n2grid/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from .rs_grid import RSGrids
from .grid import Grids, checkOpenGrid

================================================
FILE: eiseg/plugin/n2grid/grid.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import math
import numpy as np
from PIL import Image


def checkOpenGrid(img, thumbnail_min):
    H, W = img.shape[:2]
    if max(H, W) <= thumbnail_min:
        return False
    else:
        return True


class Grids:
    def __init__(self, img, gridSize=(512, 512), overlap=(24, 24)):
        self.clear()
        self.detimg = img
        self.gridSize = np.array(gridSize)
        self.overlap = np.array(overlap)

    def clear(self):
        # 图像HWC格式
        self.detimg = None  # 宫格初始图像
        self.grid_init = False  # 是否初始化了宫格
        # self.imagesGrid = []  # 图像宫格
        self.mask_grids = []  # 标签宫格
        self.grid_count = None  # (row count, col count)
        self.curr_idx = None  # (current row, current col)

    def createGrids(self):
        # 计算宫格横纵向格数
        imgSize = np.array(self.detimg.shape[:2])
        grid_count = np.ceil((imgSize + self.overlap) / self.gridSize)
        self.grid_count = grid_count = grid_count.astype("uint16")
        # ul = self.overlap - self.gridSize
        # for row in range(grid_count[0]):
        #     ul[0] = ul[0] + self.gridSize[0] - self.overlap[0]
        #     for col in range(grid_count[1]):
        #         ul[1] = ul[1] + self.gridSize[1] - self.overlap[1]
        #         lr = ul + self.gridSize
        #         # print("ul, lr", ul, lr)
        #         # 扩充
        #         det_tmp = self.detimg[ul[0]: lr[0], ul[1]: lr[1]]
        #         tmp = np.zeros((self.gridSize[0], self.gridSize[1], self.detimg.shape[-1]))
        #         tmp[:det_tmp.shape[0], :det_tmp.shape[1], :] = det_tmp
        #         self.imagesGrid.append(tmp)
        # self.mask_grids = [[np.zeros(self.gridSize)] * grid_count[1]] * grid_count[0]  # 不能用浅拷贝
        self.mask_grids = [
            [np.zeros(self.gridSize) for _ in range(grid_count[1])]
            for _ in range(grid_count[0])
        ]
        # print(len(self.mask_grids), len(self.mask_grids[0]))
        self.grid_init = True
        return list(grid_count)

    def getGrid(self, row, col):
        gridIdx = np.array([row, col])
        ul = gridIdx * (self.gridSize - self.overlap)
        lr = ul + self.gridSize
        # print("ul, lr", ul, lr)
        img = self.detimg[ul[0]:lr[0], ul[1]:lr[1]]
        mask = self.mask_grids[row][col]
        self.curr_idx = (row, col)
        return img, mask

    def splicingList(self, save_path):
        """
        将slide的out进行拼接,raw_size保证恢复到原状
        """
        imgs = self.mask_grids
        # print(len(imgs), len(imgs[0]))
        raw_size = self.detimg.shape[:2]
        # h, w = None, None
        # for i in range(len(imgs)):
        #     for j in range(len(imgs[i])):
        #         im = imgs[i][j]
        #         if im is not None:
        #             h, w = im.shape[:2]
        #             break
        # if h is None and w is None:
        #     return False
        h, w = self.gridSize
        row = math.ceil(raw_size[0] / h)
        col = math.ceil(raw_size[1] / w)
        # print('row, col:', row, col)
        result_1 = np.zeros((h * row, w * col), dtype=np.uint8)
        result_2 = result_1.copy()
        # k = 0
        for i in range(row):
            for j in range(col):
                # print('h, w:', h, w)
                ih, iw = imgs[i][j].shape[:2]
                im = np.zeros(self.gridSize)
                im[:ih, :iw] = imgs[i][j]
                start_h = (i * h) if i == 0 else (i * (h - self.overlap[0]))
                end_h = start_h + h
                start_w = (j * w) if j == 0 else (j * (w - self.overlap[1]))
                end_w = start_w + w
                # print("se: ", start_h, end_h, start_w, end_w)
                # 单区自己,重叠取或
                if (i + j) % 2 == 0:
                    result_1[start_h:end_h, start_w:end_w] = im
                else:
                    result_2[start_h:end_h, start_w:end_w] = im
                # k += 1
                # print('r, c, k:', i_r, i_c, k)
        result = np.where(result_2 != 0, result_2, result_1)
        result = result[:raw_size[0], :raw_size[1]]
        Image.fromarray(result).save(save_path, "PNG")
        return result


================================================
FILE: eiseg/plugin/n2grid/rs_grid.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import numpy as np
from typing import List, Tuple
from eiseg.plugin.remotesensing.raster import Raster


class RSGrids:
    def __init__(self, raset: Raster) -> None:
        """ 在EISeg中用于处理遥感栅格数据的宫格类.

        参数:
            tif_path (str): GTiff数据的路径.
            show_band (Union[List[int], Tuple[int]], optional): 用于RGB合成显示的波段. 默认为 [1, 1, 1].
            grid_size (Union[List[int], Tuple[int]], optional): 切片大小. 默认为 [512, 512].
            overlap (Union[List[int], Tuple[int]], optional): 重叠区域的大小. 默认为 [24, 24].
        """
        super(RSGrids, self).__init__()
        self.raster = raset
        self.clear()

    def clear(self) -> None:
        self.mask_grids = []  # 标签宫格
        self.grid_count = None  # (row count, col count)
        self.curr_idx = None  # (current row, current col)

    def createGrids(self) -> List[int]:
        img_size = (self.raster.geoinfo.ysize, self.raster.geoinfo.xsize)
        grid_count = np.ceil((img_size + self.raster.overlap) / self.raster.grid_size)
        self.grid_count = grid_count = grid_count.astype("uint16")
        self.mask_grids = [[np.zeros(self.raster.grid_size) \
                            for _ in range(grid_count[1])] for _ in range(grid_count[0])]
        return list(grid_count)

    def getGrid(self, row: int, col: int) -> Tuple[np.ndarray]:
        img, _ = self.raster.getGrid(row, col)
        mask = self.mask_grids[row][col]
        self.curr_idx = (row, col)
        return img, mask

    def splicingList(self, save_path: str) -> np.ndarray:
        mask = self.raster.saveMaskbyGrids(self.mask_grids, 
                                           save_path,
                                           self.raster.geoinfo)
        return mask

================================================
FILE: eiseg/plugin/remotesensing/__init__.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from .imgtools import *
from .shape import *
from .raster import *

================================================
FILE: eiseg/plugin/remotesensing/imgtools.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import numpy as np
import cv2
from skimage import exposure


# 2%线性拉伸
def two_percentLinear(image: np.ndarray, 
                      max_out: int=255, 
                      min_out: int=0) -> np.ndarray:
    b, g, r = cv2.split(image)

    def __gray_process(gray, maxout=max_out, minout=min_out):
        high_value = np.percentile(gray, 98)  # 取得98%直方图处对应灰度
        low_value = np.percentile(gray, 2)
        truncated_gray = np.clip(gray, a_min=low_value, a_max=high_value)
        processed_gray = ((truncated_gray - low_value) / (high_value - low_value)) * (
            maxout - minout)
        return processed_gray

    r_p = __gray_process(r)
    g_p = __gray_process(g)
    b_p = __gray_process(b)
    result = cv2.merge((b_p, g_p, r_p))
    return np.uint8(result)


# 简单图像标准化
def sample_norm(image: np.ndarray) -> np.ndarray:
    stretches = []
    if len(image.shape) == 3:
        for b in range(image.shape[-1]):
            stretched = exposure.equalize_hist(image[:, :, b])
            stretched /= float(np.max(stretched))
            stretches.append(stretched)
        stretched_img = np.stack(stretches, axis=2)
    else:  # if len(image.shape) == 2
        stretched_img = exposure.equalize_hist(image)
    return np.uint8(stretched_img * 255)


# 计算缩略图
def get_thumbnail(image: np.ndarray, 
                  range: int=2000, 
                  max_size: int=1000) -> np.ndarray:
    h, w = image.shape[:2]
    if h >= range or w >= range:
        if h >= w:
            image = cv2.resize(image, (int(max_size / h * w), max_size))
        else:
            image = cv2.resize(image, (max_size, int(max_size / w * h)))
    return image


================================================
FILE: eiseg/plugin/remotesensing/raster.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os.path as osp
import numpy as np
import cv2
import math
from typing import List, Dict, Tuple, Union
from collections import defaultdict
from easydict import EasyDict as edict
from .imgtools import sample_norm, two_percentLinear, get_thumbnail


def check_rasterio() -> bool:
    try:
        import rasterio
        return True
    except:
        return False


IMPORT_STATE = False
if check_rasterio():
    import rasterio
    from rasterio.windows import Window
    IMPORT_STATE = True


class Raster:
    def __init__(self, 
                 tif_path: str,
                 show_band: Union[List[int], Tuple[int]]=[1, 1, 1], 
                 open_grid: bool=False,
                 grid_size: Union[List[int], Tuple[int]]=[512, 512],
                 overlap: Union[List[int], Tuple[int]]=[24, 24]) -> None:
        """ 在EISeg中用于处理遥感栅格数据的类.

        参数:
            tif_path (str): GTiff数据的路径.
            show_band (Union[List[int], Tuple[int]], optional): 用于RGB合成显示的波段. 默认为 [1, 1, 1].
            open_grid (bool, optional): 是否打开了宫格切片功能. 默认为 False.
            grid_size (Union[List[int], Tuple[int]], optional): 切片大小. 默认为 [512, 512].
            overlap (Union[List[int], Tuple[int]], optional): 重叠区域的大小. 默认为 [24, 24].
        """
        super(Raster, self).__init__()
        if IMPORT_STATE is False:
            raise("Can't import rasterio!")
        if osp.exists(tif_path):
            self.src_data = rasterio.open(tif_path)
            self.geoinfo = self.__getRasterInfo()
            self.show_band = list(show_band)
            self.grid_size = np.array(grid_size)
            self.overlap = np.array(overlap)
            self.open_grid = open_grid
        else:
            raise("{0} not exists!".format(tif_path))
        self.thumbnail_min = 2000

    def __del__(self) -> None:
        self.src_data.close()

    def __getRasterInfo(self) -> Dict:
        meta = self.src_data.meta
        geoinfo = edict()
        geoinfo.count = meta["count"]
        geoinfo.dtype = meta["dtype"]
        geoinfo.xsize = meta["width"]
        geoinfo.ysize = meta["height"]
        geoinfo.geotf = meta["transform"]
        geoinfo.crs = meta["crs"]
        if geoinfo.crs is not None:
            geoinfo.crs_wkt = geoinfo.crs.wkt
        else:
            geoinfo.crs_wkt = None
        return geoinfo

    def checkOpenGrid(self, thumbnail_min: Union[int, None]) -> bool:
        if isinstance(thumbnail_min, int):
            self.thumbnail_min = thumbnail_min
        if max(self.geoinfo.xsize, self.geoinfo.ysize) <= self.thumbnail_min:
            self.open_grid = False
        else:
            self.open_grid = True
        return self.open_grid

    def setBand(self, bands: Union[List[int], Tuple[int]]) -> None:
        self.show_band = list(bands)

    # def __analysis_proj4(self) -> str:
    #     proj4 = self.geoinfo.crs.wkt  # TODO: 解析为proj4
    #     ap_dict = defaultdict(str)
    #     dinf = proj4.split("+")
    #     for df in dinf:
    #         kv = df.strip().split("=")
    #         if len(kv) == 2:
    #             k, v = kv
    #             ap_dict[k] = v
    #     return str("● 投影:{0}\n● 基准:{1}\n● 单位:{2}".format(
    #             ap_dict["proj"], ap_dict["datum"], ap_dict["units"])
    #     )

    def showGeoInfo(self) -> str:
        # return str("● 波段数:{0}\n● 数据类型:{1}\n● 行数:{2}\n● 列数:{3}\n{4}".format(
        #     self.geoinfo.count, self.geoinfo.dtype, self.geoinfo.xsize,
        #     self.geoinfo.ysize, self.__analysis_proj4())
        # )
        if self.geoinfo.crs is not None:
            crs = str(self.geoinfo.crs.to_string().split(":")[-1])
        else:
            crs = "None"
        return (str(self.geoinfo.count), str(self.geoinfo.dtype), str(self.geoinfo.xsize),
                str(self.geoinfo.ysize), crs)

    def getArray(self) -> Tuple[np.ndarray]:
        rgb = []
        if not self.open_grid:
            for b in self.show_band:
                rgb.append(self.src_data.read(b))
            geotf = self.geoinfo.geotf
        else:
            for b in self.show_band:
                rgb.append(get_thumbnail(self.src_data.read(b), self.thumbnail_min))
            geotf = None
        ima = np.stack(rgb, axis=2)  # cv2.merge(rgb)
        if self.geoinfo["dtype"] != "uint8":
            ima = sample_norm(ima)
        return two_percentLinear(ima), geotf

    def getGrid(self, row: int, col: int) -> Tuple[np.ndarray]:
        if self.open_grid is False:
            return self.getArray()
        grid_idx = np.array([row, col])
        ul = grid_idx * (self.grid_size - self.overlap)
        lr = ul + self.grid_size
        # print("ul, lr", ul, lr)
        window = Window(ul[1], ul[0], (lr[1] - ul[1]), (lr[0] - ul[0]))
        rgb = []
        for b in self.show_band:
            rgb.append(self.src_data.read(b, window=window))
        win_tf = self.src_data.window_transform(window)
        ima = cv2.merge([np.uint16(c) for c in rgb])
        if self.geoinfo["dtype"] == "uint32":
            ima = sample_norm(ima)
        return two_percentLinear(ima), win_tf

    def saveMask(self, img: np.array, save_path: str, 
                 geoinfo: Union[Dict, None]=None, count: int=1) -> None:
        if geoinfo is None:
            geoinfo = self.geoinfo
        new_meta = self.src_data.meta.copy()
        new_meta.update({
            "driver": "GTiff",
            "width": geoinfo.xsize,
            "height": geoinfo.ysize,
            "count": count,
            "dtype": geoinfo.dtype,
            "crs": geoinfo.crs,
            "transform": geoinfo.geotf[:6],
            "nodata": 0
            })
        img = np.nan_to_num(img).astype("int16")
        with rasterio.open(save_path, "w", **new_meta) as tf:
            if count == 1:
                tf.write(img, indexes=1)
            else:
                for i in range(count):
                    tf.write(img[:, :, i], indexes=(i + 1))

    def saveMaskbyGrids(self, 
                        img_list: List[List[np.ndarray]], 
                        save_path: Union[str, None]=None,
                        geoinfo: Union[Dict, None]=None) -> np.ndarray:
        if geoinfo is None:
            geoinfo = self.geoinfo
        raw_size = (geoinfo.ysize, geoinfo.xsize)
        h, w = self.grid_size
        row = math.ceil(raw_size[0] / h)
        col = math.ceil(raw_size[1] / w)
        # print("row, col:", row, col)
        result_1 = np.zeros((h * row, w * col), dtype=np.uint8)
        result_2 = result_1.copy()
        for i in range(row):
            for j in range(col):
                # print("h, w:", h, w)
                ih, iw = img_list[i][j].shape[:2]
                im = np.zeros(self.grid_size)
                im[:ih, :iw] = img_list[i][j]
                start_h = (i * h) if i == 0 else (i * (h - self.overlap[0]))
                end_h = start_h + h
                start_w = (j * w) if j == 0 else (j * (w - self.overlap[1]))
                end_w = start_w + w
                # print("se: ", start_h, end_h, start_w, end_w)
                # 单区自己,重叠取或
                if (i + j) % 2 == 0:
                    result_1[start_h: end_h, start_w: end_w] = im
                else:
                    result_2[start_h: end_h, start_w: end_w] = im
                # print("r, c, k:", i_r, i_c, k)
        result = np.where(result_2 != 0, result_2, result_1)
        result = result[:raw_size[0], :raw_size[1]]
        if save_path is not None:
            self.saveMask(result, save_path, geoinfo)
        return result

================================================
FILE: eiseg/plugin/remotesensing/shape.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os
import os.path as osp


def check_gdal() -> bool:
    try:
        import gdal
    except:
        try:
            from osgeo import gdal
        except ImportError:
            return False
    return True


IMPORT_STATE = False
if check_gdal():
    try:
        import gdal
        import osr
        import ogr
    except:
        from osgeo import gdal, osr, ogr
    IMPORT_STATE = True


# 保存shp文件
def save_shp(shp_path: str, tif_path: str, ignore_index :int=0) -> str:
    if IMPORT_STATE == True:
        ds = gdal.Open(tif_path)
        srcband = ds.GetRasterBand(1)
        maskband = srcband.GetMaskBand()
        gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")
        gdal.SetConfigOption("SHAPE_ENCODING", "UTF-8")
        ogr.RegisterAll()
        drv = ogr.GetDriverByName("ESRI Shapefile")
        if osp.exists(shp_path):
            os.remove(shp_path)
        dst_ds = drv.CreateDataSource(shp_path)
        prosrs = osr.SpatialReference(wkt=ds.GetProjection())
        dst_layer = dst_ds.CreateLayer(
            "segmentation", geom_type=ogr.wkbPolygon, srs=prosrs)
        dst_fieldname = "DN"
        fd = ogr.FieldDefn(dst_fieldname, ogr.OFTInteger)
        dst_layer.CreateField(fd)
        gdal.Polygonize(srcband, maskband, dst_layer, 0, [])
        lyr = dst_ds.GetLayer()
        lyr.SetAttributeFilter("DN = '{}'".format(str(ignore_index)))
        for holes in lyr:
            lyr.DeleteFeature(holes.GetFID())
        dst_ds.Destroy()
        ds = None
        return "Dataset creation successfully!"
    else:
        raise ImportError("can't import gdal, osr, ogr!")

================================================
FILE: eiseg/run.py
================================================
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import sys
import os
import os.path as osp
import logging
from datetime import datetime

from qtpy.QtWidgets import QApplication  # 导入PyQt相关模块
from qtpy import QtCore

from eiseg import pjpath
from app import APP_EISeg  # 导入带槽的界面


def main():
    ## -- log --
    settings = QtCore.QSettings(
        osp.join(pjpath, "config/setting.ini"), QtCore.QSettings.IniFormat
    )
    #
    # logFolder = settings.value("logFolder")
    # logLevel = settings.value("logLevel")
    # logDays = settings.value("logDays")
    #
    # if logFolder is None or len(logFolder) == 0:
    #     logFolder = osp.normcase(osp.join(pjpath, "log"))
    # if not osp.exists(logFolder):
    #     os.makedirs(logFolder)
    #
    # if logLevel:
    #     logLevel = eval(logLevel)
    # else:
    #     logLevel = logging.DEBUG
    # if logDays:
    #     logDays = int(logDays)
    # else:
    #     logDays = 7
    # # TODO: 删除大于logDays 的 log
    #
    # t = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    # logging.basicConfig(
    #     level=logging.DEBUG,
    #     filename=osp.normcase(osp.join(logFolder, f"eiseg-{t}.log")),
    #     format="%(levelname)s - %(asctime)s - %(filename)s - %(funcName)s - %(message)s",
    # )
    # logger = logging.getLogger("EISeg Logger")
    # handler = logging.FileHandler(osp.normcase(osp.join(logFolder, f"eiseg-{t}.log")))
    #
    # handler.setFormatter(
    #     logging.Formatter(
    #         "%(levelname)s - %(asctime)s - %(filename)s - %(funcName)s - %(message)s"
    #     )
    # )
Download .txt
gitextract_x7oiqy62/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── --bug.md
│       └── feature_request.md
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── README_EN.md
├── docs/
│   ├── medical.md
│   ├── medical_en.md
│   ├── remote_sensing.md
│   ├── remote_sensing_en.md
│   └── tools.md
├── eiseg/
│   ├── __init__.py
│   ├── __main__.py
│   ├── app.py
│   ├── config/
│   │   ├── colormap.txt
│   │   └── config.yaml
│   ├── controller.py
│   ├── exe.py
│   ├── inference/
│   │   ├── __init__.py
│   │   ├── clicker.py
│   │   ├── predictor/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── ops.py
│   │   └── transforms/
│   │       ├── __init__.py
│   │       ├── base.py
│   │       ├── crops.py
│   │       ├── flip.py
│   │       ├── limit_longest_side.py
│   │       └── zoom_in.py
│   ├── models.py
│   ├── plugin/
│   │   ├── __init__.py
│   │   ├── medical/
│   │   │   ├── __init__.py
│   │   │   └── med.py
│   │   ├── n2grid/
│   │   │   ├── __init__.py
│   │   │   ├── grid.py
│   │   │   └── rs_grid.py
│   │   └── remotesensing/
│   │       ├── __init__.py
│   │       ├── imgtools.py
│   │       ├── raster.py
│   │       └── shape.py
│   ├── run.py
│   ├── ui.py
│   ├── util/
│   │   ├── __init__.py
│   │   ├── coco/
│   │   │   ├── __init__.py
│   │   │   ├── _mask.pyx
│   │   │   ├── coco.py
│   │   │   ├── cocoeval.py
│   │   │   ├── common/
│   │   │   │   ├── gason.cpp
│   │   │   │   ├── gason.h
│   │   │   │   ├── maskApi.c
│   │   │   │   └── maskApi.h
│   │   │   └── mask.py
│   │   ├── coco.py.bk
│   │   ├── colormap.py
│   │   ├── config.py
│   │   ├── exp_imports/
│   │   │   └── default.py
│   │   ├── label.py
│   │   ├── language.py
│   │   ├── manager.py
│   │   ├── misc.py
│   │   ├── opath.py
│   │   ├── polygon.py
│   │   ├── qt.py
│   │   ├── regularization/
│   │   │   ├── __init__.py
│   │   │   ├── cal_line.py
│   │   │   ├── cal_point.py
│   │   │   ├── rdp_alg.py
│   │   │   ├── rotate_ang.py
│   │   │   └── rs_regularization.py
│   │   ├── serialization.py
│   │   ├── translate/
│   │   │   ├── Arabic.qm
│   │   │   └── English.qm
│   │   └── vis.py
│   └── widget/
│       ├── __init__.py
│       ├── bbox.py
│       ├── create.py
│       ├── grip.py
│       ├── line.py
│       ├── loading.py
│       ├── polygon.py
│       ├── scene.py
│       ├── shortcut.py
│       ├── table.py
│       └── view.py
├── init.sh
├── requirements-med.txt
├── requirements-rs.txt
├── requirements.txt
├── setup.py
└── tool/
    ├── baidu_translate.py
    ├── pypi.sh
    ├── semantic2instance.py
    ├── translate.pro
    ├── translateUI.py
    ├── ts/
    │   ├── Arabic.ts
    │   └── English.ts
    └── update_md5.py
Download .txt
SYMBOL INDEX (558 symbols across 56 files)

FILE: eiseg/app.py
  class ModelThread (line 47) | class ModelThread(QThread):
    method __init__ (line 50) | def __init__(self, controller, param_path):
    method run (line 55) | def run(self):
  class APP_EISeg (line 62) | class APP_EISeg(QMainWindow, Ui_EISeg):
    method __init__ (line 76) | def __init__(self, parent=None):
    method initActions (line 249) | def initActions(self):
    method __setColor (line 713) | def __setColor(self, action, setting_name):
    method setCutoutBackground (line 726) | def setCutoutBackground(self):
    method setCrossColor (line 730) | def setCrossColor(self):
    method editShortcut (line 735) | def editShortcut(self):
    method updateLanguage (line 740) | def updateLanguage(self):
    method changeLanguage (line 757) | def changeLanguage(self, lang):
    method updateRecentFile (line 762) | def updateRecentFile(self):
    method addRecentFile (line 778) | def addRecentFile(self, path):
    method clearRecentFile (line 789) | def clearRecentFile(self):
    method updateModelMenu (line 794) | def updateModelMenu(self):
    method setModelParam (line 814) | def setModelParam(self, paramPath):
    method changeParam (line 820) | def changeParam(self, param_path: str = None):
    method __change_model_callback (line 853) | def __change_model_callback(self, signal_dict: dict):
    method chooseMode (line 875) | def chooseMode(self):
    method loadRecentModelParam (line 885) | def loadRecentModelParam(self):
    method importLabelList (line 894) | def importLabelList(self, filePath=None):
    method exportLabelList (line 915) | def exportLabelList(self, savePath: str = None):
    method addLabel (line 943) | def addLabel(self):
    method adjustTableSize (line 965) | def adjustTableSize(self):
    method clearLabelList (line 978) | def clearLabelList(self):
    method refreshLabelList (line 996) | def refreshLabelList(self):
    method labelListDoubleClick (line 1023) | def labelListDoubleClick(self, row, col):
    method currLabelIdx (line 1042) | def currLabelIdx(self):
    method labelListClicked (line 1045) | def labelListClicked(self, row, col):
    method labelListItemChanged (line 1062) | def labelListItemChanged(self, row, col):
    method createPoly (line 1072) | def createPoly(self, curr_polygon, color):
    method delActivePolygon (line 1094) | def delActivePolygon(self):
    method delPolygon (line 1105) | def delPolygon(self, polygon):
    method delAllPolygon (line 1115) | def delAllPolygon(self):
    method delActivePoint (line 1119) | def delActivePoint(self):
    method getMask (line 1124) | def getMask(self):
    method openRecentImage (line 1142) | def openRecentImage(self, file_path):
    method openImage (line 1149) | def openImage(self, filePath: str = None):
    method openFolder (line 1186) | def openFolder(self, inputDir: str = None):
    method loadImage (line 1252) | def loadImage(self, path):
    method loadLabel (line 1379) | def loadLabel(self, imgPath):
    method turnImg (line 1455) | def turnImg(self, delta, list_click=False):
    method imageListClicked (line 1477) | def imageListClicked(self):
    method finishObject (line 1489) | def finishObject(self):
    method completeLastMask (line 1513) | def completeLastMask(self):
    method saveImage (line 1531) | def saveImage(self, close=False):
    method exportLabel (line 1559) | def exportLabel(self, saveAs=False, savePath=None, lab_input=None):
    method chooseSavePath (line 1689) | def chooseSavePath(self):
    method eximgsInit (line 1712) | def eximgsInit(self):
    method setDirty (line 1719) | def setDirty(self, isDirty):
    method changeOutputDir (line 1722) | def changeOutputDir(self, outputDir=None):
    method maskOpacityChanged (line 1767) | def maskOpacityChanged(self):
    method clickRadiusChanged (line 1775) | def clickRadiusChanged(self):
    method threshChanged (line 1781) | def threshChanged(self):
    method undoClick (line 1796) | def undoClick(self):
    method clearAll (line 1806) | def clearAll(self):
    method redoClick (line 1813) | def redoClick(self):
    method canvasClick (line 1821) | def canvasClick(self, x, y, isLeft):
    method updateImage (line 1842) | def updateImage(self, reset_canvas=False):
    method viewZoomed (line 1856) | def viewZoomed(self, scale):
    method resetZoom (line 1861) | def resetZoom(self, width, height):
    method keyReleaseEvent (line 1880) | def keyReleaseEvent(self, event):
    method queueEvent (line 1888) | def queueEvent(self, function):
    method toggleOrigExt (line 1891) | def toggleOrigExt(self, dst=None):
    method toggleAutoSave (line 1898) | def toggleAutoSave(self, save):
    method toggleSave (line 1906) | def toggleSave(self, type):
    method initCoco (line 1917) | def initCoco(self, coco_path: str = None):
    method toggleWidget (line 1931) | def toggleWidget(self, index=None, warn=True):
    method rsBandSet (line 1991) | def rsBandSet(self, idx):
    method updateBandList (line 2017) | def updateBandList(self, clean=False):
    method toggleLargestCC (line 2049) | def toggleLargestCC(self, on):
    method initGrid (line 2056) | def initGrid(self):
    method changeGrid (line 2072) | def changeGrid(self, row, col):
    method mask2poly (line 2098) | def mask2poly(self, mask, show=True):
    method saveGrid (line 2133) | def saveGrid(self):
    method turnGrid (line 2161) | def turnGrid(self, delta):
    method closeGrid (line 2177) | def closeGrid(self):
    method saveGridLabel (line 2182) | def saveGridLabel(self):
    method opacity (line 2240) | def opacity(self):
    method clickRadius (line 2244) | def clickRadius(self):
    method segThresh (line 2248) | def segThresh(self):
    method warnException (line 2255) | def warnException(self, e):
    method warn (line 2260) | def warn(self, title, text, buttons=QMessageBox.Yes):
    method status (line 2269) | def status(self):
    method status (line 2281) | def status(self, status):
    method loadGrid (line 2289) | def loadGrid(self, img, is_rs=True):
    method loadLayout (line 2305) | def loadLayout(self):
    method saveLayout (line 2309) | def saveLayout(self):
    method closeEvent (line 2319) | def closeEvent(self, event):
    method reportBug (line 2325) | def reportBug(self):
    method quickStart (line 2328) | def quickStart(self):
    method toggleLogging (line 2333) | def toggleLogging(self, s):
    method toBeImplemented (line 2340) | def toBeImplemented(self):
    method wwChanged (line 2344) | def wwChanged(self):
    method wcChanged (line 2356) | def wcChanged(self):
    method ww (line 2369) | def ww(self):
    method wc (line 2373) | def wc(self):
    method twwChanged (line 2376) | def twwChanged(self):
    method swwChanged (line 2384) | def swwChanged(self):
    method twcChanged (line 2388) | def twcChanged(self):
    method swcChanged (line 2396) | def swcChanged(self):
    method useQtWidget (line 2400) | def useQtWidget(self, s):

FILE: eiseg/controller.py
  class InteractiveController (line 35) | class InteractiveController:
    method __init__ (line 36) | def __init__(
    method filterLargestCC (line 72) | def filterLargestCC(self, do_filter: bool):
    method setModel (line 84) | def setModel(self, param_path=None, use_gpu=None):
    method setImage (line 121) | def setImage(self, image: np.array):
    method setLabelList (line 136) | def setLabelList(self, labelList: json):
    method addLabel (line 163) | def addLabel(self, id: int, name: str, color: list):
    method delLabel (line 166) | def delLabel(self, id: int):
    method clearLabel (line 169) | def clearLabel(self):
    method importLabel (line 172) | def importLabel(self, path):
    method exportLabel (line 175) | def exportLabel(self, path):
    method addClick (line 179) | def addClick(self, x: int, y: int, is_positive: bool):
    method undoClick (line 236) | def undoClick(self):
    method redoClick (line 249) | def redoClick(self):
    method finishObject (line 262) | def finishObject(self, building=False):
    method getPolygon (line 282) | def getPolygon(self):
    method setPolygon (line 285) | def setPolygon(self, polygon):
    method getMask (line 289) | def getMask(self):
    method setCurrLabelIdx (line 297) | def setCurrLabelIdx(self, number):
    method resetLastObject (line 302) | def resetLastObject(self, update_image=True):
    method reset_predictor (line 317) | def reset_predictor(self, predictor_params=None):
    method reset_init_mask (line 330) | def reset_init_mask(self):
    method getLargestCC (line 333) | def getLargestCC(self, mask):
    method get_visualization (line 340) | def get_visualization(self, alpha_blend: float, click_radius: int):
    method inImage (line 365) | def inImage(self, x: int, y: int):
    method result_mask (line 372) | def result_mask(self):
    method palette (line 377) | def palette(self):
    method current_object_prob (line 386) | def current_object_prob(self):
    method is_incomplete_mask (line 397) | def is_incomplete_mask(self):
    method imgShape (line 405) | def imgShape(self):
    method modelSet (line 409) | def modelSet(self):
    method modelName (line 413) | def modelName(self):
    method imageSet (line 417) | def imageSet(self):

FILE: eiseg/inference/clicker.py
  class Clicker (line 25) | class Clicker(object):
    method __init__ (line 26) | def __init__(
    method make_next_click (line 42) | def make_next_click(self, pred_mask):
    method get_clicks (line 47) | def get_clicks(self, clicks_limit=None):
    method _get_next_click (line 50) | def _get_next_click(self, pred_mask, padding=True):
    method add_click (line 85) | def add_click(self, click):
    method _remove_last_click (line 98) | def _remove_last_click(self):
    method reset_clicks (line 110) | def reset_clicks(self):
    method get_state (line 119) | def get_state(self):
    method set_state (line 122) | def set_state(self, state):
    method __len__ (line 127) | def __len__(self):
  class Click (line 131) | class Click:
    method __init__ (line 132) | def __init__(self, is_positive, coords, indx=None):
    method coords_and_indx (line 138) | def coords_and_indx(self):
    method copy (line 141) | def copy(self, **kwargs):

FILE: eiseg/inference/predictor/__init__.py
  function get_predictor (line 24) | def get_predictor(

FILE: eiseg/inference/predictor/base.py
  class BasePredictor (line 29) | class BasePredictor(object):
    method __init__ (line 30) | def __init__(
    method to_tensor (line 66) | def to_tensor(self, x):
    method set_input_image (line 73) | def set_input_image(self, image):
    method get_prediction (line 85) | def get_prediction(self, clicker, prev_mask=None):
    method prepare_input (line 128) | def prepare_input(self, image):
    method get_coord_features (line 135) | def get_coord_features(self, image, prev_mask, points):
    method _get_prediction (line 144) | def _get_prediction(self, image_nd, clicks_lists, is_image_changed):
    method _get_transform_states (line 171) | def _get_transform_states(self):
    method _set_transform_states (line 174) | def _set_transform_states(self, states):
    method apply_transforms (line 179) | def apply_transforms(self, image_nd, clicks_lists):
    method get_points_nd (line 187) | def get_points_nd(self, clicks_lists):
    method get_states (line 220) | def get_states(self):
    method set_states (line 226) | def set_states(self, states):
  function split_points_by_order (line 231) | def split_points_by_order(tpoints, groups):

FILE: eiseg/inference/predictor/ops.py
  class DistMaps (line 26) | class DistMaps(nn.Layer):
    method __init__ (line 27) | def __init__(self, norm_radius, spatial_scale=1.0, cpu_mode=True, use_...
    method get_coord_features (line 39) | def get_coord_features(self, points, batchsize, rows, cols):
    method forward (line 90) | def forward(self, x, coords):
  class ScaleLayer (line 94) | class ScaleLayer(nn.Layer):
    method __init__ (line 95) | def __init__(self, init_value=1.0, lr_mult=1):
    method forward (line 104) | def forward(self, x):
  class BatchImageNormalize (line 109) | class BatchImageNormalize:
    method __init__ (line 110) | def __init__(self, mean, std):
    method __call__ (line 118) | def __call__(self, tensor):

FILE: eiseg/inference/transforms/base.py
  class BaseTransform (line 4) | class BaseTransform(object):
    method __init__ (line 5) | def __init__(self):
    method transform (line 8) | def transform(self, image_nd, clicks_lists):
    method inv_transform (line 11) | def inv_transform(self, prob_map):
    method reset (line 14) | def reset(self):
    method get_state (line 17) | def get_state(self):
    method set_state (line 20) | def set_state(self, state):
  class SigmoidForPred (line 24) | class SigmoidForPred(BaseTransform):
    method transform (line 25) | def transform(self, image_nd, clicks_lists):
    method inv_transform (line 28) | def inv_transform(self, prob_map):
    method reset (line 31) | def reset(self):
    method get_state (line 34) | def get_state(self):
    method set_state (line 37) | def set_state(self, state):

FILE: eiseg/inference/transforms/crops.py
  class Crops (line 30) | class Crops(BaseTransform):
    method __init__ (line 31) | def __init__(self, crop_size=(320, 480), min_overlap=0.2):
    method transform (line 40) | def transform(self, image_nd, clicks_lists):
    method inv_transform (line 75) | def inv_transform(self, prob_map):
    method get_state (line 92) | def get_state(self):
    method set_state (line 95) | def set_state(self, state):
    method reset (line 98) | def reset(self):
  function get_offsets (line 104) | def get_offsets(length, crop_size, min_overlap_ratio=0.2):

FILE: eiseg/inference/transforms/flip.py
  class AddHorizontalFlip (line 26) | class AddHorizontalFlip(BaseTransform):
    method transform (line 27) | def transform(self, image_nd, clicks_lists):
    method inv_transform (line 43) | def inv_transform(self, prob_map):
    method get_state (line 50) | def get_state(self):
    method set_state (line 53) | def set_state(self, state):
    method reset (line 56) | def reset(self):

FILE: eiseg/inference/transforms/limit_longest_side.py
  class LimitLongestSide (line 23) | class LimitLongestSide(ZoomIn):
    method __init__ (line 24) | def __init__(self, max_size=800):
    method transform (line 27) | def transform(self, image_nd, clicks_lists):

FILE: eiseg/inference/transforms/zoom_in.py
  class ZoomIn (line 27) | class ZoomIn(BaseTransform):
    method __init__ (line 28) | def __init__(
    method transform (line 50) | def transform(self, image_nd, clicks_lists):
    method inv_transform (line 96) | def inv_transform(self, prob_map):
    method check_possible_recalculation (line 122) | def check_possible_recalculation(self):
    method get_state (line 145) | def get_state(self):
    method set_state (line 155) | def set_state(self, state):
    method reset (line 164) | def reset(self):
    method _transform_clicks (line 171) | def _transform_clicks(self, clicks_list):
  function get_object_roi (line 186) | def get_object_roi(pred_mask, clicks_list, expansion_ratio, min_crop_size):
  function get_roi_image_nd (line 201) | def get_roi_image_nd(image_nd, object_roi, target_size):
  function check_object_roi (line 226) | def check_object_roi(object_roi, clicks_list):

FILE: eiseg/models.py
  class EISegModel (line 26) | class EISegModel:
    method __init__ (line 28) | def __init__(self, model_path, param_path, use_gpu=False):
    method check_param (line 60) | def check_param(self, model_path, param_path):

FILE: eiseg/plugin/medical/med.py
  function has_sitk (line 22) | def has_sitk():
  function dcm_reader (line 35) | def dcm_reader(path):
  function windowlize (line 49) | def windowlize(scan, ww, wc):

FILE: eiseg/plugin/n2grid/grid.py
  function checkOpenGrid (line 21) | def checkOpenGrid(img, thumbnail_min):
  class Grids (line 29) | class Grids:
    method __init__ (line 30) | def __init__(self, img, gridSize=(512, 512), overlap=(24, 24)):
    method clear (line 36) | def clear(self):
    method createGrids (line 45) | def createGrids(self):
    method getGrid (line 71) | def getGrid(self, row, col):
    method splicingList (line 81) | def splicingList(self, save_path):

FILE: eiseg/plugin/n2grid/rs_grid.py
  class RSGrids (line 21) | class RSGrids:
    method __init__ (line 22) | def __init__(self, raset: Raster) -> None:
    method clear (line 35) | def clear(self) -> None:
    method createGrids (line 40) | def createGrids(self) -> List[int]:
    method getGrid (line 48) | def getGrid(self, row: int, col: int) -> Tuple[np.ndarray]:
    method splicingList (line 54) | def splicingList(self, save_path: str) -> np.ndarray:

FILE: eiseg/plugin/remotesensing/imgtools.py
  function two_percentLinear (line 22) | def two_percentLinear(image: np.ndarray,
  function sample_norm (line 43) | def sample_norm(image: np.ndarray) -> np.ndarray:
  function get_thumbnail (line 57) | def get_thumbnail(image: np.ndarray,

FILE: eiseg/plugin/remotesensing/raster.py
  function check_rasterio (line 26) | def check_rasterio() -> bool:
  class Raster (line 41) | class Raster:
    method __init__ (line 42) | def __init__(self,
    method __del__ (line 71) | def __del__(self) -> None:
    method __getRasterInfo (line 74) | def __getRasterInfo(self) -> Dict:
    method checkOpenGrid (line 89) | def checkOpenGrid(self, thumbnail_min: Union[int, None]) -> bool:
    method setBand (line 98) | def setBand(self, bands: Union[List[int], Tuple[int]]) -> None:
    method showGeoInfo (line 114) | def showGeoInfo(self) -> str:
    method getArray (line 126) | def getArray(self) -> Tuple[np.ndarray]:
    method getGrid (line 141) | def getGrid(self, row: int, col: int) -> Tuple[np.ndarray]:
    method saveMask (line 158) | def saveMask(self, img: np.array, save_path: str,
    method saveMaskbyGrids (line 181) | def saveMaskbyGrids(self,

FILE: eiseg/plugin/remotesensing/shape.py
  function check_gdal (line 20) | def check_gdal() -> bool:
  function save_shp (line 43) | def save_shp(shp_path: str, tif_path: str, ignore_index :int=0) -> str:

FILE: eiseg/run.py
  function main (line 29) | def main():

FILE: eiseg/ui.py
  class Ui_EISeg (line 32) | class Ui_EISeg(object):
    method __init__ (line 33) | def __init__(self):
    method setupUi (line 37) | def setupUi(self, MainWindow):
    method create_text (line 318) | def create_text(self, parent, text_name=None, text_text=None):
    method create_button (line 322) | def create_button(self, parent, btn_name, btn_text, ico_path=None, cur...
    method creat_dock (line 326) | def creat_dock(self, parent, name, text, layout):
    method show_logo (line 330) | def show_logo(self, logo_path):
    method create_slider (line 343) | def create_slider(

FILE: eiseg/util/coco/coco.py
  function _isArrayLike (line 16) | def _isArrayLike(obj):
  class COCO (line 20) | class COCO:
    method __init__ (line 21) | def __init__(self, annotation_file=None):
    method hasImage (line 58) | def hasImage(self, imageName):
    method hasCat (line 62) | def hasCat(self, catIdx):
    method createIndex (line 66) | def createIndex(self):
    method setInfo (line 106) | def setInfo(
    method addCategory (line 124) | def addCategory(
    method updateCategory (line 140) | def updateCategory(
    method addImage (line 158) | def addImage(
    method getBB (line 190) | def getBB(self, segmentation):
    method getArea (line 196) | def getArea(self, segmentation):
    method addAnnotation (line 202) | def addAnnotation(
    method delAnnotation (line 233) | def delAnnotation(self, annId, imgId):
    method updateAnnotation (line 245) | def updateAnnotation(self, id, imgId, segmentation):
    method info (line 269) | def info(self):
    method getAnnIds (line 277) | def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None):
    method getCatIds (line 319) | def getCatIds(self, catNms=[], supNms=[], catIds=[]):
    method getImgIds (line 353) | def getImgIds(self, imgIds=[], catIds=[]):
    method loadAnns (line 374) | def loadAnns(self, ids=[]):
    method loadCats (line 385) | def loadCats(self, ids=[]):
    method loadImgs (line 396) | def loadImgs(self, ids=[]):
    method download (line 570) | def download(self, tarDir=None, imgIds=[]):
    method loadNumpyAnnotations (line 596) | def loadNumpyAnnotations(self, data):

FILE: eiseg/util/coco/cocoeval.py
  class COCOeval (line 10) | class COCOeval:
    method __init__ (line 60) | def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'):
    method _prepare (line 84) | def _prepare(self):
    method evaluate (line 121) | def evaluate(self):
    method computeIoU (line 163) | def computeIoU(self, imgId, catId):
    method computeOks (line 192) | def computeOks(self, imgId, catId):
    method evaluateImg (line 235) | def evaluateImg(self, imgId, catId, aRng, maxDet):
    method accumulate (line 315) | def accumulate(self, p = None):
    method summarize (line 422) | def summarize(self):
    method __str__ (line 495) | def __str__(self):
  class Params (line 498) | class Params:
    method setDetParams (line 502) | def setDetParams(self):
    method setKpParams (line 513) | def setKpParams(self):
    method __init__ (line 525) | def __init__(self, iouType='segm'):

FILE: eiseg/util/coco/common/gason.cpp
  function isspace (line 52) | static inline bool isspace(char c) {
  function isdelim (line 56) | static inline bool isdelim(char c) {
  function isdigit (line 60) | static inline bool isdigit(char c) {
  function isxdigit (line 64) | static inline bool isxdigit(char c) {
  function char2int (line 68) | static inline int char2int(char c) {
  function string2double (line 74) | static double string2double(char *s, char **endptr) {
  function JsonNode (line 120) | static inline JsonNode *insertAfter(JsonNode *tail, JsonNode *node) {
  function JsonValue (line 128) | static inline JsonValue listToValue(JsonTag tag, JsonNode *tail) {
  function jsonParse (line 137) | int jsonParse(char *s, char **endptr, JsonValue *value, JsonAllocator &a...

FILE: eiseg/util/coco/common/gason.h
  type JsonTag (line 8) | enum JsonTag {
  type JsonNode (line 18) | struct JsonNode
  function JsonNode (line 54) | JsonNode *toNode() const {
  type JsonNode (line 60) | struct JsonNode {
  type JsonIterator (line 66) | struct JsonIterator {
  function JsonIterator (line 86) | inline JsonIterator end(JsonValue) {

FILE: eiseg/util/coco/common/maskApi.c
  function uint (line 11) | uint umin( uint a, uint b ) { return (a<b) ? a : b; }
  function uint (line 12) | uint umax( uint a, uint b ) { return (a>b) ? a : b; }
  function rleInit (line 14) | void rleInit( RLE *R, siz h, siz w, siz m, uint *cnts ) {
  function rleFree (line 19) | void rleFree( RLE *R ) {
  function rlesInit (line 23) | void rlesInit( RLE **R, siz n ) {
  function rlesFree (line 28) | void rlesFree( RLE **R, siz n ) {
  function rleEncode (line 32) | void rleEncode( RLE *R, const byte *M, siz h, siz w, siz n ) {
  function rleDecode (line 43) | void rleDecode( const RLE *R, byte *M, siz n ) {
  function rleMerge (line 49) | void rleMerge( const RLE *R, RLE *M, siz n, int intersect ) {
  function rleArea (line 72) | void rleArea( const RLE *R, siz n, uint *a ) {
  function rleIou (line 77) | void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ) {
  function rleNms (line 98) | void rleNms( RLE *dt, siz n, uint *keep, double thr ) {
  function bbIou (line 109) | void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ) {
  function bbNms (line 122) | void bbNms( BB dt, siz n, uint *keep, double thr ) {
  function rleToBbox (line 133) | void rleToBbox( const RLE *R, BB bb, siz n ) {
  function rleFrBbox (line 149) | void rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n ) {
  function uintCompare (line 158) | int uintCompare(const void *a, const void *b) {
  function rleFrPoly (line 162) | void rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w ) {
  function rleFrString (line 218) | void rleFrString( RLE *R, char *s, siz h, siz w ) {

FILE: eiseg/util/coco/common/maskApi.h
  type uint (line 9) | typedef unsigned int uint;
  type siz (line 10) | typedef unsigned long siz;
  type byte (line 11) | typedef unsigned char byte;
  type RLE (line 13) | typedef struct { siz h, w, m; uint *cnts; } RLE;

FILE: eiseg/util/coco/mask.py
  function encode (line 80) | def encode(bimask):
  function decode (line 87) | def decode(rleObjs):
  function area (line 93) | def area(rleObjs):
  function toBbox (line 99) | def toBbox(rleObjs):

FILE: eiseg/util/colormap.py
  class ColorMap (line 21) | class ColorMap(object):
    method __init__ (line 22) | def __init__(self, color_path, shuffle=False):
    method get_color (line 32) | def get_color(self):
    method __len__ (line 37) | def __len__(self):

FILE: eiseg/util/config.py
  function parse_configs (line 8) | def parse_configs(path):
  function save_configs (line 15) | def save_configs(path=None, config=None, actions=None):
  class cfgData (line 33) | class cfgData(object):
    method __init__ (line 34) | def __init__(self, yaml_file):
    method get (line 39) | def get(self, key):

FILE: eiseg/util/label.py
  class Label (line 21) | class Label:
    method __init__ (line 22) | def __init__(self, idx=None, name=None, color=None):
    method __repr__ (line 27) | def __repr__(self):
  class LabelList (line 31) | class LabelList(object):
    method __init__ (line 32) | def __init__(self, labels: dict = None):
    method add (line 39) | def add(self, idx, name, color):
    method remove (line 42) | def remove(self, index):
    method clear (line 49) | def clear(self):
    method toint (line 52) | def toint(self, seq):
    method importLabel (line 63) | def importLabel(self, path):
    method exportLabel (line 78) | def exportLabel(self, path):
    method getLabelById (line 90) | def getLabelById(self, labelIdx):
    method __repr__ (line 95) | def __repr__(self):
    method __getitem__ (line 98) | def __getitem__(self, index):
    method __len__ (line 101) | def __len__(self):
    method colors (line 105) | def colors(self):

FILE: eiseg/util/language.py
  class TransUI (line 24) | class TransUI(object):
    method __init__ (line 25) | def __init__(self, is_trans=False):
    method put (line 36) | def put(self, zh_CN):
    method tr (line 46) | def tr(self, zh_CN):

FILE: eiseg/util/manager.py
  class ComponentManager (line 5) | class ComponentManager:
    method __init__ (line 6) | def __init__(self, name=None):
    method __len__ (line 10) | def __len__(self):
    method __repr__ (line 13) | def __repr__(self):
    method __getitem__ (line 17) | def __getitem__(self, item):
    method __iter__ (line 26) | def __iter__(self):
    method keys (line 30) | def keys(self):
    method idx (line 33) | def idx(self, item):
    method components_dict (line 40) | def components_dict(self):
    method name (line 44) | def name(self):
    method _add_single_component (line 47) | def _add_single_component(self, component):
    method add_component (line 64) | def add_component(self, components):

FILE: eiseg/util/misc.py
  function get_dims_with_exclusion (line 6) | def get_dims_with_exclusion(dim, exclude=None):
  function save_checkpoint (line 14) | def save_checkpoint(
  function get_bbox_from_mask (line 36) | def get_bbox_from_mask(mask):
  function expand_bbox (line 45) | def expand_bbox(bbox, expand_ratio, min_crop_size=None):
  function clamp_bbox (line 63) | def clamp_bbox(bbox, rmin, rmax, cmin, cmax):
  function get_bbox_iou (line 72) | def get_bbox_iou(b1, b2):
  function get_segments_iou (line 78) | def get_segments_iou(s1, s2):
  function get_labels_with_sizes (line 86) | def get_labels_with_sizes(x):

FILE: eiseg/util/opath.py
  function check_cn (line 5) | def check_cn(path):
  function normcase (line 11) | def normcase(path):

FILE: eiseg/util/polygon.py
  class Instructions (line 23) | class Instructions(Enum):
  function get_polygon (line 28) | def get_polygon(label, sample="Dynamic", img_size=None, building=False):
  function __change_list (line 88) | def __change_list(polygons, idx):
  function __find_min_point (line 98) | def __find_min_point(i_list, o_list):
  function __cal_ang (line 115) | def __cal_ang(p1, p2, p3):
  function __cal_dist (line 127) | def __cal_dist(p1, p2):
  function approx_poly_DIY (line 132) | def approx_poly_DIY(contour, min_dist=10, ang_err=5):
  function check_size_minmax (line 178) | def check_size_minmax(polygons, img_size):

FILE: eiseg/util/qt.py
  function newIcon (line 16) | def newIcon(icon):
  function newButton (line 26) | def newButton(text, icon=None, slot=None):
  function newAction (line 35) | def newAction(
  function addActions (line 71) | def addActions(widget, actions):
  function labelValidator (line 81) | def labelValidator():
  class struct (line 85) | class struct(object):
    method __init__ (line 86) | def __init__(self, **kwargs):
    method __len__ (line 89) | def __len__(self):
    method append (line 92) | def append(self, action):
    method __iter__ (line 96) | def __iter__(self):
    method __getitem__ (line 99) | def __getitem__(self, idx):
    method get (line 102) | def get(self, name):
  function fmtShortcut (line 106) | def fmtShortcut(text):

FILE: eiseg/util/regularization/cal_line.py
  function line (line 25) | def line(p1, p2):
  function intersection (line 33) | def intersection(L1, L2):
  function par_line_dist (line 46) | def par_line_dist(L1, L2):
  function point_in_line (line 60) | def point_in_line(m, n, x1, y1, x2, y2):

FILE: eiseg/util/regularization/cal_point.py
  function cal_dist (line 26) | def cal_dist(point_1, point_2):
  function cal_ang (line 32) | def cal_ang(point_1, point_2, point_3):
  function cal_azimuth (line 46) | def cal_azimuth(point_0, point_1):

FILE: eiseg/util/regularization/rdp_alg.py
  function pldist (line 33) | def pldist(x0, x1, x2):
  function _rdp (line 51) | def _rdp(M, epsilon, dist):
  function _rdp_nn (line 76) | def _rdp_nn(seq, epsilon, dist):
  function rdp (line 89) | def rdp(M, epsilon=0, dist=pldist):

FILE: eiseg/util/regularization/rotate_ang.py
  function Nrotation_angle_get_coor_coordinates (line 25) | def Nrotation_angle_get_coor_coordinates(point, center, angle):
  function Srotation_angle_get_coor_coordinates (line 38) | def Srotation_angle_get_coor_coordinates(point, center, angle):

FILE: eiseg/util/regularization/rs_regularization.py
  function boundary_regularization (line 30) | def boundary_regularization(contours, img_shape, epsilon=6):

FILE: eiseg/util/serialization.py
  function serialize (line 7) | def serialize(init):
  function load_model (line 41) | def load_model(config, **kwargs):
  function get_config_repr (line 64) | def get_config_repr(config):
  function get_default_params (line 77) | def get_default_params(some_class):
  function get_classname (line 91) | def get_classname(cls):
  function get_class_from_str (line 99) | def get_class_from_str(class_str):

FILE: eiseg/util/vis.py
  function visualize_instances (line 7) | def visualize_instances(
  function get_palette (line 27) | def get_palette(num_cls):
  function visualize_mask (line 31) | def visualize_mask(mask, num_cls):
  function visualize_proposals (line 38) | def visualize_proposals(proposals_info, point_color=(255, 0, 0), point_r...
  function draw_probmap (line 48) | def draw_probmap(x):
  function draw_points (line 52) | def draw_points(image, points, color, radius=3):
  function draw_instance_map (line 60) | def draw_instance_map(x, palette=None):
  function blend_mask (line 68) | def blend_mask(image, mask, alpha=0.6):
  function get_boundaries (line 77) | def get_boundaries(instances_masks, boundaries_width=1):
  function draw_with_blend_and_clicks (line 97) | def draw_with_blend_and_clicks(

FILE: eiseg/widget/bbox.py
  class BBoxAnnotation (line 21) | class BBoxAnnotation(QtWidgets.QGraphicsPathItem):
    method __init__ (line 22) | def __init__(
    method scnenePoints (line 65) | def scnenePoints(self):
    method setAnning (line 69) | def setAnning(self, isAnning=True):
    method remove (line 72) | def remove(self):
    method create_corners (line 77) | def create_corners(self):
    method create_lines (line 99) | def create_lines(self):
    method update (line 104) | def update(self):
    method add_to_scene (line 124) | def add_to_scene(self):
    method remove_from_scene (line 129) | def remove_from_scene(self):
    method to_array (line 137) | def to_array(self):
    method _round (line 146) | def _round(self, number, ind=0):
    method __del__ (line 153) | def __del__(self):

FILE: eiseg/widget/create.py
  function create_text (line 22) | def create_text(parent, text_name=None, text_text=None):
  function create_edit (line 32) | def create_edit(parent, text_name=None, text_text=None):
  function create_button (line 44) | def create_button(parent, btn_name, btn_text, ico_path=None, curt=None):
  function create_slider (line 66) | def create_slider(
  class DockWidget (line 110) | class DockWidget(QDockWidget):
    method __init__ (line 111) | def __init__(self, parent, name, text):
    method changeBackColor (line 131) | def changeBackColor(self, isFloating):
  function creat_dock (line 139) | def creat_dock(parent, name, text, widget):

FILE: eiseg/widget/grip.py
  class GripItem (line 20) | class GripItem(QtWidgets.QGraphicsPathItem):
    method __init__ (line 24) | def __init__(self, annotation_item, index, color, img_size):
    method setColor (line 46) | def setColor(self, color):
    method setAnning (line 51) | def setAnning(self, anning=True):
    method size (line 57) | def size(self):
    method updateSize (line 71) | def updateSize(self, s=2):
    method hoverEnterEvent (line 79) | def hoverEnterEvent(self, ev):
    method hoverLeaveEvent (line 86) | def hoverLeaveEvent(self, ev):
    method mouseReleaseEvent (line 93) | def mouseReleaseEvent(self, ev):
    method itemChange (line 97) | def itemChange(self, change, value):
    method shape (line 117) | def shape(self):
    method mouseDoubleClickEvent (line 125) | def mouseDoubleClickEvent(self, ev):

FILE: eiseg/widget/line.py
  class LineItem (line 19) | class LineItem(QtWidgets.QGraphicsLineItem):
    method __init__ (line 23) | def __init__(self, annotation_item, idx, color):
    method setColor (line 39) | def setColor(self, color):
    method setAnning (line 43) | def setAnning(self, anning=True):
    method width (line 50) | def width(self):
    method updateWidth (line 64) | def updateWidth(self):
    method hoverEnterEvent (line 67) | def hoverEnterEvent(self, ev):
    method hoverLeaveEvent (line 75) | def hoverLeaveEvent(self, ev):
    method mouseDoubleClickEvent (line 80) | def mouseDoubleClickEvent(self, ev):
    method shape (line 87) | def shape(self):
    method boundingPolygon (line 99) | def boundingPolygon(self, debug):

FILE: eiseg/widget/loading.py
  class LoadingWidget (line 23) | class LoadingWidget(QWidget):
    method __init__ (line 24) | def __init__(self):

FILE: eiseg/widget/polygon.py
  class PolygonAnnotation (line 21) | class PolygonAnnotation(QtWidgets.QGraphicsPolygonItem):
    method __init__ (line 22) | def __init__(
    method scnenePoints (line 77) | def scnenePoints(self):
    method setAnning (line 84) | def setAnning(self, isAnning=True):
    method addPointMiddle (line 118) | def addPointMiddle(self, lineIdx, point):
    method addPointLast (line 145) | def addPointLast(self, p):
    method remove (line 167) | def remove(self):
    method removeFocusPoint (line 182) | def removeFocusPoint(self):
    method removeLastPoint (line 211) | def removeLastPoint(self):
    method movePoint (line 221) | def movePoint(self, i, p):
    method moveLine (line 230) | def moveLine(self, i):
    method move_item (line 247) | def move_item(self, i, pos):
    method itemChange (line 255) | def itemChange(self, change, value):
    method hoverEnterEvent (line 261) | def hoverEnterEvent(self, ev):
    method hoverLeaveEvent (line 266) | def hoverLeaveEvent(self, ev):
    method focusInEvent (line 272) | def focusInEvent(self, ev):
    method focusOutEvent (line 276) | def focusOutEvent(self, ev):
    method setColor (line 280) | def setColor(self, insideColor, borderColor):
    method __len__ (line 296) | def __len__(self):

FILE: eiseg/widget/scene.py
  class AnnotationScene (line 22) | class AnnotationScene(QtWidgets.QGraphicsScene):
    method __init__ (line 25) | def __init__(self, parent=None):
    method setPenColor (line 35) | def setPenColor(self, color_list):
    method updatePolygonSize (line 39) | def updatePolygonSize(self):
    method setCreating (line 46) | def setCreating(self, creating=True):
    method mousePressEvent (line 49) | def mousePressEvent(self, ev):
    method mouseMoveEvent (line 63) | def mouseMoveEvent(self, ev):
    method drawForeground (line 72) | def drawForeground(self, painter, rect):
    method onMouseChanged (line 81) | def onMouseChanged(self, pointf):
    method item_hovering (line 86) | def item_hovering(self):
    method polygon_hovering (line 93) | def polygon_hovering(self):
    method line_hovering (line 100) | def line_hovering(self):
    method hovering (line 107) | def hovering(self):

FILE: eiseg/widget/shortcut.py
  class RecordShortcutWidget (line 39) | class RecordShortcutWidget(QKeySequenceEdit):
    method __init__ (line 40) | def __init__(self, finishCallback, location):
    method keyReleaseEvent (line 49) | def keyReleaseEvent(self, ev):
  class ShortcutWidget (line 53) | class ShortcutWidget(QWidget):
    method __init__ (line 54) | def __init__(self, actions, pjpath):
    method initUI (line 64) | def initUI(self):
    method refreshUi (line 85) | def refreshUi(self):
    method recordShortcut (line 96) | def recordShortcut(self, action):
    method setShortcut (line 106) | def setShortcut(self, key):
    method center (line 136) | def center(self):
    method moveEvent (line 143) | def moveEvent(self, event):
    method closeEvent (line 150) | def closeEvent(self, event):

FILE: eiseg/widget/table.py
  class TableWidget (line 20) | class TableWidget(QtWidgets.QTableWidget):
    method __init__ (line 21) | def __init__(self, *args, **kwargs):
    method dropEvent (line 32) | def dropEvent(self, event):
    method drop_on (line 59) | def drop_on(self, event):
    method is_below (line 66) | def is_below(self, pos, index):

FILE: eiseg/widget/view.py
  class AnnotationView (line 20) | class AnnotationView(QtWidgets.QGraphicsView):
    method __init__ (line 24) | def __init__(self, *args):
    method wheelEvent (line 38) | def wheelEvent(self, ev):
    method mouseMoveEvent (line 53) | def mouseMoveEvent(self, ev):
    method mousePressEvent (line 68) | def mousePressEvent(self, ev):
    method mouseReleaseEvent (line 74) | def mouseReleaseEvent(self, ev):
    method leaveEvent (line 79) | def leaveEvent(self, ev):

FILE: tool/baidu_translate.py
  class BaiduTranslate (line 27) | class BaiduTranslate:
    method __init__ (line 28) | def __init__(self, fromLang, toLang):
    method trans (line 36) | def trans(self, text):
  function read_ts (line 67) | def read_ts(ts_path):

FILE: tool/semantic2instance.py
  function _savePalette (line 25) | def _savePalette(label, save_path):
  function _segMaskB2I (line 34) | def _segMaskB2I(mask_path, save_path):

FILE: tool/translateUI.py
  class BaiduTranslate (line 12) | class BaiduTranslate:
    method __init__ (line 13) | def __init__(self, fromLang, toLang):
    method BdTrans (line 21) | def BdTrans(self, text):
  function firstCharUpper (line 82) | def firstCharUpper(s):
Condensed preview — 98 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (518K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/--bug.md",
    "chars": 362,
    "preview": "---\nname: 反馈bug\nabout: 反馈使用过程中出现的各种错误\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**bug描述**\n请大致描述出错的现象,在什么情况下或操作过程中遇到,在上述条"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".gitignore",
    "chars": 2095,
    "preview": "# Byte-compiled / optimized / DLL files\ntest/\n__pycache__/\n*.py[cod]\n*$py.class\ndzq*\n.vscode\n.vscode/\nvis_temp.py\ntest.t"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "MANIFEST.in",
    "chars": 78,
    "preview": "include eiseg/config/*\ninclude eiseg/resource/*\ninclude eiseg/util/translate/*"
  },
  {
    "path": "README.md",
    "chars": 842,
    "preview": "# EISeg\r\n\r\n[![Python 3.6](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/release/p"
  },
  {
    "path": "README_EN.md",
    "chars": 1148,
    "preview": "# EISeg\n\n[![Python 3.6](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/release/pyt"
  },
  {
    "path": "docs/medical.md",
    "chars": 827,
    "preview": "# 医疗相关\n\n以下内容为EISeg中医疗垂类相关的文档,主要包括环境配置和功能介绍。\n\n## 1 环境配置\n\n使用医疗组件需要额外安装SimpleITK包用于读取医学影像,安装方式如下:\n\n```shell\npip install Sim"
  },
  {
    "path": "docs/medical_en.md",
    "chars": 2867,
    "preview": "# Medical Treatment\r\n\r\nThis part presents documents related to medical treatment in EISeg, including its environment con"
  },
  {
    "path": "docs/remote_sensing.md",
    "chars": 1951,
    "preview": "# 遥感相关\r\n\r\n以下内容为EISeg中遥感垂类相关的文档,主要包括环境配置和功能介绍两大方面。\r\n\r\n## 1 环境配置\r\n\r\nEISeg中对遥感数据的支持来自GDAL/OGR,GDAL是一个在X/MIT许可协议下的开源栅格空间数据转换"
  },
  {
    "path": "docs/remote_sensing_en.md",
    "chars": 4048,
    "preview": "# Remote Sensing\r\n\r\n以下内容为EISeg中遥感垂类相关的文档,主要包括环境配置和功能介绍两大方面。\r\n\r\nThis part presents documents related to remote sensing in"
  },
  {
    "path": "docs/tools.md",
    "chars": 528,
    "preview": "# 脚本工具相关\r\n\r\n以下内容为EISeg中的相关工具使用。位置位于EISeg/tool\r\n\r\n## 语义标签转实例标签\r\n\r\n语义分割标签转实例分割标签(原标签为0/255),结果为单通道图像采用调色板调色。通过`tool`中的`sem"
  },
  {
    "path": "eiseg/__init__.py",
    "chars": 1868,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/__main__.py",
    "chars": 673,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/app.py",
    "chars": 84208,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/config/colormap.txt",
    "chars": 247,
    "preview": "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\n1"
  },
  {
    "path": "eiseg/config/config.yaml",
    "chars": 657,
    "preview": "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:"
  },
  {
    "path": "eiseg/controller.py",
    "chars": 11835,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/exe.py",
    "chars": 774,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "eiseg/inference/clicker.py",
    "chars": 4597,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/predictor/__init__.py",
    "chars": 1492,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/predictor/base.py",
    "chars": 9446,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/predictor/ops.py",
    "chars": 4431,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/transforms/__init__.py",
    "chars": 783,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/transforms/base.py",
    "chars": 792,
    "preview": "import paddle.nn.functional as F\n\n\nclass BaseTransform(object):\n    def __init__(self):\n        self.image_changed = Fal"
  },
  {
    "path": "eiseg/inference/transforms/crops.py",
    "chars": 4084,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/transforms/flip.py",
    "chars": 1995,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/transforms/limit_longest_side.py",
    "chars": 1632,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/inference/transforms/zoom_in.py",
    "chars": 7645,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/models.py",
    "chars": 2566,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/plugin/__init__.py",
    "chars": 609,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/plugin/medical/__init__.py",
    "chars": 662,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/plugin/medical/med.py",
    "chars": 2980,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/plugin/n2grid/__init__.py",
    "chars": 679,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/plugin/n2grid/grid.py",
    "chars": 4878,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/plugin/n2grid/rs_grid.py",
    "chars": 2395,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/plugin/remotesensing/__init__.py",
    "chars": 695,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/plugin/remotesensing/imgtools.py",
    "chars": 2339,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/plugin/remotesensing/raster.py",
    "chars": 8349,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/plugin/remotesensing/shape.py",
    "chars": 2298,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/run.py",
    "chars": 2645,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/ui.py",
    "chars": 15220,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/__init__.py",
    "chars": 333,
    "preview": "from .qt import newAction, addActions, struct, newIcon\nfrom .config import parse_configs, save_configs\nfrom .colormap im"
  },
  {
    "path": "eiseg/util/coco/__init__.py",
    "chars": 21,
    "preview": "__author__ = 'tylin'\n"
  },
  {
    "path": "eiseg/util/coco/_mask.pyx",
    "chars": 11440,
    "preview": "# distutils: language = c\n# distutils: sources = ../common/maskApi.c\n\n#*************************************************"
  },
  {
    "path": "eiseg/util/coco/coco.py",
    "chars": 23909,
    "preview": "import json\nimport time\nimport matplotlib.pyplot as plt\nfrom matplotlib.collections import PatchCollection\nfrom matplotl"
  },
  {
    "path": "eiseg/util/coco/cocoeval.py",
    "chars": 24143,
    "preview": "__author__ = 'tsungyi'\n\nimport numpy as np\nimport datetime\nimport time\nfrom collections import defaultdict\nfrom . import"
  },
  {
    "path": "eiseg/util/coco/common/gason.cpp",
    "chars": 9540,
    "preview": "// https://github.com/vivkin/gason - pulled January 10, 2016\n#include \"gason.h\"\n#include <stdlib.h>\n\n#define JSON_ZONE_S"
  },
  {
    "path": "eiseg/util/coco/common/gason.h",
    "chars": 3483,
    "preview": "// https://github.com/vivkin/gason - pulled January 10, 2016\n#pragma once\n\n#include <stdint.h>\n#include <stddef.h>\n#incl"
  },
  {
    "path": "eiseg/util/coco/common/maskApi.c",
    "chars": 8308,
    "preview": "/**************************************************************************\n* Microsoft COCO Toolbox.      version 2.0\n*"
  },
  {
    "path": "eiseg/util/coco/common/maskApi.h",
    "chars": 2176,
    "preview": "/**************************************************************************\n* Microsoft COCO Toolbox.      version 2.0\n*"
  },
  {
    "path": "eiseg/util/coco/mask.py",
    "chars": 4591,
    "preview": "__author__ = 'tsungyi'\n\nimport pycocotools._mask as _mask\n\n# Interface for manipulating masks stored in RLE format.\n#\n# "
  },
  {
    "path": "eiseg/util/coco.py.bk",
    "chars": 3090,
    "preview": "cocoDict = {\n    \"info\": info,\n    \"images\": [image],\n    \"annotations\": [annotation],\n    \"categories\": [\n        {\n   "
  },
  {
    "path": "eiseg/util/colormap.py",
    "chars": 1293,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/config.py",
    "chars": 1279,
    "preview": "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 "
  },
  {
    "path": "eiseg/util/exp_imports/default.py",
    "chars": 464,
    "preview": "import paddle\nfrom functools import partial\nfrom easydict import EasyDict as edict\nfrom albumentations import *\n\nfrom da"
  },
  {
    "path": "eiseg/util/label.py",
    "chars": 3319,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/language.py",
    "chars": 1871,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/util/manager.py",
    "chars": 2468,
    "preview": "import inspect\nfrom collections.abc import Sequence\n\n\nclass ComponentManager:\n    def __init__(self, name=None):\n       "
  },
  {
    "path": "eiseg/util/misc.py",
    "chars": 2362,
    "preview": "import paddle\nimport numpy as np\nimport pickle\n\n\ndef get_dims_with_exclusion(dim, exclude=None):\n    dims = list(range(d"
  },
  {
    "path": "eiseg/util/opath.py",
    "chars": 208,
    "preview": "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(pa"
  },
  {
    "path": "eiseg/util/polygon.py",
    "chars": 6688,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/qt.py",
    "chars": 2746,
    "preview": "from math import sqrt\nimport os.path as osp\n\nimport numpy as np\n\nfrom eiseg import pjpath\nfrom qtpy import QtCore\nfrom q"
  },
  {
    "path": "eiseg/util/regularization/__init__.py",
    "chars": 888,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/util/regularization/cal_line.py",
    "chars": 1910,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/regularization/cal_point.py",
    "chars": 2137,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/regularization/rdp_alg.py",
    "chars": 3130,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/regularization/rotate_ang.py",
    "chars": 1757,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/regularization/rs_regularization.py",
    "chars": 7404,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/util/serialization.py",
    "chars": 3032,
    "preview": "from functools import wraps\nfrom copy import deepcopy\nimport inspect\nimport paddle.nn as nn\n\n\ndef serialize(init):\n    p"
  },
  {
    "path": "eiseg/util/vis.py",
    "chars": 3747,
    "preview": "from functools import lru_cache\n\nimport cv2\nimport numpy as np\n\n\ndef visualize_instances(\n    imask, bg_color=255, bound"
  },
  {
    "path": "eiseg/widget/__init__.py",
    "chars": 391,
    "preview": "from .shortcut import ShortcutWidget\nfrom .loading import LoadingWidget\nfrom .line import LineItem\nfrom .grip import Gri"
  },
  {
    "path": "eiseg/widget/bbox.py",
    "chars": 5027,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/create.py",
    "chars": 4654,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/widget/grip.py",
    "chars": 4470,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/line.py",
    "chars": 4120,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/loading.py",
    "chars": 1191,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/polygon.py",
    "chars": 11131,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/scene.py",
    "chars": 3673,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/shortcut.py",
    "chars": 4845,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "eiseg/widget/table.py",
    "chars": 3086,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "eiseg/widget/view.py",
    "chars": 3142,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "init.sh",
    "chars": 125,
    "preview": "#!/bin/bash\n\nROOT=`cd \"$(dirname ${BASH_SOURCE[0]})\" && pwd`\n\necho \"ROOT : $ROOT\"\n\nexport PYTHONPATH=$PYTHONPATH:$ROOT/e"
  },
  {
    "path": "requirements-med.txt",
    "chars": 10,
    "preview": "SimpleITK\n"
  },
  {
    "path": "requirements-rs.txt",
    "chars": 28,
    "preview": "GDAL>=3.3.0\nrasterio>=1.2.4\n"
  },
  {
    "path": "requirements.txt",
    "chars": 105,
    "preview": "pyqt5\nqtpy\nopencv-python\nscipy\npaddleseg\nalbumentations\ncython\npyyaml\nwget\nrequests\neasydict\nscikit-image"
  },
  {
    "path": "setup.py",
    "chars": 1491,
    "preview": "import pathlib\r\nfrom setuptools import setup, find_packages, Extension\r\n\r\nimport numpy as np\r\n\r\nfrom eiseg import __APPN"
  },
  {
    "path": "tool/baidu_translate.py",
    "chars": 3701,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "tool/pypi.sh",
    "chars": 268,
    "preview": "rm dist/*\npython setup.py sdist bdist_wheel\ntwine upload --repository-url https://test.pypi.org/legacy/  dist/* --verbos"
  },
  {
    "path": "tool/semantic2instance.py",
    "chars": 2742,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\r\n#\r\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "tool/translate.pro",
    "chars": 115,
    "preview": "CODECFORTR = UTF-8\nSOURCES = ../eiseg/app.py ../eiseg/ui.py ../eiseg/widget/shortcut.py\nTRANSLATIONS = ./ts/out.ts\n"
  },
  {
    "path": "tool/translateUI.py",
    "chars": 2989,
    "preview": "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.clie"
  },
  {
    "path": "tool/ts/Arabic.ts",
    "chars": 31500,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ar\" sourcelanguage=\"zh\">\n<context>\n    "
  },
  {
    "path": "tool/ts/English.ts",
    "chars": 34491,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"en\" sourcelanguage=\"zh\">\n<context>\n    "
  },
  {
    "path": "tool/update_md5.py",
    "chars": 886,
    "preview": "# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the "
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the PaddleCV-SIG/EISeg GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 98 files (461.0 KB), approximately 124.3k tokens, and a symbol index with 558 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!