patchworks 0.11.12__tar.gz → 0.11.13__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.12 → patchworks-0.11.13}/PKG-INFO +1 -1
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_distributed.py +4 -1
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_merge.py +66 -13
- {patchworks-0.11.12 → patchworks-0.11.13}/tests/test_distributed.py +30 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/.github/workflows/docs.yml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/.github/workflows/lint.yml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/.github/workflows/release.yml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/.gitignore +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/.markdownlint-cli2.yaml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/README.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/cliff.toml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/chunks.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/cluster.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/io.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/relabel.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/api/tile_process.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/assets/logo.png +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/custom.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/custom_method.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/stardist.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/getting_started.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/merging.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/performance.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/snakemake.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/guide/tiling.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/docs/index.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/mkdocs.yml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/pyproject.toml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/__init__.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_core.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_io.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/tests/test_core.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/tests/test_napari.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/tests/test_ome_zarr.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/README.md +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/Snakefile +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/config/config.yaml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/pixi.toml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/rules/common.smk +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/rules/convert.smk +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/rules/merge.smk +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/rules/segment.smk +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/scripts/_pw.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/scripts/convert.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/scripts/merge.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-0.11.12 → patchworks-0.11.13}/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.13
|
|
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
|
|
@@ -68,7 +68,8 @@ def create_stage(
|
|
|
68
68
|
component : str, optional
|
|
69
69
|
Array name inside the store (default ``"staged"``).
|
|
70
70
|
dtype : data-type, optional
|
|
71
|
-
Label dtype (default ``int32``).
|
|
71
|
+
Label dtype (default ``int32``). Tiles write local labels; the merge's
|
|
72
|
+
first pass renumbers them to a compact global range that fits int32.
|
|
72
73
|
|
|
73
74
|
Returns
|
|
74
75
|
-------
|
|
@@ -135,6 +136,8 @@ def stage_tile(
|
|
|
135
136
|
slice(left, out.shape[i] - right)
|
|
136
137
|
for i, (left, right) in enumerate(trims)
|
|
137
138
|
)
|
|
139
|
+
# Local labels (1..N) are fine here — they collide across tiles, but the
|
|
140
|
+
# merge's first pass makes them globally unique before stitching.
|
|
138
141
|
dst = zarr.open_group(str(stage_path), mode="r+")[component]
|
|
139
142
|
dst[sl] = out[sel].astype(dst.dtype)
|
|
140
143
|
return index
|
|
@@ -103,7 +103,7 @@ def _relabel_chunk_worker(chunk_slice: tuple) -> None:
|
|
|
103
103
|
block = np.asarray(src[chunk_slice], dtype=np.int64)
|
|
104
104
|
max_b = int(block.max())
|
|
105
105
|
if max_b == 0:
|
|
106
|
-
dst[chunk_slice] = block.astype(
|
|
106
|
+
dst[chunk_slice] = block.astype(dst.dtype)
|
|
107
107
|
return
|
|
108
108
|
lut = _merge_lut
|
|
109
109
|
if max_b < len(lut):
|
|
@@ -111,7 +111,7 @@ def _relabel_chunk_worker(chunk_slice: tuple) -> None:
|
|
|
111
111
|
else:
|
|
112
112
|
ext = np.arange(len(lut), max_b + 1, dtype=np.int64)
|
|
113
113
|
out = np.concatenate([lut, ext])[block]
|
|
114
|
-
dst[chunk_slice] = out.astype(
|
|
114
|
+
dst[chunk_slice] = out.astype(dst.dtype)
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def _boundary_face_specs(
|
|
@@ -238,9 +238,9 @@ def _build_relabel_lut(pairs: np.ndarray, max_label: int) -> np.ndarray:
|
|
|
238
238
|
|
|
239
239
|
|
|
240
240
|
def _create_zarr_label_array(
|
|
241
|
-
group: zarr.Group, name: str, shape: tuple, chunks: tuple
|
|
241
|
+
group: zarr.Group, name: str, shape: tuple, chunks: tuple, dtype=np.int32
|
|
242
242
|
) -> zarr.Array:
|
|
243
|
-
"""Create (replacing any existing)
|
|
243
|
+
"""Create (replacing any existing) a label array in *group*.
|
|
244
244
|
|
|
245
245
|
Parameters
|
|
246
246
|
----------
|
|
@@ -252,6 +252,9 @@ def _create_zarr_label_array(
|
|
|
252
252
|
Array shape.
|
|
253
253
|
chunks : tuple
|
|
254
254
|
Chunk shape.
|
|
255
|
+
dtype : data-type, optional
|
|
256
|
+
Label dtype (default ``int32``). Matched to the staged store so the
|
|
257
|
+
merge keeps wide (block-encoded) ids intact before they are compacted.
|
|
255
258
|
|
|
256
259
|
Returns
|
|
257
260
|
-------
|
|
@@ -261,14 +264,56 @@ def _create_zarr_label_array(
|
|
|
261
264
|
if name in group:
|
|
262
265
|
del group[name]
|
|
263
266
|
if _ZARR_V3:
|
|
264
|
-
return group.create_array(
|
|
265
|
-
name, shape=shape, chunks=chunks, dtype=np.int32
|
|
266
|
-
)
|
|
267
|
+
return group.create_array(name, shape=shape, chunks=chunks, dtype=dtype)
|
|
267
268
|
return group.zeros(
|
|
268
|
-
name, shape=shape, chunks=chunks, dtype=
|
|
269
|
+
name, shape=shape, chunks=chunks, dtype=dtype, overwrite=True
|
|
269
270
|
)
|
|
270
271
|
|
|
271
272
|
|
|
273
|
+
def _make_globally_unique(arr, shape: tuple, chunk_shape: tuple) -> int:
|
|
274
|
+
"""Renumber per-chunk local labels to a globally unique, compact range.
|
|
275
|
+
|
|
276
|
+
Each tile writes local labels (``1..N``) that repeat across tiles, so the
|
|
277
|
+
same value means different objects in different tiles. This streams the
|
|
278
|
+
chunks in row-major order and remaps every chunk's non-zero labels to a
|
|
279
|
+
fresh contiguous block ``[base+1, …]``, leaving the store globally unique
|
|
280
|
+
with ``max_label == total objects`` (compact, so the relabel LUT stays
|
|
281
|
+
``O(n_objects)``). Background (0) stays 0. In place, one chunk in RAM.
|
|
282
|
+
|
|
283
|
+
Parameters
|
|
284
|
+
----------
|
|
285
|
+
arr : zarr.Array
|
|
286
|
+
Writable staged label array.
|
|
287
|
+
shape : tuple
|
|
288
|
+
Array shape.
|
|
289
|
+
chunk_shape : tuple
|
|
290
|
+
Chunk (= tile) shape.
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
int
|
|
295
|
+
New maximum label (number of objects across all tiles, before the
|
|
296
|
+
cross-boundary merge fuses touching pairs).
|
|
297
|
+
"""
|
|
298
|
+
n_per_dim = [(s + c - 1) // c for s, c in zip(shape, chunk_shape)]
|
|
299
|
+
base = 0
|
|
300
|
+
for idx in _iproduct(*[range(n) for n in n_per_dim]):
|
|
301
|
+
sl = tuple(
|
|
302
|
+
slice(i * c, min((i + 1) * c, s))
|
|
303
|
+
for i, c, s in zip(idx, chunk_shape, shape)
|
|
304
|
+
)
|
|
305
|
+
block = np.asarray(arr[sl])
|
|
306
|
+
uniq = np.unique(block)
|
|
307
|
+
uniq = uniq[uniq > 0]
|
|
308
|
+
if uniq.size == 0:
|
|
309
|
+
continue
|
|
310
|
+
lut = np.zeros(int(uniq[-1]) + 1, dtype=np.int64)
|
|
311
|
+
lut[uniq] = np.arange(1, uniq.size + 1) + base
|
|
312
|
+
arr[sl] = lut[block].astype(arr.dtype)
|
|
313
|
+
base += int(uniq.size)
|
|
314
|
+
return base
|
|
315
|
+
|
|
316
|
+
|
|
272
317
|
def zarr_native_merge(
|
|
273
318
|
staged_path: str,
|
|
274
319
|
staged_component: str,
|
|
@@ -302,13 +347,17 @@ def zarr_native_merge(
|
|
|
302
347
|
-------
|
|
303
348
|
None
|
|
304
349
|
"""
|
|
305
|
-
root = zarr.open_group(staged_path, mode="r")
|
|
350
|
+
root = zarr.open_group(staged_path, mode="r+")
|
|
306
351
|
arr = root[staged_component]
|
|
307
352
|
shape, chunk_shape = arr.shape, arr.chunks
|
|
308
353
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
354
|
+
# Pass 0 — make labels globally unique. Each tile writes local labels
|
|
355
|
+
# (1..N) that collide across tiles (every tile has a "1"); without this the
|
|
356
|
+
# boundary merge would fuse unrelated objects that merely share a value.
|
|
357
|
+
# Stream chunks in order, remapping each chunk's labels into a fresh
|
|
358
|
+
# contiguous range [base+1, …] — unique *and* compact (so the relabel LUT
|
|
359
|
+
# stays O(n_objects), not O(n_voxels)).
|
|
360
|
+
max_label = _make_globally_unique(arr, shape, chunk_shape)
|
|
312
361
|
logger.info(
|
|
313
362
|
"zarr_native_merge: shape=%s chunks=%s max_label=%d",
|
|
314
363
|
shape,
|
|
@@ -330,7 +379,11 @@ def zarr_native_merge(
|
|
|
330
379
|
)
|
|
331
380
|
|
|
332
381
|
out_root = zarr.open_group(out_path, mode="a")
|
|
333
|
-
|
|
382
|
+
# Match the staged dtype so block-encoded (wide) ids survive the merge;
|
|
383
|
+
# they are compacted to a small range afterwards by relabel_sequential_zarr.
|
|
384
|
+
_create_zarr_label_array(
|
|
385
|
+
out_root, out_component, shape, chunk_shape, dtype=arr.dtype
|
|
386
|
+
)
|
|
334
387
|
|
|
335
388
|
n_per_dim = [(s + c - 1) // c for s, c in zip(shape, chunk_shape)]
|
|
336
389
|
chunk_slices = [
|
|
@@ -43,3 +43,33 @@ def test_stage_then_merge_stitches_boundary(tmp_path):
|
|
|
43
43
|
).compute()
|
|
44
44
|
ids = np.unique(merged[merged > 0])
|
|
45
45
|
assert ids.size == 1, f"object split into {ids.size} labels"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_separate_objects_keep_distinct_labels(tmp_path):
|
|
49
|
+
"""Different objects in different tiles must NOT collapse to one label.
|
|
50
|
+
|
|
51
|
+
Each tile produces local labels (1..N); without the merge's global-uniqueness
|
|
52
|
+
pass every tile's "1" would fuse into a single object.
|
|
53
|
+
"""
|
|
54
|
+
img = np.zeros((16, 32), "uint16")
|
|
55
|
+
# four separate objects, one per (16x16) tile, none touching a boundary
|
|
56
|
+
img[3:6, 3:6] = 500
|
|
57
|
+
img[3:6, 19:22] = 500
|
|
58
|
+
img[10:13, 3:6] = 500
|
|
59
|
+
img[10:13, 19:22] = 500
|
|
60
|
+
|
|
61
|
+
stage = str(tmp_path / "stage.zarr")
|
|
62
|
+
tile = (16, 16)
|
|
63
|
+
create_stage(stage, img.shape, tile)
|
|
64
|
+
for i in range(len(spatial_tiles(img.shape, tile))):
|
|
65
|
+
stage_tile(img, _fn, stage, i, tile_shape=tile, overlap=4)
|
|
66
|
+
|
|
67
|
+
merged = merge_tile_labels(
|
|
68
|
+
stage,
|
|
69
|
+
write_to=str(tmp_path / "out.zarr"),
|
|
70
|
+
input_component="staged",
|
|
71
|
+
sequential_labels=True,
|
|
72
|
+
).compute()
|
|
73
|
+
ids = np.unique(merged[merged > 0])
|
|
74
|
+
assert ids.size == 4, f"expected 4 distinct objects, got {ids.size}"
|
|
75
|
+
assert set(ids.tolist()) == {1, 2, 3, 4} # contiguous after relabel
|
|
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
|