patchworks 2.1.2__tar.gz → 2.3.0__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-2.1.2 → patchworks-2.3.0}/PKG-INFO +1 -1
- {patchworks-2.1.2 → patchworks-2.3.0}/cliff.toml +2 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/label_relations.md +2 -2
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/measurements.md +1 -1
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/snakemake.md +60 -10
- patchworks-2.3.0/tests/test_run_multi.py +123 -0
- patchworks-2.3.0/workflow/config/common.yaml +42 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/config/config_cilia.yaml +9 -27
- patchworks-2.3.0/workflow/config/config_cyto.yaml +22 -0
- patchworks-2.3.0/workflow/config/config_nuclei.yaml +22 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/config/multi.yaml +6 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/profile/slurm/config.yaml +8 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/run_multi.py +84 -2
- patchworks-2.1.2/workflow/config/config_cyto.yaml +0 -39
- patchworks-2.1.2/workflow/config/config_nuclei.yaml +0 -39
- {patchworks-2.1.2 → patchworks-2.3.0}/.github/workflows/docs.yml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/.github/workflows/lint.yml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/.github/workflows/release.yml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/.gitignore +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/.markdownlint-cli2.yaml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/LICENSE +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/README.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/chunks.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/cluster.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/io.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/plugins/dog.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/plugins/napari.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/postprocess.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/relabel.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/api/tile_process.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/assets/logo.png +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/custom.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/custom_method.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/dog.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/dog.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/standalone_merge.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/stardist.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/examples/stardist_2d.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/getting_started.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/merging.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/performance.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/pitfalls.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/skip_empty.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/guide/tiling.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/docs/index.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/mkdocs.yml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/pyproject.toml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/__init__.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_chunks.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_cluster.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_core.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_distributed.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_gpu.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_io.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_merge.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_occupancy.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_postprocess.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_relabel.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/_relations.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_allocation.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_core.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_distributed.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_dog.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_gpu.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_napari.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_occupancy.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_ome_zarr.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_postprocess.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/tests/test_relations.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/README.md +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/Snakefile +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/config/config.yaml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/pixi.toml +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/rules/common.smk +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/rules/convert.smk +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/rules/merge.smk +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/rules/segment.smk +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/_pw.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/convert.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/merge.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-2.1.2 → patchworks-2.3.0}/workflow/scripts/segment_tile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
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
|
|
@@ -31,6 +31,8 @@ commit_preprocessors = [
|
|
|
31
31
|
{ pattern = "(\\w+(?:\\([^)]*\\))?!?:\\s+)[^\\w\\s]+\\s*", replace = "$1" },
|
|
32
32
|
]
|
|
33
33
|
commit_parsers = [
|
|
34
|
+
# Merge commits are noise in a changelog — the merged commits are already listed.
|
|
35
|
+
{ message = "^Merge ", skip = true },
|
|
34
36
|
{ message = "^feat", group = "✨ Features" },
|
|
35
37
|
{ message = "^fix", group = "🐛 Bug Fixes" },
|
|
36
38
|
{ message = "^perf", group = "⚡ Performance" },
|
|
@@ -24,7 +24,7 @@ table[2]
|
|
|
24
24
|
`table` only contains matched `a` labels (nuclei with at least one
|
|
25
25
|
overlapping voxel in `cells`) — unmatched labels and full per-`b` coverage
|
|
26
26
|
need a bit more bookkeeping (the [cluster workflow's `run_multi.py`
|
|
27
|
-
script](snakemake.md#one-command-multiple-segmentations
|
|
27
|
+
script](snakemake.md#one-command-multiple-segmentations-relations) does
|
|
28
28
|
this for you and writes it as a two-sheet workbook).
|
|
29
29
|
|
|
30
30
|
Save it as a table yourself:
|
|
@@ -41,4 +41,4 @@ with open("nuclei_to_cell.csv", "w", newline="") as f:
|
|
|
41
41
|
|
|
42
42
|
On the cluster, producing the two label stores in the first place is a
|
|
43
43
|
matter of running the workflow twice against the same `work_dir` — see
|
|
44
|
-
[Running two segmentations](snakemake.md#running-two-segmentations-eg-nuclei
|
|
44
|
+
[Running two segmentations](snakemake.md#running-two-segmentations-eg-nuclei-cytoplasm).
|
|
@@ -10,7 +10,7 @@ is built for this — its "Measure" dock widget computes area/centroid/intensity
|
|
|
10
10
|
stats directly off a Labels layer's dask/zarr-backed array, out-of-core, and
|
|
11
11
|
scales with chunk count rather than object count. It's the best fit for
|
|
12
12
|
measuring *every* object in a store this size, not just a cropped region —
|
|
13
|
-
see [View image + labels in napari](ome_zarr_napari.md#view-image
|
|
13
|
+
see [View image + labels in napari](ome_zarr_napari.md#view-image-labels-in-napari).
|
|
14
14
|
Bundled in `patchworks[napari]`.
|
|
15
15
|
|
|
16
16
|
For interactively inspecting individual cells by clicking in the viewer (not
|
|
@@ -187,6 +187,27 @@ Snakemake submits `convert`, then `prepare`, then **one `segment` job per
|
|
|
187
187
|
batch of `tiles_per_job` non-empty tiles** (up to `jobs:` at once → that many
|
|
188
188
|
GPUs in parallel), then `merge`. Raise `jobs:` to use more GPUs.
|
|
189
189
|
|
|
190
|
+
!!! tip "Recognisable job names in `squeue`"
|
|
191
|
+
The SLURM executor names every job after its run UUID and **rejects** a
|
|
192
|
+
`--job-name` in `slurm_extra`, so by default `squeue` shows nothing you
|
|
193
|
+
can identify. A prefix is the supported lever, and it goes first in the
|
|
194
|
+
name (`<prefix>_<uuid>`) — the part a queue listing truncates to:
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
slurm-jobname-prefix: patchworks # already in the shipped profile
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`run_multi` overrides it per config, so a three-way run shows
|
|
201
|
+
`pw-convert`, then `pw-nuclei_labels` / `pw-cyto_labels` /
|
|
202
|
+
`pw-cilia_labels` — telling the concurrent runs apart at a glance:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
squeue -u $USER -o '%.18i %.24j %.8T %.10M'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Alphanumerics, underscores and hyphens only, 50 characters max; an
|
|
209
|
+
invalid prefix fails the run, so `label_name` is sanitised before use.
|
|
210
|
+
|
|
190
211
|
!!! tip "Sizing memory"
|
|
191
212
|
Every step now sizes its own worker counts from what SLURM actually
|
|
192
213
|
granted (`SLURM_CPUS_PER_TASK`, `SLURM_MEM_PER_*`, the cgroup limit)
|
|
@@ -248,13 +269,26 @@ running the workflow **twice with two configs against the same `work_dir`**
|
|
|
248
269
|
never collides: each run gets its own private subdirectory, and both reuse
|
|
249
270
|
the *same* already-converted `image.zarr` (conversion never re-runs).
|
|
250
271
|
|
|
272
|
+
Most of what those configs contain is identical — the input, the `work_dir`,
|
|
273
|
+
the tiling, everything `convert` reads. Put it in **one** shared file and let
|
|
274
|
+
each config carry only what actually differs. Snakemake merges several
|
|
275
|
+
`--configfile` values in order, with the later one winning:
|
|
276
|
+
|
|
251
277
|
```yaml
|
|
252
|
-
# config/
|
|
278
|
+
# config/common.yaml — shared by every segmentation
|
|
253
279
|
input: "/data/scan.ims"
|
|
254
280
|
work_dir: "/scratch/results"
|
|
281
|
+
tile_shape: [16, 1024, 1024]
|
|
282
|
+
shard: false # true → far fewer files, same chunks
|
|
283
|
+
tiles_per_job: 4
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```yaml
|
|
287
|
+
# config/config_nuclei.yaml — only the differences
|
|
255
288
|
label_name: "nuclei_labels"
|
|
256
289
|
channel: 1 # nuclear stain channel
|
|
257
|
-
|
|
290
|
+
overlap: [4, 30, 30]
|
|
291
|
+
method: "cellpose"
|
|
258
292
|
cellpose:
|
|
259
293
|
model: "nuclei"
|
|
260
294
|
diameter: 15
|
|
@@ -262,12 +296,11 @@ cellpose:
|
|
|
262
296
|
```
|
|
263
297
|
|
|
264
298
|
```yaml
|
|
265
|
-
# config/config_cyto.yaml
|
|
266
|
-
input: "/data/scan.ims"
|
|
267
|
-
work_dir: "/scratch/results" # same work_dir — image.zarr is reused
|
|
299
|
+
# config/config_cyto.yaml — only the differences
|
|
268
300
|
label_name: "cyto_labels"
|
|
269
301
|
channel: 0 # cytoplasm/membrane channel
|
|
270
|
-
|
|
302
|
+
overlap: [4, 30, 30]
|
|
303
|
+
method: "cellpose"
|
|
271
304
|
cellpose:
|
|
272
305
|
model: "cyto3"
|
|
273
306
|
diameter: 30
|
|
@@ -279,12 +312,23 @@ they can run concurrently. Give each its own `--directory`, because
|
|
|
279
312
|
Snakemake's lock lives in the working directory, not in the config:
|
|
280
313
|
|
|
281
314
|
```bash
|
|
282
|
-
snakemake --workflow-profile profile/slurm --configfile config/config_nuclei.yaml
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
315
|
+
snakemake --workflow-profile profile/slurm --configfile config/common.yaml config/config_nuclei.yaml --directory /scratch/results/nuclei_labels/.snakemake
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
snakemake --workflow-profile profile/slurm --configfile config/common.yaml config/config_cyto.yaml --directory /scratch/results/cyto_labels/.snakemake
|
|
286
320
|
```
|
|
287
321
|
|
|
322
|
+
!!! warning "Conversion settings belong in the shared file"
|
|
323
|
+
`convert` runs **once**, from the first config only. A `shard`, `input` or
|
|
324
|
+
`pyramid_levels` set on the second config is therefore never read, and
|
|
325
|
+
nothing logs that it was dropped. `run_multi` refuses to start when those
|
|
326
|
+
keys disagree across configs and tells you which one — but if you drive
|
|
327
|
+
the configs by hand, keep them in `common.yaml`.
|
|
328
|
+
|
|
329
|
+
Splitting the configs is optional: a self-contained config still works,
|
|
330
|
+
and `common:` can simply be left out of `multi.yaml`.
|
|
331
|
+
|
|
288
332
|
!!! tip "One command for several segmentations + relations"
|
|
289
333
|
`config/multi.yaml` lists any number of segmentation configs plus which
|
|
290
334
|
pairs to relate afterward; `pixi run multi` (or `multi-slurm`) converts
|
|
@@ -318,6 +362,8 @@ and saves every configured relation — one command instead of juggling several
|
|
|
318
362
|
|
|
319
363
|
```yaml
|
|
320
364
|
# config/multi.yaml
|
|
365
|
+
common: config/common.yaml # shared settings, merged under each config below
|
|
366
|
+
|
|
321
367
|
segmentations:
|
|
322
368
|
- config/config_nuclei.yaml
|
|
323
369
|
- config/config_cyto.yaml
|
|
@@ -328,6 +374,10 @@ relations:
|
|
|
328
374
|
output: nuclei_to_cyto.xlsx # written into work_dir
|
|
329
375
|
```
|
|
330
376
|
|
|
377
|
+
`common:` is optional — leave it out and each config must be self-contained,
|
|
378
|
+
as before. With it, changing the input path or turning on `shard` is a
|
|
379
|
+
one-line edit in one file instead of the same edit repeated per config.
|
|
380
|
+
|
|
331
381
|
```bash
|
|
332
382
|
pixi run multi-dry # dry-run every segmentation config (skips relations)
|
|
333
383
|
pixi run multi # run locally
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Tests for the multi-config driver's SLURM-facing behaviour."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
sys.path.insert(
|
|
8
|
+
0, str(Path(__file__).resolve().parents[1] / "workflow" / "scripts")
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
import pytest # noqa: E402
|
|
12
|
+
import yaml # noqa: E402
|
|
13
|
+
|
|
14
|
+
from run_multi import ( # noqa: E402
|
|
15
|
+
_CONVERT_KEYS,
|
|
16
|
+
_snakemake_cmd,
|
|
17
|
+
_validate_configs,
|
|
18
|
+
slurm_jobname_prefix,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# The SLURM executor's own rule (snakemake_executor_plugin_slurm): it raises a
|
|
22
|
+
# WorkflowError and aborts the whole run if the prefix does not match.
|
|
23
|
+
_EXECUTOR_RULE = re.compile(r"^[A-Za-z0-9_-]{1,50}$")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_jobname_prefix_satisfies_the_executor():
|
|
27
|
+
"""Whatever a label_name contains, the prefix must stay submittable.
|
|
28
|
+
|
|
29
|
+
The executor names jobs after a UUID and refuses a --job-name override, so
|
|
30
|
+
this prefix is the only thing that makes squeue readable -- and an invalid
|
|
31
|
+
one fails the run rather than degrading.
|
|
32
|
+
"""
|
|
33
|
+
for label in ("nuclei_labels", "cyto_labels", "convert", "a"):
|
|
34
|
+
assert _EXECUTOR_RULE.match(slurm_jobname_prefix(label))
|
|
35
|
+
|
|
36
|
+
# Characters a label might plausibly pick up are sanitised, not passed on.
|
|
37
|
+
assert _EXECUTOR_RULE.match(slurm_jobname_prefix("cilia/v2 (test)"))
|
|
38
|
+
assert _EXECUTOR_RULE.match(slurm_jobname_prefix("run 1: nuclei"))
|
|
39
|
+
# And an over-long label is truncated to the executor's 50-char limit.
|
|
40
|
+
assert _EXECUTOR_RULE.match(slurm_jobname_prefix("x" * 200))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_jobname_prefix_keeps_the_label_readable():
|
|
44
|
+
"""The label must lead, since that is what a queue listing truncates to."""
|
|
45
|
+
assert slurm_jobname_prefix("nuclei_labels") == "pw-nuclei_labels"
|
|
46
|
+
assert slurm_jobname_prefix("convert") == "pw-convert"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_common_configfile_is_merged_under_the_per_config_one():
|
|
50
|
+
"""Snakemake merges --configfile values in order, later winning.
|
|
51
|
+
|
|
52
|
+
That ordering is the whole mechanism: shared settings come from common.yaml
|
|
53
|
+
and the per-config file overrides only what differs. Swap the two and every
|
|
54
|
+
config would silently get the shared defaults instead of its own channel.
|
|
55
|
+
"""
|
|
56
|
+
cmd = _snakemake_cmd(
|
|
57
|
+
Path("config/config_nuclei.yaml"),
|
|
58
|
+
workflow_dir=Path("workflow"),
|
|
59
|
+
profile=None,
|
|
60
|
+
cores=8,
|
|
61
|
+
dry_run=False,
|
|
62
|
+
common=Path("config/common.yaml"),
|
|
63
|
+
)
|
|
64
|
+
i = cmd.index("--configfile")
|
|
65
|
+
assert cmd[i + 1].endswith("common.yaml")
|
|
66
|
+
assert cmd[i + 2].endswith("config_nuclei.yaml")
|
|
67
|
+
|
|
68
|
+
# Without a common file the invocation is unchanged: one configfile, so
|
|
69
|
+
# a self-contained config keeps working exactly as before.
|
|
70
|
+
plain = _snakemake_cmd(
|
|
71
|
+
Path("config/config_nuclei.yaml"),
|
|
72
|
+
workflow_dir=Path("workflow"),
|
|
73
|
+
profile=None,
|
|
74
|
+
cores=8,
|
|
75
|
+
dry_run=False,
|
|
76
|
+
)
|
|
77
|
+
j = plain.index("--configfile")
|
|
78
|
+
assert plain[j + 1].endswith("config_nuclei.yaml")
|
|
79
|
+
assert not plain[j + 2].endswith(".yaml")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_convert_keys_must_agree_across_configs():
|
|
83
|
+
"""`convert` runs once from the first config, so a later one is ignored.
|
|
84
|
+
|
|
85
|
+
Setting shard on the second config and watching a million files appear
|
|
86
|
+
anyway is invisible without this check -- there is no log line saying the
|
|
87
|
+
value was dropped, because nothing ever read it.
|
|
88
|
+
"""
|
|
89
|
+
paths = [Path("a.yaml"), Path("b.yaml")]
|
|
90
|
+
base = {"work_dir": "/w", "tile_shape": [16, 512, 512], "level": 0}
|
|
91
|
+
good = [
|
|
92
|
+
{**base, "label_name": "a", "shard": True},
|
|
93
|
+
{**base, "label_name": "b", "shard": True},
|
|
94
|
+
]
|
|
95
|
+
assert _validate_configs(paths, good) == "/w"
|
|
96
|
+
|
|
97
|
+
bad = [
|
|
98
|
+
{**base, "label_name": "a", "shard": True},
|
|
99
|
+
{**base, "label_name": "b", "shard": False},
|
|
100
|
+
]
|
|
101
|
+
# It reports every problem and exits, rather than raising, so that a
|
|
102
|
+
# mistake costs one readable message instead of a traceback.
|
|
103
|
+
with pytest.raises(SystemExit):
|
|
104
|
+
_validate_configs(paths, bad)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_shipped_multi_configs_are_consistent():
|
|
108
|
+
"""The shipped example must satisfy its own validator.
|
|
109
|
+
|
|
110
|
+
It is the thing users copy, so a config set that run_multi would refuse to
|
|
111
|
+
start is worse than no example at all.
|
|
112
|
+
"""
|
|
113
|
+
cfg_dir = Path(__file__).resolve().parents[1] / "workflow" / "config"
|
|
114
|
+
multi = yaml.safe_load((cfg_dir / "multi.yaml").read_text())
|
|
115
|
+
common = yaml.safe_load((cfg_dir.parent / multi["common"]).read_text())
|
|
116
|
+
paths = [cfg_dir.parent / p for p in multi["segmentations"]]
|
|
117
|
+
cfgs = [{**common, **yaml.safe_load(p.read_text())} for p in paths]
|
|
118
|
+
|
|
119
|
+
assert _validate_configs(paths, cfgs) == common["work_dir"]
|
|
120
|
+
# Every key convert reads comes from the shared file, not a per-config one.
|
|
121
|
+
for path in paths:
|
|
122
|
+
own = yaml.safe_load(path.read_text())
|
|
123
|
+
assert not set(own) & set(_CONVERT_KEYS), path.name
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Settings shared by every segmentation in config/multi.yaml.
|
|
2
|
+
#
|
|
3
|
+
# `common:` in multi.yaml points here, and run_multi passes it as the first of
|
|
4
|
+
# two --configfile values. Snakemake merges them in order with the later
|
|
5
|
+
# winning, so a per-config file only carries what actually differs from this
|
|
6
|
+
# one — the channel it reads, the method it uses, its label_name.
|
|
7
|
+
#
|
|
8
|
+
# Everything `convert` reads MUST live here rather than in a per-config file:
|
|
9
|
+
# the conversion runs once, up front, from the first config only, so a `shard`
|
|
10
|
+
# or `input` set on the second config would be silently ignored. run_multi
|
|
11
|
+
# refuses to start if those keys disagree across configs.
|
|
12
|
+
|
|
13
|
+
input: "/path/to/scan.ims"
|
|
14
|
+
work_dir: "/path/to/results"
|
|
15
|
+
|
|
16
|
+
# --- conversion (read once, in phase A, from the first config only) ---
|
|
17
|
+
reuse_pyramid: false
|
|
18
|
+
convert_chunks: null
|
|
19
|
+
# true → pack chunks into shards: same chunking and same memory, far fewer
|
|
20
|
+
# files. Worth turning on for anything large; convert warns when a store is
|
|
21
|
+
# heading past ~200,000 chunks, which a shared filesystem will not enjoy.
|
|
22
|
+
shard: false
|
|
23
|
+
|
|
24
|
+
# --- label pyramid (read by merge, i.e. once per config) ---
|
|
25
|
+
# Shared here for convenience, but a per-config file may override these: each
|
|
26
|
+
# segmentation builds its own label pyramid.
|
|
27
|
+
pyramid_levels: 5
|
|
28
|
+
pyramid_downscale: 2
|
|
29
|
+
|
|
30
|
+
# --- tiling (must match across configs so label arrays stay comparable) ---
|
|
31
|
+
level: 0
|
|
32
|
+
tile_shape: [16, 1024, 1024]
|
|
33
|
+
gpu_memory_gb: null
|
|
34
|
+
skip_empty: true
|
|
35
|
+
empty_threshold: null
|
|
36
|
+
# Tiles per SLURM job; they run sequentially and share one model load. Raise
|
|
37
|
+
# once you know a tile's runtime -- job wall time is ~N x per-tile time.
|
|
38
|
+
tiles_per_job: 4
|
|
39
|
+
|
|
40
|
+
# --- merge ---
|
|
41
|
+
sequential_labels: true
|
|
42
|
+
merge_workers: null
|
|
@@ -1,37 +1,24 @@
|
|
|
1
1
|
# Example segmentation config: cilia channel, via method: "custom" ->
|
|
2
2
|
# patchworks.plugins.dog.segment (deconvolution + difference-of-Gaussians).
|
|
3
3
|
# Cilia are thin/small structures a cell-body model like Cellpose isn't
|
|
4
|
-
# shaped for.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
4
|
+
# shaped for. Related to cyto/nuclei via config/multi.yaml to map each
|
|
5
|
+
# cilium to its containing cell / nucleus.
|
|
6
|
+
#
|
|
7
|
+
# Only what differs from config/common.yaml — the input, work_dir, tiling and
|
|
8
|
+
# conversion settings all live there and are merged in ahead of this file.
|
|
8
9
|
#
|
|
9
10
|
# pycudadecon is CUDA-only, so this segment job needs a GPU too — same
|
|
10
11
|
# profile/slurm as the Cellpose configs already covers it.
|
|
11
12
|
|
|
12
|
-
input: "/path/to/scan.ims"
|
|
13
|
-
work_dir: "/path/to/results"
|
|
14
|
-
|
|
15
|
-
reuse_pyramid: false
|
|
16
|
-
convert_chunks: null
|
|
17
|
-
shard: false
|
|
18
|
-
|
|
19
13
|
channel: 2 # cilia marker channel
|
|
20
|
-
|
|
21
|
-
tile_shape: [16, 1024, 1024] # keep identical to config_cyto.yaml/config_nuclei.yaml
|
|
22
|
-
gpu_memory_gb: null
|
|
14
|
+
|
|
23
15
|
# Per-axis halo [z, y, x], covering the PSF support (decon) + the DoG's
|
|
24
16
|
# high_sigma. Lateral 30 px at dxdata 0.1 = 3 um; at dzdata 0.2 that is 15
|
|
25
17
|
# z-planes -- almost the whole 16-plane tile. Deconvolution genuinely wants a
|
|
26
|
-
# deeper z tile than this: if cilia quality matters, raise tile_shape's z
|
|
27
|
-
#
|
|
28
|
-
#
|
|
18
|
+
# deeper z tile than this: if cilia quality matters, raise tile_shape's z in
|
|
19
|
+
# common.yaml (label_relations needs it identical across configs) rather than
|
|
20
|
+
# pushing the halo up against the tile depth.
|
|
29
21
|
overlap: [8, 30, 30]
|
|
30
|
-
skip_empty: true
|
|
31
|
-
# Tiles per SLURM job; they run sequentially and share one decon/GPU context.
|
|
32
|
-
# Raise once you know a tile's runtime -- job wall time is ~N x per-tile time.
|
|
33
|
-
tiles_per_job: 4
|
|
34
|
-
empty_threshold: null
|
|
35
22
|
|
|
36
23
|
method: "custom"
|
|
37
24
|
# dilate: 2 # optional: pixels to grow labels by after segmentation
|
|
@@ -58,8 +45,3 @@ custom:
|
|
|
58
45
|
wavelength: 525
|
|
59
46
|
na: 1.4
|
|
60
47
|
nimm: 1.515
|
|
61
|
-
|
|
62
|
-
pyramid_levels: 5
|
|
63
|
-
pyramid_downscale: 2
|
|
64
|
-
sequential_labels: true
|
|
65
|
-
merge_workers: null
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Example segmentation config: cytoplasm/membrane channel.
|
|
2
|
+
#
|
|
3
|
+
# Only what differs from config/common.yaml — the input, work_dir, tiling and
|
|
4
|
+
# conversion settings all live there and are merged in ahead of this file.
|
|
5
|
+
# Run it via config/multi.yaml, or on its own with both files:
|
|
6
|
+
#
|
|
7
|
+
# snakemake -s Snakefile --configfile config/common.yaml config/config_cyto.yaml
|
|
8
|
+
|
|
9
|
+
channel: 0 # cytoplasm/membrane channel
|
|
10
|
+
|
|
11
|
+
# Per-axis halo [z, y, x]. A scalar 30 would expand a [16, 1024, 1024] tile to
|
|
12
|
+
# 76 x 1084 x 1084 = 5.3x the voxels it keeps, nearly all of it wasted z. The
|
|
13
|
+
# z-halo only needs to cover one cell in z, not one cell in x/y.
|
|
14
|
+
overlap: [4, 30, 30]
|
|
15
|
+
|
|
16
|
+
method: "cellpose"
|
|
17
|
+
label_name: "cyto_labels"
|
|
18
|
+
cellpose:
|
|
19
|
+
model: "cyto3"
|
|
20
|
+
diameter: 30
|
|
21
|
+
do_3D: true
|
|
22
|
+
gpu: true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Example segmentation config: nuclei channel.
|
|
2
|
+
#
|
|
3
|
+
# Only what differs from config/common.yaml — the input, work_dir, tiling and
|
|
4
|
+
# conversion settings all live there and are merged in ahead of this file.
|
|
5
|
+
# Run it via config/multi.yaml, or on its own with both files:
|
|
6
|
+
#
|
|
7
|
+
# snakemake -s Snakefile --configfile config/common.yaml config/config_nuclei.yaml
|
|
8
|
+
|
|
9
|
+
channel: 1 # nuclear stain channel
|
|
10
|
+
|
|
11
|
+
# Per-axis halo [z, y, x]. A scalar 30 would expand a [16, 1024, 1024] tile to
|
|
12
|
+
# 76 x 1084 x 1084 = 5.3x the voxels it keeps, nearly all of it wasted z. The
|
|
13
|
+
# z-halo only needs to cover one nucleus in z, not one nucleus in x/y.
|
|
14
|
+
overlap: [4, 30, 30]
|
|
15
|
+
|
|
16
|
+
method: "cellpose"
|
|
17
|
+
label_name: "nuclei_labels"
|
|
18
|
+
cellpose:
|
|
19
|
+
model: "nuclei"
|
|
20
|
+
diameter: 15
|
|
21
|
+
do_3D: true
|
|
22
|
+
gpu: true
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
# `relations` (there's no automatic "chain"; list every pair explicitly, e.g.
|
|
14
14
|
# nuclei->cyto, nuclei->membrane, cyto->membrane).
|
|
15
15
|
|
|
16
|
+
# Settings every segmentation shares — the input, the work_dir, the tiling,
|
|
17
|
+
# everything `convert` reads. Merged in ahead of each config below, so a
|
|
18
|
+
# per-config file carries only what differs. Optional: drop this line and each
|
|
19
|
+
# config can be self-contained again.
|
|
20
|
+
common: config/common.yaml
|
|
21
|
+
|
|
16
22
|
segmentations:
|
|
17
23
|
- config/config_nuclei.yaml
|
|
18
24
|
- config/config_cyto.yaml
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
# Edit the partitions / account / GPU request for your cluster.
|
|
6
6
|
|
|
7
7
|
executor: slurm
|
|
8
|
+
# The SLURM executor names every job after its run UUID and rejects a
|
|
9
|
+
# --job-name in slurm_extra, so `squeue` shows nothing recognisable without
|
|
10
|
+
# this. The prefix goes first in the name (<prefix>_<uuid>), which is the part
|
|
11
|
+
# that survives truncation in a queue listing.
|
|
12
|
+
# Alphanumerics, underscores and hyphens only; 50 characters max.
|
|
13
|
+
# run_multi overrides this per config, so a three-way run shows pw-convert,
|
|
14
|
+
# then pw-nuclei_labels / pw-cyto_labels / pw-cilia_labels.
|
|
15
|
+
slurm-jobname-prefix: patchworks
|
|
8
16
|
# Max concurrent SLURM jobs Snakemake will have in flight at once. This is a
|
|
9
17
|
# submission-side cap, not a cluster limit — raising it just lets more
|
|
10
18
|
# "segment" tiles queue/run together; scicore's scheduler still enforces your
|
|
@@ -26,6 +26,7 @@ overlap, including b-objects with zero matches).
|
|
|
26
26
|
from __future__ import annotations
|
|
27
27
|
|
|
28
28
|
import argparse
|
|
29
|
+
import re
|
|
29
30
|
import subprocess
|
|
30
31
|
import sys
|
|
31
32
|
from pathlib import Path
|
|
@@ -47,25 +48,39 @@ def _snakemake_cmd(
|
|
|
47
48
|
state_dir: Path | None = None,
|
|
48
49
|
targets: list[str] | None = None,
|
|
49
50
|
extra: list[str] | None = None,
|
|
51
|
+
jobname_prefix: str | None = None,
|
|
52
|
+
common: Path | None = None,
|
|
50
53
|
) -> list[str]:
|
|
51
54
|
"""Build one snakemake invocation.
|
|
52
55
|
|
|
53
56
|
Every path is absolutised because ``--directory`` moves the working
|
|
54
57
|
directory: each config needs its own ``.snakemake`` state directory, or
|
|
55
58
|
concurrent runs would contend for the same ``.snakemake/locks/``.
|
|
59
|
+
|
|
60
|
+
*common*, when given, is passed as the first of two ``--configfile``
|
|
61
|
+
values. Snakemake merges them in order with the later winning, so the
|
|
62
|
+
settings every config shares -- the input, the work_dir, everything
|
|
63
|
+
``convert`` reads -- live in one file and the per-config file carries only
|
|
64
|
+
what actually differs.
|
|
56
65
|
"""
|
|
66
|
+
configfiles = [str(configfile.resolve())]
|
|
67
|
+
if common is not None:
|
|
68
|
+
configfiles.insert(0, str(common.resolve()))
|
|
57
69
|
cmd = [
|
|
58
70
|
"snakemake",
|
|
59
71
|
"-s",
|
|
60
72
|
str(workflow_dir / "Snakefile"),
|
|
61
73
|
"--configfile",
|
|
62
|
-
|
|
74
|
+
*configfiles,
|
|
63
75
|
]
|
|
64
76
|
if state_dir is not None:
|
|
65
77
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
66
78
|
cmd += ["--directory", str(state_dir.resolve())]
|
|
67
79
|
if profile:
|
|
68
80
|
cmd += ["--workflow-profile", str((workflow_dir / profile).resolve())]
|
|
81
|
+
if jobname_prefix:
|
|
82
|
+
# A SLURM-executor setting, so only valid alongside the profile.
|
|
83
|
+
cmd += ["--slurm-jobname-prefix", jobname_prefix]
|
|
69
84
|
else:
|
|
70
85
|
cmd += ["--cores", str(cores), "--rerun-triggers", "mtime"]
|
|
71
86
|
if dry_run:
|
|
@@ -79,11 +94,50 @@ def _snakemake_cmd(
|
|
|
79
94
|
return cmd
|
|
80
95
|
|
|
81
96
|
|
|
97
|
+
def slurm_jobname_prefix(label: str) -> str:
|
|
98
|
+
"""Sanitise *label* into a SLURM job-name prefix the executor accepts.
|
|
99
|
+
|
|
100
|
+
The SLURM executor names every job after its run UUID and refuses a
|
|
101
|
+
``--job-name`` in ``slurm_extra``, so a prefix is the only way to get
|
|
102
|
+
something recognisable into ``squeue``. It becomes ``<prefix>_<uuid>``,
|
|
103
|
+
which puts the readable part first -- the part that survives truncation
|
|
104
|
+
in a queue listing.
|
|
105
|
+
|
|
106
|
+
The executor requires alphanumerics, underscores and hyphens only, at
|
|
107
|
+
most 50 characters, and rejects the whole run otherwise.
|
|
108
|
+
|
|
109
|
+
Examples
|
|
110
|
+
--------
|
|
111
|
+
>>> slurm_jobname_prefix("nuclei_labels")
|
|
112
|
+
'pw-nuclei_labels'
|
|
113
|
+
>>> slurm_jobname_prefix("cilia/v2 (test)")
|
|
114
|
+
'pw-cilia-v2--test-'
|
|
115
|
+
"""
|
|
116
|
+
safe = re.sub(r"[^A-Za-z0-9_-]", "-", label)
|
|
117
|
+
return f"pw-{safe}"[:50]
|
|
118
|
+
|
|
119
|
+
|
|
82
120
|
def _run(cmd: list[str], workflow_dir: Path) -> int:
|
|
83
121
|
print(f"[run_multi] $ {' '.join(cmd)}", flush=True)
|
|
84
122
|
return subprocess.run(cmd, cwd=workflow_dir).returncode
|
|
85
123
|
|
|
86
124
|
|
|
125
|
+
# Exactly the config keys scripts/convert.py reads -- keep the two in step.
|
|
126
|
+
# Conversion runs once, in phase A, from the first config, so these have to
|
|
127
|
+
# agree across all of them or the disagreement is invisible.
|
|
128
|
+
#
|
|
129
|
+
# Deliberately NOT here: pyramid_levels / pyramid_downscale. Those are read by
|
|
130
|
+
# merge.py, which runs once per config and builds that config's own label
|
|
131
|
+
# pyramid, so they may legitimately differ.
|
|
132
|
+
_CONVERT_KEYS = (
|
|
133
|
+
"input",
|
|
134
|
+
"sequence_pattern",
|
|
135
|
+
"convert_chunks",
|
|
136
|
+
"shard",
|
|
137
|
+
"reuse_pyramid",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
87
141
|
def _validate_configs(paths: list[Path], cfgs: list[dict]) -> str:
|
|
88
142
|
"""Check the cross-config invariants before anything is submitted.
|
|
89
143
|
|
|
@@ -117,6 +171,20 @@ def _validate_configs(paths: list[Path], cfgs: list[dict]) -> str:
|
|
|
117
171
|
f"share a chunk layout; got {_spread(key)}"
|
|
118
172
|
)
|
|
119
173
|
|
|
174
|
+
# Phase A converts once, from the first config. Anything `convert` reads
|
|
175
|
+
# out of a later config is therefore silently ignored -- someone setting
|
|
176
|
+
# `shard: true` on the second config and watching a million files appear
|
|
177
|
+
# anyway has no way to see why. Refuse instead, and point at common.yaml.
|
|
178
|
+
for key in _CONVERT_KEYS:
|
|
179
|
+
values = {repr(cfg.get(key)) for cfg in cfgs}
|
|
180
|
+
if len(values) != 1:
|
|
181
|
+
problems.append(
|
|
182
|
+
f"{key} affects `convert`, which runs once from the first "
|
|
183
|
+
f"config, so the other values would be silently ignored; got "
|
|
184
|
+
f"{_spread(key)}. Put the settings every config shares in one "
|
|
185
|
+
f"file and point `common:` in multi.yaml at it."
|
|
186
|
+
)
|
|
187
|
+
|
|
120
188
|
for path, cfg in zip(paths, cfgs):
|
|
121
189
|
source = str(cfg.get("input", ""))
|
|
122
190
|
if any(ch in source for ch in "*?[") and not cfg.get(
|
|
@@ -193,7 +261,15 @@ def main() -> None:
|
|
|
193
261
|
seg_config_paths = [
|
|
194
262
|
_resolve(workflow_dir, c) for c in multi_cfg["segmentations"]
|
|
195
263
|
]
|
|
196
|
-
|
|
264
|
+
# Optional shared config: Snakemake merges --configfile values in order,
|
|
265
|
+
# so `common` holds what every segmentation agrees on and each per-config
|
|
266
|
+
# file overrides only what differs. Validation has to see the same merged
|
|
267
|
+
# view Snakemake will, or it would report a missing work_dir that is
|
|
268
|
+
# simply defined one file over.
|
|
269
|
+
common_path = multi_cfg.get("common")
|
|
270
|
+
common_path = _resolve(workflow_dir, common_path) if common_path else None
|
|
271
|
+
common_cfg = _load_yaml(common_path) if common_path else {}
|
|
272
|
+
seg_cfgs = [{**common_cfg, **_load_yaml(p)} for p in seg_config_paths]
|
|
197
273
|
work_dir = _validate_configs(seg_config_paths, seg_cfgs)
|
|
198
274
|
image_store = f"{work_dir}/image.zarr"
|
|
199
275
|
|
|
@@ -217,6 +293,7 @@ def main() -> None:
|
|
|
217
293
|
dry_run=False,
|
|
218
294
|
state_dir=state_dir,
|
|
219
295
|
extra=["--unlock"],
|
|
296
|
+
common=common_path,
|
|
220
297
|
),
|
|
221
298
|
workflow_dir,
|
|
222
299
|
)
|
|
@@ -235,6 +312,8 @@ def main() -> None:
|
|
|
235
312
|
dry_run=args.dry_run,
|
|
236
313
|
state_dir=Path(work_dir) / ".snakemake_convert",
|
|
237
314
|
targets=[f"{image_store}/zarr.json"],
|
|
315
|
+
jobname_prefix=slurm_jobname_prefix("convert"),
|
|
316
|
+
common=common_path,
|
|
238
317
|
),
|
|
239
318
|
workflow_dir,
|
|
240
319
|
)
|
|
@@ -287,6 +366,9 @@ def main() -> None:
|
|
|
287
366
|
cores=args.cores,
|
|
288
367
|
dry_run=args.dry_run,
|
|
289
368
|
state_dir=Path(cfg["work_dir"]) / cfg["label_name"] / ".snakemake",
|
|
369
|
+
# Names the config in squeue, so concurrent runs are tellable apart.
|
|
370
|
+
jobname_prefix=slurm_jobname_prefix(cfg["label_name"]),
|
|
371
|
+
common=common_path,
|
|
290
372
|
)
|
|
291
373
|
print(f"[run_multi] $ {' '.join(cmd)}", flush=True)
|
|
292
374
|
procs.append((cfg_path.name, subprocess.Popen(cmd, cwd=workflow_dir)))
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Example segmentation config: cytoplasm/membrane channel.
|
|
2
|
-
# Paired with config_nuclei.yaml via config/multi.yaml — see
|
|
3
|
-
# docs/guide/snakemake.md "Running two segmentations". Same work_dir as
|
|
4
|
-
# config_nuclei.yaml, and the same tile_shape so patchworks.label_relations()
|
|
5
|
-
# can compare the two label arrays chunk-for-chunk.
|
|
6
|
-
|
|
7
|
-
input: "/path/to/scan.ims"
|
|
8
|
-
work_dir: "/path/to/results"
|
|
9
|
-
|
|
10
|
-
reuse_pyramid: false
|
|
11
|
-
convert_chunks: null
|
|
12
|
-
shard: false
|
|
13
|
-
|
|
14
|
-
channel: 0 # cytoplasm/membrane channel
|
|
15
|
-
level: 0
|
|
16
|
-
tile_shape: [16, 1024, 1024] # keep identical to config_nuclei.yaml
|
|
17
|
-
gpu_memory_gb: null
|
|
18
|
-
# Per-axis halo [z, y, x]. A scalar 30 would expand a [16, 1024, 1024] tile to
|
|
19
|
-
# 76 x 1084 x 1084 = 5.3x the voxels it keeps, nearly all of it wasted z. The
|
|
20
|
-
# z-halo only needs to cover one cell in z, not one cell in x/y.
|
|
21
|
-
overlap: [4, 30, 30]
|
|
22
|
-
skip_empty: true
|
|
23
|
-
# Tiles per SLURM job; they run sequentially and share one model load. Raise
|
|
24
|
-
# once you know a tile's runtime -- job wall time is ~N x per-tile time.
|
|
25
|
-
tiles_per_job: 4
|
|
26
|
-
empty_threshold: null
|
|
27
|
-
|
|
28
|
-
method: "cellpose"
|
|
29
|
-
label_name: "cyto_labels"
|
|
30
|
-
cellpose:
|
|
31
|
-
model: "cyto3"
|
|
32
|
-
diameter: 30
|
|
33
|
-
do_3D: true
|
|
34
|
-
gpu: true
|
|
35
|
-
|
|
36
|
-
pyramid_levels: 5
|
|
37
|
-
pyramid_downscale: 2
|
|
38
|
-
sequential_labels: true
|
|
39
|
-
merge_workers: null
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Example segmentation config: nuclei channel.
|
|
2
|
-
# Paired with config_cyto.yaml via config/multi.yaml — see
|
|
3
|
-
# docs/guide/snakemake.md "Running two segmentations". Both configs share
|
|
4
|
-
# work_dir (and thus image.zarr) with config_cyto.yaml, but keep tile_shape
|
|
5
|
-
# identical across the two so patchworks.label_relations() can compare them.
|
|
6
|
-
|
|
7
|
-
input: "/path/to/scan.ims"
|
|
8
|
-
work_dir: "/path/to/results"
|
|
9
|
-
|
|
10
|
-
reuse_pyramid: false
|
|
11
|
-
convert_chunks: null
|
|
12
|
-
shard: false
|
|
13
|
-
|
|
14
|
-
channel: 1 # nuclear stain channel
|
|
15
|
-
level: 0
|
|
16
|
-
tile_shape: [16, 1024, 1024] # keep identical to config_cyto.yaml
|
|
17
|
-
gpu_memory_gb: null
|
|
18
|
-
# Per-axis halo [z, y, x]. A scalar 30 would expand a [16, 1024, 1024] tile to
|
|
19
|
-
# 76 x 1084 x 1084 = 5.3x the voxels it keeps, nearly all of it wasted z. The
|
|
20
|
-
# z-halo only needs to cover one nucleus in z, not one nucleus in x/y.
|
|
21
|
-
overlap: [4, 30, 30]
|
|
22
|
-
skip_empty: true
|
|
23
|
-
# Tiles per SLURM job; they run sequentially and share one model load. Raise
|
|
24
|
-
# once you know a tile's runtime -- job wall time is ~N x per-tile time.
|
|
25
|
-
tiles_per_job: 4
|
|
26
|
-
empty_threshold: null
|
|
27
|
-
|
|
28
|
-
method: "cellpose"
|
|
29
|
-
label_name: "nuclei_labels"
|
|
30
|
-
cellpose:
|
|
31
|
-
model: "nuclei"
|
|
32
|
-
diameter: 15
|
|
33
|
-
do_3D: true
|
|
34
|
-
gpu: true
|
|
35
|
-
|
|
36
|
-
pyramid_levels: 5
|
|
37
|
-
pyramid_downscale: 2
|
|
38
|
-
sequential_labels: true
|
|
39
|
-
merge_workers: null
|
|
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
|
|
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
|