如何为 scikit-image 做出贡献#

开发开源非常有趣!加入我们的 scikit-image 开发人员论坛

如果你正在寻找要实施或修复的东西,你可以浏览 GitHub 上的公开问题

警告

鉴于对 AI 生成的代码的许可的不确定性,我们要求你在开发任何对 scikit-image 的贡献时不得使用这些工具。

开发流程#

以下是有关如何将对源代码和文档所做的更改贡献给 scikit-image 的简要概述。

  1. 如果你是一个首次贡献者

    • 前往 scikit-image/scikit-image,单击“fork”按钮创建项目副本。

    • 克隆(下载)本地计算机上携带项目源的存储库

      git clone https://github.com/your-username/scikit-image.git
      
    • 切换到克隆存储库的根目录

      cd scikit-image
      
    • 添加上游存储库

      git remote add upstream https://github.com/scikit-image/scikit-image.git
      
    • 现在,您拥有名为

      • upstream,它引用 scikit-image 存储库,

      • origin,它引用您的个人分支。

    • 接下来,设置您的构建环境

    • 最后,我们建议您使用一个 pre-commit 钩子,它在您每次执行 git commit 的时候运行代码检查器和格式化程序

      pip install pre-commit
      pre-commit install
      
  2. 开发您的贡献

    • 从上游拉取最新变更

      git checkout main
      git pull upstream main
      
    • 为要进行工作的特性创建分支。使用一个明智的名称,诸如“transform-speedups”

      git checkout -b transform-speedups
      
    • 在您进行的过程中本地提交(使用 git addgit commit)。请编写优秀提交消息

  3. 提交您的贡献

    • 将您的变更推送到您在 GitHub 上的分支

      git push origin transform-speedups
      
    • 输入您的 GitHub 用户名和密码(重复贡献者或高级用户可以通过用 SSH 连接到 GitHub来移除此步骤)。

    • 前往 GitHub。新分支将显现绿色“pull request”按钮,单击它。

    • 如果您愿意,请在开发者论坛发帖解释您的变更或提出审查。

有关更详细的讨论,请阅读这些详细文档,了解如何将 Git 与scikit-image 一起使用(使用 scikit-image 源代码)。

  1. 审查流程

    • 评审员(其他开发者和感兴趣的社区成员)将在你的请求请求(PR)上撰写内联和/或常规评论,以帮助你提升其实现、文档和样式。项目中工作的每一位开发者都会对他们的代码进行评审,我们已经将其视为一项友好的沟通,我们从中可以学习并且可以提升整体代码质量。因此,请不要让评审阻止你贡献:它的唯一目的是提升项目质量,而非批评(毕竟,我们非常感谢你所捐赠的时间!)。

    • 若要更新你的请求请求,请在你的本地存储库中进行更改并提交。当这些更改被提交(到与之前相同的分支)后,请求请求将会自动更新。

    • 持续集成(CI)服务在每次提交请求请求后都会被触发,以构建包、运行单元测试、测量代码覆盖率并检查你的分支的编码样式(PEP8)。你的 PR 必须经过测试才能进行合并。如果 CI 失败,你可以通过点击“失败”图标(红色叉号)并检查构建和测试日志来找出原因。

    • 必须由两个核心团队成员在合并前批准请求请求。

  1. 记录更改

    如果你的更改引入了弃用,请将提醒添加到 TODO.txt,让团队在未来移除弃用的功能。

    scikit-image 使用 changelist 自动从请求请求中生成发布说明列表。默认情况下,changelist 将使用请求请求的标题及其 GitHub 标签将其分类到相应的部分。但是,对于更为复杂的更改,我们鼓励你在请求请求描述中使用 release-note 代码块更详细地描述它们;如

    ```release-note
    Remove the deprecated function `skimage.color.blue`. Blend
    `skimage.color.cyan` and `skimage.color.magenta` instead.
    ```
    

    你可以参考 发布说明 获取示例,也可以参考 changelist 的文档 获取更多详细信息。

注意

给评审员:如果 PR 描述中未明显说明,请确保在合并消息中描述更改的原因和背景。

上游 和功能分支之间的差异#

如果 GitHub 指出你的 PR 的分支不再能自动合并,请将主分支合并到你的分支中

git fetch upstream main
git merge upstream/main

如果发生任何冲突,在继续操作之前需要修复这些冲突。使用以下命令查看哪些文件有冲突

git status

它显示类似于以下内容的消息

