patchworks 1.2.2__tar.gz → 1.3.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-1.2.2 → patchworks-1.3.1}/PKG-INFO +1 -1
- patchworks-1.3.1/docs/api/postprocess.md +3 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/dog.md +18 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/ome_zarr_napari.md +10 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/snakemake.md +55 -6
- {patchworks-1.2.2 → patchworks-1.3.1}/mkdocs.yml +1 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/pyproject.toml +6 -5
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/__init__.py +2 -0
- patchworks-1.3.1/src/patchworks/_postprocess.py +101 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/napari.py +2 -2
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/ome_zarr.py +4 -2
- patchworks-1.3.1/tests/test_postprocess.py +41 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config.yaml +1 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config_cilia.yaml +1 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/_pw.py +28 -1
- {patchworks-1.2.2 → patchworks-1.3.1}/.github/workflows/docs.yml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/.github/workflows/lint.yml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/.github/workflows/release.yml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/.gitignore +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/.markdownlint-cli2.yaml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/LICENSE +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/README.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/cliff.toml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/chunks.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/cluster.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/io.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/dog.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/napari.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/relabel.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/tile_process.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/assets/logo.png +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/custom.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/custom_method.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/dog.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/standalone_merge.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/stardist.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/stardist_2d.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/getting_started.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/merging.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/performance.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/pitfalls.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/skip_empty.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/tiling.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/docs/index.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_chunks.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_cluster.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_core.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_distributed.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_io.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_merge.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_relabel.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_relations.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_core.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_distributed.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_dog.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_napari.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_ome_zarr.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_relations.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/README.md +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/Snakefile +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/multi.yaml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/pixi.toml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/common.smk +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/convert.smk +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/merge.smk +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/segment.smk +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/convert.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/merge.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/run_multi.py +0 -0
- {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/segment_tile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -81,6 +81,24 @@ result = tile_process(IMAGE, fn, tile_shape=(1, 1024, 1024), overlap=32)
|
|
|
81
81
|
so edge tiles keep enough context (a plain intensity/threshold halo is
|
|
82
82
|
too thin).
|
|
83
83
|
|
|
84
|
+
## Growing the labels afterwards
|
|
85
|
+
|
|
86
|
+
DoG spots/threads are often thin — grow each label by a few pixels with
|
|
87
|
+
[`dilate_labels`](../api/postprocess.md):
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from patchworks import tile_process, dilate_labels
|
|
91
|
+
from patchworks.plugins.dog import dog_label_fn
|
|
92
|
+
|
|
93
|
+
fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
|
|
94
|
+
fn = dilate_labels(fn, iterations=2)
|
|
95
|
+
tile_process(IMAGE, fn, tile_shape=(1, 1024, 1024), overlap=8, write_to=OUTPUT)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
On the cluster, set `dilate: 2` in the YAML config instead — it applies to
|
|
99
|
+
`method: "custom"` (this plugin) the same way it does for `cellpose`/
|
|
100
|
+
`threshold`, see [Growing labels after segmentation](../guide/snakemake.md#growing-labels-afterwards-dilation).
|
|
101
|
+
|
|
84
102
|
## Using it in the Snakemake workflow
|
|
85
103
|
|
|
86
104
|
No dedicated wiring needed — `patchworks.plugins.dog` exposes a `segment(tile, **kwargs)`
|
|
@@ -152,6 +152,16 @@ just one channel instead: `view_in_napari("scan.zarr", channel=0)`.
|
|
|
152
152
|
napari ships in `patchworks[all]`, or install just it with
|
|
153
153
|
`pip install "patchworks[napari]"`.
|
|
154
154
|
|
|
155
|
+
!!! tip "Measuring all the objects"
|
|
156
|
+
Once labels are loaded, use
|
|
157
|
+
[napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)'s
|
|
158
|
+
"Measure" dock widget for area/centroid/intensity stats — it works
|
|
159
|
+
out-of-core straight off the Labels layer's backing dask/zarr array, so
|
|
160
|
+
it scales to the same huge label images `tile_process` writes, unlike
|
|
161
|
+
plain `skimage.measure.regionprops`. Bundled in `patchworks[napari]`. See
|
|
162
|
+
[Measurements](snakemake.md#measurements-fast-whole-volume-regionprops)
|
|
163
|
+
for the non-interactive/headless equivalent.
|
|
164
|
+
|
|
155
165
|
## End-to-end
|
|
156
166
|
|
|
157
167
|
```python
|
|
@@ -62,8 +62,9 @@ skip_empty: true # skip background tiles
|
|
|
62
62
|
empty_threshold: null # null → Otsu
|
|
63
63
|
|
|
64
64
|
# segmentation
|
|
65
|
-
method: "cellpose" # "cellpose" (GPU)
|
|
65
|
+
method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU), "custom"
|
|
66
66
|
label_name: "cellpose" # name under image.zarr/labels/
|
|
67
|
+
dilate: 0 # optional: pixels to grow labels by, any method
|
|
67
68
|
cellpose:
|
|
68
69
|
model: "cyto3"
|
|
69
70
|
diameter: 30
|
|
@@ -77,6 +78,18 @@ pyramid_downscale: 2
|
|
|
77
78
|
sequential_labels: true # renumber labels to a contiguous 1..N
|
|
78
79
|
```
|
|
79
80
|
|
|
81
|
+
!!! tip "Growing labels after segmentation"
|
|
82
|
+
`dilate: N` grows every label by `N` pixels once segmentation finishes,
|
|
83
|
+
regardless of `method` (`cellpose`, `threshold`, or `custom`). It runs
|
|
84
|
+
per-tile, before the overlap halo is trimmed and tiles are merged, so
|
|
85
|
+
dilated labels still stitch correctly across tile boundaries — just make
|
|
86
|
+
sure `overlap` covers the dilation amount plus the usual object-diameter
|
|
87
|
+
halo. `0` (default) disables it. Under the hood this wraps whatever
|
|
88
|
+
segmentation function `method` builds with
|
|
89
|
+
[`patchworks.dilate_labels`](../api/postprocess.md); see [Custom
|
|
90
|
+
segmentation function](#custom-segmentation-function) below for using it
|
|
91
|
+
directly from Python instead of via config.
|
|
92
|
+
|
|
80
93
|
!!! tip "Tile size vs runtime"
|
|
81
94
|
`tile_shape: "auto"` sizes each tile to your GPU's VRAM. Smaller tiles =
|
|
82
95
|
more (faster) jobs; very large 3-D tiles are slow. Keep `do_3D: false` (2-D
|
|
@@ -348,10 +361,20 @@ needs `pip install "patchworks[dog]"` in the segment jobs' environment.
|
|
|
348
361
|
## Measurements (fast, whole-volume regionprops)
|
|
349
362
|
|
|
350
363
|
`skimage.measure.regionprops` needs the full labelled + intensity array in
|
|
351
|
-
RAM — fine for one tile, not for a hundred-thousand-object OME-ZARR.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
364
|
+
RAM — fine for one tile, not for a hundred-thousand-object OME-ZARR.
|
|
365
|
+
|
|
366
|
+
**Interactively, in napari**, this is what
|
|
367
|
+
[napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)
|
|
368
|
+
is for — its "Measure" dock widget computes area/centroid/intensity stats
|
|
369
|
+
directly off a Labels layer's dask/zarr-backed array, out-of-core, and scales
|
|
370
|
+
with chunk count rather than object count. It's the best fit for measuring
|
|
371
|
+
*every* object in a store this size, not just a cropped region — see
|
|
372
|
+
[View image + labels in napari](ome_zarr_napari.md#view-image--labels-in-napari).
|
|
373
|
+
Bundled in `patchworks[napari]`.
|
|
374
|
+
|
|
375
|
+
**Headless/scripted**, use [`dask-image`](https://image.dask.org)'s
|
|
376
|
+
`ndmeasure`, which computes directly on the dask/zarr-backed arrays,
|
|
377
|
+
chunk-parallel, without materializing the volume:
|
|
355
378
|
|
|
356
379
|
```bash
|
|
357
380
|
pip install dask-image
|
|
@@ -379,7 +402,10 @@ For interactively inspecting individual cells by clicking in the viewer
|
|
|
379
402
|
(not all objects at once), the
|
|
380
403
|
[napari-skimage-regionprops](https://github.com/haesleinhuepf/napari-skimage-regionprops)
|
|
381
404
|
plugin's table widget works well — point it at a cropped region rather than
|
|
382
|
-
the full volume, since it loads its input fully into memory.
|
|
405
|
+
the full volume, since it loads its input fully into memory. For measuring
|
|
406
|
+
*all* objects at once, use
|
|
407
|
+
[napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)
|
|
408
|
+
instead (see above) — it doesn't have this in-memory limitation.
|
|
383
409
|
|
|
384
410
|
## Custom segmentation function
|
|
385
411
|
|
|
@@ -433,6 +459,29 @@ custom:
|
|
|
433
459
|
sigma: 1.5
|
|
434
460
|
```
|
|
435
461
|
|
|
462
|
+
### Growing labels afterwards (dilation)
|
|
463
|
+
|
|
464
|
+
To grow every label by a few pixels after segmentation — any method, not
|
|
465
|
+
just `custom` — set `dilate: N` in the config (see the tip above), or wrap
|
|
466
|
+
your function directly with
|
|
467
|
+
[`patchworks.dilate_labels`](../api/postprocess.md) when calling the API
|
|
468
|
+
yourself:
|
|
469
|
+
|
|
470
|
+
```python
|
|
471
|
+
from patchworks import tile_process, dilate_labels
|
|
472
|
+
from patchworks.plugins.dog import dog_label_fn
|
|
473
|
+
|
|
474
|
+
fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
|
|
475
|
+
fn = dilate_labels(fn, iterations=2) # grow each label by 2 px, then run
|
|
476
|
+
result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048),
|
|
477
|
+
overlap=8, write_to="labels.zarr")
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
`dilate_labels` wraps any `(tile) -> labels` function — the same contract
|
|
481
|
+
described above — so it works with `dog_label_fn`, `cellpose_fn`, or your
|
|
482
|
+
own `segment`. It dilates each tile's labels before the halo is trimmed and
|
|
483
|
+
tiles are merged, so `overlap` must still cover the dilation amount.
|
|
484
|
+
|
|
436
485
|
### Real example: StarDist 3-D, with model caching
|
|
437
486
|
|
|
438
487
|
Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
|
|
@@ -72,11 +72,12 @@ imaris = ["imaris-ims-file-reader"]
|
|
|
72
72
|
# - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split
|
|
73
73
|
# into a separate package in lxml >= 5.2 (else ImportError on Viewer()).
|
|
74
74
|
# - glasbey: distinct high-contrast label LUTs for view_in_napari.
|
|
75
|
-
# - napari-chunked-regionprops
|
|
76
|
-
# (area/centroid/intensity
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
75
|
+
# - napari-chunked-regionprops (https://github.com/imcf/napari-chunked-regionprops):
|
|
76
|
+
# out-of-core regionprops-style measurements (area/centroid/intensity
|
|
77
|
+
# stats) for huge Labels layers, straight off their backing dask/zarr
|
|
78
|
+
# arrays — the "Measure" dock widget. Formerly napari-dask-ndmeasure;
|
|
79
|
+
# renamed when its engine dropped dask_image.ndmeasure for a chunk-local
|
|
80
|
+
# map/merge that scales with chunk count, not object count.
|
|
80
81
|
# - pyqt6 < 6.10: PyQt6-Qt6 6.10.2's Windows wheel fails to import (`DLL load
|
|
81
82
|
# failed while importing QtWidgets: The specified procedure could not be
|
|
82
83
|
# found`) — confirmed unrelated to napari/qtpy, reproduces on a bare
|
|
@@ -35,6 +35,7 @@ from ._core import tile_process
|
|
|
35
35
|
from ._distributed import create_stage, spatial_tiles, stage_tile
|
|
36
36
|
from ._io import estimate_empty_tiles, load_ome_zarr
|
|
37
37
|
from ._merge import merge_tile_labels
|
|
38
|
+
from ._postprocess import dilate_labels
|
|
38
39
|
from ._relabel import relabel_sequential_array, relabel_sequential_zarr
|
|
39
40
|
from ._relations import label_relations
|
|
40
41
|
|
|
@@ -57,4 +58,5 @@ __all__ = [
|
|
|
57
58
|
"spatial_tiles",
|
|
58
59
|
"create_stage",
|
|
59
60
|
"stage_tile",
|
|
61
|
+
"dilate_labels",
|
|
60
62
|
]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Generic post-segmentation wrappers for patchworks.
|
|
2
|
+
|
|
3
|
+
These wrap any ``fn(tile) -> labels`` callable (a plugin, a custom function,
|
|
4
|
+
whatever ``method`` in the Snakemake workflow builds) so the same
|
|
5
|
+
post-processing applies regardless of which segmentation method produced the
|
|
6
|
+
labels.
|
|
7
|
+
|
|
8
|
+
Usage
|
|
9
|
+
-----
|
|
10
|
+
>>> from patchworks import tile_process, dilate_labels
|
|
11
|
+
>>> from patchworks.plugins.dog import dog_label_fn
|
|
12
|
+
>>>
|
|
13
|
+
>>> fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
|
|
14
|
+
>>> fn = dilate_labels(fn, iterations=2)
|
|
15
|
+
>>> result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048),
|
|
16
|
+
... overlap=8, write_to="labels.zarr")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from functools import partial
|
|
22
|
+
from typing import Callable
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def dilate_labels(
|
|
28
|
+
fn: Callable[[np.ndarray], np.ndarray],
|
|
29
|
+
iterations: int = 1,
|
|
30
|
+
*,
|
|
31
|
+
use_gpu: bool = False,
|
|
32
|
+
) -> Callable[[np.ndarray], np.ndarray]:
|
|
33
|
+
"""Wrap a segmentation callable to grow its labels after each tile.
|
|
34
|
+
|
|
35
|
+
Applies a single-pass grey dilation to whatever ``fn`` returns, before
|
|
36
|
+
``tile_process``/``stage_tile`` trim the overlap halo and merge across
|
|
37
|
+
tile boundaries — so dilated labels still stitch correctly at tile
|
|
38
|
+
edges.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
fn : Callable[[np.ndarray], np.ndarray]
|
|
43
|
+
Any segmentation function with the ``tile_process``/``stage_tile``
|
|
44
|
+
contract (one tile in, integer label array out).
|
|
45
|
+
iterations : int, optional
|
|
46
|
+
Pixels to grow each label by (grey-dilation footprint size
|
|
47
|
+
``2 * iterations + 1``, single pass). Default 1. Values ``<= 0``
|
|
48
|
+
disable dilation — ``fn`` is returned unwrapped.
|
|
49
|
+
use_gpu : bool, optional
|
|
50
|
+
Dilate via cupyx instead of scipy. Independent of whatever backend
|
|
51
|
+
``fn`` itself uses internally.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
Callable[[np.ndarray], np.ndarray]
|
|
56
|
+
Picklable function ready for ``tile_process``/``stage_tile``. If
|
|
57
|
+
``iterations <= 0``, this is ``fn`` itself.
|
|
58
|
+
"""
|
|
59
|
+
if iterations <= 0:
|
|
60
|
+
return fn
|
|
61
|
+
return partial(_run, fn=fn, iterations=iterations, use_gpu=use_gpu)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _run(
|
|
65
|
+
block: np.ndarray,
|
|
66
|
+
fn: Callable[[np.ndarray], np.ndarray],
|
|
67
|
+
iterations: int,
|
|
68
|
+
use_gpu: bool,
|
|
69
|
+
) -> np.ndarray:
|
|
70
|
+
"""Run ``fn`` on ``block``, then grow the resulting labels.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
block : np.ndarray
|
|
75
|
+
One image tile.
|
|
76
|
+
fn : Callable[[np.ndarray], np.ndarray]
|
|
77
|
+
Segmentation function to run first.
|
|
78
|
+
iterations : int
|
|
79
|
+
Pixels to grow each label by.
|
|
80
|
+
use_gpu : bool
|
|
81
|
+
Dilate via cupyx instead of scipy.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
np.ndarray
|
|
86
|
+
Dilated integer label array, same shape as ``fn``'s output.
|
|
87
|
+
"""
|
|
88
|
+
labels = fn(block)
|
|
89
|
+
size = 2 * iterations + 1
|
|
90
|
+
|
|
91
|
+
if use_gpu:
|
|
92
|
+
import cupy as cp
|
|
93
|
+
from cupyx.scipy.ndimage import grey_dilation
|
|
94
|
+
|
|
95
|
+
labels = cp.asnumpy(grey_dilation(cp.asarray(labels), size=size))
|
|
96
|
+
else:
|
|
97
|
+
from scipy.ndimage import grey_dilation
|
|
98
|
+
|
|
99
|
+
labels = grey_dilation(labels, size=size)
|
|
100
|
+
|
|
101
|
+
return labels
|
|
@@ -187,8 +187,8 @@ def _label_hint(path: Union[str, Path]) -> dict[str, Any]:
|
|
|
187
187
|
during the merge) — the exact id set is then ``range(1, n_objects +
|
|
188
188
|
1)`` by construction, with no scan needed. Passed through as a Labels
|
|
189
189
|
layer's ``metadata`` so a downstream consumer (e.g.
|
|
190
|
-
napari-chunked-regionprops
|
|
191
|
-
set from the array itself.
|
|
190
|
+
napari-chunked-regionprops, https://github.com/imcf/napari-chunked-regionprops)
|
|
191
|
+
can use it instead of re-deriving the id set from the array itself.
|
|
192
192
|
|
|
193
193
|
Parameters
|
|
194
194
|
----------
|
|
@@ -1083,8 +1083,10 @@ def register_labels(
|
|
|
1083
1083
|
``sequential_labels=True``, which means ``ids == range(1, n_objects
|
|
1084
1084
|
+ 1)`` by construction). When given, written into the label group's
|
|
1085
1085
|
attrs as ``n_objects``/``sequential_labels`` so a downstream reader
|
|
1086
|
-
(e.g. napari-chunked-regionprops
|
|
1087
|
-
|
|
1086
|
+
(e.g. napari-chunked-regionprops,
|
|
1087
|
+
https://github.com/imcf/napari-chunked-regionprops) can use the
|
|
1088
|
+
known id set instead of re-deriving it with a full-volume scan of
|
|
1089
|
+
its own.
|
|
1088
1090
|
|
|
1089
1091
|
Returns
|
|
1090
1092
|
-------
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Self-contained tests for the dilate_labels post-processing wrapper."""
|
|
2
|
+
|
|
3
|
+
import pickle
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _make_blob_labels(shape=(1, 64, 64)):
|
|
9
|
+
labels = np.zeros(shape, dtype="int32")
|
|
10
|
+
labels[0, 28:36, 28:36] = 1
|
|
11
|
+
return labels
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_dilate_labels_grows_mask():
|
|
15
|
+
from patchworks import dilate_labels
|
|
16
|
+
|
|
17
|
+
fn = lambda tile: _make_blob_labels(tile.shape) # noqa: E731
|
|
18
|
+
plain = fn(np.zeros((1, 64, 64)))
|
|
19
|
+
dilated = dilate_labels(fn, iterations=2)(np.zeros((1, 64, 64)))
|
|
20
|
+
|
|
21
|
+
assert (dilated > 0).sum() > (plain > 0).sum()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_dilate_labels_zero_iterations_is_noop():
|
|
25
|
+
from patchworks import dilate_labels
|
|
26
|
+
|
|
27
|
+
fn = lambda tile: _make_blob_labels(tile.shape) # noqa: E731
|
|
28
|
+
|
|
29
|
+
assert dilate_labels(fn, iterations=0) is fn
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_dilate_labels_picklable():
|
|
33
|
+
from patchworks.plugins.dog import dog_label_fn
|
|
34
|
+
|
|
35
|
+
from patchworks import dilate_labels
|
|
36
|
+
|
|
37
|
+
fn = dilate_labels(
|
|
38
|
+
dog_label_fn(low_sigma=1.0, high_sigma=4.0, threshold=0.01),
|
|
39
|
+
iterations=2,
|
|
40
|
+
)
|
|
41
|
+
pickle.loads(pickle.dumps(fn))
|
|
@@ -30,6 +30,7 @@ empty_threshold: null # null → Otsu; or a number
|
|
|
30
30
|
|
|
31
31
|
# ---- segmentation -----------------------------------------------------------
|
|
32
32
|
method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU; testing), "custom"
|
|
33
|
+
# dilate: 2 # optional: pixels to grow labels by after segmentation, any method
|
|
33
34
|
# Also namespaces this run's intermediate files under work_dir/<label_name>/,
|
|
34
35
|
# so a second segmentation (different label_name, e.g. nuclei vs cytoplasm)
|
|
35
36
|
# can safely target the same work_dir — see docs/guide/snakemake.md
|
|
@@ -133,7 +133,34 @@ def build_fn(cfg):
|
|
|
133
133
|
cfg : dict
|
|
134
134
|
Snakemake config. ``method`` selects ``"cellpose"`` (default), a simple
|
|
135
135
|
``"threshold"`` (testing / no-GPU), or ``"custom"`` to import your own
|
|
136
|
-
function (``cfg["custom"] = {module, function, kwargs}``).
|
|
136
|
+
function (``cfg["custom"] = {module, function, kwargs}``). Optional
|
|
137
|
+
``cfg["dilate"]``: int, pixels to grow labels by after segmentation
|
|
138
|
+
(via ``patchworks.dilate_labels``), applied regardless of ``method``.
|
|
139
|
+
Omitted/0 disables dilation.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
callable
|
|
144
|
+
``(ndarray) -> ndarray`` returning integer labels.
|
|
145
|
+
"""
|
|
146
|
+
fn = _build_method_fn(cfg)
|
|
147
|
+
|
|
148
|
+
dilate = cfg.get("dilate")
|
|
149
|
+
if dilate:
|
|
150
|
+
from patchworks import dilate_labels
|
|
151
|
+
|
|
152
|
+
fn = dilate_labels(fn, iterations=dilate)
|
|
153
|
+
|
|
154
|
+
return fn
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _build_method_fn(cfg):
|
|
158
|
+
"""Build the per-tile segmentation function for ``cfg["method"]``.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
cfg : dict
|
|
163
|
+
Snakemake config, see :func:`build_fn`.
|
|
137
164
|
|
|
138
165
|
Returns
|
|
139
166
|
-------
|
|
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
|