patchworks 0.11.18__tar.gz → 0.11.20__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.18 → patchworks-0.11.20}/PKG-INFO +3 -1
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/snakemake.md +43 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/pyproject.toml +4 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_relations.py +9 -3
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/plugins/napari.py +3 -1
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/README.md +9 -0
- patchworks-0.11.20/workflow/config/config_cyto.yaml +33 -0
- patchworks-0.11.20/workflow/config/config_nuclei.yaml +33 -0
- patchworks-0.11.20/workflow/config/multi.yaml +21 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/pixi.toml +14 -3
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/scripts/merge.py +3 -1
- patchworks-0.11.20/workflow/scripts/run_multi.py +160 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/.github/workflows/docs.yml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/.github/workflows/lint.yml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/.github/workflows/release.yml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/.gitignore +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/.markdownlint-cli2.yaml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/LICENSE +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/README.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/cliff.toml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/chunks.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/cluster.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/io.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/relabel.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/api/tile_process.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/assets/logo.png +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/custom.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/custom_method.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/stardist.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/getting_started.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/merging.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/performance.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/guide/tiling.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/docs/index.md +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/mkdocs.yml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/__init__.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_core.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_distributed.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_io.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_merge.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/tests/test_core.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/tests/test_distributed.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/tests/test_napari.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/tests/test_ome_zarr.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/tests/test_relations.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/Snakefile +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/config/config.yaml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/rules/common.smk +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/rules/convert.smk +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/rules/merge.smk +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/rules/segment.smk +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/scripts/_pw.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/scripts/convert.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-0.11.18 → patchworks-0.11.20}/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.20
|
|
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
|
|
@@ -34,6 +34,7 @@ Requires-Dist: glasbey; extra == 'all'
|
|
|
34
34
|
Requires-Dist: imaris-ims-file-reader; extra == 'all'
|
|
35
35
|
Requires-Dist: ipykernel<7; extra == 'all'
|
|
36
36
|
Requires-Dist: lxml-html-clean; extra == 'all'
|
|
37
|
+
Requires-Dist: napari-dask-ndmeasure>=0.1.0; extra == 'all'
|
|
37
38
|
Requires-Dist: napari[all]>=0.7.1; extra == 'all'
|
|
38
39
|
Requires-Dist: numpy<2.5; extra == 'all'
|
|
39
40
|
Requires-Dist: nvidia-ml-py; extra == 'all'
|
|
@@ -74,6 +75,7 @@ Provides-Extra: napari
|
|
|
74
75
|
Requires-Dist: glasbey; extra == 'napari'
|
|
75
76
|
Requires-Dist: ipykernel<7; extra == 'napari'
|
|
76
77
|
Requires-Dist: lxml-html-clean; extra == 'napari'
|
|
78
|
+
Requires-Dist: napari-dask-ndmeasure>=0.1.0; extra == 'napari'
|
|
77
79
|
Requires-Dist: napari[all]>=0.7.1; extra == 'napari'
|
|
78
80
|
Requires-Dist: numpy<2.5; extra == 'napari'
|
|
79
81
|
Provides-Extra: workflow
|
|
@@ -236,6 +236,12 @@ snakemake --workflow-profile profile/slurm --configfile config/config_nuclei.yam
|
|
|
236
236
|
snakemake --workflow-profile profile/slurm --configfile config/config_cyto.yaml
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
+
!!! tip "One command for several segmentations + relations"
|
|
240
|
+
`config/multi.yaml` lists any number of segmentation configs plus which
|
|
241
|
+
pairs to relate afterward; `pixi run multi` (or `multi-slurm`) runs them
|
|
242
|
+
in order and writes a CSV per pair — see *One command: multiple
|
|
243
|
+
segmentations + relations* below for the config format.
|
|
244
|
+
|
|
239
245
|
Both land side by side in the same store:
|
|
240
246
|
|
|
241
247
|
```text
|
|
@@ -281,6 +287,43 @@ with open("nuclei_to_cell.csv", "w", newline="") as f:
|
|
|
281
287
|
w.writerow([nucleus_id, m["match"], m["overlap_voxels"], m["overlap_fraction"]])
|
|
282
288
|
```
|
|
283
289
|
|
|
290
|
+
### One command: multiple segmentations + relations
|
|
291
|
+
|
|
292
|
+
`scripts/run_multi.py` (wired up as `pixi run multi`) sequences the above
|
|
293
|
+
manually: run every segmentation config listed, then compute + save every
|
|
294
|
+
configured relation — one command instead of juggling several `snakemake`
|
|
295
|
+
calls and a separate Python step.
|
|
296
|
+
|
|
297
|
+
```yaml
|
|
298
|
+
# config/multi.yaml
|
|
299
|
+
segmentations:
|
|
300
|
+
- config/config_nuclei.yaml
|
|
301
|
+
- config/config_cyto.yaml
|
|
302
|
+
|
|
303
|
+
relations:
|
|
304
|
+
- a: nuclei_labels
|
|
305
|
+
b: cyto_labels
|
|
306
|
+
output: nuclei_to_cyto.csv # written into work_dir
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
pixi run multi-dry # dry-run every segmentation config (skips relations)
|
|
311
|
+
pixi run multi # run locally
|
|
312
|
+
pixi run multi-slurm # submit every segmentation to SLURM
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Every listed segmentation config must share the same `work_dir` (so
|
|
316
|
+
`label_relations` has one `image.zarr` to read both label groups from) — the
|
|
317
|
+
script checks this and errors out otherwise. `relations` is optional; omit it
|
|
318
|
+
to just chain segmentations without a relation step.
|
|
319
|
+
|
|
320
|
+
Both lists are ordinary lists, so 3+ segmentations work the same way — add
|
|
321
|
+
more entries to `segmentations`, then list whichever pairs to relate. There's
|
|
322
|
+
no automatic "chain": list every pair explicitly, e.g. for nuclei + cyto +
|
|
323
|
+
membrane you'd add `nuclei_labels -> cyto_labels`, `nuclei_labels ->
|
|
324
|
+
membrane_labels`, and `cyto_labels -> membrane_labels` as three separate
|
|
325
|
+
entries under `relations`.
|
|
326
|
+
|
|
284
327
|
## Measurements (fast, whole-volume regionprops)
|
|
285
328
|
|
|
286
329
|
`skimage.measure.regionprops` needs the full labelled + intensity array in
|
|
@@ -67,12 +67,16 @@ imaris = ["imaris-ims-file-reader"]
|
|
|
67
67
|
# - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split
|
|
68
68
|
# into a separate package in lxml >= 5.2 (else ImportError on Viewer()).
|
|
69
69
|
# - glasbey: distinct high-contrast label LUTs for view_in_napari.
|
|
70
|
+
# - napari-dask-ndmeasure: out-of-core regionprops-style measurements
|
|
71
|
+
# (area/centroid/intensity stats) for huge Labels layers, straight off
|
|
72
|
+
# their backing dask/zarr arrays — the "Measure" dock widget.
|
|
70
73
|
napari = [
|
|
71
74
|
"napari[all]>=0.7.1",
|
|
72
75
|
"numpy<2.5",
|
|
73
76
|
"ipykernel<7",
|
|
74
77
|
"lxml-html-clean",
|
|
75
78
|
"glasbey",
|
|
79
|
+
"napari-dask-ndmeasure>=0.1.0",
|
|
76
80
|
]
|
|
77
81
|
# workflow runs the Snakemake pipeline (per-tile SLURM jobs across GPUs).
|
|
78
82
|
workflow = ["snakemake>=8", "snakemake-executor-plugin-slurm"]
|
|
@@ -14,7 +14,9 @@ import numpy as np
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def _as_dask(
|
|
17
|
+
def _as_dask(
|
|
18
|
+
source: Union["da.Array", str, Path], component: str
|
|
19
|
+
) -> "da.Array":
|
|
18
20
|
if isinstance(source, (str, Path)):
|
|
19
21
|
return da.from_zarr(str(source), component=component)
|
|
20
22
|
return source
|
|
@@ -96,7 +98,9 @@ def label_relations(
|
|
|
96
98
|
|
|
97
99
|
def _one(flat_idx: int) -> np.ndarray:
|
|
98
100
|
idx = np.unravel_index(flat_idx, n_blocks)
|
|
99
|
-
return _chunk_pairs(
|
|
101
|
+
return _chunk_pairs(
|
|
102
|
+
np.asarray(a.blocks[idx]), np.asarray(b.blocks[idx])
|
|
103
|
+
)
|
|
100
104
|
|
|
101
105
|
with ThreadPoolExecutor(max_workers=nw) as ex:
|
|
102
106
|
parts = list(ex.map(_one, range(total)))
|
|
@@ -130,7 +134,9 @@ def label_relations(
|
|
|
130
134
|
best[a_id] = (b_id, count)
|
|
131
135
|
|
|
132
136
|
logger.info(
|
|
133
|
-
"label_relations: %d a-labels matched across %d chunks",
|
|
137
|
+
"label_relations: %d a-labels matched across %d chunks",
|
|
138
|
+
len(best),
|
|
139
|
+
total,
|
|
134
140
|
)
|
|
135
141
|
return {
|
|
136
142
|
a_id: {
|
|
@@ -299,7 +299,9 @@ def view_in_napari(
|
|
|
299
299
|
img = _resolve_image(image, channel)
|
|
300
300
|
img_ndim = img[0].ndim if isinstance(img, list) else img.ndim
|
|
301
301
|
img_scale, img_units = (
|
|
302
|
-
_pyramid_calibration(image, img_ndim)
|
|
302
|
+
_pyramid_calibration(image, img_ndim)
|
|
303
|
+
if _is_zarr(image)
|
|
304
|
+
else (None, None)
|
|
303
305
|
)
|
|
304
306
|
viewer = napari.Viewer()
|
|
305
307
|
viewer.add_image(
|
|
@@ -86,6 +86,15 @@ the one converted `image.zarr` and land side by side in
|
|
|
86
86
|
for the two-config recipe and `patchworks.label_relations()` for mapping one
|
|
87
87
|
segmentation onto the other (e.g. nucleus → containing cell).
|
|
88
88
|
|
|
89
|
+
One command instead of juggling several manual runs: list your configs (and
|
|
90
|
+
which label pairs to relate) in `config/multi.yaml`, then:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pixi run multi-dry # dry-run every segmentation config
|
|
94
|
+
pixi run multi # run locally
|
|
95
|
+
pixi run multi-slurm # submit every segmentation to SLURM
|
|
96
|
+
```
|
|
97
|
+
|
|
89
98
|
## Output
|
|
90
99
|
|
|
91
100
|
`<work_dir>/image.zarr` — the image plus `labels/<label_name>/` (multi-scale,
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
overlap: 30
|
|
19
|
+
skip_empty: true
|
|
20
|
+
empty_threshold: null
|
|
21
|
+
|
|
22
|
+
method: "cellpose"
|
|
23
|
+
label_name: "cyto_labels"
|
|
24
|
+
cellpose:
|
|
25
|
+
model: "cyto3"
|
|
26
|
+
diameter: 30
|
|
27
|
+
do_3D: true
|
|
28
|
+
gpu: true
|
|
29
|
+
|
|
30
|
+
pyramid_levels: 5
|
|
31
|
+
pyramid_downscale: 2
|
|
32
|
+
sequential_labels: true
|
|
33
|
+
merge_workers: null
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
overlap: 30
|
|
19
|
+
skip_empty: true
|
|
20
|
+
empty_threshold: null
|
|
21
|
+
|
|
22
|
+
method: "cellpose"
|
|
23
|
+
label_name: "nuclei_labels"
|
|
24
|
+
cellpose:
|
|
25
|
+
model: "nuclei"
|
|
26
|
+
diameter: 15
|
|
27
|
+
do_3D: true
|
|
28
|
+
gpu: true
|
|
29
|
+
|
|
30
|
+
pyramid_levels: 5
|
|
31
|
+
pyramid_downscale: 2
|
|
32
|
+
sequential_labels: true
|
|
33
|
+
merge_workers: null
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Drives `pixi run multi` / `scripts/run_multi.py`: runs several segmentation
|
|
2
|
+
# configs (in order) against the same work_dir, then relates their labels by
|
|
3
|
+
# voxel overlap (patchworks.label_relations) and writes a CSV per pair.
|
|
4
|
+
#
|
|
5
|
+
# All listed segmentation configs must share the same work_dir/image.zarr and
|
|
6
|
+
# the same tile_shape/level, so the resulting label arrays are directly
|
|
7
|
+
# comparable — see docs/guide/snakemake.md "Running two segmentations".
|
|
8
|
+
#
|
|
9
|
+
# Both lists are plain lists: 3+ segmentations work the same way — add more
|
|
10
|
+
# entries to `segmentations`, then list whichever pairs you want related in
|
|
11
|
+
# `relations` (there's no automatic "chain"; list every pair explicitly, e.g.
|
|
12
|
+
# nuclei->cyto, nuclei->membrane, cyto->membrane).
|
|
13
|
+
|
|
14
|
+
segmentations:
|
|
15
|
+
- config/config_nuclei.yaml
|
|
16
|
+
- config/config_cyto.yaml
|
|
17
|
+
|
|
18
|
+
relations:
|
|
19
|
+
- a: nuclei_labels
|
|
20
|
+
b: cyto_labels
|
|
21
|
+
output: nuclei_to_cyto.csv
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# pixi environment for the patchworks Snakemake workflow.
|
|
2
2
|
# pixi install
|
|
3
|
-
# pixi run dry
|
|
4
|
-
# pixi run go
|
|
5
|
-
# pixi run slurm
|
|
3
|
+
# pixi run dry # dry-run (plan only)
|
|
4
|
+
# pixi run go # run locally
|
|
5
|
+
# pixi run slurm # submit to SLURM (edit profile/slurm/config.yaml first)
|
|
6
|
+
# pixi run multi # run several segmentations + relate their labels (local)
|
|
7
|
+
# pixi run multi-slurm # same, on SLURM
|
|
8
|
+
# pixi run multi-dry # dry-run every segmentation in config/multi.yaml
|
|
6
9
|
#
|
|
7
10
|
# On a cluster, put this project on a shared filesystem the compute nodes can
|
|
8
11
|
# read: the SLURM jobs re-launch snakemake from this env's interpreter.
|
|
@@ -38,3 +41,11 @@ cellpose4 = { features = ["cp4"], solve-group = "cp4" }
|
|
|
38
41
|
dry = "snakemake -s Snakefile --configfile config/config.yaml --rerun-triggers mtime -n -p"
|
|
39
42
|
go = "snakemake -s Snakefile --configfile config/config.yaml --rerun-triggers mtime --cores 8"
|
|
40
43
|
slurm = "snakemake --workflow-profile profile/slurm --configfile config/config.yaml"
|
|
44
|
+
|
|
45
|
+
# Run every segmentation config listed in config/multi.yaml (in order), then
|
|
46
|
+
# relate their labels by voxel overlap (patchworks.label_relations) and write
|
|
47
|
+
# a CSV per configured pair — see config/multi.yaml and
|
|
48
|
+
# docs/guide/snakemake.md "Running two segmentations".
|
|
49
|
+
multi-dry = "python scripts/run_multi.py --config config/multi.yaml -n"
|
|
50
|
+
multi = "python scripts/run_multi.py --config config/multi.yaml"
|
|
51
|
+
multi-slurm = "python scripts/run_multi.py --config config/multi.yaml --profile profile/slurm"
|
|
@@ -25,7 +25,9 @@ merged_store = str(Path(work_dir) / label_name / "_merged.zarr")
|
|
|
25
25
|
# whatever cpus_per_task the "merge" rule was actually allocated in the SLURM
|
|
26
26
|
# profile. Read the real allocation (SLURM_CPUS_PER_TASK) so the job uses all
|
|
27
27
|
# the cores it's paying for; merge_workers: in config.yaml can still override.
|
|
28
|
-
default_workers = int(
|
|
28
|
+
default_workers = int(
|
|
29
|
+
os.environ.get("SLURM_CPUS_PER_TASK", os.cpu_count() or 4)
|
|
30
|
+
)
|
|
29
31
|
merged = merge_tile_labels(
|
|
30
32
|
stage_path(work_dir, label_name),
|
|
31
33
|
write_to=merged_store,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Run several segmentation configs, then relate their labels by overlap.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python scripts/run_multi.py --config config/multi.yaml
|
|
5
|
+
python scripts/run_multi.py --config config/multi.yaml --profile profile/slurm
|
|
6
|
+
python scripts/run_multi.py --config config/multi.yaml -n # dry-run only
|
|
7
|
+
|
|
8
|
+
See config/multi.yaml and docs/guide/snakemake.md "Running two segmentations"
|
|
9
|
+
for the config format. Each listed segmentation config is run as an ordinary
|
|
10
|
+
`snakemake --configfile ...` invocation (this script is a thin sequencer, not
|
|
11
|
+
a Snakemake rule — the segmentations already namespace their own paths under
|
|
12
|
+
work_dir/<label_name>/, so running them one after another here is exactly
|
|
13
|
+
equivalent to running each snakemake command by hand). Once all segmentations
|
|
14
|
+
finish, each configured relation pair is computed via
|
|
15
|
+
patchworks.label_relations and written as a CSV in work_dir.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import csv
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import yaml
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_yaml(path: Path) -> dict:
|
|
30
|
+
return yaml.safe_load(path.read_text())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _run_snakemake(
|
|
34
|
+
configfile: Path,
|
|
35
|
+
*,
|
|
36
|
+
workflow_dir: Path,
|
|
37
|
+
profile: str | None,
|
|
38
|
+
cores: int,
|
|
39
|
+
dry_run: bool,
|
|
40
|
+
) -> None:
|
|
41
|
+
cmd = [
|
|
42
|
+
"snakemake",
|
|
43
|
+
"-s",
|
|
44
|
+
str(workflow_dir / "Snakefile"),
|
|
45
|
+
"--configfile",
|
|
46
|
+
str(configfile),
|
|
47
|
+
]
|
|
48
|
+
if profile:
|
|
49
|
+
cmd += ["--workflow-profile", profile]
|
|
50
|
+
else:
|
|
51
|
+
cmd += ["--cores", str(cores), "--rerun-triggers", "mtime"]
|
|
52
|
+
if dry_run:
|
|
53
|
+
cmd += ["-n", "-p"]
|
|
54
|
+
print(f"[run_multi] $ {' '.join(cmd)}", flush=True)
|
|
55
|
+
subprocess.run(cmd, check=True, cwd=workflow_dir)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _resolve(workflow_dir: Path, path_str: str) -> Path:
|
|
59
|
+
path = Path(path_str)
|
|
60
|
+
return path if path.is_absolute() else workflow_dir / path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> None:
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
description=__doc__,
|
|
66
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--config", required=True, help="multi-segmentation config YAML"
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--profile",
|
|
73
|
+
default=None,
|
|
74
|
+
help="Snakemake --workflow-profile (e.g. profile/slurm); omit to run locally",
|
|
75
|
+
)
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--cores",
|
|
78
|
+
type=int,
|
|
79
|
+
default=8,
|
|
80
|
+
help="local run: --cores (ignored with --profile)",
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"-n",
|
|
84
|
+
"--dry-run",
|
|
85
|
+
action="store_true",
|
|
86
|
+
help="pass -n -p to every Snakemake run; skips relations",
|
|
87
|
+
)
|
|
88
|
+
args = parser.parse_args()
|
|
89
|
+
|
|
90
|
+
workflow_dir = Path(__file__).resolve().parent.parent
|
|
91
|
+
multi_cfg_path = _resolve(workflow_dir, args.config)
|
|
92
|
+
multi_cfg = _load_yaml(multi_cfg_path)
|
|
93
|
+
|
|
94
|
+
seg_config_paths = [
|
|
95
|
+
_resolve(workflow_dir, c) for c in multi_cfg["segmentations"]
|
|
96
|
+
]
|
|
97
|
+
for cfg_path in seg_config_paths:
|
|
98
|
+
_run_snakemake(
|
|
99
|
+
cfg_path,
|
|
100
|
+
workflow_dir=workflow_dir,
|
|
101
|
+
profile=args.profile,
|
|
102
|
+
cores=args.cores,
|
|
103
|
+
dry_run=args.dry_run,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
relations = multi_cfg.get("relations", [])
|
|
107
|
+
if args.dry_run or not relations:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
seg_cfgs = [_load_yaml(p) for p in seg_config_paths]
|
|
111
|
+
work_dirs = {cfg["work_dir"] for cfg in seg_cfgs}
|
|
112
|
+
if len(work_dirs) != 1:
|
|
113
|
+
print(
|
|
114
|
+
f"[run_multi] ERROR: segmentation configs use different work_dir "
|
|
115
|
+
f"({sorted(work_dirs)}); label_relations needs one shared "
|
|
116
|
+
"image.zarr to compare against.",
|
|
117
|
+
file=sys.stderr,
|
|
118
|
+
)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
work_dir = work_dirs.pop()
|
|
121
|
+
image_store = f"{work_dir}/image.zarr"
|
|
122
|
+
|
|
123
|
+
import dask.array as da
|
|
124
|
+
|
|
125
|
+
from patchworks import label_relations
|
|
126
|
+
|
|
127
|
+
for rel in relations:
|
|
128
|
+
a_name, b_name = rel["a"], rel["b"]
|
|
129
|
+
out_path = Path(work_dir) / rel.get(
|
|
130
|
+
"output", f"{a_name}_to_{b_name}.csv"
|
|
131
|
+
)
|
|
132
|
+
print(f"[run_multi] relating {a_name} -> {b_name} …", flush=True)
|
|
133
|
+
a = da.from_zarr(image_store, component=f"labels/{a_name}/0")
|
|
134
|
+
b = da.from_zarr(image_store, component=f"labels/{b_name}/0")
|
|
135
|
+
table = label_relations(a, b)
|
|
136
|
+
|
|
137
|
+
with open(out_path, "w", newline="") as f:
|
|
138
|
+
writer = csv.writer(f)
|
|
139
|
+
writer.writerow(
|
|
140
|
+
[
|
|
141
|
+
f"{a_name}_id",
|
|
142
|
+
f"{b_name}_id",
|
|
143
|
+
"overlap_voxels",
|
|
144
|
+
"overlap_fraction",
|
|
145
|
+
]
|
|
146
|
+
)
|
|
147
|
+
for a_id, m in table.items():
|
|
148
|
+
writer.writerow(
|
|
149
|
+
[
|
|
150
|
+
a_id,
|
|
151
|
+
m["match"],
|
|
152
|
+
m["overlap_voxels"],
|
|
153
|
+
m["overlap_fraction"],
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
print(f"[run_multi] wrote {out_path} ({len(table)} rows)", flush=True)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
main()
|
|
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
|