Unmerged paths:
  (use "git add <file>..." to mark resolution)

  both modified:   file_with_conflict.txt

在有冲突的文件内部,你会发现类似于以下内容的部分

The way the text looks in your branch

选择应该保留的文本版本,并删除其余部分

The way the text looks in your branch

现在,添加固定文件

git add file_with_conflict.txt

一旦你修复所有合并冲突,请执行以下操作

git commit

注意

建议高级 Git 用户重新设置基础而不是合并,但无论如何我们都会合并和压缩大多数 PR。

准则#

  • 所有代码都应进行测试(有关详情,请参阅下面的测试覆盖范围)。

  • 所有代码都应按照与 NumPy 和 SciPy 相同的标准进行记录。

  • 对于新功能,务必在信息中心中添加一个示例(有关详情,请参阅下面的信息中心)。

  • 未经两个核心团队成员的审查和批准,绝不会合并任何变更。此规则有两个例外。首先,在大多数情况下,仅影响文档的请求请求只需要一个核心团队成员的审查和批准。如果维护者认为这些变更是重大的或者很可能引起争议,仍应鼓励进行两次审查。第二种情况是恢复 CI 至工作状态的轻微修复,因为这些问题应该合并得很快。如果你未收到对请求请求的答复,请访问开发者论坛切勿合并你自己的请求请求。

风格准则#

  • 将编辑器设置为删除前导空格。遵循PEP08

  • 使用 numpy 数据类型而不是字符串(np.uint8 而不是 "uint8")。

  • 使用以下导入约定

    import numpy as np
    import matplotlib.pyplot as plt
    import scipy as sp
    import skimage as ski
    
    sp.ndimage.label(...)
    ski.measure.label(...)
    
    # only in Cython code
    cimport numpy as cnp
    cnp.import_array()
    
  • 在记录数组参数时,使用 image : (M, N) ndarray 然后在文档字符串中参考 MN(如有必要)。

  • 参考数组维度作为(平面)、行、列,而不是 x、y、z。有关更多信息,请参阅用户指南中的坐标约定

  • 函数应支持所有输入图像数据类型。使用 img_as_float 等实用程序函数来帮助转换为适当类型。输出格式可以是效率最高的任何格式。这使我们可以将几个函数链接到一个管道中,例如

    hough(canny(my_image))
    
  • 使用 Py_ssize_t 作为 C/C++ 和 Cython 代码中所有索引、形状和大小变量的数据类型。

  • 使用相对模块导入,即可 from .._shared import xyz 而非 from skimage._shared import xyz

  • 使用定义了 API 的纯 Python 函数来包装 Cython 代码。这可以提高与代码自检工具的兼容性,这些工具可能不识别 Cython 代码。

  • 对于 Cython 函数,使用 with nogil: 尽可能地释放 GIL。

测试#

在合并拉取请求之前必须通过测试套件,并且应该新增测试来涵盖所有行为的修改。

我们使用 pytest 测试框架,测试位于 skimage/submodule/tests 各文件夹下。

测试要求列在 requirements/test.txt 中。运行

  • 所有测试: spin test

  • 子模块测试: spin test skimage/morphology

  • 特定文件 运行测试: spin test skimage/morphology/tests/test_gray.py

  • 文件中运行测试: spin test skimage/morphology/tests/test_gray.py::test_3d_fallback_black_tophat

  • 使用 任意 ``pytest`` 选项 运行测试: spin test -- any pytest args you want

  • 运行所有测试和 doctest: spin test -- --doctest-plus skimage

测试阶段的警告#

默认情况下,测试套件引发的警告会导致错误。可通过将环境变量 SKIMAGE_TEST_STRICT_WARNINGS 设置为 0 来关闭该行为。

测试覆盖率#

模块的测试应理想情况下涵盖模块中的所有代码,即语句覆盖率应达到 100%。

运行来测量测试覆盖率

$ spin coverage

这样就会打印一份报告,其中包括 skimage 中的每一个文件的详细测试覆盖率

Name                                             Stmts   Exec  Cover   Missing
------------------------------------------------------------------------------
skimage/color/colorconv                             77     77   100%
skimage/filter/__init__                              1      1   100%
...

生成文档#

要生成 HTML 文档,请运行

spin docs

输出位于 scikit-image/doc/build/html/ 中。添加 --clean 标志以从头开始生成,删除任何缓存输出。

