如何为 scikit-image 做贡献#
开发开源软件非常有趣!加入我们在 scikit-image 开发者论坛。
如果你在寻找需要实现或修复的内容,可以浏览 GitHub 上的开放问题。
警告
鉴于AI生成代码的许可存在不确定性,我们要求你在为scikit-image做出任何贡献的开发过程中**不要**使用这些工具。
开发过程#
以下是关于如何为 scikit-image 贡献源代码和文档更改的简要概述。
如果你是首次贡献者:
前往 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
,指的是你的个人分支。
接下来,设置你的构建环境。
最后,我们建议您使用一个预提交钩子,它会在每次执行
git commit
时运行代码检查器和格式化工具:pip install pre-commit pre-commit install
开发你的贡献:
从上游拉取最新更改:
git checkout main git pull upstream main
为你要开发的功能创建一个分支。使用一个合理的名称,例如 ‘transform-speedups’:
git checkout -b transform-speedups
在进展过程中本地提交(使用
git add
和git commit
)。请撰写 良好的提交信息。
要提交您的贡献:
将你的更改推送回GitHub上的你的分支:
git push origin transform-speedups
输入您的GitHub用户名和密码(重复贡献者或高级用户可以通过 使用SSH连接到GitHub 来跳过此步骤)。
前往 GitHub。新分支将显示一个绿色的“拉取请求”按钮 – 点击它。
如果你想,可以在 开发者论坛 上发布你的更改,或者请求审查。
有关更详细的讨论,请阅读这些关于如何将 Git 与 scikit-image
一起使用的 详细文档 (使用-git)。
评审流程:
评审者(其他开发者和感兴趣的社区成员)会在你的拉取请求(PR)上写入内联和/或一般性的评论,以帮助你改进其实现、文档和风格。每个参与项目的开发者都会经历代码评审,我们将其视为一个友好的对话,从中我们都能学到东西,并且整体代码质量得到提升。因此,请不要让评审打击你贡献的积极性:其唯一目的是提高项目的质量,而不是批评(毕竟,我们非常感谢你捐赠的时间!)。
要更新你的拉取请求,请在你的本地仓库中进行更改并提交。一旦这些更改被推送(到与之前相同的分支),拉取请求将自动更新。
持续集成 (CI) 服务在每次提交拉取请求后触发,以构建包、运行单元测试、测量代码覆盖率,并检查分支的编码风格 (PEP8)。在您的 PR 可以合并之前,测试必须通过。如果 CI 失败,您可以通过点击“失败”图标(红色叉号)并检查构建和测试日志来找出原因。
在合并之前,拉取请求必须由两名核心团队成员批准。
文档变更
如果你的更改引入了弃用功能,请在
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描述中不明显,请确保在合并消息中描述更改的原因和背景。
upstream main
和你的特性分支之间的差异#
如果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。
指南#
风格指南#
设置你的编辑器以去除尾随空白。遵循 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
然后在必要时在文档字符串中引用M
和N
。将数组维度称为(平面)、行、列,而不是 x、y、z。更多信息请参阅用户指南中的 坐标约定。
函数应支持所有输入图像的数据类型。使用
img_as_float
等实用函数来帮助转换为适当的数据类型。输出格式可以是任何最有效的格式。这使我们能够将多个函数串联成一个管道,例如:hough(canny(my_image))
在 C/C++ 和 Cython 代码中,使用
Py_ssize_t
作为所有索引、形状和大小的数据类型。使用相对模块导入,即
from .._shared import xyz
而不是from skimage._shared import xyz
。将 Cython 代码包装在一个纯 Python 函数中,该函数定义了 API。这提高了与代码自省工具的兼容性,这些工具通常不了解 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 -- 任何你想要的 pytest 参数
。运行所有测试和 doctests:
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
标志以从头开始构建,删除任何缓存的输出。
画廊#
示例图库是使用 Sphinx-Gallery 构建的。请参考他们的文档以获取完整的用法说明,并参考 doc/examples
中的现有示例。
图库示例的最大图形宽度应为8英寸。您还可以 更改图库条目的缩略图。
修复警告#
“未找到引用: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。
在函数中,如果 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_parameter
和 skimage._shared.utils.deprecate_func
实用函数来执行上述过程。
添加数据#
虽然代码托管在 github 上,但示例数据集在 gitlab 上。这些数据集在使用 skimage.data.* 时通过 pooch 获取。
新数据集提交到 GitLab,一旦合并,主 GitHub 仓库中的数据注册表 skimage/data/_registry.py
可以更新。
基准测试#
虽然大多数拉取请求不是强制性的,但我们要求与性能相关的PR包含一个基准测试,以便清楚地描绘正在优化的用例。我们快照的历史视图可以在以下 网站 找到。
在本节中,我们将回顾如何设置基准测试,以及三个命令 spin asv -- dev
、spin asv -- run
和 spin 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”(不可用)而不是“失败”,设置方法本身可以引发 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
比较结果与主分支#
通常,PR 的目标是比较修改后的代码在速度上与 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 和 main 之间的差异不足以让 airspeed velocity 报告。
还可以通过 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^!