SKIP 4 — 向 scikit-image 2.0 过渡#

作者:

Juan Nunez-Iglesias <juan.nunez-iglesias@monash.edu>

作者:

Lars Grüter

状态:

草稿

类型:

标准跟踪

创建时间:

2022-04-08

解决时间:

<null>

解决方案:

<null>

生效版本:

摘要#

scikit-image 正在准备发布 1.0 版本。这被视为清理 API 的一个机会,包括向后不兼容的更改。其中一些更改涉及更改返回值而不会更改函数签名,这通常只能通过添加一个无用的关键字参数(例如 new_return_style=True)来完成,该参数的默认值在多个版本中会发生变化。结果是仍然是一个向后不兼容的更改,但是在更长的时间内完成的。

尽管处于 beta 版本并且处于 0.x 版本系列,但 scikit-image 的使用范围非常广,任何向后不兼容的更改都可能造成破坏。鉴于SKIP-3的拒绝,本文档提出了一种创建新 API 的替代途径。新途径涉及以下步骤

  • 所有计划在 v0.20 和 v0.21 中弃用的内容都已完成(v0.19 中弃用消息建议的新 API 成为唯一 API)。

  • 这将作为 1.0 版本发布。

  • 此时,分支 main 将包和导入名称更改为 skimage2,并且 API 可以自由发展。

API 更改的进一步动机将在下面解释,并且在很大程度上与SKIP-3相同。

动机和范围#

scikit-image 在过去的 12 多年中一直有机地发展,其功能是由来自不同背景的广泛社区贡献者添加的。这导致 API 的各个部分不一致:例如,skimage.transform.warp 反转坐标的顺序,因此 (45, 32) 的平移实际上将 NumPy 数组中的值沿着第 0 轴移动 32,沿着第 1 轴移动 45,但仅限于 2D。在 3D 中,(45, 32, 77) 的平移会将每个轴的值沿着对应位置的值移动。

此外,随着我们用户群的增长,一些早期的 API 选择变得比有用更有迷惑性。例如,scikit-image 会自动将图像转换为各种数据类型,在这个过程中进行重新缩放。范围在 [0, 255] 内的 uint8 图像将自动转换为范围在 [0, 1] 内的 float64 图像。这最初看起来很合理,但为了保持一致性,范围在 [0, 65535] 内的 uint16 图像被重新缩放到 [0, 1] 浮点数,而范围在 [0, 4095] 内的 uint16 图像(在显微镜中很常见)被重新缩放到 [0, 0.0625]。这些静默转换导致了许多用户困惑。

更改此约定将需要在几乎所有 scikit-image 函数中添加一个 preserve_range= 关键字参数,其默认值将在 4 个版本中从 False 更改为 True。最终,无论我们如何调整弃用曲线,更改都将是向后不兼容的。

其他主要功能,如 skimage.measure.regionprops,可以使用 API 调整,例如通过返回一个将标签映射到属性的字典,而不是列表。

鉴于积累的潜在 API 更改,这些更改证明用标准弃用周期来修复过于繁重且过于嘈杂,主要是因为它们涉及更改相同输入的函数输出,因此在过渡到 2.0 版本时进行所有这些更改是合理的。

尽管语义版本控制 [6] 技术上允许在主要版本号提升时进行 API 更改,但我们必须承认 (1) 许多项目依赖于 scikit-image,因此会受到向后不兼容更改的影响,以及 (2) 在科学 Python 社区中,对依赖项设置上限版本号的做法还不普遍,因此极不可能有人在他们的依赖项列表中使用 scikit-image<1.*scikit-image<2.*。这意味着发布具有重大 API 更改的 scikit-image 2.0 版本将破坏大量用户。此外,如此广泛的更改将使大量 StackOverflow 和其他用户指南失效。最后,发布具有大量更改的新版本会阻止用户逐步迁移到新的 API:旧代码库必须整体迁移,因为不可能依赖于两个版本的 API。这对许多用户来说将是一道巨大的门槛。

鉴于上述情况,此 SKIP 建议我们发布一个新的包,在这个包中我们可以应用从十多年的开发中获得的所有经验教训,而不会破坏我们现有的用户群。

详细说明#

本文档无法列出所有针对 skimage2 提出的 API 更改,其中许多尚未确定。实际上,如果此 SKIP 被接受,2.0 过渡的范围和雄心壮志可能会扩大。此 SKIP 反而提出了一种在不破坏用户代码的情况下管理过渡的机制。可以在 GitHub 上找到跟踪提出的更改的元问题,即 scikit-image/scikit-image#5439 [7]。为了说明目的,下面简要列出了一些示例

  • 当数据类型必须强制转换为浮点数时,停止重新缩放输入数组。

  • 停止在不同情况下交换坐标轴顺序,例如绘制或扭曲。

  • 允许自动返回非 NumPy 类型,只要它们可以使用 numpy.asarray 强制转换为 NumPy。

  • 协调不同函数中类似的参数,使它们具有相同的名称;例如,我们目前在不同的函数中使用 random_seedrandom_stateseedsample_seed,它们都具有相同的含义。

  • measure.regionprops 更改为返回字典而不是列表。

  • 合并具有相同目的的函数,例如 watershedslicfelzenschwalb,放入一个公共命名空间中。这将使新用户更容易找到适合特定任务的函数。它还有助于围绕公共 API 的社区发展,而现在 scikit-image API 本质上是每个函数独一无二的。

