patchworks 2.6.5__tar.gz → 2.6.6__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.6.5 → patchworks-2.6.6}/PKG-INFO +1 -1
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/label_relations.md +15 -1
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/snakemake.md +14 -12
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_run_multi.py +60 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/relate.py +35 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/.github/workflows/docs.yml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/.github/workflows/lint.yml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/.github/workflows/release.yml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/.gitignore +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/.markdownlint-cli2.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/LICENSE +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/README.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/cliff.toml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/chunks.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/cluster.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/io.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/plugins/dog.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/plugins/napari.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/postprocess.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/relabel.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/api/tile_process.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/assets/logo.png +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/custom.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/custom_method.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/dog.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/dog.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/standalone_merge.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/stardist.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/examples/stardist_2d.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/getting_started.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/measurements.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/merging.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/performance.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/pitfalls.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/skip_empty.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/guide/tiling.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/docs/index.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/mkdocs.yml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/pyproject.toml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/__init__.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_chunks.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_cluster.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_core.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_distributed.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_gpu.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_io.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_merge.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_notify.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_occupancy.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_postprocess.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_progress.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_relabel.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/_relations.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_allocation.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_core.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_distributed.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_dog.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_gpu.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_napari.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_notify.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_occupancy.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_ome_zarr.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_postprocess.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_progress.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/tests/test_relations.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/README.md +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/Snakefile +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/config/common.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/config/config.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/config/config_cilia.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/config/multi.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/pixi.toml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/rules/common.smk +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/rules/convert.smk +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/rules/merge.smk +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/rules/segment.smk +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/_pw.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/build_occupancy.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/convert.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/merge.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/run_multi.py +0 -0
- {patchworks-2.6.5 → patchworks-2.6.6}/workflow/scripts/segment_tile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.6
|
|
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
|
|
@@ -6,7 +6,21 @@ the other — by streaming both arrays chunk by chunk, so it scales to
|
|
|
6
6
|
hundreds of thousands of objects without loading anything fully into RAM.
|
|
7
7
|
|
|
8
8
|
Both label arrays must share the exact same chunk layout — same
|
|
9
|
-
`tile_shape`/pyramid `level` when they were produced.
|
|
9
|
+
`tile_shape`/pyramid `level` when they were produced. `label_relations()`
|
|
10
|
+
raises rather than silently doing something slower if they don't; rechunk
|
|
11
|
+
one side to match first (same shape, different chunking is a normal dask
|
|
12
|
+
op — some extra I/O reading across misaligned source chunks, not a
|
|
13
|
+
correctness issue):
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
if nuclei.chunks != cells.chunks:
|
|
17
|
+
cells = cells.rechunk(nuclei.chunks)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
(The cluster workflow's `run_multi.py`/`relate.py` does this automatically
|
|
21
|
+
— see below — so two configs segmented at different `tile_shape` still
|
|
22
|
+
relate correctly; you only need to do it by hand when calling
|
|
23
|
+
`label_relations()` directly.)
|
|
10
24
|
|
|
11
25
|
```python
|
|
12
26
|
import dask.array as da
|
|
@@ -398,20 +398,22 @@ results/image.zarr/labels/nuclei_labels/
|
|
|
398
398
|
results/image.zarr/labels/cyto_labels/
|
|
399
399
|
```
|
|
400
400
|
|
|
401
|
-
!!! tip "Keep `level` identical across configs; `tile_shape`
|
|
401
|
+
!!! tip "Keep `level` identical across configs; `tile_shape` doesn't have to match anymore"
|
|
402
402
|
Different segmentations of the same image can use different `channel`
|
|
403
403
|
and `cellpose:` settings freely, but keep `level` the same across
|
|
404
|
-
configs
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
404
|
+
configs so the label arrays cover the same voxel grid.
|
|
405
|
+
|
|
406
|
+
`tile_shape` itself no longer has to match. Two configs naturally
|
|
407
|
+
produce different on-disk chunk layouts when `tile_shape: "auto"`
|
|
408
|
+
charges a `nuclei_channel` config for a smaller tile than a
|
|
409
|
+
single-channel one, or when one config was segmented before the other's
|
|
410
|
+
settings changed, or a cheaper method (e.g. a DoG detector) sized its
|
|
411
|
+
own tile differently — `relate.py` detects a chunk mismatch per pair and
|
|
412
|
+
rechunks the finer side to match before relating, so this is no longer
|
|
413
|
+
something you need to plan around. (`run_multi.py` *also* auto-pins
|
|
414
|
+
every config under `"auto"` to one shared, smallest-computed tile
|
|
415
|
+
up front — mainly useful to keep segmentation itself GPU-efficient
|
|
416
|
+
across configs, not required for the relate step to work.)
|
|
415
417
|
|
|
416
418
|
See [Relating labels across segmentations](label_relations.md) for what
|
|
417
419
|
`label_relations()` returns and how to save it yourself — the cluster
|
|
@@ -8,6 +8,8 @@ sys.path.insert(
|
|
|
8
8
|
0, str(Path(__file__).resolve().parents[1] / "workflow" / "scripts")
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
import numpy as np # noqa: E402
|
|
12
|
+
import openpyxl # noqa: E402
|
|
11
13
|
import pytest # noqa: E402
|
|
12
14
|
import yaml # noqa: E402
|
|
13
15
|
|
|
@@ -186,6 +188,64 @@ def test_relate_script_has_the_real_bookkeeping():
|
|
|
186
188
|
assert "openpyxl" in src
|
|
187
189
|
|
|
188
190
|
|
|
191
|
+
def test_relate_rechunks_mismatched_label_arrays(tmp_path):
|
|
192
|
+
"""A chunk-layout mismatch must be rechunked away, not require a re-run.
|
|
193
|
+
|
|
194
|
+
Two configs are free to have segmented at different tile_shape (one
|
|
195
|
+
published before the other's config changed, or a cheaper method sized
|
|
196
|
+
its own tile differently) -- label_relations() itself refuses mismatched
|
|
197
|
+
chunks by design, but that only means the caller has to rechunk one side
|
|
198
|
+
first, not that the whole segmentation needs redoing.
|
|
199
|
+
"""
|
|
200
|
+
import zarr
|
|
201
|
+
|
|
202
|
+
from relate import run_relations
|
|
203
|
+
|
|
204
|
+
image_store = str(tmp_path / "image.zarr")
|
|
205
|
+
|
|
206
|
+
# a: labels 1 and 2, split at x=5. b: a single label 10 covering all of
|
|
207
|
+
# a's label 1 and none of label 2 -- built with a *different* chunking.
|
|
208
|
+
a_data = np.zeros((1, 10), dtype=np.int32)
|
|
209
|
+
a_data[0, :5] = 1
|
|
210
|
+
a_data[0, 5:] = 2
|
|
211
|
+
b_data = np.zeros((1, 10), dtype=np.int32)
|
|
212
|
+
b_data[0, :5] = 10
|
|
213
|
+
|
|
214
|
+
root = zarr.open_group(image_store, mode="w")
|
|
215
|
+
labels = root.require_group("labels")
|
|
216
|
+
a_grp = labels.require_group("nuclei_labels")
|
|
217
|
+
a_arr = a_grp.create_array(
|
|
218
|
+
name="0", shape=a_data.shape, chunks=(1, 2), dtype=np.int32
|
|
219
|
+
)
|
|
220
|
+
a_arr[:] = a_data
|
|
221
|
+
a_grp.attrs["sequential_labels"] = True
|
|
222
|
+
a_grp.attrs["n_objects"] = 2
|
|
223
|
+
|
|
224
|
+
b_grp = labels.require_group("cyto_labels")
|
|
225
|
+
b_arr = b_grp.create_array(
|
|
226
|
+
name="0", shape=b_data.shape, chunks=(1, 5), dtype=np.int32
|
|
227
|
+
)
|
|
228
|
+
b_arr[:] = b_data
|
|
229
|
+
b_grp.attrs["sequential_labels"] = True
|
|
230
|
+
b_grp.attrs["n_objects"] = 1
|
|
231
|
+
|
|
232
|
+
out_dir = tmp_path / "work"
|
|
233
|
+
out_dir.mkdir()
|
|
234
|
+
run_relations(
|
|
235
|
+
str(out_dir),
|
|
236
|
+
image_store,
|
|
237
|
+
[{"a": "nuclei_labels", "b": "cyto_labels", "output": "rel.xlsx"}],
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
wb = openpyxl.load_workbook(out_dir / "rel.xlsx")
|
|
241
|
+
rows = {
|
|
242
|
+
row[0]: (row[1], row[2], row[3])
|
|
243
|
+
for row in wb["nuclei_labels"].iter_rows(min_row=2, values_only=True)
|
|
244
|
+
}
|
|
245
|
+
assert rows[1] == (10, 5, 1.0) # label 1 fully inside b's label 10
|
|
246
|
+
assert rows[2] == (None, 0, 0) # label 2 touches nothing in b
|
|
247
|
+
|
|
248
|
+
|
|
189
249
|
def test_mixed_nuclei_channel_auto_passes_validation():
|
|
190
250
|
"""A channel-count mismatch under `tile_shape: "auto"` is no longer
|
|
191
251
|
|
|
@@ -16,9 +16,15 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
18
|
import json
|
|
19
|
+
import math
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def _num_chunks(arr: "da.Array") -> int: # noqa: F821 - dask imported lazily by callers
|
|
24
|
+
"""Total chunk count of a dask array, for picking the coarser side to rechunk to."""
|
|
25
|
+
return math.prod(len(c) for c in arr.chunks)
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def _label_ids(image_store: str, name: str) -> list[int]:
|
|
23
29
|
"""Ids present in a label image, without scanning the volume.
|
|
24
30
|
|
|
@@ -72,6 +78,35 @@ def run_relations(
|
|
|
72
78
|
print(f"[relate] relating {a_name} -> {b_name} …", flush=True)
|
|
73
79
|
a = da.from_zarr(image_store, component=f"labels/{a_name}/0")
|
|
74
80
|
b = da.from_zarr(image_store, component=f"labels/{b_name}/0")
|
|
81
|
+
|
|
82
|
+
# label_relations() requires matching chunk layouts (it walks both
|
|
83
|
+
# arrays block-by-block at the same index) but two configs are free
|
|
84
|
+
# to have segmented at different tile_shape -- e.g. one already
|
|
85
|
+
# published before the other's config changed, or a cheaper method
|
|
86
|
+
# naturally sized its tile differently. Same shape, different
|
|
87
|
+
# chunking is a normal dask op (extra I/O reading across misaligned
|
|
88
|
+
# source chunks, not a correctness issue), so rechunk the finer side
|
|
89
|
+
# to the coarser one here rather than require identical tile_shape
|
|
90
|
+
# across every config up front.
|
|
91
|
+
if a.chunks != b.chunks:
|
|
92
|
+
a_n, b_n = _num_chunks(a), _num_chunks(b)
|
|
93
|
+
if a_n <= b_n:
|
|
94
|
+
print(
|
|
95
|
+
f"[relate] {a_name} chunks {a.chunks} != {b_name} "
|
|
96
|
+
f"chunks {b.chunks}; rechunking {b_name} to match "
|
|
97
|
+
f"{a_name} (fewer chunks)",
|
|
98
|
+
flush=True,
|
|
99
|
+
)
|
|
100
|
+
b = b.rechunk(a.chunks)
|
|
101
|
+
else:
|
|
102
|
+
print(
|
|
103
|
+
f"[relate] {a_name} chunks {a.chunks} != {b_name} "
|
|
104
|
+
f"chunks {b.chunks}; rechunking {a_name} to match "
|
|
105
|
+
f"{b_name} (fewer chunks)",
|
|
106
|
+
flush=True,
|
|
107
|
+
)
|
|
108
|
+
a = a.rechunk(b.chunks)
|
|
109
|
+
|
|
75
110
|
table = label_relations(a, b)
|
|
76
111
|
|
|
77
112
|
# label_relations() only returns a-objects that touch a b-object.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|