修复警告#

  • “找不到引用:R###” 在文档字符串的第一行的引用后可能有一个下划线(例如 [1]_)。使用此方法查找源文件:$ cd doc/build; grep -rin R####

  • “重复的引用 R###,另一个实例在…”” 其中一个文档字符串中可能有一个 [2] 而没有 [1]

  • 确保对图片使用 sphinx 化前的路径(而不是 _images 目录)

弃用周期#

如果必须更改调用函数的方式,则必须遵循弃用周期来警告用户。

以下情况不需要弃用周期

  • 添加新函数,或

  • 在函数签名的末尾添加新关键字参数,或

  • 修复意外或不正确的行为。

以下情况需要弃用周期

  • 重命名关键字参数,或

  • 更改参数或关键字的顺序,或

  • 向函数添加参数,或

  • 更改函数的名称或位置,或

  • 更改函数参数或关键字的默认值。

通常,在进行更改之前,弃用警告会持续两个版本。

例如,考虑在函数签名中修改默认值。在版本 N 中,我们有

def some_function(image, rescale=True):
    """Do something.

    Parameters
    ----------
    image : ndarray
        Input image.
    rescale : bool, optional
        Rescale the image unless ``False`` is given.

    Returns
    -------
    out : ndarray
        The resulting image.
    """
    out = do_something(image, rescale=rescale)
    return out

在版本 N+1 中,我们将更改为

def some_function(image, rescale=None):
    """Do something.

    Parameters
    ----------
    image : ndarray
        Input image.
    rescale : bool, optional
        Rescale the image unless ``False`` is given.

        .. warning:: The default value will change from ``True`` to
                     ``False`` in skimage N+3.

    Returns
    -------
    out : ndarray
        The resulting image.
    """
    if rescale is None:
        warn('The default value of rescale will change '
             'to `False` in version N+3.', stacklevel=2)
        rescale = True
    out = do_something(image, rescale=rescale)
    return out

在版本 N+3 中,我们将更改为

def some_function(image, rescale=False):
    """Do something.

    Parameters
    ----------
    image : ndarray
        Input image.
    rescale : bool, optional
        Rescale the image if ``True`` is given.

    Returns
    -------
    out : ndarray
        The resulting image.
    """
    out = do_something(image, rescale=rescale)
    return out

以下是 3 版本弃用周期的流程

  • 将默认值设置为 None,并修改文档字符串以指定默认值为 True

  • 在该函数中,如果 _if_ rescale 为 None,则将其设置为 True,并警告说在版本 N+3 中,该默认值将更改为 False

  • doc/release/release_dev.rst 中,在弃用内容下添加“在 some_function 中,rescale 参数在 N+3 中将默认为 False”。

  • TODO.txt 中,创建与版本 N+3 相关的部分中的一个项目,并编写“将 some_function 中的 rescale 默认值更改为 False”。

请注意,3 次版本弃用周期不是一项严格的规则,在某些情况下,开发者可以商定不同的流程。

发出警告#

skimage 会发出 FutureWarning 以突出其 API 中的变化,例如

from warnings import warn
warn(
    "Automatic detection of the color channel was deprecated in "
    "v0.19, and `channel_axis=None` will be the new default in "
    "v0.22. Set `channel_axis=-1` explicitly to silence this "
    "warning.",
    FutureWarning,
    stacklevel=2,
)

stacklevel 有点技术性,但它确保警告指向用户调用的函数,而不是内部的实用程序函数。

在大多数情况下,将 stacklevel 设置为 2。当警告源自 scikit-image 库内部的帮助程序时,将其设置为 3

要测试您的警告是否正确发出,请尝试从 IPython 控制台中调用该函数。它应该指向控制台输入本身,而不是由 scikit-image 库中的文件发出

  • 正确: ipython:1: UserWarning: ...

  • 错误: scikit-image/skimage/measure/_structural_similarity.py:155: UserWarning:

弃用关键字和函数#

在删除关键字或整个函数时,可以使用 skimage._shared.utils.deprecate_parameterskimage._shared.utils.deprecate_func 实用程序函数来执行上述过程。

添加数据#

github 上托管代码时,示例数据集位于 gitlab 上。访问 skimage.data.* 时,它们会通过 pooch 获取。

新数据集提交到 gitlab,合并后,在 GitHub 主要代码仓库中的数据注册表 skimage/data/_registry.py 可以更新。

基准#

对于大多数拉取请求来说,虽然不是强制的,但我们要求与性能相关的拉取请求包括基准,以便清楚地描述正在优化的用例。可以在以下 网站 上找到我们的历史快照。

