跳过 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)来实现,其默认值在几个版本中逐渐改变。结果仍然是向后不兼容的更改,但时间跨度更长。

尽管处于测试阶段并且处于 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] 的 12 位 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 改为返回字典而不是列表。

  • 将具有相同目的的函数,如 watershedslicfelzenszwalb,合并到一个共同的命名空间中。这将使新用户更容易找到他们应该尝试的特定任务的函数。它还将有助于围绕通用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 上将不会有版本为 2.0 的 “scikit-image” 包。用户执行 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-image 在内的其他 scikits 所遵循的惯例。(有人指出,这一惯例已经有一段时间不适用了,而且在名称中引入版本号无论如何都是一个先例。)

早前的章节“相关工作”描述了其他项目如何处理类似问题。

讨论#

此 SKIP 是 SKIP-3 讨论的结果。有关此 SKIP 动机的进一步背景,请参阅该文档的“决议”部分。

分辨率#

参考文献和脚注#

所有 SKIP 应使用 CC0 许可证 [1] 声明为专用于公共领域,如下面 版权 部分所示,并鼓励使用 CC0+BY [2] 进行署名。