patchworks 2.1.2__tar.gz → 2.2.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.
Files changed (95) hide show
  1. {patchworks-2.1.2 → patchworks-2.2.0}/PKG-INFO +1 -1
  2. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/label_relations.md +2 -2
  3. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/measurements.md +1 -1
  4. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/snakemake.md +21 -0
  5. patchworks-2.2.0/tests/test_run_multi.py +38 -0
  6. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/profile/slurm/config.yaml +8 -0
  7. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/scripts/run_multi.py +31 -0
  8. {patchworks-2.1.2 → patchworks-2.2.0}/.github/workflows/docs.yml +0 -0
  9. {patchworks-2.1.2 → patchworks-2.2.0}/.github/workflows/lint.yml +0 -0
  10. {patchworks-2.1.2 → patchworks-2.2.0}/.github/workflows/release.yml +0 -0
  11. {patchworks-2.1.2 → patchworks-2.2.0}/.gitignore +0 -0
  12. {patchworks-2.1.2 → patchworks-2.2.0}/.markdownlint-cli2.yaml +0 -0
  13. {patchworks-2.1.2 → patchworks-2.2.0}/LICENSE +0 -0
  14. {patchworks-2.1.2 → patchworks-2.2.0}/README.md +0 -0
  15. {patchworks-2.1.2 → patchworks-2.2.0}/cliff.toml +0 -0
  16. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/chunks.md +0 -0
  17. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/cluster.md +0 -0
  18. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/io.md +0 -0
  19. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/merge_tile_labels.md +0 -0
  20. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/plugins/cellpose.md +0 -0
  21. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/plugins/dog.md +0 -0
  22. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/plugins/napari.md +0 -0
  23. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/plugins/ome_zarr.md +0 -0
  24. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/postprocess.md +0 -0
  25. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/relabel.md +0 -0
  26. {patchworks-2.1.2 → patchworks-2.2.0}/docs/api/tile_process.md +0 -0
  27. {patchworks-2.1.2 → patchworks-2.2.0}/docs/assets/logo.png +0 -0
  28. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/cellpose_2d.md +0 -0
  29. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/cellpose_2d.py +0 -0
  30. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/cellpose_3d.md +0 -0
  31. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/cellpose_3d.py +0 -0
  32. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/custom.md +0 -0
  33. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/custom_method.py +0 -0
  34. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/dog.md +0 -0
  35. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/dog.py +0 -0
  36. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/standalone_merge.md +0 -0
  37. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/stardist.md +0 -0
  38. {patchworks-2.1.2 → patchworks-2.2.0}/docs/examples/stardist_2d.py +0 -0
  39. {patchworks-2.1.2 → patchworks-2.2.0}/docs/getting_started.md +0 -0
  40. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/custom_segmentation.md +0 -0
  41. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/gpu_distributed.md +0 -0
  42. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/merging.md +0 -0
  43. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/ome_zarr_napari.md +0 -0
  44. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/performance.md +0 -0
  45. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/pitfalls.md +0 -0
  46. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/skip_empty.md +0 -0
  47. {patchworks-2.1.2 → patchworks-2.2.0}/docs/guide/tiling.md +0 -0
  48. {patchworks-2.1.2 → patchworks-2.2.0}/docs/index.md +0 -0
  49. {patchworks-2.1.2 → patchworks-2.2.0}/mkdocs.yml +0 -0
  50. {patchworks-2.1.2 → patchworks-2.2.0}/pyproject.toml +0 -0
  51. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/__init__.py +0 -0
  52. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_chunks.py +0 -0
  53. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_cluster.py +0 -0
  54. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_core.py +0 -0
  55. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_distributed.py +0 -0
  56. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_gpu.py +0 -0
  57. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_io.py +0 -0
  58. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_merge.py +0 -0
  59. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_occupancy.py +0 -0
  60. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_postprocess.py +0 -0
  61. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_relabel.py +0 -0
  62. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/_relations.py +0 -0
  63. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/plugins/__init__.py +0 -0
  64. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/plugins/cellpose.py +0 -0
  65. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/plugins/dog.py +0 -0
  66. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/plugins/napari.py +0 -0
  67. {patchworks-2.1.2 → patchworks-2.2.0}/src/patchworks/plugins/ome_zarr.py +0 -0
  68. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_allocation.py +0 -0
  69. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_core.py +0 -0
  70. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_distributed.py +0 -0
  71. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_dog.py +0 -0
  72. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_gpu.py +0 -0
  73. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_napari.py +0 -0
  74. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_occupancy.py +0 -0
  75. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_ome_zarr.py +0 -0
  76. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_postprocess.py +0 -0
  77. {patchworks-2.1.2 → patchworks-2.2.0}/tests/test_relations.py +0 -0
  78. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/README.md +0 -0
  79. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/Snakefile +0 -0
  80. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/config/config.yaml +0 -0
  81. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/config/config_cilia.yaml +0 -0
  82. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/config/config_cyto.yaml +0 -0
  83. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/config/config_nuclei.yaml +0 -0
  84. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/config/multi.yaml +0 -0
  85. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/pixi.toml +0 -0
  86. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/rules/common.smk +0 -0
  87. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/rules/convert.smk +0 -0
  88. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/rules/merge.smk +0 -0
  89. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/rules/segment.smk +0 -0
  90. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/scripts/_pw.py +0 -0
  91. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/scripts/convert.py +0 -0
  92. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/scripts/fetch_model.py +0 -0
  93. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/scripts/merge.py +0 -0
  94. {patchworks-2.1.2 → patchworks-2.2.0}/workflow/scripts/prepare_tiles.py +0 -0
  95. {patchworks-2.1.2 → patchworks-2.2.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.1.2
3
+ Version: 2.2.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
@@ -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--relations) does
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--cytoplasm).
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--labels-in-napari).
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)
@@ -0,0 +1,38 @@
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
+ from run_multi import slurm_jobname_prefix # noqa: E402
12
+
13
+ # The SLURM executor's own rule (snakemake_executor_plugin_slurm): it raises a
14
+ # WorkflowError and aborts the whole run if the prefix does not match.
15
+ _EXECUTOR_RULE = re.compile(r"^[A-Za-z0-9_-]{1,50}$")
16
+
17
+
18
+ def test_jobname_prefix_satisfies_the_executor():
19
+ """Whatever a label_name contains, the prefix must stay submittable.
20
+
21
+ The executor names jobs after a UUID and refuses a --job-name override, so
22
+ this prefix is the only thing that makes squeue readable -- and an invalid
23
+ one fails the run rather than degrading.
24
+ """
25
+ for label in ("nuclei_labels", "cyto_labels", "convert", "a"):
26
+ assert _EXECUTOR_RULE.match(slurm_jobname_prefix(label))
27
+
28
+ # Characters a label might plausibly pick up are sanitised, not passed on.
29
+ assert _EXECUTOR_RULE.match(slurm_jobname_prefix("cilia/v2 (test)"))
30
+ assert _EXECUTOR_RULE.match(slurm_jobname_prefix("run 1: nuclei"))
31
+ # And an over-long label is truncated to the executor's 50-char limit.
32
+ assert _EXECUTOR_RULE.match(slurm_jobname_prefix("x" * 200))
33
+
34
+
35
+ def test_jobname_prefix_keeps_the_label_readable():
36
+ """The label must lead, since that is what a queue listing truncates to."""
37
+ assert slurm_jobname_prefix("nuclei_labels") == "pw-nuclei_labels"
38
+ assert slurm_jobname_prefix("convert") == "pw-convert"
@@ -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,6 +48,7 @@ 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,
50
52
  ) -> list[str]:
51
53
  """Build one snakemake invocation.
52
54
 
@@ -66,6 +68,9 @@ def _snakemake_cmd(
66
68
  cmd += ["--directory", str(state_dir.resolve())]
67
69
  if profile:
68
70
  cmd += ["--workflow-profile", str((workflow_dir / profile).resolve())]
71
+ if jobname_prefix:
72
+ # A SLURM-executor setting, so only valid alongside the profile.
73
+ cmd += ["--slurm-jobname-prefix", jobname_prefix]
69
74
  else:
70
75
  cmd += ["--cores", str(cores), "--rerun-triggers", "mtime"]
71
76
  if dry_run:
@@ -79,6 +84,29 @@ def _snakemake_cmd(
79
84
  return cmd
80
85
 
81
86
 
87
+ def slurm_jobname_prefix(label: str) -> str:
88
+ """Sanitise *label* into a SLURM job-name prefix the executor accepts.
89
+
90
+ The SLURM executor names every job after its run UUID and refuses a
91
+ ``--job-name`` in ``slurm_extra``, so a prefix is the only way to get
92
+ something recognisable into ``squeue``. It becomes ``<prefix>_<uuid>``,
93
+ which puts the readable part first -- the part that survives truncation
94
+ in a queue listing.
95
+
96
+ The executor requires alphanumerics, underscores and hyphens only, at
97
+ most 50 characters, and rejects the whole run otherwise.
98
+
99
+ Examples
100
+ --------
101
+ >>> slurm_jobname_prefix("nuclei_labels")
102
+ 'pw-nuclei_labels'
103
+ >>> slurm_jobname_prefix("cilia/v2 (test)")
104
+ 'pw-cilia-v2--test-'
105
+ """
106
+ safe = re.sub(r"[^A-Za-z0-9_-]", "-", label)
107
+ return f"pw-{safe}"[:50]
108
+
109
+
82
110
  def _run(cmd: list[str], workflow_dir: Path) -> int:
83
111
  print(f"[run_multi] $ {' '.join(cmd)}", flush=True)
84
112
  return subprocess.run(cmd, cwd=workflow_dir).returncode
@@ -235,6 +263,7 @@ def main() -> None:
235
263
  dry_run=args.dry_run,
236
264
  state_dir=Path(work_dir) / ".snakemake_convert",
237
265
  targets=[f"{image_store}/zarr.json"],
266
+ jobname_prefix=slurm_jobname_prefix("convert"),
238
267
  ),
239
268
  workflow_dir,
240
269
  )
@@ -287,6 +316,8 @@ def main() -> None:
287
316
  cores=args.cores,
288
317
  dry_run=args.dry_run,
289
318
  state_dir=Path(cfg["work_dir"]) / cfg["label_name"] / ".snakemake",
319
+ # Names the config in squeue, so concurrent runs are tellable apart.
320
+ jobname_prefix=slurm_jobname_prefix(cfg["label_name"]),
290
321
  )
291
322
  print(f"[run_multi] $ {' '.join(cmd)}", flush=True)
292
323
  procs.append((cfg_path.name, subprocess.Popen(cmd, cwd=workflow_dir)))
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