scikit-ops 0.1.0.dev0__py3-none-any.whl → 0.1.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scikit_ops-0.1.0.dev2.dist-info/METADATA +50 -0
- {scikit_ops-0.1.0.dev0.dist-info → scikit_ops-0.1.0.dev2.dist-info}/RECORD +14 -14
- {scikit_ops-0.1.0.dev0.dist-info → scikit_ops-0.1.0.dev2.dist-info}/WHEEL +1 -1
- skop/envs/cellpose3/pixi.toml +1 -1
- skop/envs/cupy/pixi.toml +3 -5
- skop/envs/minimal/pixi.toml +3 -5
- skop/envs/pytorch/pixi.toml +3 -5
- skop/envs/sdeconv/pixi.toml +3 -5
- skop/envs/segment-everything/pixi.toml +3 -5
- skop/envs/skimage/pixi.toml +3 -5
- skop/envs/stardist-tf/pixi.toml +3 -5
- skop/ops/deconvolve/richardson_lucy.py +2 -1
- skop/ops/deconvolve/richardson_lucy_cupy.py +2 -1
- scikit_ops-0.1.0.dev0.dist-info/METADATA +0 -372
- {scikit_ops-0.1.0.dev0.dist-info → scikit_ops-0.1.0.dev2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: scikit-ops
|
|
3
|
+
Version: 0.1.0.dev2
|
|
4
|
+
Summary: Appose-powered ops, and the machinery for running them.
|
|
5
|
+
Project-URL: homepage, https://github.com/apposed/scikit-ops
|
|
6
|
+
Project-URL: documentation, https://github.com/apposed/scikit-ops/blob/main/README.md
|
|
7
|
+
Project-URL: source, https://github.com/apposed/scikit-ops
|
|
8
|
+
Project-URL: download, https://pypi.org/project/scikit-ops
|
|
9
|
+
Project-URL: tracker, https://github.com/apposed/appose/issues
|
|
10
|
+
Author: scikit-ops developers
|
|
11
|
+
License-Expression: BSD-2-Clause
|
|
12
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: Unix
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Topic :: Utilities
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: numpy
|
|
32
|
+
Requires-Dist: ophelper==0.1.0.dev0
|
|
33
|
+
Requires-Dist: oprunner==0.1.0.dev0
|
|
34
|
+
Requires-Dist: opspec==0.1.0.dev0
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# scikit-ops
|
|
38
|
+
|
|
39
|
+
Image-processing ops you can call directly, run in their own environment, or
|
|
40
|
+
use from napari and Fiji.
|
|
41
|
+
|
|
42
|
+
This is an early development release, for early adopters. Things will
|
|
43
|
+
change.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install --pre scikit-ops
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- Source: https://github.com/apposed/scikit-ops
|
|
50
|
+
- Docs: https://scikit-ops.readthedocs.io
|
|
@@ -13,8 +13,8 @@ skop/ops/toy.py,sha256=7VCUBpkINYc6wsHteYvNypN02CsMb_4jXiyw8qGA_fg,3330
|
|
|
13
13
|
skop/ops/deconvolve/__init__.py,sha256=Xx_xR_3qXdLPfkB5f_onTXLuOQwY2p5Kxn5cgj-D-G0,728
|
|
14
14
|
skop/ops/deconvolve/_edges.py,sha256=nlavBoTw1J8D_uHIMusWgXxBmkuD_OtQyEc7g0ehEBM,3345
|
|
15
15
|
skop/ops/deconvolve/_pad.py,sha256=0k3bnKJOLC9ihVXpS_Rhy-ti2CIeBeqZZmt1-0viaVA,3535
|
|
16
|
-
skop/ops/deconvolve/richardson_lucy.py,sha256=
|
|
17
|
-
skop/ops/deconvolve/richardson_lucy_cupy.py,sha256=
|
|
16
|
+
skop/ops/deconvolve/richardson_lucy.py,sha256=rb_ePL1oLhfstpQqlLsqqw-6K_JC2jMK4JkTZjbSK04,3771
|
|
17
|
+
skop/ops/deconvolve/richardson_lucy_cupy.py,sha256=jyFJxEEiXSyowAElUHAyMW3LAkz2R4giixFS78a2e34,4842
|
|
18
18
|
skop/ops/detect/__init__.py,sha256=mSBR9M8xmfKBjGsN6ZBHqhDZsfkSeC9OJUdKJTq618Y,1145
|
|
19
19
|
skop/ops/detect/_result.py,sha256=hQqN7TgLkK8ZcuydbGogbh1ydMgWqIZNVWLI1br6nq4,1578
|
|
20
20
|
skop/ops/detect/fastsam.py,sha256=g1L4B29H4JtosA1gfuJzWsFlcTaQjbziI3Vf_W99j_w,3517
|
|
@@ -51,16 +51,16 @@ skop/ops/workflows/mask/__init__.py,sha256=nKt0l8GrX44sCQ9jTtExBARnNrPZu6Y2AWVsM
|
|
|
51
51
|
skop/ops/workflows/mask/detect_then_mask.py,sha256=-6DtBC0rdrs1XZKM5rN-ruFO7kRJatIpZGDp4cb0ZA0,4166
|
|
52
52
|
skop/ops/workflows/segment/__init__.py,sha256=RBdgluVVsBwuyPgYdkXbfYnb6sAWUrMQ7vcMV8rxOB4,184
|
|
53
53
|
skop/ops/workflows/segment/connect_2d_in_3d.py,sha256=gV9M5AJQbV0Q4weeP-BF_LHiOg6TSoBUsZhndc5jzhU,6377
|
|
54
|
-
skop/envs/cellpose3/pixi.toml,sha256=
|
|
55
|
-
skop/envs/cupy/pixi.toml,sha256=
|
|
56
|
-
skop/envs/minimal/pixi.toml,sha256=
|
|
57
|
-
skop/envs/pytorch/pixi.toml,sha256=
|
|
58
|
-
skop/envs/sdeconv/pixi.toml,sha256=
|
|
59
|
-
skop/envs/segment-everything/pixi.toml,sha256=
|
|
60
|
-
skop/envs/skimage/pixi.toml,sha256=
|
|
61
|
-
skop/envs/stardist-tf/pixi.toml,sha256=
|
|
54
|
+
skop/envs/cellpose3/pixi.toml,sha256=iITLZdO4M6UdWI8MYbEPsCT_mDG-mlPEvlPoGvhfRn4,1696
|
|
55
|
+
skop/envs/cupy/pixi.toml,sha256=JF-jHEh6WpqyK4CtaFnYzHc7OZRNyf2xezuWYb-MFek,1377
|
|
56
|
+
skop/envs/minimal/pixi.toml,sha256=bZWIUkttfC9klPbRMHl01E4e7OGlrTpJ6V84veVHCr0,1031
|
|
57
|
+
skop/envs/pytorch/pixi.toml,sha256=GdSJ6_lFAmlLLTFHEcmiZmbd_4YWqsgzH9tD_ivIvUY,3644
|
|
58
|
+
skop/envs/sdeconv/pixi.toml,sha256=VNt7w0guatGlS6vU79BqKWKfNKKr9VkfmvU_RwUxi5Y,3822
|
|
59
|
+
skop/envs/segment-everything/pixi.toml,sha256=1EmfO-fL9bj6SH-RnxdZpGw75pXmx6-g4EGxj_sqBJE,3774
|
|
60
|
+
skop/envs/skimage/pixi.toml,sha256=wAfyOSuvsUpmKIj_Hqk3ylxck_uh6wYeMZ2BTjoSBTI,1136
|
|
61
|
+
skop/envs/stardist-tf/pixi.toml,sha256=oIY1XmErvbApe_pLGyQzzSf1K_RgN2rrgAvyVWYfKag,3487
|
|
62
62
|
skop/envs/unseg-cv/pixi.toml,sha256=TwcYzpAnfBd5taAB0b-kRXBhbw5aXHoLzoKRaebvyDw,1065
|
|
63
|
-
scikit_ops-0.1.0.
|
|
64
|
-
scikit_ops-0.1.0.
|
|
65
|
-
scikit_ops-0.1.0.
|
|
66
|
-
scikit_ops-0.1.0.
|
|
63
|
+
scikit_ops-0.1.0.dev2.dist-info/METADATA,sha256=_slMnyAKToraL0PX1q1pY4muQAqPYKpCHit0mQ6ObZA,1935
|
|
64
|
+
scikit_ops-0.1.0.dev2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
65
|
+
scikit_ops-0.1.0.dev2.dist-info/entry_points.txt,sha256=0gDhmxYQNjvBwJO4L0Q2fB-5fIEBbRVUS5u5heCYulQ,43
|
|
66
|
+
scikit_ops-0.1.0.dev2.dist-info/RECORD,,
|
skop/envs/cellpose3/pixi.toml
CHANGED
|
@@ -29,7 +29,7 @@ cellpose = ">=3,<4"
|
|
|
29
29
|
[pypi-dependencies]
|
|
30
30
|
# Pinned, as in every environment: a Fiji with no scikit-ops checkout runs
|
|
31
31
|
# this copy. See envs/pytorch for why the pin is deliberate.
|
|
32
|
-
scikit-ops =
|
|
32
|
+
scikit-ops = "==0.1.0.dev2"
|
|
33
33
|
|
|
34
34
|
# PyTorch varies by platform: CUDA builds where there are GPUs to use,
|
|
35
35
|
# and the plain package on Apple Silicon, where MPS comes built in.
|
skop/envs/cupy/pixi.toml
CHANGED
|
@@ -28,8 +28,6 @@ cuda-version = "12.2"
|
|
|
28
28
|
|
|
29
29
|
[pypi-dependencies]
|
|
30
30
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
31
|
-
# so bumping it rebuilds every environment.
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
# once scikit-ops is released.
|
|
35
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
31
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
32
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
33
|
+
scikit-ops = "==0.1.0.dev2"
|
skop/envs/minimal/pixi.toml
CHANGED
|
@@ -18,8 +18,6 @@ numpy = "*"
|
|
|
18
18
|
|
|
19
19
|
[pypi-dependencies]
|
|
20
20
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
21
|
-
# so bumping it rebuilds every environment.
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
# once scikit-ops is released.
|
|
25
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
21
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
22
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
23
|
+
scikit-ops = "==0.1.0.dev2"
|
skop/envs/pytorch/pixi.toml
CHANGED
|
@@ -69,11 +69,9 @@ dinov3 = { git = "https://github.com/facebookresearch/dinov3" }
|
|
|
69
69
|
instanseg-torch = "*"
|
|
70
70
|
|
|
71
71
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
72
|
-
# so bumping it rebuilds every environment.
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
# once scikit-ops is released.
|
|
76
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
72
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
73
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
74
|
+
scikit-ops = "==0.1.0.dev2"
|
|
77
75
|
|
|
78
76
|
[target.linux-64.dependencies]
|
|
79
77
|
pytorch-gpu = "*"
|
skop/envs/sdeconv/pixi.toml
CHANGED
|
@@ -61,11 +61,9 @@ torchvision = "*"
|
|
|
61
61
|
|
|
62
62
|
[pypi-dependencies]
|
|
63
63
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
64
|
-
# so bumping it rebuilds every environment.
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
# once scikit-ops is released.
|
|
68
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
64
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
65
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
66
|
+
scikit-ops = "==0.1.0.dev2"
|
|
69
67
|
sdeconv = ">=1,<2"
|
|
70
68
|
|
|
71
69
|
# Torch, stated exactly as envs/pytorch states it: CUDA builds where there are
|
|
@@ -70,11 +70,9 @@ setuptools = "<81"
|
|
|
70
70
|
# why these are two environments and not one. See docs/design/0007.
|
|
71
71
|
[pypi-dependencies]
|
|
72
72
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
73
|
-
# so bumping it rebuilds every environment.
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
# once scikit-ops is released.
|
|
77
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
73
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
74
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
75
|
+
scikit-ops = "==0.1.0.dev2"
|
|
78
76
|
segment-everything = "==0.3"
|
|
79
77
|
|
|
80
78
|
# Torch is pinned to the 2.9 series rather than left free. The vendored fork is
|
skop/envs/skimage/pixi.toml
CHANGED
|
@@ -21,8 +21,6 @@ pywavelets = "*" # Note: an optional scikit-image dep, needed by smooth:wavelet
|
|
|
21
21
|
|
|
22
22
|
[pypi-dependencies]
|
|
23
23
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
24
|
-
# so bumping it rebuilds every environment.
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
# once scikit-ops is released.
|
|
28
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
24
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
25
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
26
|
+
scikit-ops = "==0.1.0.dev2"
|
skop/envs/stardist-tf/pixi.toml
CHANGED
|
@@ -29,11 +29,9 @@ setuptools = "<81"
|
|
|
29
29
|
|
|
30
30
|
[pypi-dependencies]
|
|
31
31
|
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
32
|
-
# so bumping it rebuilds every environment.
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
# once scikit-ops is released.
|
|
36
|
-
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
32
|
+
# so bumping it rebuilds every environment. It comes from PyPI, so building
|
|
33
|
+
# needs no checkout; a checkout still shadows it (see oprunner.host.INIT).
|
|
34
|
+
scikit-ops = "==0.1.0.dev2"
|
|
37
35
|
stardist = "==0.9.1"
|
|
38
36
|
csbdeep = "*"
|
|
39
37
|
|
|
@@ -38,7 +38,8 @@ _PAD = Overlap(param="psf", of="shape", scale=0.5, only_if="noncirc")
|
|
|
38
38
|
tile=("image", "mask"),
|
|
39
39
|
split=("y", "x"),
|
|
40
40
|
overlap=_OVERLAP,
|
|
41
|
-
peak_memory=PeakMemory(10, "float64"
|
|
41
|
+
peak_memory=PeakMemory(10, "float64"),
|
|
42
|
+
pad=_PAD,
|
|
42
43
|
merge="blend",
|
|
43
44
|
)
|
|
44
45
|
def richardson_lucy(
|
|
@@ -41,7 +41,8 @@ _PAD = Overlap(param="psf", of="shape", scale=0.5, only_if="noncirc")
|
|
|
41
41
|
# buffers are live at once by the code -- image, PSF, weights, estimate,
|
|
42
42
|
# two complex OTFs, and an iteration's complex FFTs -- plus cuFFT's
|
|
43
43
|
# workspace. Not yet measured.
|
|
44
|
-
peak_memory=PeakMemory(16, "float32",
|
|
44
|
+
peak_memory=PeakMemory(16, "float32", device="gpu"),
|
|
45
|
+
pad=_PAD,
|
|
45
46
|
merge="blend",
|
|
46
47
|
)
|
|
47
48
|
def richardson_lucy_cupy(
|
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: scikit-ops
|
|
3
|
-
Version: 0.1.0.dev0
|
|
4
|
-
Summary: Appose-powered ops, and the machinery for running them.
|
|
5
|
-
Project-URL: homepage, https://github.com/apposed/scikit-ops
|
|
6
|
-
Project-URL: documentation, https://github.com/apposed/scikit-ops/blob/main/README.md
|
|
7
|
-
Project-URL: source, https://github.com/apposed/scikit-ops
|
|
8
|
-
Project-URL: download, https://pypi.org/project/scikit-ops
|
|
9
|
-
Project-URL: tracker, https://github.com/apposed/appose/issues
|
|
10
|
-
Author: scikit-ops developers
|
|
11
|
-
License-Expression: BSD-2-Clause
|
|
12
|
-
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Intended Audience :: Education
|
|
15
|
-
Classifier: Intended Audience :: Science/Research
|
|
16
|
-
Classifier: Operating System :: MacOS
|
|
17
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
-
Classifier: Operating System :: Unix
|
|
19
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
-
Classifier: Topic :: Scientific/Engineering
|
|
26
|
-
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
27
|
-
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
28
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
-
Classifier: Topic :: Utilities
|
|
30
|
-
Requires-Python: >=3.10
|
|
31
|
-
Requires-Dist: numpy
|
|
32
|
-
Description-Content-Type: text/markdown
|
|
33
|
-
|
|
34
|
-
# scikit-ops
|
|
35
|
-
|
|
36
|
-
A collection of image-processing **ops**, and `skop` — the machinery that runs
|
|
37
|
-
them.
|
|
38
|
-
|
|
39
|
-
An op is an ordinary Python function. The same function can be called directly,
|
|
40
|
-
run in its own isolated environment through
|
|
41
|
-
[Appose](https://github.com/apposed/appose), or wrapped in a GUI. Nothing about
|
|
42
|
-
the op changes between those modes.
|
|
43
|
-
|
|
44
|
-
Design notes — why the machinery is shaped this way, and what else was
|
|
45
|
-
considered — are in [`docs/`](docs/README.md). How an op describes itself is
|
|
46
|
-
[opspec](opspec/README.md), a separate package kept in this repository.
|
|
47
|
-
|
|
48
|
-
## Install
|
|
49
|
-
|
|
50
|
-
Not on PyPI yet, so from GitHub:
|
|
51
|
-
|
|
52
|
-
```sh
|
|
53
|
-
pip install "git+https://github.com/apposed/scikit-ops"
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
That brings opspec with it. opspec on its own, standard library only, for
|
|
57
|
-
a host or plugin that describes ops without running them:
|
|
58
|
-
|
|
59
|
-
```sh
|
|
60
|
-
pip install "opspec @ git+https://github.com/apposed/scikit-ops#subdirectory=opspec"
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Working on the code instead, from a checkout: `uv sync`, which installs both,
|
|
64
|
-
editable. See [Development](#development).
|
|
65
|
-
|
|
66
|
-
## Layout
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
envs/<env-id>/pixi.toml environment definitions, named and shared between ops
|
|
70
|
-
envs/<env-id>/init.py optional; runs in each worker before its I/O loop
|
|
71
|
-
src/skop/ op-independent machinery
|
|
72
|
-
src/skop/ops/<ns>.py an op namespace, when its ops share one environment
|
|
73
|
-
src/skop/ops/<ns>/<op>.py an op namespace, when its ops do not
|
|
74
|
-
opspec/ how an op describes itself: @op, OpSpec, roles, plans
|
|
75
|
-
src/imgops/ napari/magicgui front ends (not published)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Environments are keyed by ID, and several ops may declare the same one. Ops
|
|
79
|
-
sharing an environment also share a warm worker process, unless one asks for
|
|
80
|
-
`exclusive=True`.
|
|
81
|
-
|
|
82
|
-
## What's here
|
|
83
|
-
|
|
84
|
-
| Op | Environment | Does |
|
|
85
|
-
| --- | --- | --- |
|
|
86
|
-
| `skop.ops.threshold:*` | `skimage` | Global thresholding: Otsu, isodata, Li, mean, minimum, triangle, Yen, multi-Otsu |
|
|
87
|
-
| `skop.ops.smooth:*` | `skimage` | Smoothing and denoising: Gaussian, median, mean, Kuwahara, bilateral, TV, wavelet, NL-means, Butterworth |
|
|
88
|
-
| `skop.ops.edges:*` | `skimage` | Derivative filters: Sobel, Scharr, Prewitt, Farid, Roberts, Laplace, DoG, unsharp mask, Frangi, Sato, Meijering |
|
|
89
|
-
| `skop.ops.morphology:*` | `skimage` | Grayscale morphology: erosion, dilation, opening, closing, white/black tophat |
|
|
90
|
-
| `skop.ops.kernels.psf:gaussian_psf` | `skimage` | Gaussian PSF, 2D or 3D |
|
|
91
|
-
| `skop.ops.kernels.paraxial:paraxial_psf` | `skimage` | Paraxial PSF, from wavelength and NA |
|
|
92
|
-
| `skop.ops.kernels.paraxial:paraxial_otf` | `skimage` | Paraxial OTF, the transform of the above |
|
|
93
|
-
| `skop.ops.kernels.gibson_lanni:gibson_lanni` | `sdeconv` | Gibson-Lanni 3D PSF, with spherical aberration |
|
|
94
|
-
| `skop.ops.deconvolve.richardson_lucy:richardson_lucy` | `skimage` | Richardson-Lucy, on the CPU |
|
|
95
|
-
| `skop.ops.deconvolve.richardson_lucy_cupy:richardson_lucy_cupy` | `cupy` | Richardson-Lucy, on an NVIDIA GPU |
|
|
96
|
-
| `skop.ops.segment.cellpose:cellpose` | `cellpose` | Cellpose segmentation |
|
|
97
|
-
| `skop.ops.segment.stardist2d:stardist2d_fluo` | `stardist-tf` | StarDist 2D, pretrained fluorescence |
|
|
98
|
-
| `skop.ops.segment.stardist2d:stardist2d_he` | `stardist-tf` | StarDist 2D, pretrained H&E |
|
|
99
|
-
| `skop.ops.segment.stardist2d:stardist2d_custom` | `stardist-tf` | StarDist 2D, model loaded from a directory |
|
|
100
|
-
| `skop.ops.train.stardist2d:train_stardist2d` | `stardist-tf` | StarDist 2D training, file pairs in, model path out |
|
|
101
|
-
| `skop.ops.segment.starfun3d:segment_nuclei` | `stardist-tf` | StarDist 3D nuclei |
|
|
102
|
-
| `skop.ops.labels:connect` | `skimage` | One label per object along an axis, by linking overlaps |
|
|
103
|
-
| `skop.ops.generate:synthetic_nuclei` | `skimage` | Synthetic 3D test volume |
|
|
104
|
-
| `skop.ops.segment.unseg:unseg` | `unseg-cv` | Unsupervised nuclei + cells |
|
|
105
|
-
| `skop.ops.detect.fastsam:fastsam` | `pytorch` | Class-agnostic boxes, via FastSAM |
|
|
106
|
-
| `skop.ops.detect.object_aware_yolo:object_aware_yolo` | `segment-everything` | Class-agnostic boxes, via MobileSAMv2 |
|
|
107
|
-
| `skop.ops.detect.yolo:yolo` | `pytorch` | Tiled YOLO detection, with automatic GPU batching, confidences and classes |
|
|
108
|
-
| `skop.ops.toy:*` | `minimal` | Exercises for skop itself |
|
|
109
|
-
|
|
110
|
-
Those are the fully qualified op IDs, as `discover()` reports them. Callers
|
|
111
|
-
import from the namespace instead, and never need to know whether it is one
|
|
112
|
-
module or a package of them:
|
|
113
|
-
|
|
114
|
-
```python
|
|
115
|
-
from skop.ops.threshold import otsu
|
|
116
|
-
from skop.ops.segment import cellpose, stardist2d_fluo
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
`stardist2d_fluo` and `segment_nuclei` share one TensorFlow build and one warm
|
|
120
|
-
worker — the point of naming environments rather than tying them to ops.
|
|
121
|
-
`unseg-cv` shares nothing with anything: it pins Python 3.9, numpy 1.24 and an
|
|
122
|
-
old scikit-image, which is exactly why it needs an environment of its own.
|
|
123
|
-
|
|
124
|
-
The two Richardson-Lucy ops are the same algorithm against different hardware,
|
|
125
|
-
and there are two of them because `@op(env=...)` is fixed per function — a
|
|
126
|
-
backend cannot be chosen at call time. Their signatures are identical and a
|
|
127
|
-
test holds them numerically equivalent, so picking one is picking on speed.
|
|
128
|
-
|
|
129
|
-
To list what the collection currently offers:
|
|
130
|
-
|
|
131
|
-
```python
|
|
132
|
-
import oprunner
|
|
133
|
-
|
|
134
|
-
specs, failures = oprunner.discover()
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
For a large image and a YOLO `.pt` model:
|
|
138
|
-
|
|
139
|
-
```python
|
|
140
|
-
from pathlib import Path
|
|
141
|
-
from skop.ops.detect import yolo
|
|
142
|
-
|
|
143
|
-
with oprunner.Runner() as runner:
|
|
144
|
-
found = runner.run(
|
|
145
|
-
yolo, image=image, weights=Path("best.pt"),
|
|
146
|
-
object_size=100, overlap=0.15, gpu_fraction=0.9,
|
|
147
|
-
)
|
|
148
|
-
# found.boxes: [min_y, min_x, max_y, max_x]
|
|
149
|
-
# found.confidences and found.classes: one value per box
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
`object_size` is area in pixels²; `None` runs the whole image once. Areas
|
|
153
|
-
outside 0.1%-50% of the image select square tiles with side
|
|
154
|
-
`ceil(sqrt(1.5 * object_size / 0.001))`. Input size comes from the checkpoint.
|
|
155
|
-
Tiles align with the image edges. Class-aware GreedyNMM matches SAHI 0.12.8:
|
|
156
|
-
same-class boxes with IoS at least `merge_threshold` become an enclosing box
|
|
157
|
-
with the maximum confidence. This includes matches within a tile. The default
|
|
158
|
-
merge threshold is 0.5, independent of the per-tile NMS `iou` parameter.
|
|
159
|
-
Outputs follow class order, then SAHI's keeper order within each class.
|
|
160
|
-
A multiscale grid finds nearby candidates without an all-pairs matrix; the
|
|
161
|
-
runtime needs no SAHI dependency. To run the optional reference comparisons:
|
|
162
|
-
|
|
163
|
-
```sh
|
|
164
|
-
uv run --with sahi==0.12.8 pytest tests/test_yolo.py -k sahi
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
GPU batches are calibrated after warm-up, with intermediate sizes before
|
|
168
|
-
larger batches. Measured peaks adjust the size up or down; an out-of-memory
|
|
169
|
-
retry establishes an upper bound rather than permanently halving throughput.
|
|
170
|
-
CUDA measures the reserved allocator footprint; MPS samples driver memory.
|
|
171
|
-
One CPU batch is prepared ahead of inference, using pinned buffers for CUDA.
|
|
172
|
-
Inference transfers only detection data back to the host. The image must fit
|
|
173
|
-
in host RAM; only the current resized batch enters the GPU.
|
|
174
|
-
|
|
175
|
-
## Writing an op
|
|
176
|
-
|
|
177
|
-
```python
|
|
178
|
-
from typing import Annotated, NamedTuple
|
|
179
|
-
|
|
180
|
-
import numpy as np
|
|
181
|
-
from opspec.op import op
|
|
182
|
-
from opspec.types import ImageOf, LabelsOf
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
class Result(NamedTuple):
|
|
186
|
-
labels: LabelsOf[np.ndarray]
|
|
187
|
-
count: int
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
@op(env="stardist-tf")
|
|
191
|
-
def segment(
|
|
192
|
-
image: ImageOf[np.ndarray],
|
|
193
|
-
prob_thresh: Annotated[
|
|
194
|
-
float, {"widget_type": "FloatSlider", "min": 0.0, "max": 1.0}
|
|
195
|
-
] = 0.5,
|
|
196
|
-
) -> Result:
|
|
197
|
-
"""Segment nuclei."""
|
|
198
|
-
from stardist.models import StarDist3D # NB: heavy imports go in the body.
|
|
199
|
-
|
|
200
|
-
...
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Two rules, and both exist for the same reason — ops are discovered by importing
|
|
204
|
-
them into a minimal environment that has little more than numpy:
|
|
205
|
-
|
|
206
|
-
1. **Heavy imports go inside the function body.** A module-scope
|
|
207
|
-
`import tensorflow` makes the op undiscoverable.
|
|
208
|
-
2. **Type your inputs and outputs with plain types.** Annotations *are*
|
|
209
|
-
evaluated at discovery time, so `-> tf.Tensor` fails even if the import is
|
|
210
|
-
in the body.
|
|
211
|
-
|
|
212
|
-
Multiple outputs come from a `NamedTuple`: its field names become the names of
|
|
213
|
-
the op's outputs. `progress(...)` and `cancel_requested()`, from
|
|
214
|
-
`opspec.runtime`, report to whoever is running the op, and do nothing when it
|
|
215
|
-
is called directly.
|
|
216
|
-
|
|
217
|
-
### Saying what an array is
|
|
218
|
-
|
|
219
|
-
Every op here passes `np.ndarray` around, which tells a front end nothing
|
|
220
|
-
about how to display it. opspec's types add that missing half:
|
|
221
|
-
|
|
222
|
-
| Write | Role | napari shows it as |
|
|
223
|
-
| --- | --- | --- |
|
|
224
|
-
| `ImageOf[np.ndarray]` | `Role.image` | an Image layer |
|
|
225
|
-
| `LabelsOf[np.ndarray]` | `Role.labels` | a Labels layer |
|
|
226
|
-
| `PointsOf[np.ndarray]` | `Role.points` | a Points layer |
|
|
227
|
-
| `VectorsOf[np.ndarray]` | `Role.vectors` | a Vectors layer |
|
|
228
|
-
| `TracksOf[np.ndarray]` | `Role.tracks` | a Tracks layer |
|
|
229
|
-
| `BoxesOf[np.ndarray]` | `Role.boxes` | a Shapes layer |
|
|
230
|
-
| `MasksOf[np.ndarray]` | `Role.masks` | a Labels layer, once projected |
|
|
231
|
-
|
|
232
|
-
`MasksOf` is the odd one: an `(N, Y, X)` stack of masks that may overlap, so
|
|
233
|
-
it is not a label image and no viewer shows it directly. A front end projects
|
|
234
|
-
it with `ophelper.masks` — flattened to 2-D, or kept as a rotatable 3-D stack —
|
|
235
|
-
and gets to offer that as a choice, since re-projecting costs a numpy call
|
|
236
|
-
rather than another run of the op.
|
|
237
|
-
|
|
238
|
-
They are `Annotated[np.ndarray, ...]`, so nothing else changes: the codec
|
|
239
|
-
still sees an array, direct callers still get an array, and `ParamSpec.type`
|
|
240
|
-
is still `np.ndarray`. They compose with the rest —
|
|
241
|
-
`Out[LabelsOf[np.ndarray]]` and
|
|
242
|
-
`Annotated[LabelsOf[np.ndarray], {"label": "Nuclei"}]` both work.
|
|
243
|
-
|
|
244
|
-
Roles are advisory and never guessed. An unannotated array reports
|
|
245
|
-
`role is None`, and deciding what to do with that is the front end's business,
|
|
246
|
-
not opspec's. Nothing in `opspec.types` imports napari; the roles simply
|
|
247
|
-
mirror `napari.types` so that mapping is a lookup rather than a judgement call,
|
|
248
|
-
and `Role` is equally the hook a Fiji or plain-magicgui front end reads.
|
|
249
|
-
|
|
250
|
-
### Where it goes
|
|
251
|
-
|
|
252
|
-
Ops live in a namespace under `skop/ops/`, named for what they do. How the
|
|
253
|
-
namespace is laid out inside depends on its ops, and the split point is the
|
|
254
|
-
environment:
|
|
255
|
-
|
|
256
|
-
- **One module** when the ops are variations on one call sharing one
|
|
257
|
-
environment — every `skop.ops.threshold` op is a few lines against
|
|
258
|
-
`skimage.filters`, so they share `threshold.py`.
|
|
259
|
-
- **A package, one module per op**, when they do not — `skop.ops.segment`
|
|
260
|
-
spans three environments and each op carries its own models and types, so
|
|
261
|
-
each gets a file and `segment/__init__.py` re-exports them.
|
|
262
|
-
|
|
263
|
-
An op needing several files of its own becomes a package
|
|
264
|
-
(`segment/unseg/__init__.py` plus siblings). Underscore-prefixed modules are
|
|
265
|
-
private and never imported by discovery, which is where shared helpers and
|
|
266
|
-
vendored code belong.
|
|
267
|
-
|
|
268
|
-
Because `from skop.ops.<ns> import <op>` reads the same either way, a
|
|
269
|
-
namespace can grow from a module into a package without breaking callers.
|
|
270
|
-
|
|
271
|
-
### Computation forms
|
|
272
|
-
|
|
273
|
-
Following [SciJava Ops](https://ops.scijava.org/en/latest/Concepts.html), an op
|
|
274
|
-
is one of three forms, declared through its signature:
|
|
275
|
-
|
|
276
|
-
| Form | Signature | Meaning |
|
|
277
|
-
| --- | --- | --- |
|
|
278
|
-
| function | `def f(image) -> Result` | allocates and returns its output |
|
|
279
|
-
| computer | `def f(image, out: Out[np.ndarray])` | fills a buffer the caller owns |
|
|
280
|
-
| inplace | `def f(image: Mut[np.ndarray])` | mutates its input |
|
|
281
|
-
|
|
282
|
-
`Out` parameters are hidden from generated GUIs — a user is never asked for an
|
|
283
|
-
output buffer. Adapting between forms is not implemented yet: a computer or
|
|
284
|
-
inplace op must be handed its buffers.
|
|
285
|
-
|
|
286
|
-
## Running an op
|
|
287
|
-
|
|
288
|
-
```python
|
|
289
|
-
import oprunner
|
|
290
|
-
from skop.ops import toy
|
|
291
|
-
|
|
292
|
-
# Directly, in this process, using this process's dependencies.
|
|
293
|
-
toy.add(2, 3)
|
|
294
|
-
|
|
295
|
-
# Or in the op's own environment, in a worker process.
|
|
296
|
-
with oprunner.Runner() as runner:
|
|
297
|
-
runner.run(toy.add, a=2, b=3)
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
The first run of an environment builds it, which takes a while. Later runs
|
|
301
|
-
reuse it, since Appose keys environments by name.
|
|
302
|
-
|
|
303
|
-
Because that build happens inside `run()`, a caller with a progress bar can
|
|
304
|
-
subscribe to it:
|
|
305
|
-
|
|
306
|
-
```python
|
|
307
|
-
runner.subscribe_build_progress(lambda title, current, maximum: ...)
|
|
308
|
-
runner.subscribe_build_output(lambda text: ...)
|
|
309
|
-
runner.subscribe_build_error(lambda text: ...)
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
These pass straight through to Appose's builder. Progress titles name the
|
|
313
|
-
phase — `Solving`, `Installing conda packages`, `Downloading PyPI packages`,
|
|
314
|
-
`Installing PyPI packages`, `Done` — and come with a real denominator.
|
|
315
|
-
|
|
316
|
-
Two things worth knowing. Subscribing to progress is what *enables* it:
|
|
317
|
-
Appose's `PixiInstallMonitor` runs pixi under `-vv` to read its phase
|
|
318
|
-
transitions, and only wires up when a progress subscriber exists. And
|
|
319
|
-
`build_error` is the stderr *stream* rather than a failure report — pixi
|
|
320
|
-
writes its ordinary status there, and under `-vv` its whole debug log as
|
|
321
|
-
well. A build that actually fails raises out of `run()`.
|
|
322
|
-
|
|
323
|
-
This needs Appose newer than 0.11, which is why `pyproject.toml` currently
|
|
324
|
-
sources it from the main branch.
|
|
325
|
-
|
|
326
|
-
## Development
|
|
327
|
-
|
|
328
|
-
```sh
|
|
329
|
-
uv sync --all-groups
|
|
330
|
-
uv run pytest
|
|
331
|
-
uv run ruff check --fix && uv run ruff format
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Testing the ops for real
|
|
335
|
-
|
|
336
|
-
Ops are exercised end to end in their own environments, in `test_ops_e2e.py`.
|
|
337
|
-
Each such test declares what it needs with `@pytest.mark.env("<env-id>")`, and
|
|
338
|
-
is skipped when that environment is not built — so a plain `pytest` run stays
|
|
339
|
-
fast and offline, testing against whatever happens to be installed already.
|
|
340
|
-
|
|
341
|
-
To run them all, building whatever is missing:
|
|
342
|
-
|
|
343
|
-
```sh
|
|
344
|
-
uv run pytest --build-envs # everything; slow the first time
|
|
345
|
-
uv run pytest --build-envs -m env # only the op tests
|
|
346
|
-
uv run pytest -m "not env" # only the fast ones
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
Running the notebooks and the interactive tests is a separate setup, since they
|
|
350
|
-
need a host environment and the ops do not run in it:
|
|
351
|
-
[notebooks/README.md](notebooks/README.md).
|
|
352
|
-
|
|
353
|
-
### Checking that the environments still solve
|
|
354
|
-
|
|
355
|
-
Building is slow; solving is not. `scripts/check_envs.py` resolves every
|
|
356
|
-
`envs/*/pixi.toml` without downloading anything, and exits non-zero if one is
|
|
357
|
-
unsatisfiable:
|
|
358
|
-
|
|
359
|
-
```sh
|
|
360
|
-
python scripts/check_envs.py
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
This catches what the tests structurally cannot: they run *inside* an
|
|
364
|
-
environment, and so cannot tell you whether that environment could still be
|
|
365
|
-
created today. One already built keeps working from its lock file however
|
|
366
|
-
broken its `pixi.toml` becomes — which is how an unsolvable `envs/pytorch`
|
|
367
|
-
survived a week. Run it after editing anything under `envs/`.
|
|
368
|
-
|
|
369
|
-
CI runs `--build-envs`, since these ops are the whole point of the project and
|
|
370
|
-
nothing else validates them against the stacks they target. The flag is also
|
|
371
|
-
settable as `PYTEST_ADDOPTS=--build-envs`, which is handy when the pytest
|
|
372
|
-
invocation is not yours to edit.
|
|
File without changes
|