patchworks 0.11.7__tar.gz → 0.11.9__tar.gz
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.
- {patchworks-0.11.7 → patchworks-0.11.9}/.gitignore +2 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/PKG-INFO +11 -3
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/snakemake.md +140 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/pyproject.toml +14 -1
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/plugins/napari.py +25 -2
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/config/config.yaml +11 -4
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/profile/slurm/config.yaml +6 -3
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/scripts/_pw.py +19 -2
- patchworks-0.11.7/workflow/.snakemake/iocache/latest.pkl +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/.github/workflows/docs.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/.github/workflows/lint.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/.github/workflows/release.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/.markdownlint-cli2.yaml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/README.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/cliff.toml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/chunks.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/cluster.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/io.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/relabel.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/api/tile_process.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/custom.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/custom_method.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/stardist.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/getting_started.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/merging.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/performance.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/guide/tiling.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/docs/index.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/mkdocs.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/__init__.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_core.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_distributed.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_io.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_merge.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/tests/test_core.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/tests/test_distributed.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/tests/test_napari.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/tests/test_ome_zarr.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/README.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/Snakefile +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/pixi.toml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/rules/common.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/rules/convert.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/rules/merge.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/rules/segment.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/scripts/convert.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/scripts/merge.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.9}/workflow/scripts/segment_tile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.9
|
|
4
4
|
Summary: Tiled processing of arbitrarily large images with globally consistent labels
|
|
5
5
|
Project-URL: Homepage, https://github.com/imcf/patchworks
|
|
6
6
|
Project-URL: Issues, https://github.com/imcf/patchworks/issues
|
|
@@ -29,8 +29,12 @@ Requires-Dist: bioio-lif; extra == 'all'
|
|
|
29
29
|
Requires-Dist: bioio-nd2; extra == 'all'
|
|
30
30
|
Requires-Dist: bioio-ome-tiff; extra == 'all'
|
|
31
31
|
Requires-Dist: bioio-tifffile; extra == 'all'
|
|
32
|
+
Requires-Dist: glasbey; extra == 'all'
|
|
32
33
|
Requires-Dist: imaris-ims-file-reader; extra == 'all'
|
|
33
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: ipykernel<7; extra == 'all'
|
|
35
|
+
Requires-Dist: lxml-html-clean; extra == 'all'
|
|
36
|
+
Requires-Dist: napari[all]>=0.5.5; extra == 'all'
|
|
37
|
+
Requires-Dist: numpy<2.5; extra == 'all'
|
|
34
38
|
Requires-Dist: nvidia-ml-py; extra == 'all'
|
|
35
39
|
Requires-Dist: psutil; extra == 'all'
|
|
36
40
|
Requires-Dist: scikit-image; extra == 'all'
|
|
@@ -62,7 +66,11 @@ Provides-Extra: io
|
|
|
62
66
|
Requires-Dist: psutil; extra == 'io'
|
|
63
67
|
Requires-Dist: tqdm; extra == 'io'
|
|
64
68
|
Provides-Extra: napari
|
|
65
|
-
Requires-Dist:
|
|
69
|
+
Requires-Dist: glasbey; extra == 'napari'
|
|
70
|
+
Requires-Dist: ipykernel<7; extra == 'napari'
|
|
71
|
+
Requires-Dist: lxml-html-clean; extra == 'napari'
|
|
72
|
+
Requires-Dist: napari[all]>=0.5.5; extra == 'napari'
|
|
73
|
+
Requires-Dist: numpy<2.5; extra == 'napari'
|
|
66
74
|
Provides-Extra: workflow
|
|
67
75
|
Requires-Dist: snakemake-executor-plugin-slurm; extra == 'workflow'
|
|
68
76
|
Requires-Dist: snakemake>=8; extra == 'workflow'
|
|
@@ -194,6 +194,146 @@ also re-runs a step when its **code, params or software environment** change —
|
|
|
194
194
|
so upgrading patchworks would re-do the conversion and overwrite an existing
|
|
195
195
|
result. Keep `mtime` and reruns happen only when an output is missing or stale.
|
|
196
196
|
|
|
197
|
+
## Custom segmentation function
|
|
198
|
+
|
|
199
|
+
Not using Cellpose? Run **your own** per-tile function — no need to edit the
|
|
200
|
+
package. You write one function; patchworks handles everything around it
|
|
201
|
+
(tiling, halos, skipping empty tiles, the zarr-native merge, global relabelling,
|
|
202
|
+
resume, logs).
|
|
203
|
+
|
|
204
|
+
### The contract
|
|
205
|
+
|
|
206
|
+
Your function is called **once per tile**:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
labels = segment(tile) # plus any kwargs you configure
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| | What you get / must return |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| **Input `tile`** | A NumPy array of **one** tile, with the overlap halo already included. The channel and pyramid level from the config are already selected, so it is purely spatial: `(z, y, x)` for a 3-D run, `(y, x)` for 2-D. Dtype is the image's (e.g. `uint16`). |
|
|
215
|
+
| **Return** | An integer **label** array (not a boolean mask), **same shape** as `tile`. `0` = background; each object a distinct positive integer. |
|
|
216
|
+
| **Labels** | Only need to be unique **within the tile**. Don't try to make them globally unique — the merge step stitches objects across tile borders and renumbers everything to a contiguous `1..N` (`sequential_labels: true`). |
|
|
217
|
+
| **Shape** | Must match the input exactly — patchworks trims the halo off your output, so a wrong shape is an error. Don't crop or resize inside the function. |
|
|
218
|
+
|
|
219
|
+
That is the whole interface. Anything that turns an image tile into a label
|
|
220
|
+
image works: classic image processing, StarDist, a trained model, an external
|
|
221
|
+
binary you shell out to, …
|
|
222
|
+
|
|
223
|
+
### Minimal example (no GPU, no deps beyond scikit-image)
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# my_seg.py
|
|
227
|
+
import numpy as np
|
|
228
|
+
from skimage.measure import label
|
|
229
|
+
|
|
230
|
+
def segment(tile: np.ndarray, sigma: float = 2.0) -> np.ndarray:
|
|
231
|
+
"""Threshold + connected components. Returns int32 labels (0 = bg)."""
|
|
232
|
+
from skimage.filters import gaussian, threshold_otsu
|
|
233
|
+
|
|
234
|
+
smooth = gaussian(tile, sigma=sigma, preserve_range=True)
|
|
235
|
+
thr = threshold_otsu(smooth) if smooth.max() > smooth.min() else np.inf
|
|
236
|
+
return label(smooth > thr).astype("int32")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
method: "custom"
|
|
241
|
+
label_name: "my_labels"
|
|
242
|
+
custom:
|
|
243
|
+
module: "my_seg" # import name (see "Make it importable")
|
|
244
|
+
function: "segment" # default is "segment"
|
|
245
|
+
kwargs: # optional — forwarded as segment(tile, **kwargs)
|
|
246
|
+
sigma: 1.5
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Real example: StarDist 3-D, with model caching
|
|
250
|
+
|
|
251
|
+
Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
|
|
252
|
+
own process so this matters less, but for local runs one process segments many
|
|
253
|
+
tiles — cache the model at module level (or with `functools.lru_cache`):
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
# stardist_seg.py
|
|
257
|
+
import numpy as np
|
|
258
|
+
|
|
259
|
+
_MODEL = None
|
|
260
|
+
|
|
261
|
+
def _model():
|
|
262
|
+
global _MODEL
|
|
263
|
+
if _MODEL is None: # loaded once per worker process
|
|
264
|
+
from stardist.models import StarDist3D
|
|
265
|
+
_MODEL = StarDist3D.from_pretrained("3D_demo")
|
|
266
|
+
return _MODEL
|
|
267
|
+
|
|
268
|
+
def segment(tile: np.ndarray, prob_thresh: float = 0.5) -> np.ndarray:
|
|
269
|
+
from csbdeep.utils import normalize
|
|
270
|
+
|
|
271
|
+
labels, _ = _model().predict_instances(
|
|
272
|
+
normalize(tile), prob_thresh=prob_thresh
|
|
273
|
+
)
|
|
274
|
+
return labels.astype("int32")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```yaml
|
|
278
|
+
method: "custom"
|
|
279
|
+
label_name: "stardist"
|
|
280
|
+
custom:
|
|
281
|
+
module: "stardist_seg"
|
|
282
|
+
function: "segment"
|
|
283
|
+
kwargs:
|
|
284
|
+
prob_thresh: 0.5
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Using a GPU? The segment jobs already hold one (the `gres: "gpu:1"` request),
|
|
288
|
+
so just let your framework see it — nothing extra in the config.
|
|
289
|
+
|
|
290
|
+
### Test it before you submit
|
|
291
|
+
|
|
292
|
+
Run your function on one real tile first — it catches shape/dtype bugs in
|
|
293
|
+
seconds instead of after a queue wait. Output must be integer, same shape, `0`
|
|
294
|
+
for background:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from patchworks import load_ome_zarr
|
|
298
|
+
from my_seg import segment
|
|
299
|
+
|
|
300
|
+
img = load_ome_zarr("results/image.zarr", channel=0, level=0)
|
|
301
|
+
tile = img[:, :512, :512].compute() # a small spatial block
|
|
302
|
+
out = segment(tile)
|
|
303
|
+
|
|
304
|
+
assert out.shape == tile.shape, (out.shape, tile.shape)
|
|
305
|
+
assert out.dtype.kind in "iu" # integer labels, not a float mask
|
|
306
|
+
print("objects in tile:", int(out.max()))
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Make it importable on the cluster
|
|
310
|
+
|
|
311
|
+
The segment job runs `import <module>`, so the module must be on the path. Pick
|
|
312
|
+
one:
|
|
313
|
+
|
|
314
|
+
1. **Drop the file in `workflow/scripts/`** — Snakemake adds the script dir to
|
|
315
|
+
`sys.path`, so `module: "my_seg"` just works. Simplest for a single file.
|
|
316
|
+
2. **Install it** into the run env (`pip install -e .`, `pixi add --pypi …`),
|
|
317
|
+
then use its import name. Best for a real package with dependencies.
|
|
318
|
+
3. **Set `PYTHONPATH`** to the file's directory before launching Snakemake.
|
|
319
|
+
|
|
320
|
+
### Cluster checklist
|
|
321
|
+
|
|
322
|
+
- **Dependencies:** the env that runs the **segment** jobs must have everything
|
|
323
|
+
your function imports (`pip`/`pixi add` it). A missing import or any crash
|
|
324
|
+
shows up in `logs/segment/<index>.log`, not the (empty) SLURM log.
|
|
325
|
+
- **Offline GPU nodes:** the built-in `fetch_model` prefetch covers Cellpose
|
|
326
|
+
only. If your function downloads weights/data on first use, fetch them once on
|
|
327
|
+
the **login node** (network access) so they land in shared `$HOME`; otherwise
|
|
328
|
+
the segment jobs fail with `Network is unreachable`. See *Troubleshooting*.
|
|
329
|
+
- **Memory / walltime:** tune `segment:` in `profile/slurm/config.yaml` for your
|
|
330
|
+
model (`mem_mb`, `runtime`) just as for Cellpose.
|
|
331
|
+
- Everything else — tiling, halos, empty-tile skipping, the zarr-native merge,
|
|
332
|
+
resume, and per-tile logs — is identical to a Cellpose run.
|
|
333
|
+
|
|
334
|
+
For full control (your own tiling/merge loop instead of the bundled rules), call
|
|
335
|
+
the public API directly — see *How it works* below.
|
|
336
|
+
|
|
197
337
|
## pixi (instead of conda)
|
|
198
338
|
|
|
199
339
|
Conda is **not** required — Snakemake runs in whatever environment launches it.
|
|
@@ -57,7 +57,20 @@ bioio = [
|
|
|
57
57
|
# imaris reads .ims files natively (HDF5, no JVM) for OME-ZARR conversion.
|
|
58
58
|
imaris = ["imaris-ims-file-reader"]
|
|
59
59
|
# napari enables the interactive viewer plugin.
|
|
60
|
-
napari =
|
|
60
|
+
# - napari >= 0.5: NumPy 2.0 compatible (older 0.4.x uses np.array(copy=False),
|
|
61
|
+
# which raises under NumPy 2 -> ValidationError on Viewer()).
|
|
62
|
+
# - numpy < 2.5: napari/numba don't support numpy 2.5 yet.
|
|
63
|
+
# - ipykernel < 7: napari-console requires ipykernel < 7.
|
|
64
|
+
# - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split
|
|
65
|
+
# into a separate package in lxml >= 5.2 (else ImportError on Viewer()).
|
|
66
|
+
# - glasbey: distinct high-contrast label LUTs for view_in_napari.
|
|
67
|
+
napari = [
|
|
68
|
+
"napari[all]>=0.5.5",
|
|
69
|
+
"numpy<2.5",
|
|
70
|
+
"ipykernel<7",
|
|
71
|
+
"lxml-html-clean",
|
|
72
|
+
"glasbey",
|
|
73
|
+
]
|
|
61
74
|
# workflow runs the Snakemake pipeline (per-tile SLURM jobs across GPUs).
|
|
62
75
|
workflow = ["snakemake>=8", "snakemake-executor-plugin-slurm"]
|
|
63
76
|
dev = ["pytest", "pytest-cov", "scikit-image", "psutil", "tqdm"]
|
|
@@ -199,6 +199,7 @@ def view_in_napari(
|
|
|
199
199
|
labels_component: str = "labels",
|
|
200
200
|
image_name: str = "image",
|
|
201
201
|
labels_name: str = "labels",
|
|
202
|
+
glasbey: bool = True,
|
|
202
203
|
show: bool = True,
|
|
203
204
|
**add_image_kwargs: Any,
|
|
204
205
|
):
|
|
@@ -223,6 +224,11 @@ def view_in_napari(
|
|
|
223
224
|
matching ``tile_process``'s ``output_component``).
|
|
224
225
|
image_name, labels_name : str, optional
|
|
225
226
|
Layer names shown in napari.
|
|
227
|
+
glasbey : bool, optional
|
|
228
|
+
Colour the labels with a glasbey palette (many distinct, high-contrast
|
|
229
|
+
colours, tuned to read on the dark canvas) instead of napari's default.
|
|
230
|
+
Default ``True``. Needs the ``glasbey`` package (ships with
|
|
231
|
+
``patchworks[napari]``).
|
|
226
232
|
show : bool, optional
|
|
227
233
|
Start the napari event loop (blocking). Set ``False`` in scripts/tests
|
|
228
234
|
that manage the loop themselves.
|
|
@@ -250,9 +256,24 @@ def view_in_napari(
|
|
|
250
256
|
**add_image_kwargs,
|
|
251
257
|
)
|
|
252
258
|
|
|
259
|
+
label_kwargs: dict[str, Any] = {}
|
|
260
|
+
if glasbey:
|
|
261
|
+
try:
|
|
262
|
+
import glasbey as _glasbey
|
|
263
|
+
|
|
264
|
+
# bias lighter so colours read on napari's dark canvas
|
|
265
|
+
label_kwargs["colormap"] = _glasbey.create_palette(
|
|
266
|
+
256, lightness_bounds=(40, 100)
|
|
267
|
+
)
|
|
268
|
+
except ImportError:
|
|
269
|
+
logger.warning(
|
|
270
|
+
"glasbey not installed; using napari's default label colours "
|
|
271
|
+
"(pip install glasbey, or it ships with patchworks[napari])."
|
|
272
|
+
)
|
|
273
|
+
|
|
253
274
|
if labels is not None:
|
|
254
275
|
lab = _resolve_labels(labels, labels_component)
|
|
255
|
-
viewer.add_labels(lab, name=labels_name)
|
|
276
|
+
viewer.add_labels(lab, name=labels_name, **label_kwargs)
|
|
256
277
|
elif _is_zarr(image):
|
|
257
278
|
# No labels given → auto-overlay every label image stored inside the
|
|
258
279
|
# OME-ZARR under labels/<name>/ (the default place tile_process writes
|
|
@@ -260,7 +281,9 @@ def view_in_napari(
|
|
|
260
281
|
for name in _inner_label_names(image):
|
|
261
282
|
levels = _multiscale_levels(f"{image}/labels/{name}", None)
|
|
262
283
|
lab = [lvl.astype("int32") for lvl in levels]
|
|
263
|
-
viewer.add_labels(
|
|
284
|
+
viewer.add_labels(
|
|
285
|
+
lab if len(lab) > 1 else lab[0], name=name, **label_kwargs
|
|
286
|
+
)
|
|
264
287
|
logger.info("auto-loaded labels/%s from %s", name, image)
|
|
265
288
|
|
|
266
289
|
if show:
|
|
@@ -12,7 +12,7 @@ work_dir: "/path/to/results" # everything is written under here
|
|
|
12
12
|
# image.zarr/labels/<name>/ — the segmentation (multi-scale), in the image
|
|
13
13
|
|
|
14
14
|
# ---- conversion -------------------------------------------------------------
|
|
15
|
-
reuse_pyramid:
|
|
15
|
+
reuse_pyramid: false # for .ims: copy its own pyramid (fast); else rebuild
|
|
16
16
|
convert_chunks: null # null → patchworks' bounded auto chunks; or [c,z,y,x]
|
|
17
17
|
shard: false # true → pack chunks into shards (zarr v3; fewer files)
|
|
18
18
|
# Re-running reuses an existing image.zarr automatically (skips conversion).
|
|
@@ -29,10 +29,10 @@ skip_empty: true # skip background tiles
|
|
|
29
29
|
empty_threshold: null # null → Otsu; or a number
|
|
30
30
|
|
|
31
31
|
# ---- segmentation -----------------------------------------------------------
|
|
32
|
-
method: "cellpose" # "cellpose" (GPU)
|
|
33
|
-
label_name: "
|
|
32
|
+
method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU; testing), "custom"
|
|
33
|
+
label_name: "cellpose_labels"
|
|
34
34
|
cellpose:
|
|
35
|
-
model: "
|
|
35
|
+
model: "nuclei"
|
|
36
36
|
diameter: 30
|
|
37
37
|
do_3D: true
|
|
38
38
|
gpu: true
|
|
@@ -40,6 +40,13 @@ cellpose:
|
|
|
40
40
|
# flow_threshold: 0.4
|
|
41
41
|
# cellprob_threshold: 0.0
|
|
42
42
|
|
|
43
|
+
# Your own per-tile function (method: "custom"). See the "custom function"
|
|
44
|
+
# section of docs/guide/snakemake.md.
|
|
45
|
+
# custom:
|
|
46
|
+
# module: "my_seg" # importable on the cluster (workflow/scripts/, PYTHONPATH, or pip-installed)
|
|
47
|
+
# function: "segment" # def segment(tile: np.ndarray) -> np.ndarray (int32 labels)
|
|
48
|
+
# kwargs: {} # optional extra keyword args
|
|
49
|
+
|
|
43
50
|
# ---- pyramid for the labels -------------------------------------------------
|
|
44
51
|
pyramid_levels: 5
|
|
45
52
|
pyramid_downscale: 2
|
|
@@ -33,11 +33,14 @@ set-resources:
|
|
|
33
33
|
mem_mb: 32000
|
|
34
34
|
runtime: 120
|
|
35
35
|
segment:
|
|
36
|
-
# one GPU per tile — this is what spreads Cellpose across GPUs
|
|
36
|
+
# one GPU per tile — this is what spreads Cellpose across GPUs.
|
|
37
|
+
# Use the plugin's native `gres` resource → emits --gres=gpu:1, which is
|
|
38
|
+
# how scicore binds a CUDA device. (The `gpu:` resource emits --gpus and
|
|
39
|
+
# leaves the job without a device.)
|
|
37
40
|
slurm_partition: "rtx4090"
|
|
38
|
-
gpu:
|
|
41
|
+
gres: "gpu:1"
|
|
39
42
|
qos: "rtx4090-6hours" # scicore: <partition>-<duration> QOS
|
|
40
|
-
mem_mb: 32000 # plenty — a tile used ~1G
|
|
43
|
+
mem_mb: 32000 # plenty — a tile used ~1G
|
|
41
44
|
cpus_per_task: 4
|
|
42
45
|
runtime: 360 # 6 hours — must match the QOS, NOT 120 (=2h → killed early)
|
|
43
46
|
merge:
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
12
|
import sys
|
|
13
|
+
from functools import partial
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
|
|
15
16
|
from patchworks import load_ome_zarr
|
|
@@ -124,8 +125,9 @@ def build_fn(cfg):
|
|
|
124
125
|
Parameters
|
|
125
126
|
----------
|
|
126
127
|
cfg : dict
|
|
127
|
-
Snakemake config. ``method`` selects ``"cellpose"`` (default)
|
|
128
|
-
|
|
128
|
+
Snakemake config. ``method`` selects ``"cellpose"`` (default), a simple
|
|
129
|
+
``"threshold"`` (testing / no-GPU), or ``"custom"`` to import your own
|
|
130
|
+
function (``cfg["custom"] = {module, function, kwargs}``).
|
|
129
131
|
|
|
130
132
|
Returns
|
|
131
133
|
-------
|
|
@@ -133,6 +135,21 @@ def build_fn(cfg):
|
|
|
133
135
|
``(ndarray) -> ndarray`` returning integer labels.
|
|
134
136
|
"""
|
|
135
137
|
method = cfg.get("method", "cellpose")
|
|
138
|
+
if method == "custom":
|
|
139
|
+
# Import a user-provided function, e.g.
|
|
140
|
+
# custom: {module: my_seg, function: segment, kwargs: {...}}
|
|
141
|
+
# The module must be importable on the cluster (a file in
|
|
142
|
+
# workflow/scripts/, on PYTHONPATH, or an installed package).
|
|
143
|
+
import importlib
|
|
144
|
+
|
|
145
|
+
spec = cfg["custom"]
|
|
146
|
+
fn = getattr(
|
|
147
|
+
importlib.import_module(spec["module"]),
|
|
148
|
+
spec.get("function", "segment"),
|
|
149
|
+
)
|
|
150
|
+
kwargs = spec.get("kwargs") or {}
|
|
151
|
+
return partial(fn, **kwargs) if kwargs else fn
|
|
152
|
+
|
|
136
153
|
if method == "threshold":
|
|
137
154
|
|
|
138
155
|
def fn(tile):
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|