在此部分,我们将回顾如何设置基准,以及三个命令 spin asv -- devspin asv -- runspin asv -- continuous

先决条件#

首先在开发环境中安装 airspeed velocity。在安装之前,务必激活开发环境,然后如果使用 venv,可以使用以下方法安装需求

source skimage-dev/bin/activate
pip install asv

如果使用 conda,那么命令

conda activate skimage-dev
conda install asv

就更合适。安装完成后,运行以下命令很有用

spin asv -- machine

以便让 airspeed velocity 了解更多关于机器的信息。

编写基准#

要编写基准,请在 benchmarks 目录中添加一个文件,其中包含一个带有 setup 方法的类和至少一个以 time_ 为前缀的方法。

time_ 方法应该只包含需要进行基准测试的代码。因此,将准备基准测试场景的所有内容移至 setup 方法非常有用。该函数在调用 time_ 方法之前被调用,并且其执行时间不计入基准测试中。

例如 TransformSuite 基准测试

import numpy as np
from skimage import transform

class TransformSuite:
    """Benchmark for transform routines in scikit-image."""

    def setup(self):
        self.image = np.zeros((2000, 2000))
        idx = np.arange(500, 1500)
        self.image[idx[::-1], idx] = 255
        self.image[idx, idx] = 255

    def time_hough_line(self):
        result1, result2, result3 = transform.hough_line(self.image)

在此,图像创建工作在 setup 方法中完成,且不包含在基准测试报告的时间中。

还可以对峰值内存使用量等特性进行基准测试。要详细了解特性,请参阅官方 airspeed velocity 文档

另外,在对旧版本的 scikit-image 进行基准测试时,基准测试文件需要可导入。所以如果从 scikit-image 中导入任何东西时都在最顶层,应该像下面这样操作

try:
    from skimage import metrics
except ImportError:
    pass

基准测试本身不需要对缺失功能进行任何保护,只需要最顶层导入即可。

为了允许将较新函数的测试标记为“n/a”(不可用)而不是较旧版本中的“failed”,设置方法本身可以引发 NotImplemented 错误。请参阅注册模块中的以下示例

try:
    from skimage import registration
except ImportError:
    raise NotImplementedError("registration module not available")

在本地测试基准测试#

在运行真正的基准测试之前,通常值得测试代码中是否没有错别字。若要这样做,您可以使用命令

spin asv -- dev -b TransformSuite

其中上面的 TransformSuite 将在您当前的环境中运行一次,以测试一切是否正常。

运行您的基准测试#

上面的命令很快,但并没有充分测试代码的性能。若要执行此操作,您可能希望在当前环境中运行基准测试,以查看在开发新功能时的变更性能。命令 asv run -E existing 指定您希望在现有环境中运行基准测试。由于构建 scikit-image 可能是一项耗时的任务,这将节省大量时间

spin asv -- run -E existing -b TransformSuite

将结果与主结果比较#

通常,公关的目标是在速度方面比较修改后的结果与 scikit-image 存储库主分支中的代码快照。这里命令 asv continuous 有所帮助

spin asv -- continuous main -b TransformSuite

此调用将在 asv.conf.json 文件中指定的的环境中构建,并比较当前提交与主分支中的代码之间的基准测试性能。

输出可能如下所示

$ spin asv -- continuous main -b TransformSuite
· Creating environments
· Discovering benchmarks
·· Uninstalling from conda-py3.7-cython-numpy1.15-scipy
·· Installing 544c0fe3 <benchmark_docs> into conda-py3.7-cython-numpy1.15-scipy.
· Running 4 total benchmarks (2 commits * 2 environments * 1 benchmarks)
[  0.00%] · For scikit-image commit 37c764cb <benchmark_docs~1> (round 1/2):
[...]
[100.00%] ··· ...ansform.TransformSuite.time_hough_line           33.2±2ms

BENCHMARKS NOT SIGNIFICANTLY CHANGED.

在这种情况下,HEAD 与主之间的差异对于空速无法报告是不够显著的。

还可以通过 asv compare 命令获取先前运行过基准测试结果的两个特定修订的结果比较

spin asv -- compare v0.14.5 v0.17.2

最后,还可以通过向提交或标签名称追加 ^! 来仅针对特定提交哈希或发行版本运行 ASV 基准测试。例如,要在 v0.17.2 版本上运行 skimage.filter 模块基准测试

spin asv -- run -b Filter v0.17.2^!