patchworks 3.2.0__tar.gz → 3.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-3.2.0 → patchworks-3.3.0}/.github/workflows/test.yml +2 -4
- {patchworks-3.2.0 → patchworks-3.3.0}/PKG-INFO +2 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/dog.md +18 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/cli.md +10 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/membrane_cells.md +32 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/review.md +7 -3
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/snakemake.md +49 -18
- {patchworks-3.2.0 → patchworks-3.3.0}/pyproject.toml +2 -1
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_distributed.py +40 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_merge.py +253 -18
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_occupancy.py +1 -1
- patchworks-3.3.0/src/patchworks/_relations.py +329 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_review.py +43 -7
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_tables.py +187 -36
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_volume_filter.py +44 -8
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/cli.py +26 -1
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/dog.py +39 -1
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/ome_zarr.py +176 -8
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/plantseg.py +282 -20
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_distributed.py +163 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_dog.py +17 -0
- patchworks-3.3.0/tests/test_ngff_conformance.py +177 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_ome_zarr.py +4 -3
- patchworks-3.3.0/tests/test_plantseg.py +369 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_position.py +90 -7
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_pw.py +144 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_relations.py +59 -1
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_run_multi.py +614 -27
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_volume_filter.py +35 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/config.yaml +10 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/config_cilia.yaml +5 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/multi.yaml +5 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/pixi.toml +53 -17
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/rules/common.smk +3 -1
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/_pw.py +180 -2
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/export_iso.py +35 -3
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/merge.py +85 -28
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/prepare_tiles.py +12 -1
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/relate.py +124 -65
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/run_multi.py +455 -46
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/segment_tile.py +9 -0
- patchworks-3.2.0/src/patchworks/_relations.py +0 -194
- patchworks-3.2.0/tests/test_plantseg.py +0 -175
- {patchworks-3.2.0 → patchworks-3.3.0}/.github/min-versions.txt +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/.github/workflows/docs.yml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/.github/workflows/lint.yml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/.github/workflows/release.yml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/.gitignore +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/.markdownlint-cli2.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/.pre-commit-config.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/LICENSE +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/README.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/benchmarks/bench.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/benchmarks/compare.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/cliff.toml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/chunks.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/cluster.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/io.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/careamics.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/dog.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/napari.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/plantseg.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/plugins/watershed.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/postprocess.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/provenance.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/relabel.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/review.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/seams.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/tile_process.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/api/volume_filter.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/assets/logo.png +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/assets/review_panel.png +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/assets/review_position.png +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/custom.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/custom_method.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/dog.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/standalone_merge.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/stardist.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/examples/stardist_2d.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/getting_started.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/label_relations.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/launcher.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/measurements.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/merging.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/performance.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/pitfalls.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/skip_empty.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/guide/tiling.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/docs/index.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/mkdocs.yml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/__init__.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_autotune.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_chunks.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_cluster.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_core.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_gpu.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_io.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_notify.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_postprocess.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_progress.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_provenance.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_relabel.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/_seams.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/careamics.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/review.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/plugins/watershed.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/src/patchworks/py.typed +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.4/image.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.4/label.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.4/ome.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.5/_version.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.5/image.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.5/label.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/0.5/ome.schema +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/ngff_schemas/README.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_allocation.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_autotune.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_careamics.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_cellpose.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_cli.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_core.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_gpu.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_launcher.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_napari.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_notify.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_occupancy.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_postprocess.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_progress.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_remote.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_review.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_review_napari.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_seams.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_tables.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/tests/test_watershed.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/README.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/Snakefile +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/common.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/config_cyto_plantseg.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/config/multi_plantseg.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/launcher/README.md +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/launcher/app.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/launcher/clusters.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/launcher/launcher_core.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/launcher/requirements.txt +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/rules/convert.smk +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/rules/merge.smk +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/rules/segment.smk +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/build_occupancy.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/convert.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/reshard_store.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/scripts/view.py +0 -0
- {patchworks-3.2.0 → patchworks-3.3.0}/workflow/viewer/pixi.toml +0 -0
|
@@ -32,11 +32,9 @@ jobs:
|
|
|
32
32
|
run: pytest -q --doctest-modules src
|
|
33
33
|
|
|
34
34
|
other-platforms:
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# above once it is green.
|
|
35
|
+
# Green on macOS and Windows since 2026-10-07: a failure there now fails
|
|
36
|
+
# the build like any other.
|
|
38
37
|
runs-on: ${{ matrix.os }}
|
|
39
|
-
continue-on-error: true
|
|
40
38
|
strategy:
|
|
41
39
|
fail-fast: false
|
|
42
40
|
matrix:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.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
|
|
@@ -102,7 +102,7 @@ Provides-Extra: workflow
|
|
|
102
102
|
Requires-Dist: openpyxl; extra == 'workflow'
|
|
103
103
|
Requires-Dist: pandas>=2.0; extra == 'workflow'
|
|
104
104
|
Requires-Dist: scikit-image; extra == 'workflow'
|
|
105
|
-
Requires-Dist: snakemake-executor-plugin-slurm; extra == 'workflow'
|
|
105
|
+
Requires-Dist: snakemake-executor-plugin-slurm>=2.2; extra == 'workflow'
|
|
106
106
|
Requires-Dist: snakemake>=8; extra == 'workflow'
|
|
107
107
|
Description-Content-Type: text/markdown
|
|
108
108
|
|
|
@@ -54,6 +54,24 @@ directly to the DoG image — start near the DoG's typical peak value on a
|
|
|
54
54
|
known-positive region and adjust from there; there's no auto (Otsu-style)
|
|
55
55
|
option, since the DoG image isn't bimodal the way a raw intensity image is.
|
|
56
56
|
|
|
57
|
+
## Thin, oblique objects: `connectivity`
|
|
58
|
+
|
|
59
|
+
The thresholded voxels are joined into objects across shared **faces** by
|
|
60
|
+
default. A cilium lying obliquely is a staircase of voxels touching only
|
|
61
|
+
along edges or at corners, and comes out as a row of fragments.
|
|
62
|
+
`connectivity=2` also joins voxels sharing an edge, `connectivity=3` (3-D)
|
|
63
|
+
also a corner:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02, connectivity=3)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The merge has to join tiles the same way, or objects crossing a tile
|
|
70
|
+
boundary diagonally are split there: the workflow does this by itself; with
|
|
71
|
+
the API, pass the same value to `merge_tile_labels(..., connectivity=3)`.
|
|
72
|
+
The merged result is then exactly that of labelling the whole image at once,
|
|
73
|
+
whatever the tile size.
|
|
74
|
+
|
|
57
75
|
## GPU
|
|
58
76
|
|
|
59
77
|
```python
|
|
@@ -25,8 +25,18 @@ patchworks tables scan.zarr --relate nuclei:cells
|
|
|
25
25
|
|
|
26
26
|
# Look at the likely mistakes one by one and fix them (napari)
|
|
27
27
|
patchworks review scan.zarr --expect cells:nuclei=1
|
|
28
|
+
|
|
29
|
+
# Bring a store written by an older patchworks up to the OME-Zarr spec
|
|
30
|
+
patchworks fix-metadata scan.zarr
|
|
28
31
|
```
|
|
29
32
|
|
|
33
|
+
`patchworks fix-metadata` changes metadata only, in place: it gives every
|
|
34
|
+
OME-Zarr 0.5 array its `dimension_names` (required by 0.5; versions before
|
|
35
|
+
this one wrote none, which strict readers such as `ome-zarr-models` refuse),
|
|
36
|
+
and drops surplus coarse levels from a label image with more pyramid levels
|
|
37
|
+
than its image (the spec requires the same number). Running it twice
|
|
38
|
+
changes nothing the second time.
|
|
39
|
+
|
|
30
40
|
`patchworks review` without a window: `--summary` (counts and error
|
|
31
41
|
estimate), `--export DIR --format csv|xlsx|parquet` (corrected tables),
|
|
32
42
|
`--workbooks DIR` (relation workbooks), `--write-labels NAME` (a label image
|
|
@@ -68,6 +68,7 @@ gets closed by the partitioning, where Cellpose would merge across it.
|
|
|
68
68
|
PlantSeg is on conda-forge only, so it has its own pixi environment:
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
+
export CONDA_OVERRIDE_CUDA=12.0 # login node without a GPU: see below
|
|
71
72
|
pixi install -e plantseg
|
|
72
73
|
pixi run -e plantseg plantseg-fetch generic_confocal_3D_unet # once, with internet
|
|
73
74
|
pixi run -e plantseg multi-slurm
|
|
@@ -162,8 +163,37 @@ pixi run -e plantseg multi-plantseg-dry # check the plan
|
|
|
162
163
|
pixi run -e plantseg multi-plantseg-slurm # submit
|
|
163
164
|
```
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
Your own multi config with a PlantSeg segmentation in it runs the same
|
|
167
|
+
way, from the same environment:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pixi run -e plantseg multi-slurm --config /path/to/my_multi.yaml
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
!!! note "`Virtual package '__cuda' does not match` on a login node"
|
|
174
|
+
|
|
175
|
+
The `plantseg` environment holds a CUDA build of PyTorch, and pixi
|
|
176
|
+
refuses to install or run an environment needing CUDA on a machine with
|
|
177
|
+
no GPU driver -- a login node. Only the GPU jobs need CUDA, and they
|
|
178
|
+
start the environment's Python directly, without pixi. Tell pixi on the
|
|
179
|
+
login node that a driver is there:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
export CONDA_OVERRIDE_CUDA=12.0 # any 12.x; put it in ~/.bashrc
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
It must be set for every `pixi run -e plantseg ...` there, not only for
|
|
186
|
+
the install. Check the GPU nodes' driver supports CUDA 12 (`nvidia-smi`
|
|
187
|
+
in a GPU job prints "CUDA Version: 12.x" or later).
|
|
188
|
+
|
|
189
|
+
The `-e plantseg` matters: every job runs in the environment the command
|
|
190
|
+
was started from, and the default one has no PlantSeg. `run_multi` checks
|
|
191
|
+
this before converting anything and says which environment to use (as it
|
|
192
|
+
does for `denoise:`, which needs `-e careamics`).
|
|
193
|
+
|
|
194
|
+
The `plantseg` environment is the default one plus PlantSeg and cupy, so
|
|
195
|
+
the Cellpose run comes from it too, as does a cilia config with the DoG
|
|
196
|
+
plugin's `use_gpu: true` (cupy) and deconvolution. Afterwards, `nuclei_to_cyto.xlsx` gives
|
|
167
197
|
each nucleus its cell, and `pixi run -e viewer review <work_dir>/image.zarr`
|
|
168
198
|
lists any cell not holding exactly one nucleus.
|
|
169
199
|
|
|
@@ -223,9 +223,13 @@ image.zarr/labels/cilia_labels/
|
|
|
223
223
|
So it travels with the labels, including in a zip bundle, and it is
|
|
224
224
|
replaced whenever the labels are. A table computed from older labels is
|
|
225
225
|
recognised and ignored, never shown against the wrong segmentation.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
The table costs no extra read of the labels: each segment job measures
|
|
227
|
+
its tiles' objects as it writes them, and the merge adds those sums up per
|
|
228
|
+
merged object (an object cut by tile boundaries gets exactly the values it
|
|
229
|
+
would have measured whole). Intensity columns (`table_channels: [0, 2]`)
|
|
230
|
+
need the image, so with them the merged labels are measured once instead.
|
|
231
|
+
Turn tables off with `object_table: false` (see the
|
|
232
|
+
[workflow config](snakemake.md)).
|
|
229
233
|
|
|
230
234
|
For a store that has none, for example a run made before tables existed,
|
|
231
235
|
or labels from elsewhere:
|
|
@@ -363,8 +363,12 @@ shard_labels: false # true → also reshard label level 0 after the
|
|
|
363
363
|
one blob). Set either, both, or neither (`null`, the default, disables
|
|
364
364
|
each). Both run once on the **fully merged** image — not per tile, where
|
|
365
365
|
an object crossing a tile boundary would look smaller or larger than it
|
|
366
|
-
really is.
|
|
367
|
-
|
|
366
|
+
really is. The sizes come from what the segment jobs measured per tile,
|
|
367
|
+
added up per merged object, and the filter is applied in the merge's
|
|
368
|
+
own relabelling pass, so it costs no pass of its own over the image
|
|
369
|
+
(runs whose tiles were segmented by an older version filter the merged
|
|
370
|
+
labels in a separate pass instead). Every pyramid level reflects the
|
|
371
|
+
filtered result. It needs `image.zarr` to
|
|
368
372
|
carry a pixel size (the same calibration deconvolution's voxel sizes and
|
|
369
373
|
Cellpose's `anisotropy` are derived from — see the tip below); an
|
|
370
374
|
uncalibrated store raises rather than silently skipping the filter. See
|
|
@@ -650,6 +654,14 @@ running the workflow **twice with two configs against the same `work_dir`**
|
|
|
650
654
|
never collides: each run gets its own private subdirectory, and both reuse
|
|
651
655
|
the *same* already-converted `image.zarr` (conversion never re-runs).
|
|
652
656
|
|
|
657
|
+
`labels.done` is what makes Snakemake call a segmentation finished, while
|
|
658
|
+
the labels themselves are in `image.zarr/labels/<label_name>`. Deleting only
|
|
659
|
+
the latter leaves the run "done" with nothing re-making it: `run_multi`
|
|
660
|
+
refuses that up front and names the `work_dir/<label_name>` folder to remove
|
|
661
|
+
for a fresh segmentation. It also refuses a relation naming a label image
|
|
662
|
+
that no listed config makes and the store does not hold, and skips (with a
|
|
663
|
+
message) a relation whose labels are still missing once everything has run.
|
|
664
|
+
|
|
653
665
|
Most of what those configs contain is identical — the input, the `work_dir`,
|
|
654
666
|
the tiling, everything `convert` reads. Put it in **one** shared file and let
|
|
655
667
|
each config carry only what actually differs. Snakemake merges several
|
|
@@ -810,9 +822,10 @@ results/image.zarr/labels/cyto_labels/
|
|
|
810
822
|
rule, so it doesn't get a `log:` directive for free. Standalone (or
|
|
811
823
|
under plain `multi`), it writes to `<work_dir>/logs/relate.log`
|
|
812
824
|
(override with `relate.py --log`), the same tee-to-file-and-stdout
|
|
813
|
-
behaviour as the other steps. Under `multi-slurm`, where each
|
|
814
|
-
|
|
815
|
-
instead — `<work_dir>/logs/relate
|
|
825
|
+
behaviour as the other steps. Under `multi-slurm`, where each parent's
|
|
826
|
+
relations are one concurrent job, `run_multi.py` points each job at its
|
|
827
|
+
own file instead — `<work_dir>/logs/relate/to_<parent>.log`, or
|
|
828
|
+
`<a>_to_<b>.log` for a parent with a single child — so concurrent jobs
|
|
816
829
|
don't interleave into one log; check there instead of scrolling back
|
|
817
830
|
through `srun`'s live output.
|
|
818
831
|
|
|
@@ -856,6 +869,19 @@ pixi run multi # run locally
|
|
|
856
869
|
pixi run multi-slurm # submit every segmentation to SLURM
|
|
857
870
|
```
|
|
858
871
|
|
|
872
|
+
They read `config/multi.yaml` unless given another with `--config`:
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
pixi run multi-slurm --config /data/run42/multi.yaml
|
|
876
|
+
pixi run multi-dry --config my_multi.yaml # relative to where you run pixi
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
A relative `--config` is looked for in the directory you run `pixi` from,
|
|
880
|
+
then in `workflow/`. The `common:` and `segmentations:` paths inside it are
|
|
881
|
+
looked for next to the multi config first, then in `workflow/` -- so a run's
|
|
882
|
+
configs can live together in a folder of their own, beside the data, while
|
|
883
|
+
the shipped `config/multi.yaml` keeps working as it is.
|
|
884
|
+
|
|
859
885
|
Before anything is submitted, the script checks that every listed config
|
|
860
886
|
shares one `work_dir` (so `label_relations` has a single `image.zarr` to read
|
|
861
887
|
both label groups from), that `tile_shape` and `level` are identical (so the
|
|
@@ -893,7 +919,7 @@ abort the others; you get a per-config status and a non-zero exit.
|
|
|
893
919
|
# config/multi.yaml
|
|
894
920
|
relate:
|
|
895
921
|
qos: "1day"
|
|
896
|
-
time: 720 # minutes, per
|
|
922
|
+
time: 720 # minutes, per job (one per parent); under the QOS's MaxWall
|
|
897
923
|
```
|
|
898
924
|
|
|
899
925
|
A `--relate-*` flag overrides the block for that one key; anything the
|
|
@@ -902,18 +928,23 @@ abort the others; you get a per-config status and a non-zero exit.
|
|
|
902
928
|
default you meant to replace. Under plain `multi` (no `--profile`), relations
|
|
903
929
|
still run locally, in-process, one after another, as before.
|
|
904
930
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
931
|
+
The relations are grouped by parent: one job reads each parent label
|
|
932
|
+
image once for all its children (e.g. nuclei, cilia and the other cell
|
|
933
|
+
segmentation against `cyto_labels`), and only the chunks some child has
|
|
934
|
+
labels in. Each job logs its images' shape, chunking and object counts
|
|
935
|
+
before it starts, then a progress line roughly once a minute
|
|
936
|
+
(`label_relations: 412/3,600 (11%) after 7m, ~55m left`), so a long
|
|
937
|
+
relation is distinguishable from a hung one in `logs/relate/`.
|
|
938
|
+
|
|
939
|
+
Because each parent gets its own job, one running long does not starve
|
|
940
|
+
the others out of a shared time budget. `relate.py` skips a pair whose
|
|
941
|
+
workbook (the `.xlsx`, or the two `.csv` files a sheet too long for
|
|
942
|
+
Excel is written as) is already newer than both labels' merge marker,
|
|
943
|
+
and rewrites a missing workbook from the object tables when they
|
|
944
|
+
already hold the relation for the current labels, so **re-running the
|
|
945
|
+
exact same `multi-slurm` command only recomputes what's still missing
|
|
946
|
+
or stale** — delete a specific workbook to force just that one. The
|
|
947
|
+
bundle is likewise left alone when nothing in the store changed.
|
|
917
948
|
|
|
918
949
|
!!! tip "After a killed run"
|
|
919
950
|
Snakemake only releases its lock on a clean exit, so a run that was killed
|
|
@@ -119,7 +119,8 @@ napari = [
|
|
|
119
119
|
# workbook (per-object + per-container sheets) from the object tables.
|
|
120
120
|
workflow = [
|
|
121
121
|
"snakemake>=8",
|
|
122
|
-
|
|
122
|
+
# >=2.2: profile/slurm sets slurm-jobname-prefix, unknown before 2.2
|
|
123
|
+
"snakemake-executor-plugin-slurm>=2.2",
|
|
123
124
|
"openpyxl",
|
|
124
125
|
"pandas>=2.0",
|
|
125
126
|
# method: "threshold" and the nuclei-seeded watershed plugin
|
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import itertools
|
|
15
15
|
import logging
|
|
16
|
+
import os
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from typing import Callable, Sequence, Union
|
|
18
19
|
|
|
@@ -124,6 +125,7 @@ def create_stage(
|
|
|
124
125
|
*,
|
|
125
126
|
component: str = "staged",
|
|
126
127
|
dtype=np.int32,
|
|
128
|
+
zarr_format: int | None = None,
|
|
127
129
|
) -> str:
|
|
128
130
|
"""Create the empty (zero-filled) shared stage store for tiled writes.
|
|
129
131
|
|
|
@@ -140,19 +142,27 @@ def create_stage(
|
|
|
140
142
|
dtype : data-type, optional
|
|
141
143
|
Label dtype (default ``int32``). Tiles write local labels; the merge's
|
|
142
144
|
first pass renumbers them to a compact global range that fits int32.
|
|
145
|
+
zarr_format : int, optional
|
|
146
|
+
2 or 3. Give the image store's own format when staging straight into
|
|
147
|
+
its ``labels/<name>`` group: a v3 group inside a v2 (OME-Zarr 0.4)
|
|
148
|
+
store is something no reader can open. Default: zarr's.
|
|
143
149
|
|
|
144
150
|
Returns
|
|
145
151
|
-------
|
|
146
152
|
str
|
|
147
153
|
The stage store path.
|
|
148
154
|
"""
|
|
149
|
-
root = zarr.open_group(
|
|
155
|
+
root = zarr.open_group(
|
|
156
|
+
str(stage_path),
|
|
157
|
+
mode="w",
|
|
158
|
+
**({"zarr_format": zarr_format} if zarr_format else {}),
|
|
159
|
+
)
|
|
150
160
|
root.create_array(
|
|
151
161
|
name=component,
|
|
152
162
|
shape=shape,
|
|
153
163
|
chunks=tile_shape,
|
|
154
164
|
dtype=dtype,
|
|
155
|
-
**zarr_compressor_kwargs(),
|
|
165
|
+
**zarr_compressor_kwargs(root.metadata.zarr_format),
|
|
156
166
|
)
|
|
157
167
|
return str(stage_path)
|
|
158
168
|
|
|
@@ -168,6 +178,7 @@ def stage_tile(
|
|
|
168
178
|
component: str = "staged",
|
|
169
179
|
channel_axis: int | None = None,
|
|
170
180
|
halo_dir: Union[str, Path, None] = None,
|
|
181
|
+
parts_dir: Union[str, Path, None] = None,
|
|
171
182
|
) -> int:
|
|
172
183
|
"""Run *fn* on a single tile and write it into the shared stage store.
|
|
173
184
|
|
|
@@ -208,6 +219,13 @@ def stage_tile(
|
|
|
208
219
|
(keys ``"<axis>+"``/``"<axis>-"``), labelled with the same ``1..n``
|
|
209
220
|
ids as the staged core. An object seen only in the halo is 0 there:
|
|
210
221
|
it has no id in this tile. ``None`` (default) keeps nothing.
|
|
222
|
+
parts_dir : str or Path, optional
|
|
223
|
+
Also measure the tile's objects as written (size, centroid, spread,
|
|
224
|
+
bounding box; :func:`patchworks._tables.tile_partial`) into
|
|
225
|
+
``<parts_dir>/<index>.npz``, keyed by the staged ``1..n`` ids. The
|
|
226
|
+
merge combines them into the object table without reading the
|
|
227
|
+
labels again (``merge_tile_labels(..., parts_dir=...)``). A tile
|
|
228
|
+
without labels writes none.
|
|
211
229
|
|
|
212
230
|
Returns
|
|
213
231
|
-------
|
|
@@ -272,9 +290,29 @@ def stage_tile(
|
|
|
272
290
|
n_labels = int(trimmed.max())
|
|
273
291
|
dst = zarr.open_group(str(stage_path), mode="r+")[component]
|
|
274
292
|
dst[sl] = trimmed.astype(dst.dtype)
|
|
293
|
+
if parts_dir is not None and n_labels:
|
|
294
|
+
_save_partial(trimmed, sl, parts_dir, index)
|
|
275
295
|
return n_labels
|
|
276
296
|
|
|
277
297
|
|
|
298
|
+
def _save_partial(trimmed, sl, parts_dir, index) -> None:
|
|
299
|
+
"""Write one tile's per-object sums to ``<parts_dir>/<index>.npz``.
|
|
300
|
+
|
|
301
|
+
Through a temporary name and a rename, so a job killed mid-write leaves
|
|
302
|
+
no truncated file for the merge to trust.
|
|
303
|
+
"""
|
|
304
|
+
from ._tables import tile_partial
|
|
305
|
+
|
|
306
|
+
part = tile_partial(trimmed, [s.start for s in sl])
|
|
307
|
+
if part is None:
|
|
308
|
+
return
|
|
309
|
+
parts_dir = Path(parts_dir)
|
|
310
|
+
parts_dir.mkdir(parents=True, exist_ok=True)
|
|
311
|
+
tmp = parts_dir / f".{int(index)}.tmp.npz"
|
|
312
|
+
np.savez(tmp, **part)
|
|
313
|
+
os.replace(tmp, parts_dir / f"{int(index)}.npz")
|
|
314
|
+
|
|
315
|
+
|
|
278
316
|
def _core_lut(core: np.ndarray, max_id: int) -> np.ndarray:
|
|
279
317
|
"""LUT renumbering *core*'s ids to ``1..n`` (as ``relabel_sequential``);
|
|
280
318
|
ids absent from the core map to 0."""
|