patchworks 0.11.6__tar.gz → 0.11.8__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.6 → patchworks-0.11.8}/.gitignore +2 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/PKG-INFO +5 -3
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/snakemake.md +143 -1
- {patchworks-0.11.6 → patchworks-0.11.8}/pyproject.toml +5 -1
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/Snakefile +5 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/config/config.yaml +11 -4
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/pixi.toml +1 -1
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/profile/slurm/config.yaml +6 -3
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/rules/common.smk +4 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/rules/segment.smk +16 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/scripts/_pw.py +19 -2
- patchworks-0.11.8/workflow/scripts/fetch_model.py +21 -0
- patchworks-0.11.6/workflow/.snakemake/iocache/latest.pkl +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/.github/workflows/docs.yml +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/.github/workflows/lint.yml +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/.github/workflows/release.yml +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/.markdownlint-cli2.yaml +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/README.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/cliff.toml +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/chunks.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/cluster.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/io.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/relabel.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/api/tile_process.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/custom.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/custom_method.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/stardist.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/getting_started.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/merging.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/performance.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/guide/tiling.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/docs/index.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/mkdocs.yml +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/__init__.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_core.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_distributed.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_io.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_merge.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/tests/test_core.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/tests/test_distributed.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/tests/test_napari.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/tests/test_ome_zarr.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/README.md +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/rules/convert.smk +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/rules/merge.smk +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/scripts/convert.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/scripts/merge.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-0.11.6 → patchworks-0.11.8}/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.8
|
|
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
|
|
@@ -30,7 +30,8 @@ Requires-Dist: bioio-nd2; extra == 'all'
|
|
|
30
30
|
Requires-Dist: bioio-ome-tiff; extra == 'all'
|
|
31
31
|
Requires-Dist: bioio-tifffile; extra == 'all'
|
|
32
32
|
Requires-Dist: imaris-ims-file-reader; extra == 'all'
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: lxml-html-clean; extra == 'all'
|
|
34
|
+
Requires-Dist: napari[all]>=0.5.5; extra == 'all'
|
|
34
35
|
Requires-Dist: nvidia-ml-py; extra == 'all'
|
|
35
36
|
Requires-Dist: psutil; extra == 'all'
|
|
36
37
|
Requires-Dist: scikit-image; extra == 'all'
|
|
@@ -62,7 +63,8 @@ Provides-Extra: io
|
|
|
62
63
|
Requires-Dist: psutil; extra == 'io'
|
|
63
64
|
Requires-Dist: tqdm; extra == 'io'
|
|
64
65
|
Provides-Extra: napari
|
|
65
|
-
Requires-Dist:
|
|
66
|
+
Requires-Dist: lxml-html-clean; extra == 'napari'
|
|
67
|
+
Requires-Dist: napari[all]>=0.5.5; extra == 'napari'
|
|
66
68
|
Provides-Extra: workflow
|
|
67
69
|
Requires-Dist: snakemake-executor-plugin-slurm; extra == 'workflow'
|
|
68
70
|
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.
|
|
@@ -223,10 +363,12 @@ prologue. The simplest path is a single shared env that the compute nodes see.
|
|
|
223
363
|
| Symptom | Fix |
|
|
224
364
|
|---------|-----|
|
|
225
365
|
| `snakemake: command not found` | use `python -m snakemake` |
|
|
226
|
-
| Segment jobs pend forever | wrong `slurm_partition
|
|
366
|
+
| Segment jobs pend forever | wrong `slurm_partition`/GPU request; on scicore use `gres: "gpu:1"` |
|
|
367
|
+
| Segment dies, `Network is unreachable` | offline GPU nodes — the `fetch_model` localrule caches the model on the submit host first; if it still fails, your submit host has no network either (pre-download manually) |
|
|
227
368
|
| `cellpose is not installed` in a job | the job's env lacks `patchworks[cellpose]` |
|
|
228
369
|
| Reading the input fails | install the matching reader (`patchworks[imaris]`/`[bioio]` + a `bioio-*`) |
|
|
229
370
|
| Out of GPU memory | smaller `tile_shape`, or `do_3D: false` |
|
|
371
|
+
| A job fails with an empty SLURM log | read `logs/segment/<index>.log` (per tile) or `logs/steps.log` — the real traceback is there |
|
|
230
372
|
| Very slow | confirm GPU is used (`nvidia-smi`); try 2-D or a lower `level` |
|
|
231
373
|
|
|
232
374
|
## How it works (for the curious)
|
|
@@ -57,7 +57,11 @@ 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
|
+
# - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split
|
|
63
|
+
# into a separate package in lxml >= 5.2 (else ImportError on Viewer()).
|
|
64
|
+
napari = ["napari[all]>=0.5.5", "lxml-html-clean"]
|
|
61
65
|
# workflow runs the Snakemake pipeline (per-tile SLURM jobs across GPUs).
|
|
62
66
|
workflow = ["snakemake>=8", "snakemake-executor-plugin-slurm"]
|
|
63
67
|
dev = ["pytest", "pytest-cov", "scikit-image", "psutil", "tqdm"]
|
|
@@ -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
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# On a cluster, put this project on a shared filesystem the compute nodes can
|
|
8
8
|
# read: the SLURM jobs re-launch snakemake from this env's interpreter.
|
|
9
9
|
|
|
10
|
-
[
|
|
10
|
+
[workspace]
|
|
11
11
|
name = "patchworks-workflow"
|
|
12
12
|
channels = ["conda-forge"]
|
|
13
13
|
platforms = ["linux-64"]
|
|
@@ -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:
|
|
@@ -20,6 +20,10 @@ STAGE_OK = f"{STAGE}.done"
|
|
|
20
20
|
LOGS = f"{WORK}/logs"
|
|
21
21
|
STEPLOG = f"{LOGS}/steps.log"
|
|
22
22
|
|
|
23
|
+
# Marker that the segmentation model is cached locally. Produced by a local
|
|
24
|
+
# rule (runs on the networked submit host) so offline GPU nodes never download.
|
|
25
|
+
MODEL_OK = f"{WORK}/model.ready"
|
|
26
|
+
|
|
23
27
|
|
|
24
28
|
def occupied_done(wildcards):
|
|
25
29
|
"""Per-tile markers for the occupied tiles (resolved after the checkpoint)."""
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Plan tiles (checkpoint) and segment each tile on a GPU.
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
rule fetch_model:
|
|
5
|
+
"""Cache the segmentation model on the (networked) submit host.
|
|
6
|
+
|
|
7
|
+
Declared local (see ``localrules`` in the Snakefile) so it never runs on an
|
|
8
|
+
offline GPU node — Cellpose downloads its weights here, into shared $HOME.
|
|
9
|
+
"""
|
|
10
|
+
output:
|
|
11
|
+
touch(MODEL_OK),
|
|
12
|
+
log:
|
|
13
|
+
f"{LOGS}/fetch_model.log",
|
|
14
|
+
script:
|
|
15
|
+
"../scripts/fetch_model.py"
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
checkpoint prepare:
|
|
4
19
|
input:
|
|
5
20
|
IMAGE_OK,
|
|
@@ -18,6 +33,7 @@ rule segment:
|
|
|
18
33
|
tiles=TILES,
|
|
19
34
|
stage=STAGE_OK,
|
|
20
35
|
image=IMAGE_OK,
|
|
36
|
+
model=MODEL_OK,
|
|
21
37
|
output:
|
|
22
38
|
f"{WORK}/seg/{{index}}.done",
|
|
23
39
|
log:
|
|
@@ -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):
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Snakemake (local) script: cache the segmentation model before segmenting.
|
|
2
|
+
|
|
3
|
+
Runs on the submit host, which has network access, so the offline GPU nodes
|
|
4
|
+
never try to download Cellpose weights at run time (they read the shared
|
|
5
|
+
``$HOME/.cellpose`` cache instead).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from _pw import start_log
|
|
9
|
+
|
|
10
|
+
start_log(snakemake.log[0]) # noqa: F821
|
|
11
|
+
cfg = snakemake.config # noqa: F821
|
|
12
|
+
|
|
13
|
+
if cfg.get("method", "cellpose") == "cellpose":
|
|
14
|
+
# _get_model downloads + caches the weights keyed by (model, gpu).
|
|
15
|
+
from patchworks.plugins.cellpose import _get_model
|
|
16
|
+
|
|
17
|
+
model = cfg["cellpose"]["model"]
|
|
18
|
+
_get_model({"model": model, "gpu": False})
|
|
19
|
+
print(f"[patchworks] cached segmentation model: {model}")
|
|
20
|
+
else:
|
|
21
|
+
print(f"[patchworks] method={cfg.get('method')!r}; no model to prefetch")
|
|
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
|