Эх сурвалжийг харах

[Doc] Add contribution guidelines and developer manual (#10)

* Add issue and pr templates

* Add contributing guidelines

* Update description

* [Test] Update tests
Lin Manhui 2 жил өмнө
parent
commit
255c172384

+ 32 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,32 @@
+---
+name: "\U0001F41B Bug report / Bug反馈"
+about: Create a report to help us improve / 反馈Bug问题
+title: "[Bug]"
+labels: bug
+assignees: ''
+
+---
+
+Thanks for your bug report. To help us better solve the issue, please provide the following information:
+ 1. PaddleRS version: (please specify the branch as well,e.g. PaddleRS release/1.0)
+ 2. PaddlePaddle version: (e.g. PaddlePaddle 2.3.0)
+ 3. Operation system: (e.g. Linux/Windows/MacOS)
+ 4. Python version: (e.g. Python3.7/8)
+ 5. CUDA/cuDNN version: (e.g. CUDA10.2/cuDNN 7.6.5)
+ 6. Full codes: (if you modify any original code,please show the comparison of the codes before and after)
+ 7. Detailed error information and releated running log: (if you used multi-gpus,the log can be found in `log/worklog.0` by default)
+ 8. Steps to reproduce the problem:
+ 9. Additional context: (add any other context about the problem)
+
+---
+
+欢迎您反馈PaddleRS使用问题。辛苦您提供以下信息,以方便我们快速定位和解决问题:
+ 1. PaddleRS版本:(请提供版本号和分支信息,如PaddleRS release/1.0)
+ 2. PaddlePaddle版本:(如PaddlePaddle 2.3.0)
+ 3. 操作系统信息:(如Linux/Windows/MacOS)
+ 4. Python版本号:(如Python3.7/8)
+ 5. CUDA/cuDNN版本:( 如CUDA10.2/cuDNN 7.6.5等)
+ 6. 完整的代码:(若修改过原代码,请提供修改前后代码对比)
+ 7. 详细的错误信息与相关log:(若使用多卡,log默认保存在`log/worklog.0`)
+ 8. 问题复现步骤:
+ 9. 其他内容: (增加其他与问题相关的内容)

+ 20 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -0,0 +1,20 @@
+---
+name: "\U0001F680 Feature request / 新功能需求"
+about: Suggest an idea for this project / 提出一个新的功能需求或改进建议
+title: "[Feature Request]"
+labels: enhancement
+assignees: ''
+
+---
+
+Welcome to request a new feature! To help us better understand your request, please provide the following information:
+1. A clear and concise description of the requested feature.
+2. Tell us why the feature will be useful.
+3. If possible, please show related codes.
+
+---
+
+欢迎提出一个新的功能需求。为了帮助我们更好理解您的需求,辛苦您提供以下信息:
+1. 请使用清晰简洁的语言描述该项功能需求。
+2. 请分析这个功能的必要性。
+3. 如果可能的话,请提供相关代码实现效果。

+ 26 - 0
.github/ISSUE_TEMPLATE/general_issue.md

@@ -0,0 +1,26 @@
+---
+name: "\U0001F4DD General issue / 一般问题"
+about: Report any issue about PaddleRS / 提出任何一个与PaddleRS相关的问题
+title: "[General Issue]"
+labels: question
+assignees: ''
+
+---
+
+Thanks for your issue. To help us better solve the issue, please provide the following information:
+ 1. PaddleRS version: (please specify the branch as well,e.g. PaddleRS release/1.0)
+ 2. PaddlePaddle version: (e.g. PaddlePaddle 2.3.0)
+ 3. Operation system: (e.g. Linux/Windows/MacOS)
+ 4. Python version: (e.g. Python3.7/8)
+ 5. CUDA/cuDNN version: (e.g. CUDA10.2/cuDNN 7.6.5)
+ 6. Additional context: (add any other context about the problem)
+
+---
+
+欢迎您的提问。辛苦您提供以下信息,以方便我们快速定位和解决问题:
+ 1. PaddleRS版本:(请提供版本号和分支信息,如PaddleRS release/1.0)
+ 2. PaddlePaddle版本:(如PaddlePaddle 2.3.0)
+ 3. 操作系统信息:(如Linux/Windows/MacOS)
+ 4. Python版本号:(如Python3.7/8)
+ 5. CUDA/cuDNN版本:( 如CUDA10.2/cuDNN 7.6.5等)
+ 6. 其他内容: (增加其他与问题相关的内容)

+ 8 - 0
.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,8 @@
+### PR types
+<!-- One of [ New features | Bug fixes | Function optimization | Performance optimization | Breaking changes | Others ] -->
+
+### PR changes
+<!-- One of [ Models | APIs | Docs | Others ] -->
+
+### Description
+<!-- Describe what this PR does -->

+ 1 - 0
README.md

@@ -206,6 +206,7 @@ PaddleRS是遥感科研院所、相关高校共同基于飞桨开发的遥感处
 
 * 非常感谢国家对地观测科学数据中心、中国科学院空天信息创新研究院、北京航空航天大学、武汉大学、中国石油大学(华东)、中国地质大学、中国四维、航天宏图、中科星图、超图等单位对PaddleRS项目的贡献。注:排名不分先后。
 * 非常感谢[geoyee](https://github.com/geoyee)(陈奕州), [Bobholamovic](https://github.com/Bobholamovic)(林漫晖), [kongdebug](https://github.com/kongdebug)(孔远杭), [huilin16](https://github.com/huilin16)(赵慧琳)等开发者对PaddleRS项目的贡献。
+* PaddleRS欢迎来自开源社区的贡献。如果您想要为PaddleRS贡献源码/案例,请参考[贡献指南](docs/CONTRIBUTING.md)。
 
 ## 许可证书
 

+ 129 - 0
docs/CONTRIBUTING.md

@@ -0,0 +1,129 @@
+# PaddleRS贡献指南
+
+## 贡献代码
+
+本指南首先阐述为PaddleRS贡献代码的必要步骤,然后对新增文件自查、代码风格规范和测试相关步骤三个方面进行详细说明。
+
+### 1 代码贡献步骤
+
+PaddleRS使用[git](https://git-scm.com/doc)作为版本控制工具,并托管在GitHub平台。这意味着,在贡献代码前,您需要熟悉git相关操作,并且对以[pull request (PR)](https://docs.github.com/cn/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests)为基础的GitHub工作流有所了解。
+
+为PaddleRS贡献代码的具体步骤如下:
+
+1. 在GitHub上fork PaddleRS官方仓库,将代码克隆到本地,并拉取develop分支的最新版本。
+2. 根据[《开发指南》](dev/dev_guide.md)编写代码(建议在新建的功能分支上开发)。
+3. 安装pre-commit钩子以便在每次commit前执行代码风格方面的检查。详见[代码风格规范](#3-代码风格规范)。
+4. 为新增的代码编写单元测试,并保证所有测试能够跑通。详见[测试相关步骤](#4-测试相关步骤)。
+5. 为您的分支新建一个PR,确保CLA协议签署且CI/CE通过。在这之后,会有PaddleRS团队人员对您贡献的代码进行review。
+6. 根据review意见修改代码,并重新提交,直到PR合入或关闭。
+
+如果您贡献的代码需要用到PaddleRS目前不依赖的第三方库,请在提交PR时说明,并阐述需要用到该第三方库的必要性。
+
+### 2 新增文件自查
+
+与代码风格规范不同,pre-commit钩子并不对本小节所阐述的规则做强制要求,因此需要开发者自行检查。
+
+#### 2.1 版权信息
+
+PaddleRS中每个新增的文件都需要添加版权信息,如下所示:
+
+```python
+# Copyright (c) 2022 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.
+```
+
+*注:版权信息中的年份需要按照当前自然年改写。*
+
+#### 2.2 模块导入顺序
+
+所有的全局导入语句都必须位于模块开头处、版权信息之后。按照如下顺序导入包或模块:
+
+1. Python标准库;
+2. 通过`pip`等包管理器安装的第三方库(注意`paddle`为第三方库,但`paddlers`本身不算第三方库);
+3. `paddlers`及`paddlers`下属的包和模块。
+
+不同类型的导入语句之间空1行。文件中不应该包含未使用的包或模块的导入语句。此外,当导入语句的长度相差较大时,建议按照长度递增顺序排列。如下显示了一个例子:
+
+```python
+import os
+
+import numpy as np
+import paddle.nn as nn
+import paddle.nn.functional as F
+
+import paddlers.transforms as T
+from paddlers.transforms import DecodeImg
+```
+
+### 3 代码风格规范
+
+PaddleRS对代码风格的规范基本与[Google Python风格规范](https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/)一致,但PaddleRS对类型注释不做强制要求。较为重要的代码风格规范如下:
+
+- 空行:顶层定义(例如顶层的函数或者类的定义)之间空2行。类内部不同方法的定义之间、以及类名与第一个方法定义之间空1行。在函数内部需要注意在逻辑上有间断的地方添加1个空行。
+
+- 行长度:每行(无论是代码行还是注释行)不超过80个字符,对于docstring中的行尤其要注意这一点。
+
+- 括号:括号可以用于行连接,但是不要在`if`判断中使用没有必要的括号。
+
+- 异常:抛出和捕获异常时使用尽可能具体的异常类型,几乎永远不要使用基类`Exception`(除非目的是捕获不限类型的任何异常)。
+
+- 注释:所有注释使用英文书写。所有提供给用户的API都必须添加docstring,且至少具有“API功能描述”和“API参数”两个部分。使用三双引号`"""`包围一个docstring。docstring书写的具体细节可参考[《代码注释规范》](dev/docstring.md)。
+
+- 命名:不同类型的变量名适用的大小写规则如下:模块名:`module_name`;包名:`package_name`;类名:`ClassName`;方法名:`method_name`;函数名:`function_name`;全局常量(指程序运行期间值不发生改变的变量)名:`GLOBAL_CONSTANT_NAME`;全局变量名:`global_var_name`;实例名:`instance_var_name`;函数参数名:`function_param_name`;局部变量名:`local_var_name`。
+
+### 4 测试相关步骤
+
+为了保证代码质量,您需要为新增的功能组件编写单元测试脚本。请根据您贡献的内容阅读相应的单测编写步骤。
+
+#### 4.1 模型单测
+
+1. 在`tests/rs_models/`中找到模型所属任务对应的测试用例定义文件,例如变化检测任务对应`tests/rs_models/test_cd_models.py`。
+2. 仿照文件中已有的例子,为新增的模型定义一个继承自`Test{任务名}Model`的测试类,将其`MODEL_CLASS`属性设置为新增的模型。
+3. 重写新的测试类的`test_specs()`方法。该方法将`self.specs`设置为一个列表,列表中的每一项为一个字典,字典中的键值对被用作构造模型的配置项。也即,`self.specs`中每一项对应一组测试用例,每组用例用来测试以某种特定参数构造的模型。
+
+#### 4.2 数据预处理/数据增强单测
+
+- 如果您编写的是数据预处理/数据增强算子(继承自`paddlers.transforms.operators.Transform`),构造该算子所需的所有输入参数都具有默认值,且算子能够处理任意任务、任意波段的数据,则您需要仿照`test_Resize()`或`test_RandomFlipOrRotate()`方法,在`tests/transforms/test_operators.py`中为`TestTransform`类添加新的方法。
+- 如果您编写的算子只支持对特定任务的处理或是对输入数据的波段数目有要求,请在编写完测试逻辑后,在`OP2FILTER`全局变量中为算子绑定`_InputFilter`。
+- 如果您编写的是数据预处理/数据增强函数(即`paddlers/transforms/functions.py`中的内容),请在`tests/transforms/test_functions.py`中仿造已有的例子添加测试类。
+
+#### 4.3 工具单测
+
+1. 在`tests/tools/`目录中新建文件,命名为`test_{工具名}.py`。
+2. 在新建的脚本中编写测试用例。
+
+#### 4.4 执行测试
+
+添加完测试用例后,您需要完整执行所有的测试(因为新增的代码可能影响了项目原有的代码,使部分功能不能正常使用)。输入如下指令:
+
+```bash
+cd tests
+bash run_tests.sh
+```
+
+这一过程可能较为费时,需要耐心等待。如果其中某些测试用例没有通过,请根据报错信息修改,直到所有用例都通过。
+
+执行以下脚本可以获取覆盖率相关信息:
+
+```bash
+bash check_coverage.sh
+```
+
+#### 4.5 TIPC
+
+如果您贡献的内容包含TIPC,请在提交PR时附上TIPC基础训推链条执行成功的日志信息。
+
+## 贡献案例
+
+tbd

+ 91 - 0
docs/dev/dev_guide.md

@@ -0,0 +1,91 @@
+# PaddleRS开发指南
+
+## 0 目录
+
+- [新增遥感专用模型](#1-新增遥感专用模型)
+
+- [新增数据预处理数据增强函数或算子](#2-新增数据预处理数据增强函数或算子)
+
+- [新增遥感影像处理工具](#3-新增遥感影像处理工具)
+
+## 1 新增遥感专用模型
+
+### 1.1 编写模型定义
+
+首先,在`paddlers/rs_models`中找到任务对应的子目录(包),任务和子目录的对应关系如下:
+
+- 变化检测:`cd`;
+- 场景分类:`clas`;
+- 目标检测:`det`;
+- 图像复原:`res`;
+- 图像分割:`seg`。
+
+在子目录中新建文件,以`{模型名称小写}.py`命名。在文件中编写完整的模型定义。
+
+新模型必须是`paddle.nn.Layer`的子类。对于图像分割、目标检测和场景分类任务,分别需要遵循[PaddleSeg](https://github.com/PaddlePaddle/PaddleSeg)、[PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection)和[PaddleClas](https://github.com/PaddlePaddle/PaddleClas)套件中制定的相关规范。**对于变化检测、场景分类和图像分割任务,模型构造时必须传入`num_classes`参数以指定输出的类别数目**。对于变化检测任务,模型定义需遵循的规范与分割模型类似,但有以下几点不同:
+
+- `forward()`方法接受3个输入参数,分别是`self`、`t1`和`t2`,其中`t1`和`t2`分别表示前、后两个时相的输入影像。
+- 对于多任务变化检测模型(例如模型同时输出变化检测结果与两个时相的建筑物提取结果),需要指定类的`USE_MULTITASK_DECODER`属性为`True`,同时在`OUT_TYPES`属性中设置模型前向输出的列表中每一个元素对应的标签类型。可参考`ChangeStar`模型的定义。
+
+需要注意的是,如果子目录中存在公共组件,例如`paddlers/rs_models/cd/layers`、`paddlers/rs_models/cd/backbones`、`paddlers/rs_models/seg/layers`中的内容,应当尽可能复用这些组件。
+
+### 1.2 添加docstring
+
+必须为新模型添加docstring,并在其中给出原文引用和链接(对引用格式不做严格要求,但希望尽可能和该任务已有的其他模型保持一致)。详细的注释规范请参考[《代码注释规范》](docstring.md)。一个例子如下所示:
+
+```python
+"""
+The ChangeStar implementation with a FarSeg encoder based on PaddlePaddle.
+
+The original article refers to
+    Z. Zheng, et al., "Change is Everywhere: Single-Temporal Supervised Object Change Detection in Remote Sensing Imagery"
+    (https://arxiv.org/abs/2108.07002).
+
+Note that this implementation differs from the original code in two aspects:
+1. The encoder of the FarSeg model is ResNet50.
+2. We use conv-bn-relu instead of conv-relu-bn.
+
+Args:
+    num_classes (int): Number of target classes.
+    mid_channels (int, optional): Number of channels required by the ChangeMixin module. Default: 256.
+    inner_channels (int, optional): Number of filters used in the convolutional layers in the ChangeMixin module.
+        Default: 16.
+    num_convs (int, optional): Number of convolutional layers used in the ChangeMixin module. Default: 4.
+    scale_factor (float, optional): Scaling factor of the output upsampling layer. Default: 4.0.
+"""
+```
+
+### 1.3 编写训练器
+
+请遵循如下步骤:
+
+1. 在`paddlers/rs_models/{任务子目录}`的`__init__.py`文件中添加`from ... import`语句,可仿造文件中已有的例子。
+
+2. 在`paddlers/tasks`目录中找到任务对应的训练器定义文件(例如变化检测任务对应`paddlers/tasks/change_detector.py`)。
+
+3. 在文件尾部追加新的训练器定义。训练器需要继承自相关的基类(例如`BaseChangeDetector`),重写`__init__()`方法,并根据需要重写其他方法。对训练器`__init__()`方法编写的要求如下:
+    - 对于变化检测、场景分类、目标检测、图像分割任务,`__init__()`方法的第1个输入参数是`num_classes`,表示模型输出类别数;对于变化检测、场景分类、图像分割任务,第2个输入参数是`use_mixed_loss`,表示用户是否使用默认定义的混合损失。
+    - `__init__()`的所有输入参数都必须有默认值,且在**取默认值的情况下,模型接收3通道RGB输入**。
+    - 在`__init__()`中需要更新`params`字典,该字典中的键值对将被用作模型构造时的输入参数。
+
+4. 在全局变量`__all__`中添加新增训练器的类名。
+
+## 2 新增数据预处理/数据增强函数或算子
+
+### 2.1 新增数据预处理/数据增强函数
+
+在`paddlers/transforms/functions.py`中定义新函数。若该函数需要对外暴露、提供给用户使用,则必须为其添加docstring。
+
+### 2.2 新增数据预处理/数据增强算子
+
+在`paddlers/transforms/operators.py`中定义新算子,所有算子均继承自`paddlers.transforms.Transform`类。算子的`apply()`方法接收一个字典`sample`作为输入,取出其中存储的相关对象,处理后对字典进行in-place修改,最后返回修改后的字典。在定义算子时,只有极少数的情况需要重写`apply()`方法。大多数情况下,只需要重写`apply_im()`、`apply_mask()`、`apply_bbox()`和`apply_segm()`方法就分别可以实现对输入图像、分割标签、目标框以及目标多边形的处理。
+
+如果处理逻辑较为复杂,建议先封装为函数,添加到`paddlers/transforms/functions.py`中,然后在算子的`apply*()`方法中调用函数。
+
+在编写完算子的实现后,**必须撰写docstring,并在`__all__`中添加类名**。
+
+## 3 新增遥感影像处理工具
+
+遥感影像处理工具存储在`tools/`目录中。每个工具应该是相对独立的脚本,不依赖于`paddlers/`目录中的内容,用户在不安装PaddleRS的情况下也能够直接执行。
+
+在编写脚本时,请使用Python标准库`argparse`处理用户输入的命令行参数,并在`if __name__ == '__main__':`代码块中执行具体的逻辑。如果有多个工具用到相同的函数或类,请在`tools/utils`中定义这些通用组件。

+ 237 - 0
docs/dev/docstring.md

@@ -0,0 +1,237 @@
+# PaddleRS代码注释规范
+
+## 1 注释规范
+
+函数的docstring由5个模块构成:
+
+- 函数功能描述;
+- 函数参数;
+- (可选)函数返回值;
+- (可选)函数可能抛出的异常;
+- (可选)使用示例。
+
+类的docstring也由5个模块构成:
+
+- 类功能描述;
+- 实例化类所需参数;
+- (可选)实例化类得到的对象;
+- (可选)实例化类时可能抛出的异常;
+- (可选)使用示例。
+
+以下将详细叙述每个模块的规范。
+
+### 1.1 函数/类功能描述
+
+目标是让用户能快速看懂。该模块又可以拆解为3个部分,功能叙述 + 计算公式 + 注解。
+
+- 功能叙述:描述该函数或类的具体功能。由于用户不一定具有相应背景知识,所以需要补充必要的细节。
+- (可选)计算公式:如有需要,给出函数的计算公式。公式建议以LaTex文法编写。
+- (可选)注解:如需要特殊说明,可以在该部分给出。
+
+示例:
+
+```python
+"""
+    Add two tensors element-wise. The equation is:
+
+        out = x + y
+
+    Note: This function supports broadcasting. If you want know more about broadcasting, please
+        refer to :ref:`user_guide_broadcasting` .
+"""
+```
+
+### 1.2 函数参数/类构造参数
+
+要解释清楚每个参数的**类型**、**含义**和**默认值**(如果有)。
+
+注意事项:
+
+- 可选参数要备注`optional`,例如:`name (str|None, optinoal)`;
+- 若参数具有多种可选类型,用`|`分隔;
+- 参数名和类型之间需要空1格;
+- 可使用`list[{类型名}]`和`tuple[{类型名}]`的方式表示包含某种类型对象的列表或元组(注意大小写),例如`list[int]`表示包含`int`类型元素的列表,`tuple[int|float]`等价于`tuple[int] | tuple[float]`;
+- 使用`list[{类型名}]`和`tuple[{类型名}]`的描述时,默认假设列表或元组参数为同质的(即其中包含的所有元素具有相同的类型),若允许或需要列表、元组参数为异质的,需要在文字描述中说明;
+- 被分隔的类型如果是简单类型如`int`、`Tensor`等则`|`前后不需要添加空格,如果是多个复杂类型如`list[int]`和`tuple[float]`则需要在`|`前后添加空格;
+- 对于有默认值的参数,至少要讲清楚在取默认值时的逻辑,而不仅仅是介绍这个参数是什么以及默认值是什么。
+
+示例:
+
+```python
+"""
+    Args:
+        x (Tensor|np.ndarray): Input tensor or numpy array.
+        points (list[int] | tuple[int|float]): List or tuple of data points.
+        name (str|None, optional): Name for the operation. If None, the operation will not be named.
+            Default: None.
+"""
+```
+
+### 1.3 返回值/构造对象
+
+对于函数返回值,先描述返回值的类型(用`(``)`包围,语法与参数类型描述一致),然后说明返回值的含义。对于实例化类得到的对象,无需说明类型。
+
+示例1:
+
+```python
+"""
+    Returns:
+        (tuple): When label is None, it returns (im, im_info); otherwise it returns (im, im_info, label).
+"""
+```
+
+示例2:
+
+```python
+"""
+    Returns:
+        (N-D Tensor): A location into which the result is stored.
+"""
+```
+
+示例3(类定义中):
+
+```python
+"""
+    Returns:
+        A callable object of Activation.
+"""
+```
+
+### 1.4 可能抛出的异常
+
+需给出异常类型和抛出异常的条件。
+
+示例:
+
+```python
+"""
+    Raises:
+        ValueError: When memory() is called outside block().
+        TypeError: When init is set and is not a Variable.
+"""
+```
+
+### 1.5 使用示例
+
+为函数或类的各种使用场景尽可能地提供示例,并在注释中给出执行代码预期得到的结果。
+
+要求:用户复制示例代码到脚本即可运行。注意需要加必要的`import`。
+
+单example示例:
+
+```python
+"""
+    Examples:
+
+            import paddle
+            import numpy as np
+
+            paddle.enable_imperative()
+            np_x = np.array([2, 3, 4]).astype('float64')
+            np_y = np.array([1, 5, 2]).astype('float64')
+            x = paddle.imperative.to_variable(np_x)
+            y = paddle.imperative.to_variable(np_y)
+
+            z = paddle.add(x, y)
+            np_z = z.numpy()
+            # [3., 8., 6. ]
+
+            z = paddle.add(x, y, alpha=10)
+            np_z = z.numpy()
+            # [12., 53., 24. ]
+"""
+```
+
+多examples示例:
+
+```python
+"""
+    Examples 1:
+
+        from paddleseg.cvlibs.manager import ComponentManager
+
+        model_manager = ComponentManager()
+
+        class AlexNet: ...
+        class ResNet: ...
+
+        model_manager.add_component(AlexNet)
+        model_manager.add_component(ResNet)
+
+        # Alternatively, pass a sequence:
+        model_manager.add_component([AlexNet, ResNet])
+        print(model_manager.components_dict)
+        # {'AlexNet': <class '__main__.AlexNet'>, 'ResNet': <class '__main__.ResNet'>}
+
+    Examples 2:
+
+        # Use it as a Python decorator.
+        from paddleseg.cvlibs.manager import ComponentManager
+
+        model_manager = ComponentManager()
+
+        @model_manager.add_component
+        class AlexNet: ...
+
+        @model_manager.add_component
+        class ResNet: ...
+
+        print(model_manager.components_dict)
+        # {'AlexNet': <class '__main__.AlexNet'>, 'ResNet': <class '__main__.ResNet'>}
+"""
+```
+
+### 1.6 语法
+
+- 措词准确,使用深度学习领域通用的词汇和说法。
+- 语句通顺,符合英文语法。
+- 文档中对同一事物的表述要做到前后一致,比如避免有时用label、有时用ground truth。
+
+### 1.7 其他注意事项
+
+- 不同模块间以**1**个空行分隔。
+- 注意首字母大写以及添加标点符号(尤其是**句号**),符合英语语法规则。
+- 在代码示例内容中可适当加空行以体现层次感。
+- 对于注释中出现的**输入参数名**、**输入参数的属性或方法**以及**文件路径**,使用反引号`\``包围。
+- 每个模块的标题/子标题和具体内容之间需要有换行和缩进,`Examples:`标题与示例代码内容之间插入**1**个空行。
+- 单段描述跨行时需要使用悬挂式缩进。
+
+## 2 完整docstring示例
+
+```python
+class Activation(nn.Layer):
+    """
+    The wrapper of activations.
+
+    Args:
+        act (str, optional): Activation name in lowercase. It must be one of {'elu', 'gelu',
+            'hardshrink', 'tanh', 'hardtanh', 'prelu', 'relu', 'relu6', 'selu', 'leakyrelu', 'sigmoid',
+            'softmax', 'softplus', 'softshrink', 'softsign', 'tanhshrink', 'logsigmoid', 'logsoftmax',
+            'hsigmoid'}. Default: None, means identical transformation.
+
+    Returns:
+        A callable object of Activation.
+
+    Raises:
+        KeyError: When parameter `act` is not in the optional range.
+
+    Examples:
+
+        from paddleseg.models.common.activation import Activation
+
+        relu = Activation("relu")
+        print(relu)
+        # <class 'paddle.nn.layer.activation.ReLU'>
+
+        sigmoid = Activation("sigmoid")
+        print(sigmoid)
+        # <class 'paddle.nn.layer.activation.Sigmoid'>
+
+        not_exit_one = Activation("not_exit_one")
+        # KeyError: "not_exit_one does not exist in the current dict_keys(['elu', 'gelu', 'hardshrink',
+        # 'tanh', 'hardtanh', 'prelu', 'relu', 'relu6', 'selu', 'leakyrelu', 'sigmoid', 'softmax',
+        # 'softplus', 'softshrink', 'softsign', 'tanhshrink', 'logsigmoid', 'logsoftmax', 'hsigmoid'])"
+    """
+    ...
+```

+ 1 - 2
tests/check_coverage.sh

@@ -1,6 +1,5 @@
 #!/usr/bin/env bash
 
-bash download_test_data.sh
-coverage run --source paddlers,$(ls -d ../tools/* | tr '\n' ',') --omit=../paddlers/models/* -m unittest discover -v
+coverage run --source paddlers --omit=../paddlers/models/* -m unittest discover -v
 coverage report
 coverage html -d coverage_html

+ 1 - 0
tests/run_examples.sh

@@ -0,0 +1 @@
+#!/usr/bin/env bash

+ 17 - 1
tests/run_tests.sh

@@ -1,4 +1,20 @@
 #!/usr/bin/env bash
 
+set -e
+
+# Download data for test
 bash download_test_data.sh
-python -m unittest discover -v
+
+# Run unit tests
+python -m unittest discover -v
+
+# Test tools
+for script in $(ls run*.py); do
+    python ${script}
+done
+
+# Test tutorials
+bash run_tutorials.sh
+
+# Test examples
+bash run_examples.sh

+ 11 - 0
tests/run_tutorials.sh

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+cd ../tutorials/train/
+
+for subdir in $(ls -d */); do
+    cd ${subdir}
+    for script in $(find -name '*.py'); do
+        python ${script}
+    done
+    cd ..
+done

+ 0 - 88
tests/test_tutorials.py

@@ -1,88 +0,0 @@
-# Copyright (c) 2022 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 re
-import tempfile
-import shutil
-from glob import iglob
-
-from testing_utils import run_script, CpuCommonTest
-
-
-class TestTutorial(CpuCommonTest):
-    SUBDIR = "./"
-    TIMEOUT = 300
-    REGEX = ".*"
-
-    @classmethod
-    def setUpClass(cls):
-        cls._td = tempfile.TemporaryDirectory(dir='./')
-        # Recursively copy the content of cls.SUBDIR to td.
-        # This is necessary for running scripts in td.
-        cls._TSUBDIR = osp.join(cls._td.name, osp.basename(cls.SUBDIR))
-        shutil.copytree(cls.SUBDIR, cls._TSUBDIR)
-        return super().setUpClass()
-
-    @classmethod
-    def tearDownClass(cls):
-        cls._td.cleanup()
-
-    @staticmethod
-    def add_tests(cls):
-        """
-        Automatically patch testing functions to cls.
-        """
-
-        def _test_tutorial(script_name):
-            def _test_tutorial_impl(self):
-                # Set working directory to cls._TSUBDIR such that the 
-                # files generated by the script will be automatically cleaned.
-                run_script(f"python {script_name}", wd=cls._TSUBDIR)
-
-            return _test_tutorial_impl
-
-        for script_path in filter(
-                re.compile(cls.REGEX).match,
-                iglob(osp.join(cls.SUBDIR, '*.py'))):
-            script_name = osp.basename(script_path)
-            if osp.normpath(osp.join(cls.SUBDIR, script_name)) != osp.normpath(
-                    script_path):
-                raise ValueError(
-                    f"{script_name} should be directly contained in {cls.SUBDIR}"
-                )
-            setattr(cls, 'test_' + osp.splitext(script_name)[0],
-                    _test_tutorial(script_name))
-
-        return cls
-
-
-@TestTutorial.add_tests
-class TestCDTutorial(TestTutorial):
-    SUBDIR = "../tutorials/train/change_detection"
-
-
-@TestTutorial.add_tests
-class TestClasTutorial(TestTutorial):
-    SUBDIR = "../tutorials/train/classification"
-
-
-@TestTutorial.add_tests
-class TestDetTutorial(TestTutorial):
-    SUBDIR = "../tutorials/train/object_detection"
-
-
-@TestTutorial.add_tests
-class TestSegTutorial(TestTutorial):
-    SUBDIR = "../tutorials/train/semantic_segmentation"

+ 10 - 0
tests/test_examples.py → tests/tools/run_match.py

@@ -11,3 +11,13 @@
 # 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 tempfile
+
+from testing_utils import run_script
+
+if __name__ == '__main__':
+    with tempfile.TemporaryDirectory() as td:
+        run_script(
+            f"python match.py --im1_path ../tests/data/ssmt/multispectral_t1.tif --im2_path ../tests/data/ssmt/multispectral_t1.tif --save_path {td}/out.tiff",
+            wd="../tools")

+ 6 - 4
tests/tools/__init__.py → tests/tools/run_oif.py

@@ -12,7 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from .test_match import TestMatch
-from .test_oif import TestOIF
-from .test_pca import TestPCA
-from .test_split import TestSplit
+from testing_utils import run_script
+
+if __name__ == '__main__':
+    run_script(
+        f"python oif.py --im_path ../tests/data/ssst/multispectral.tif",
+        wd="../tools")

+ 4 - 5
tests/tools/test_oif.py → tests/tools/run_pca.py

@@ -14,11 +14,10 @@
 
 import tempfile
 
-from testing_utils import CpuCommonTest, run_script
+from testing_utils import run_script
 
-
-class TestOIF(CpuCommonTest):
-    def test_script(self):
+if __name__ == '__main__':
+    with tempfile.TemporaryDirectory() as td:
         run_script(
-            f"python oif.py --im_path ../tests/data/ssst/multispectral.tif",
+            f"python pca.py --im_path ../tests/data/ssst/multispectral.tif --save_dir {td} --dim 5",
             wd="../tools")

+ 5 - 7
tests/tools/test_pca.py → tests/tools/run_split.py

@@ -16,10 +16,8 @@ import tempfile
 
 from testing_utils import CpuCommonTest, run_script
 
-
-class TestPCA(CpuCommonTest):
-    def test_script(self):
-        with tempfile.TemporaryDirectory() as td:
-            run_script(
-                f"python pca.py --im_path ../tests/data/ssst/multispectral.tif --save_dir {td} --dim 5",
-                wd="../tools")
+if __name__ == '__main__':
+    with tempfile.TemporaryDirectory() as td:
+        run_script(
+            f"python split.py --image_path ../tests/data/ssst/multispectral.tif --mask_path ../tests/data/ssst/multiclass_gt2.png --block_size 128 --save_dir {td}",
+            wd="../tools")

+ 0 - 25
tests/tools/test_match.py

@@ -1,25 +0,0 @@
-# Copyright (c) 2022 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 tempfile
-
-from testing_utils import CpuCommonTest, run_script
-
-
-class TestMatch(CpuCommonTest):
-    def test_script(self):
-        with tempfile.TemporaryDirectory() as td:
-            run_script(
-                f"python match.py --im1_path ../tests/data/ssmt/multispectral_t1.tif --im2_path ../tests/data/ssmt/multispectral_t1.tif --save_path {td}/out.tiff",
-                wd="../tools")

+ 0 - 25
tests/tools/test_split.py

@@ -1,25 +0,0 @@
-# Copyright (c) 2022 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 tempfile
-
-from testing_utils import CpuCommonTest, run_script
-
-
-class TestSplit(CpuCommonTest):
-    def test_script(self):
-        with tempfile.TemporaryDirectory() as td:
-            run_script(
-                f"python split.py --image_path ../tests/data/ssst/multispectral.tif --mask_path ../tests/data/ssst/multiclass_gt2.png --block_size 128 --save_dir {td}",
-                wd="../tools")