patchworks 2.1.0__tar.gz → 2.1.1__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.0 → patchworks-2.1.1}/PKG-INFO +1 -1
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/snakemake.md +15 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/plugins/ome_zarr.py +16 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_ome_zarr.py +16 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/scripts/run_multi.py +57 -1
- {patchworks-2.1.0 → patchworks-2.1.1}/.github/workflows/docs.yml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/.github/workflows/lint.yml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/.github/workflows/release.yml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/.gitignore +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/.markdownlint-cli2.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/LICENSE +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/README.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/cliff.toml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/chunks.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/cluster.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/io.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/plugins/dog.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/plugins/napari.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/postprocess.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/relabel.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/api/tile_process.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/assets/logo.png +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/custom.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/custom_method.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/dog.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/dog.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/standalone_merge.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/stardist.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/examples/stardist_2d.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/getting_started.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/label_relations.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/measurements.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/merging.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/performance.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/pitfalls.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/skip_empty.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/guide/tiling.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/docs/index.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/mkdocs.yml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/pyproject.toml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/__init__.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_chunks.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_cluster.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_core.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_distributed.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_gpu.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_io.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_merge.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_occupancy.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_postprocess.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_relabel.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/_relations.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_allocation.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_core.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_distributed.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_dog.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_gpu.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_napari.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_occupancy.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_postprocess.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/tests/test_relations.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/README.md +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/Snakefile +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/config/config.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/config/config_cilia.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/config/multi.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/pixi.toml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/rules/common.smk +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/rules/convert.smk +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/rules/merge.smk +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/rules/segment.smk +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/scripts/_pw.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/scripts/convert.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/scripts/merge.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-2.1.0 → patchworks-2.1.1}/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.
|
|
3
|
+
Version: 2.1.1
|
|
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
|
|
@@ -346,6 +346,19 @@ the GPU partition stays busy instead of idling through every config's
|
|
|
346
346
|
`prepare` and multi-hour `merge` in turn. A config that fails does **not**
|
|
347
347
|
abort the others; you get a per-config status and a non-zero exit.
|
|
348
348
|
|
|
349
|
+
!!! tip "After a killed run"
|
|
350
|
+
Snakemake only releases its lock on a clean exit, so a run that was killed
|
|
351
|
+
(Ctrl-C, an SSH drop, an OOM) leaves the directory locked. Each phase has
|
|
352
|
+
its own state directory, so releasing them by hand means reconstructing
|
|
353
|
+
several paths — use the flag instead:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
pixi run multi -- --unlock # then re-run normally
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Running the orchestrator under `tmux` avoids most of these in the first
|
|
360
|
+
place: it survives a dropped connection.
|
|
361
|
+
|
|
349
362
|
!!! warning "`jobs:` is per config"
|
|
350
363
|
The profile's `jobs:` caps one Snakemake process. Running three configs
|
|
351
364
|
concurrently can therefore have 3× that many jobs in flight — lower it if
|
|
@@ -450,6 +463,8 @@ prologue. The simplest path is a single shared env that the compute nodes see.
|
|
|
450
463
|
| Symptom | Fix |
|
|
451
464
|
|---------|-----|
|
|
452
465
|
| `snakemake: command not found` | use `python -m snakemake` |
|
|
466
|
+
| `Directory cannot be locked` | a previous run was killed instead of exiting cleanly. For a multi-run: `pixi run multi -- --unlock` (it covers every state directory, including the conversion one). For a single config: add `--unlock --directory <the same one you ran with>` |
|
|
467
|
+
| `BioImage does not support the image: '.../*tif'` | a glob input needs `sequence_pattern` — see [A folder of single-plane TIFFs](ome_zarr_napari.md#a-folder-of-single-plane-tiffs) |
|
|
453
468
|
| Segment jobs pend forever | wrong `slurm_partition`/GPU request; on scicore use `gres: "gpu:1"` |
|
|
454
469
|
| Segment dies, `Network is unreachable` | offline GPU nodes — the `fetch_model` localrule caches the model on the submit host first; if it still fails, your submit host has no network either (pre-download manually) |
|
|
455
470
|
| `cellpose is not installed` in a job | the job's env lacks `patchworks[cellpose]` |
|
|
@@ -45,6 +45,7 @@ Usage
|
|
|
45
45
|
|
|
46
46
|
from __future__ import annotations
|
|
47
47
|
|
|
48
|
+
import glob
|
|
48
49
|
import logging
|
|
49
50
|
import math
|
|
50
51
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -1201,6 +1202,21 @@ def _to_dask(
|
|
|
1201
1202
|
if sequence_pattern is not None:
|
|
1202
1203
|
arr, detected, ps = _open_tiff_sequence(path, sequence_pattern)
|
|
1203
1204
|
return arr, axes or detected, ps
|
|
1205
|
+
if any(ch in path for ch in "*?[") or (
|
|
1206
|
+
not Path(path).exists() and glob.glob(path)
|
|
1207
|
+
):
|
|
1208
|
+
# Without sequence_pattern this would be handed to bioio, which reads
|
|
1209
|
+
# single files and reports only that it cannot handle the "format" --
|
|
1210
|
+
# pointing at the glob rather than the missing setting.
|
|
1211
|
+
raise ValueError(
|
|
1212
|
+
f"{path!r} looks like a glob over several files, but "
|
|
1213
|
+
"sequence_pattern is not set, so there is no way to tell which "
|
|
1214
|
+
"part of each filename is Z, C or T.\n"
|
|
1215
|
+
"Set it to a regex with named groups, e.g.\n"
|
|
1216
|
+
" sequence_pattern: '_Z(?P<Z>\\d+)_C(?P<C>\\d+)_V\\d+'\n"
|
|
1217
|
+
"for files like sample_Z001_C0_V0.tif. Pass a single file "
|
|
1218
|
+
"instead if you did not mean a sequence."
|
|
1219
|
+
)
|
|
1204
1220
|
if path.endswith(".zarr"):
|
|
1205
1221
|
arr = load_ome_zarr(source, channel=None)
|
|
1206
1222
|
ax = axes or _default_axes(arr.ndim)
|
|
@@ -399,3 +399,19 @@ def test_sharding(tmp_path):
|
|
|
399
399
|
assert (
|
|
400
400
|
getattr(_zarr.open_array(f"{out3}/0", mode="r"), "shards", None) is None
|
|
401
401
|
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def test_glob_without_sequence_pattern_says_so(tmp_path):
|
|
405
|
+
"""A glob input must name the missing setting, not blame the format.
|
|
406
|
+
|
|
407
|
+
Without sequence_pattern the glob fell through to bioio, which reads one
|
|
408
|
+
file and could only report "does not support the image" -- pointing at the
|
|
409
|
+
path instead of at the config key that was missing.
|
|
410
|
+
"""
|
|
411
|
+
with pytest.raises(ValueError, match="sequence_pattern is not set"):
|
|
412
|
+
to_ome_zarr(str(tmp_path / "*.tif"), tmp_path / "out.zarr")
|
|
413
|
+
|
|
414
|
+
# A real single file must still reach the normal readers.
|
|
415
|
+
with pytest.raises(Exception) as exc:
|
|
416
|
+
to_ome_zarr(str(tmp_path / "scan.ims"), tmp_path / "o2.zarr")
|
|
417
|
+
assert "sequence_pattern" not in str(exc.value)
|
|
@@ -46,6 +46,7 @@ def _snakemake_cmd(
|
|
|
46
46
|
dry_run: bool,
|
|
47
47
|
state_dir: Path | None = None,
|
|
48
48
|
targets: list[str] | None = None,
|
|
49
|
+
extra: list[str] | None = None,
|
|
49
50
|
) -> list[str]:
|
|
50
51
|
"""Build one snakemake invocation.
|
|
51
52
|
|
|
@@ -69,6 +70,8 @@ def _snakemake_cmd(
|
|
|
69
70
|
cmd += ["--cores", str(cores), "--rerun-triggers", "mtime"]
|
|
70
71
|
if dry_run:
|
|
71
72
|
cmd += ["-n", "-p"]
|
|
73
|
+
if extra:
|
|
74
|
+
cmd += extra
|
|
72
75
|
if targets:
|
|
73
76
|
# "--" ends option parsing: --rerun-triggers takes a variable number
|
|
74
77
|
# of values and would otherwise swallow the target path.
|
|
@@ -114,6 +117,18 @@ def _validate_configs(paths: list[Path], cfgs: list[dict]) -> str:
|
|
|
114
117
|
f"share a chunk layout; got {_spread(key)}"
|
|
115
118
|
)
|
|
116
119
|
|
|
120
|
+
for path, cfg in zip(paths, cfgs):
|
|
121
|
+
source = str(cfg.get("input", ""))
|
|
122
|
+
if any(ch in source for ch in "*?[") and not cfg.get(
|
|
123
|
+
"sequence_pattern"
|
|
124
|
+
):
|
|
125
|
+
problems.append(
|
|
126
|
+
f"{path.name}: input {source!r} is a glob over several files "
|
|
127
|
+
"but sequence_pattern is unset, so nothing says which part of "
|
|
128
|
+
"each filename is Z/C/T. Set e.g. "
|
|
129
|
+
r"sequence_pattern: '_Z(?P<Z>\d+)_C(?P<C>\d+)_V\d+'"
|
|
130
|
+
)
|
|
131
|
+
|
|
117
132
|
names = [cfg.get("label_name") for cfg in cfgs]
|
|
118
133
|
duplicates = {n for n in names if names.count(n) > 1}
|
|
119
134
|
if duplicates:
|
|
@@ -160,6 +175,15 @@ def main() -> None:
|
|
|
160
175
|
action="store_true",
|
|
161
176
|
help="pass -n -p to every Snakemake run; skips relations",
|
|
162
177
|
)
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
"--unlock",
|
|
180
|
+
action="store_true",
|
|
181
|
+
help=(
|
|
182
|
+
"release stale Snakemake locks in every state directory this "
|
|
183
|
+
"script manages, then exit. Needed after a run was killed or "
|
|
184
|
+
"died: the lock is only released on a clean exit."
|
|
185
|
+
),
|
|
186
|
+
)
|
|
163
187
|
args = parser.parse_args()
|
|
164
188
|
|
|
165
189
|
workflow_dir = Path(__file__).resolve().parent.parent
|
|
@@ -173,6 +197,32 @@ def main() -> None:
|
|
|
173
197
|
work_dir = _validate_configs(seg_config_paths, seg_cfgs)
|
|
174
198
|
image_store = f"{work_dir}/image.zarr"
|
|
175
199
|
|
|
200
|
+
# Each phase gets its own Snakemake state directory (the lock lives in the
|
|
201
|
+
# working directory, not the config), so unlocking has to cover all of
|
|
202
|
+
# them -- and nobody should have to reconstruct these paths by hand.
|
|
203
|
+
state_dirs = [Path(work_dir) / ".snakemake_convert"] + [
|
|
204
|
+
Path(cfg["work_dir"]) / cfg["label_name"] / ".snakemake"
|
|
205
|
+
for cfg in seg_cfgs
|
|
206
|
+
]
|
|
207
|
+
if args.unlock:
|
|
208
|
+
for state_dir in state_dirs:
|
|
209
|
+
if not state_dir.exists():
|
|
210
|
+
continue
|
|
211
|
+
_run(
|
|
212
|
+
_snakemake_cmd(
|
|
213
|
+
seg_config_paths[0],
|
|
214
|
+
workflow_dir=workflow_dir,
|
|
215
|
+
profile=args.profile,
|
|
216
|
+
cores=args.cores,
|
|
217
|
+
dry_run=False,
|
|
218
|
+
state_dir=state_dir,
|
|
219
|
+
extra=["--unlock"],
|
|
220
|
+
),
|
|
221
|
+
workflow_dir,
|
|
222
|
+
)
|
|
223
|
+
print("[run_multi] unlocked; re-run without --unlock", flush=True)
|
|
224
|
+
return
|
|
225
|
+
|
|
176
226
|
# Phase A: convert exactly once. The three runs are about to go concurrent
|
|
177
227
|
# and `convert` writes with overwrite=True, so letting them race on it
|
|
178
228
|
# would have them clobbering one store. Ask for its marker explicitly.
|
|
@@ -189,7 +239,13 @@ def main() -> None:
|
|
|
189
239
|
workflow_dir,
|
|
190
240
|
)
|
|
191
241
|
if rc != 0:
|
|
192
|
-
print(
|
|
242
|
+
print(
|
|
243
|
+
"[run_multi] ERROR: conversion failed.\n"
|
|
244
|
+
" If the log says the directory cannot be locked, a previous run "
|
|
245
|
+
"was killed rather than exiting cleanly; release it with:\n"
|
|
246
|
+
f" {Path(sys.argv[0]).name} --config {args.config} --unlock",
|
|
247
|
+
file=sys.stderr,
|
|
248
|
+
)
|
|
193
249
|
sys.exit(rc)
|
|
194
250
|
|
|
195
251
|
# Still phase A: build the occupancy map here too. Every config's `prepare`
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|