scikit-ops 0.1.0.dev0__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.dev0.dist-info/METADATA +372 -0
- scikit_ops-0.1.0.dev0.dist-info/RECORD +66 -0
- scikit_ops-0.1.0.dev0.dist-info/WHEEL +4 -0
- scikit_ops-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- skop/__init__.py +14 -0
- skop/envs/cellpose3/pixi.toml +46 -0
- skop/envs/cupy/pixi.toml +35 -0
- skop/envs/minimal/pixi.toml +25 -0
- skop/envs/pytorch/pixi.toml +88 -0
- skop/envs/sdeconv/pixi.toml +85 -0
- skop/envs/segment-everything/pixi.toml +96 -0
- skop/envs/skimage/pixi.toml +28 -0
- skop/envs/stardist-tf/pixi.toml +91 -0
- skop/envs/unseg-cv/pixi.toml +27 -0
- skop/models.py +145 -0
- skop/ops/__init__.py +19 -0
- skop/ops/_util.py +115 -0
- skop/ops/deconvolve/__init__.py +18 -0
- skop/ops/deconvolve/_edges.py +92 -0
- skop/ops/deconvolve/_pad.py +117 -0
- skop/ops/deconvolve/richardson_lucy.py +100 -0
- skop/ops/deconvolve/richardson_lucy_cupy.py +116 -0
- skop/ops/detect/__init__.py +28 -0
- skop/ops/detect/_result.py +45 -0
- skop/ops/detect/fastsam.py +103 -0
- skop/ops/detect/jdll_yolo.py +115 -0
- skop/ops/detect/object_aware_yolo.py +112 -0
- skop/ops/detect/yolo.py +441 -0
- skop/ops/edges.py +382 -0
- skop/ops/generate.py +77 -0
- skop/ops/kernels/__init__.py +39 -0
- skop/ops/kernels/_fluorophore.py +42 -0
- skop/ops/kernels/_recenter.py +60 -0
- skop/ops/kernels/gibson_lanni.py +147 -0
- skop/ops/kernels/paraxial.py +128 -0
- skop/ops/kernels/psf.py +59 -0
- skop/ops/labels.py +144 -0
- skop/ops/mask/__init__.py +29 -0
- skop/ops/mask/_result.py +40 -0
- skop/ops/mask/microsam.py +114 -0
- skop/ops/mask/mobilesam.py +176 -0
- skop/ops/morphology.py +214 -0
- skop/ops/normalize.py +85 -0
- skop/ops/segment/__init__.py +24 -0
- skop/ops/segment/cellpose3.py +245 -0
- skop/ops/segment/cellpose4.py +127 -0
- skop/ops/segment/instanseg.py +162 -0
- skop/ops/segment/stardist2d.py +192 -0
- skop/ops/segment/starfun3d.py +104 -0
- skop/ops/segment/unseg/__init__.py +138 -0
- skop/ops/segment/unseg/_algorithm.py +1618 -0
- skop/ops/smooth.py +452 -0
- skop/ops/threshold.py +278 -0
- skop/ops/toy.py +114 -0
- skop/ops/train/__init__.py +81 -0
- skop/ops/train/_util.py +152 -0
- skop/ops/train/cellpose3.py +189 -0
- skop/ops/train/cellpose4.py +164 -0
- skop/ops/train/stardist2d.py +439 -0
- skop/ops/workflows/__init__.py +43 -0
- skop/ops/workflows/deconvolve/__init__.py +7 -0
- skop/ops/workflows/deconvolve/with_psf.py +103 -0
- skop/ops/workflows/mask/__init__.py +7 -0
- skop/ops/workflows/mask/detect_then_mask.py +97 -0
- skop/ops/workflows/segment/__init__.py +7 -0
- skop/ops/workflows/segment/connect_2d_in_3d.py +145 -0
|
@@ -0,0 +1,372 @@
|
|
|
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.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
skop/__init__.py,sha256=q3aBnPDP8M-mFYHVcXGB3nIwAJ9Woq6i5TKJp-T6_3o,518
|
|
2
|
+
skop/models.py,sha256=fWPjTVLoqISXYBsf2HllaRhCx0c_QoOInA5cxp6mgH4,5311
|
|
3
|
+
skop/ops/__init__.py,sha256=DYALgX7N8j17grfDurLsAEYe90MY6xnAvWBH1ctJWik,808
|
|
4
|
+
skop/ops/_util.py,sha256=4aTMa_NVv6IqmRXmVnQB6NZY_qHa7yvjduMYme-rGd4,4076
|
|
5
|
+
skop/ops/edges.py,sha256=oom-p_Qgvdp64dG2k5clqvzNNMeSTC7u_LsC1pem3DA,13777
|
|
6
|
+
skop/ops/generate.py,sha256=0cKxIdDyJXIRloFfAeypx7DxIYhvvxdux1WwrkgBMBw,2722
|
|
7
|
+
skop/ops/labels.py,sha256=-fE2apWk4KcQQ5u0BHigUwMKNTb2amtCE7bSTlg6svo,6382
|
|
8
|
+
skop/ops/morphology.py,sha256=9vUMsuo5ZiC-hCYnw-5tgBWA3bMPmdcYwb3H_V1DTWI,7600
|
|
9
|
+
skop/ops/normalize.py,sha256=f_pKdDDXBjRS83rcsxZZF3pBuB-BEHyCLwsTrTksqTY,3652
|
|
10
|
+
skop/ops/smooth.py,sha256=bz0wYYQ7UzruNXp6V4-7pKolgxmbANkLy7_YMzkFrNk,17615
|
|
11
|
+
skop/ops/threshold.py,sha256=CZ2ETPRt8kcFqc2sC50fxiLWrRlCCAmGdtylJVM7Pig,9988
|
|
12
|
+
skop/ops/toy.py,sha256=7VCUBpkINYc6wsHteYvNypN02CsMb_4jXiyw8qGA_fg,3330
|
|
13
|
+
skop/ops/deconvolve/__init__.py,sha256=Xx_xR_3qXdLPfkB5f_onTXLuOQwY2p5Kxn5cgj-D-G0,728
|
|
14
|
+
skop/ops/deconvolve/_edges.py,sha256=nlavBoTw1J8D_uHIMusWgXxBmkuD_OtQyEc7g0ehEBM,3345
|
|
15
|
+
skop/ops/deconvolve/_pad.py,sha256=0k3bnKJOLC9ihVXpS_Rhy-ti2CIeBeqZZmt1-0viaVA,3535
|
|
16
|
+
skop/ops/deconvolve/richardson_lucy.py,sha256=tHNWcgQVpDBBEfzN8DQOy7E39qK1qjufqfXtktu-TGU,3767
|
|
17
|
+
skop/ops/deconvolve/richardson_lucy_cupy.py,sha256=yhlpdgyaj-BpR6zE2oQ8oe8pJF_oEEZ-lgijs-GpEgo,4838
|
|
18
|
+
skop/ops/detect/__init__.py,sha256=mSBR9M8xmfKBjGsN6ZBHqhDZsfkSeC9OJUdKJTq618Y,1145
|
|
19
|
+
skop/ops/detect/_result.py,sha256=hQqN7TgLkK8ZcuydbGogbh1ydMgWqIZNVWLI1br6nq4,1578
|
|
20
|
+
skop/ops/detect/fastsam.py,sha256=g1L4B29H4JtosA1gfuJzWsFlcTaQjbziI3Vf_W99j_w,3517
|
|
21
|
+
skop/ops/detect/jdll_yolo.py,sha256=G55NAmLj2FN_to8Dt6nV5I56JijSv2JcBaoXy50P24A,4172
|
|
22
|
+
skop/ops/detect/object_aware_yolo.py,sha256=ybY5fjTHR5zLLsBqJ_m1MJP4X7iwFdKlkOxbR2l1y9Q,4226
|
|
23
|
+
skop/ops/detect/yolo.py,sha256=E43l_-j-WLeax2dTjedKNPBy5y1m1_o3RaKLEW3uTRE,18559
|
|
24
|
+
skop/ops/kernels/__init__.py,sha256=GCCv7rT8Uu46oSh7emDwn-C1xm54zEYPGulTr1J7HFo,1422
|
|
25
|
+
skop/ops/kernels/_fluorophore.py,sha256=Dh2apJl1Dgfl4LfY6cgHH_YjwB3L_atyyerV2uDpzaw,1654
|
|
26
|
+
skop/ops/kernels/_recenter.py,sha256=jHQYKKK-l84HTQgCZgH3UQQzxekazeviG2sNzRAc0tk,2370
|
|
27
|
+
skop/ops/kernels/gibson_lanni.py,sha256=NngT_BAZnnwViHcCbLt0abK5SWYdGzJfr6K4kfGXov4,6745
|
|
28
|
+
skop/ops/kernels/paraxial.py,sha256=CEqv4Fzq0ZXlIFelqWTOsbVuHEcgEWQLxz87zISTxbc,5554
|
|
29
|
+
skop/ops/kernels/psf.py,sha256=wntCqZ6wsq9HeVA9wtA6tSotns03BrpVOLwFNZGOH-Q,2087
|
|
30
|
+
skop/ops/mask/__init__.py,sha256=hk0mQqFiypPG9ZucNuKfU1tjQkab5H5jRsG6kTuZx6s,1387
|
|
31
|
+
skop/ops/mask/_result.py,sha256=OtEe58jlYC88g-uTujypyUaMoucMDO_OT9VIhPGBkJk,1717
|
|
32
|
+
skop/ops/mask/microsam.py,sha256=Rq3CxPKPu2gn-7w8PN1v3ow0yp41H_lc6U2rlV073yg,4670
|
|
33
|
+
skop/ops/mask/mobilesam.py,sha256=PqiXvF0CrH4n7PnJPItXg_24PpMMw0JzRoqnzBu8FXk,7436
|
|
34
|
+
skop/ops/segment/__init__.py,sha256=GblPOpbYOPKcTUZMP2X3VU8m3EMAuFMttEhicnlIw8E,667
|
|
35
|
+
skop/ops/segment/cellpose3.py,sha256=GxEAVOpAMYurYMcbTf5hMlzQDt0KgJq1qMPTxRFFF60,10375
|
|
36
|
+
skop/ops/segment/cellpose4.py,sha256=y7g7ZXK0M8l11IJAPKJ-OtIZBrxWK5-mmNdJRUUMlqc,5149
|
|
37
|
+
skop/ops/segment/instanseg.py,sha256=TvQbmwqDm4_pqBbQX6H1kYvKuNb5fdppxoIIJOK8G9M,6296
|
|
38
|
+
skop/ops/segment/stardist2d.py,sha256=XSG4kabcfeY5fsQr1e3kYmGbkWOqyoTTyEp98MB679M,6605
|
|
39
|
+
skop/ops/segment/starfun3d.py,sha256=03RB3QL59NkkbgVABXTf9_VKZ-1Li_wUeJFPFjTqeAQ,3417
|
|
40
|
+
skop/ops/segment/unseg/__init__.py,sha256=nxfXLG3xmnL5zwAlUB7YPdFeLNQk5R6L8JZ8BQ3c1nk,5088
|
|
41
|
+
skop/ops/segment/unseg/_algorithm.py,sha256=M1l3g8g7hBr3af9aEwLBvnUbo_PvikEoZHRNiED7mfA,68689
|
|
42
|
+
skop/ops/train/__init__.py,sha256=dIxjseVRpKvmW9nPvfQCWmZUsBfs4t31CeJagj9Ym0s,3000
|
|
43
|
+
skop/ops/train/_util.py,sha256=lZi2PjsJ4SQtir2pu7qAeZkZa53HenTbpl7aA1cijvk,5749
|
|
44
|
+
skop/ops/train/cellpose3.py,sha256=uh1SI72eYwnmSQjo0jmKhlkTW3FE5MKTd_H9_6onwac,8482
|
|
45
|
+
skop/ops/train/cellpose4.py,sha256=akcfij7wsR4kdqlw9AmBBZ5Jh3UCF6dMLSyUnxFhVYA,7212
|
|
46
|
+
skop/ops/train/stardist2d.py,sha256=dioZYyyw6coHoPLGWIBJxVIYmAHtOp6GjxsDkGDBCSQ,18876
|
|
47
|
+
skop/ops/workflows/__init__.py,sha256=7GyWvVd9hIuDhup-ElcIeinjoI6xAe_JLwS67UDsIZM,1934
|
|
48
|
+
skop/ops/workflows/deconvolve/__init__.py,sha256=7f6Sd4cJCfPPfEVwlXo3TIyN00P974zjXA-s3xv5YZ8,189
|
|
49
|
+
skop/ops/workflows/deconvolve/with_psf.py,sha256=yATu3eqPW37haDJPEFxtI3DEMuWqaMxtrgFK0glZadM,4260
|
|
50
|
+
skop/ops/workflows/mask/__init__.py,sha256=nKt0l8GrX44sCQ9jTtExBARnNrPZu6Y2AWVsMBPzdto,190
|
|
51
|
+
skop/ops/workflows/mask/detect_then_mask.py,sha256=-6DtBC0rdrs1XZKM5rN-ruFO7kRJatIpZGDp4cb0ZA0,4166
|
|
52
|
+
skop/ops/workflows/segment/__init__.py,sha256=RBdgluVVsBwuyPgYdkXbfYnb6sAWUrMQ7vcMV8rxOB4,184
|
|
53
|
+
skop/ops/workflows/segment/connect_2d_in_3d.py,sha256=gV9M5AJQbV0Q4weeP-BF_LHiOg6TSoBUsZhndc5jzhU,6377
|
|
54
|
+
skop/envs/cellpose3/pixi.toml,sha256=hvpkH9eTrUa1xbbJkWM-LwlVXBDJjpRJclCOQvbqUr4,1785
|
|
55
|
+
skop/envs/cupy/pixi.toml,sha256=cQ4bFbGOYMolGnO8IeSO_6JEf806YeXLgtxuybWn09c,1546
|
|
56
|
+
skop/envs/minimal/pixi.toml,sha256=WjHWc35qybmlHaP1KeUD2E-518Rue1fjmNOpIVR1AxE,1200
|
|
57
|
+
skop/envs/pytorch/pixi.toml,sha256=3BU2nmhZMmt6x-MGGu4lQqsnk_1TqUwZwQpi2I7GkY0,3813
|
|
58
|
+
skop/envs/sdeconv/pixi.toml,sha256=o5GhRrDG-ypUj155RriWR0W77CDPBunru-2xriFrLRs,3991
|
|
59
|
+
skop/envs/segment-everything/pixi.toml,sha256=KMq2GUazmzN_Sw3dd5FuuAKMdiMCKtJTcXmhcWoc76Y,3943
|
|
60
|
+
skop/envs/skimage/pixi.toml,sha256=X7W1E79E4UBHYSFiybeCgAYXvKumGCBT00reQnfNOHo,1305
|
|
61
|
+
skop/envs/stardist-tf/pixi.toml,sha256=WyiniXNs6w2_-vz2XX1l_jr62vIVIG2mUOiLeyXV9dg,3656
|
|
62
|
+
skop/envs/unseg-cv/pixi.toml,sha256=TwcYzpAnfBd5taAB0b-kRXBhbw5aXHoLzoKRaebvyDw,1065
|
|
63
|
+
scikit_ops-0.1.0.dev0.dist-info/METADATA,sha256=-rNlEQzsKaScbKYxSG6vMSOVhhjFmy9z7D9FLx70ehQ,16089
|
|
64
|
+
scikit_ops-0.1.0.dev0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
65
|
+
scikit_ops-0.1.0.dev0.dist-info/entry_points.txt,sha256=0gDhmxYQNjvBwJO4L0Q2fB-5fIEBbRVUS5u5heCYulQ,43
|
|
66
|
+
scikit_ops-0.1.0.dev0.dist-info/RECORD,,
|
skop/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""skop: scikit-ops' collection of image-processing ops.
|
|
2
|
+
|
|
3
|
+
Each op is an ordinary Python function declared with opspec's ``@op``, in
|
|
4
|
+
``skop.ops.<namespace>``; its environment is in ``envs/<id>/pixi.toml``. Call
|
|
5
|
+
one directly, or run it in its own environment with oprunner::
|
|
6
|
+
|
|
7
|
+
from oprunner import Runner
|
|
8
|
+
from skop.ops.smooth import gaussian
|
|
9
|
+
|
|
10
|
+
with Runner() as runner:
|
|
11
|
+
blurred = runner.run(gaussian, image=image, sigma=2.0)
|
|
12
|
+
|
|
13
|
+
skop is the working name; the collection will be renamed before release.
|
|
14
|
+
"""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
name = "skop-cellpose3"
|
|
3
|
+
description = "Cellpose 3, whose cyto3 and nuclei models predate CellposeSAM."
|
|
4
|
+
channels = ["conda-forge"]
|
|
5
|
+
platforms = ["osx-arm64", "linux-64", "win-64"]
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
|
|
8
|
+
[system-requirements]
|
|
9
|
+
cuda = "12.0"
|
|
10
|
+
|
|
11
|
+
# NB: an environment of its own rather than a version knob on envs/cellpose.
|
|
12
|
+
# Cellpose 4 replaced the model zoo with a single CellposeSAM model and moved
|
|
13
|
+
# the API with it -- v3's models.Cellpose became v4's models.CellposeModel,
|
|
14
|
+
# and eval lost a return value. One environment cannot hold both, and an op
|
|
15
|
+
# that branched on cellpose.version would be back to the runtime version
|
|
16
|
+
# sniffing this project exists to avoid.
|
|
17
|
+
#
|
|
18
|
+
# Deliberately shaped like envs/pytorch and envs/cellpose otherwise: same
|
|
19
|
+
# python, same per-platform torch split. pixi caches conda packages globally,
|
|
20
|
+
# so the torch and CUDA payload is shared with those environments rather than
|
|
21
|
+
# downloaded again -- provided cellpose 3 does not drag the solve onto older
|
|
22
|
+
# builds of them. It is the one thing worth watching in the lock file.
|
|
23
|
+
[dependencies]
|
|
24
|
+
python = "3.11.*"
|
|
25
|
+
numpy = "*"
|
|
26
|
+
cellpose = ">=3,<4"
|
|
27
|
+
# appose comes with the pinned scikit-ops below, as in envs/pytorch.
|
|
28
|
+
|
|
29
|
+
[pypi-dependencies]
|
|
30
|
+
# Pinned, as in every environment: a Fiji with no scikit-ops checkout runs
|
|
31
|
+
# this copy. See envs/pytorch for why the pin is deliberate.
|
|
32
|
+
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
33
|
+
|
|
34
|
+
# PyTorch varies by platform: CUDA builds where there are GPUs to use,
|
|
35
|
+
# and the plain package on Apple Silicon, where MPS comes built in.
|
|
36
|
+
[target.linux-64.dependencies]
|
|
37
|
+
pytorch-gpu = "*"
|
|
38
|
+
cudnn = "*"
|
|
39
|
+
cuda-version = "12.2"
|
|
40
|
+
|
|
41
|
+
[target.win-64.dependencies]
|
|
42
|
+
pytorch-gpu = "*"
|
|
43
|
+
cudnn = "*"
|
|
44
|
+
|
|
45
|
+
[target.osx-arm64.dependencies]
|
|
46
|
+
pytorch = "*"
|
skop/envs/cupy/pixi.toml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
name = "skop-cupy"
|
|
3
|
+
description = "GPU array computing on CUDA, with cupy."
|
|
4
|
+
channels = ["conda-forge"]
|
|
5
|
+
# No macOS: CUDA is not available there. Listing a platform this env cannot
|
|
6
|
+
# resolve on would turn a clean "no such platform" into a solver failure.
|
|
7
|
+
platforms = ["linux-64", "win-64"]
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
|
|
10
|
+
[system-requirements]
|
|
11
|
+
cuda = "12.0"
|
|
12
|
+
|
|
13
|
+
[dependencies]
|
|
14
|
+
python = "3.11.*"
|
|
15
|
+
numpy = "*"
|
|
16
|
+
cupy = "*"
|
|
17
|
+
# NB: pinned as envs/cellpose pins it. cuda-version selects which CUDA runtime
|
|
18
|
+
# the packages are built against, and it must not exceed what the installed
|
|
19
|
+
# driver supports.
|
|
20
|
+
cuda-version = "12.2"
|
|
21
|
+
# NB: appose is not listed as a conda dependency here, unlike in
|
|
22
|
+
# envs/unseg-cv, and this is temporary. A conda appose does satisfy the
|
|
23
|
+
# pinned scikit-ops' PyPI dependency on it -- but only once the pin is new
|
|
24
|
+
# enough to have dropped scikit-ops' [tool.uv.sources] git source for
|
|
25
|
+
# appose, which uv honors transitively and which makes the solve
|
|
26
|
+
# unsatisfiable. So this goes back to `appose = ">=0.12"` above as soon as
|
|
27
|
+
# the rev below is bumped past that change and conda-forge carries 0.12.
|
|
28
|
+
|
|
29
|
+
[pypi-dependencies]
|
|
30
|
+
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
31
|
+
# so bumping it rebuilds every environment. A checkout still shadows it,
|
|
32
|
+
# because skop.host.INIT puts src at sys.path[0]. See
|
|
33
|
+
# docs/spec/fiji-front-end.md; it becomes an ordinary version constraint
|
|
34
|
+
# once scikit-ops is released.
|
|
35
|
+
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
name = "skop-minimal"
|
|
3
|
+
description = "Smallest environment an op can run in: python, numpy, appose."
|
|
4
|
+
channels = ["conda-forge"]
|
|
5
|
+
platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"]
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
|
|
8
|
+
[dependencies]
|
|
9
|
+
python = "3.11.*"
|
|
10
|
+
numpy = "*"
|
|
11
|
+
# NB: appose is not listed as a conda dependency here, unlike in
|
|
12
|
+
# envs/unseg-cv, and this is temporary. A conda appose does satisfy the
|
|
13
|
+
# pinned scikit-ops' PyPI dependency on it -- but only once the pin is new
|
|
14
|
+
# enough to have dropped scikit-ops' [tool.uv.sources] git source for
|
|
15
|
+
# appose, which uv honors transitively and which makes the solve
|
|
16
|
+
# unsatisfiable. So this goes back to `appose = ">=0.12"` above as soon as
|
|
17
|
+
# the rev below is bumped past that change and conda-forge carries 0.12.
|
|
18
|
+
|
|
19
|
+
[pypi-dependencies]
|
|
20
|
+
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
21
|
+
# so bumping it rebuilds every environment. A checkout still shadows it,
|
|
22
|
+
# because skop.host.INIT puts src at sys.path[0]. See
|
|
23
|
+
# docs/spec/fiji-front-end.md; it becomes an ordinary version constraint
|
|
24
|
+
# once scikit-ops is released.
|
|
25
|
+
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
name = "skop-pytorch"
|
|
3
|
+
description = "The shared PyTorch environment: well-maintained torch libraries that can coexist."
|
|
4
|
+
channels = ["conda-forge"]
|
|
5
|
+
platforms = ["osx-arm64", "linux-64", "win-64"]
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
|
|
8
|
+
# This environment is meant to hold several ops, not one. Torch installs are
|
|
9
|
+
# large and slow, so anything torch-based that solves cleanly alongside what is
|
|
10
|
+
# already here belongs here rather than in an environment of its own.
|
|
11
|
+
#
|
|
12
|
+
# Three occupants now: ultralytics for the FastSAM detector, micro_sam for the
|
|
13
|
+
# mask detector of docs/design/0008, and Cellpose, whose own environment this
|
|
14
|
+
# replaced -- which is why envs/cellpose is gone.
|
|
15
|
+
#
|
|
16
|
+
# Conda-forge alone until Cellpose 4.2, which it does not carry; see
|
|
17
|
+
# [pypi-dependencies].
|
|
18
|
+
#
|
|
19
|
+
# envs/cellpose3 stays separate, and is the exception that shows the rule:
|
|
20
|
+
# Cellpose 4 replaced the v3 model zoo and moved the API with it, so the two
|
|
21
|
+
# cannot share an environment at any version pin.
|
|
22
|
+
|
|
23
|
+
[system-requirements]
|
|
24
|
+
cuda = "12.0"
|
|
25
|
+
|
|
26
|
+
[dependencies]
|
|
27
|
+
python = "3.11.*"
|
|
28
|
+
numpy = "*"
|
|
29
|
+
ultralytics = ">=8.3"
|
|
30
|
+
# NB: appose is not listed as a conda dependency here, unlike in
|
|
31
|
+
# envs/unseg-cv, and this is temporary. A conda appose does satisfy the
|
|
32
|
+
# pinned scikit-ops' PyPI dependency on it -- but only once the pin is new
|
|
33
|
+
# enough to have dropped scikit-ops' [tool.uv.sources] git source for
|
|
34
|
+
# appose, which uv honors transitively and which makes the solve
|
|
35
|
+
# unsatisfiable. So this goes back to `appose = ">=0.12"` above as soon as
|
|
36
|
+
# the rev below is bumped past that change and conda-forge carries 0.12.
|
|
37
|
+
|
|
38
|
+
# NB: micro_sam depends on `napari >=0.5,<0.6` and `pyqt` outright -- it ships
|
|
39
|
+
# a napari plugin and does not split it out. So this environment installs a GUI
|
|
40
|
+
# stack that nothing in it imports; the ops use micro_sam.util and
|
|
41
|
+
# micro_sam.prompt_based_segmentation, neither of which touches napari. Heavy
|
|
42
|
+
# but harmless: a worker process never opens a window, and 0002's whole point
|
|
43
|
+
# is that this napari pin cannot reach the front end's.
|
|
44
|
+
#
|
|
45
|
+
# It also carries `zarr <3` and `xarray <2025.3` -- worth knowing when the next
|
|
46
|
+
# occupant fails to solve.
|
|
47
|
+
micro_sam = ">=1.6"
|
|
48
|
+
|
|
49
|
+
# NB: torchvision is required explicitly even though nothing here imports it
|
|
50
|
+
# directly. conda's ultralytics does not depend on it, but reads
|
|
51
|
+
# `importlib.metadata.version("torchvision")` at import time and raises
|
|
52
|
+
# PackageNotFoundError when it is absent -- so leaving it out fails at the
|
|
53
|
+
# first `from ultralytics import ...`, not at some later feature.
|
|
54
|
+
torchvision = "*"
|
|
55
|
+
|
|
56
|
+
# PyTorch varies by platform: CUDA builds where there are GPUs to use,
|
|
57
|
+
# and the plain package on Apple Silicon, where MPS comes built in.
|
|
58
|
+
|
|
59
|
+
[pypi-dependencies]
|
|
60
|
+
# Conda-forge stops at cellpose 4.1.1, and 4.2 is where the DINO models
|
|
61
|
+
# arrived; dinov3 is only on GitHub. Neither pulls a second torch -- pixi maps
|
|
62
|
+
# the conda pytorch onto the `torch` requirement.
|
|
63
|
+
cellpose = ">=4.2"
|
|
64
|
+
dinov3 = { git = "https://github.com/facebookresearch/dinov3" }
|
|
65
|
+
|
|
66
|
+
# InstanSeg is plain torch, PyPI only. Since 0.1 its file readers (bioio,
|
|
67
|
+
# rasterio, slideio) are an optional `io` extra, left out: the op is handed
|
|
68
|
+
# an array. The base install is torch, numpy, scikit-image and small helpers.
|
|
69
|
+
instanseg-torch = "*"
|
|
70
|
+
|
|
71
|
+
# Note: pinned deliberately -- this is part of the environment's identity,
|
|
72
|
+
# so bumping it rebuilds every environment. A checkout still shadows it,
|
|
73
|
+
# because skop.host.INIT puts src at sys.path[0]. See
|
|
74
|
+
# docs/spec/fiji-front-end.md; it becomes an ordinary version constraint
|
|
75
|
+
# once scikit-ops is released.
|
|
76
|
+
scikit-ops = { git = "https://github.com/apposed/scikit-ops.git", rev = "00925fea3d92f3c974b7b1ec67d6633307d27bcc" }
|
|
77
|
+
|
|
78
|
+
[target.linux-64.dependencies]
|
|
79
|
+
pytorch-gpu = "*"
|
|
80
|
+
cudnn = "*"
|
|
81
|
+
cuda-version = "12.2"
|
|
82
|
+
|
|
83
|
+
[target.win-64.dependencies]
|
|
84
|
+
pytorch-gpu = "*"
|
|
85
|
+
cudnn = "*"
|
|
86
|
+
|
|
87
|
+
[target.osx-arm64.dependencies]
|
|
88
|
+
pytorch = "*"
|