为了以最小的用户干扰来实现这种过渡,此 SKIP 建议发布一个新的库 skimage2,该库将取代现有的库,但只有在用户明确选择的情况下。此外,通过发布一个新库,用户可以同时依赖 scikit-image (1.0) 和 skimage2,允许用户逐步迁移代码。

实现#

提案的详细信息如下

  • scikit-image 0.19 将紧随其后的是 scikit-image 1.0。1.0 中将删除所有弃用消息,并且 API 将被视为 scikit-image 1.0 API。

  • 在 1.0 之后,主分支将被更改为 (a) 将导入名称更改为 skimage2,(b) 将包名称更改为 skimage2,以及 (c) 将版本号更改为 2.0-dev。

  • PyPI 上将没有“scikit-image”包具有 2.0 版本。使用 pip install scikit-image 的用户将始终获得 1.x 版本的包。要安装 scikit-image 2.0,用户需要 pip install skimage2conda install skimage2 或类似的操作。

  • 在达成关于新 API 的共识后,将发布 skimage2。

  • 在发布 skimage2 之后,将发布 scikit-image 1.1 版本。此版本与 1.0 版本相同(包括错误修复),但会建议用户 (a) 升级到 skimage2 或 (b) 将包固定到 scikit-image<1.1 以避免警告。

  • scikit-image 1.0.x 和 1.1.x 将会持续接收关键性错误修复,持续时间不定,将视错误严重程度和修复所需的工作量而定。

向后兼容性#

此提案在库的多个地方打破了向后兼容性。然而,它是在一个新的命名空间中实现的,因此该提案不会对我们的用户造成向后兼容性问题。也就是说,作者将尽力将向后不兼容的变更限制在那些可能显著改善整体用户体验的变更。预计将 skimage 代码移植到 skimage2 将是一个简单的过程,我们将在 skimage2 发布时发布用户指南,以指导用户进行转换。用户将在 scikit-image 1.1 中收到警告,其中将包含这些资源的信息 - 以及其他内容。

备选方案#

在同一包中使用语义版本控制发布新的 API#

这是 SKIP-3,该提案在与社区讨论后被否决了。

跨多个版本持续弃用#

这种转换可以在多个版本中逐渐进行。例如,对于自动转换和重新缩放浮点数输入的函数,我们可以添加一个 preserve_range 关键字参数,该参数最初默认为 False,但 False 的默认值将被弃用,用户会收到警告,要求他们切换到 True。切换之后,我们可以(可选地)弃用该参数,在另外两个版本之后,我们将回到同一个位置:scikit-image 不再自动重新缩放数据,API 中不再存在不必要的关键字参数。

当然,这种操作必须在所有上述提出的更改中同时进行。

最终,核心团队认为这种方法会给 scikit-image 开发人员和下游库的开发人员带来更多工作,而带来的益处却很可疑:最终,scikit-image 的后续版本仍然与之前的版本不兼容,尽管时间跨度更长。

包含两个版本的单个包#

由于导入名称即将更改,因此可以创建一个包含 skimageskimage2 命名空间的单个包,至少在一段时间内。这个选项很有吸引力,但它意味着需要对 1.0 命名空间进行长期维护,而我们可能缺乏维护人员的时间,或者对 1.0 命名空间进行很长的弃用周期,这最终会导致许多用户因其 scikit-image 使用而收到弃用消息。

不进行提出的 API 更改#

另一种可能性是完全拒绝向后不兼容的 API 更改,除非在极端情况下。核心团队认为,这本质上等同于将库固定在 0.19 版本。

“scikit-image2” 作为新的包名称#

作者承认,应谨慎选择新的名称,以最大限度地减少对 scikit-image 的用户群和社区的影响。然而,为了保护没有上层版本约束的用户不会意外升级到新的 API,必须更改包名称 scikit-image。更改导入名称 skimage 同样有利,因为它允许在同一环境中使用两个 API。

本文档建议将 skimage2 作为 scikit-image 的 API 版本 2.0 的唯一新名称,用于导入名称和 PyPI、conda-forge 等平台上的名称。以下理由支持该建议

  • 项目中只引入了单个新名称,从而将相关名称的数量降至最低。

  • 通过这种更改,导入名称和包名称保持一致。

  • 用户可能会混淆应该安装 scikit-image2 还是 scikit-image-2。大家认为 skimage2 可以避免这种混淆。

  • 了解 skimage 的用户在安装说明中看到 skimage2 时,很可能会推断出它是一个更新的版本。

  • 用户不太可能知道新的 API 2.0,但不知道新的包名称。建议发布的 scikit-image 1.1 版本可能会在安装和更新过程中将用户引导到 skimage2,从而明确告知后续版本名称。

以下理由反对将包命名为 skimage2

  • 根据“最小惊奇原则”,scikit-image2 可能是对包名称最不令人惊讶的演变。

  • 它违背了其他 scikit(包括 scikit-image)遵循的惯例。(有人指出,这一惯例已经有一段时间没有遵守了,在名称中引入版本号本身就是一个先例。)

前面的“相关工作”部分描述了其他项目如何解决类似问题。

讨论#

此 SKIP 是对 SKIP-3 的讨论结果。有关此 SKIP 动机的更多背景信息,请参阅该文档的“决议”部分。

决议#

参考文献和脚注#

所有 SKIP 应声明为根据 CC0 许可证[1] 献给公共领域,如以下版权部分所示,鼓励使用 CC0+BY[2] 进行归属。