patchworks 1.2.1__tar.gz → 1.3.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.
- {patchworks-1.2.1 → patchworks-1.3.0}/PKG-INFO +1 -1
- patchworks-1.3.0/docs/api/postprocess.md +3 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/dog.md +80 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/ome_zarr_napari.md +6 -1
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/snakemake.md +37 -1
- {patchworks-1.2.1 → patchworks-1.3.0}/mkdocs.yml +1 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/__init__.py +2 -0
- patchworks-1.3.0/src/patchworks/_postprocess.py +101 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/plugins/napari.py +4 -2
- patchworks-1.3.0/tests/test_postprocess.py +41 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/config/config.yaml +1 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/config/config_cilia.yaml +1 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/scripts/_pw.py +28 -1
- {patchworks-1.2.1 → patchworks-1.3.0}/.github/workflows/docs.yml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/.github/workflows/lint.yml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/.github/workflows/release.yml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/.gitignore +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/.markdownlint-cli2.yaml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/LICENSE +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/README.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/cliff.toml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/chunks.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/cluster.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/io.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/plugins/dog.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/plugins/napari.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/relabel.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/api/tile_process.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/assets/logo.png +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/custom.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/custom_method.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/dog.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/standalone_merge.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/stardist.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/examples/stardist_2d.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/getting_started.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/merging.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/performance.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/pitfalls.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/skip_empty.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/guide/tiling.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/docs/index.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/pyproject.toml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_chunks.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_cluster.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_core.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_distributed.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_io.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_merge.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_relabel.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/_relations.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/tests/test_core.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/tests/test_distributed.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/tests/test_dog.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/tests/test_napari.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/tests/test_ome_zarr.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/tests/test_relations.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/README.md +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/Snakefile +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/config/multi.yaml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/pixi.toml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/rules/common.smk +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/rules/convert.smk +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/rules/merge.smk +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/rules/segment.smk +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/scripts/convert.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/scripts/merge.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/workflow/scripts/run_multi.py +0 -0
- {patchworks-1.2.1 → patchworks-1.3.0}/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.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
|
|
@@ -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)`
|
|
@@ -101,6 +119,68 @@ custom:
|
|
|
101
119
|
See `workflow/config/config_cilia.yaml` for a full example, including
|
|
102
120
|
deconvolution.
|
|
103
121
|
|
|
122
|
+
### With deconvolution, on SLURM
|
|
123
|
+
|
|
124
|
+
Add `decon_kwargs` under `custom.kwargs` — same keys as the plain-Python
|
|
125
|
+
example above — and the segment job deconvolves each tile with
|
|
126
|
+
`pycudadecon` before running the DoG detector:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# config/config_cilia.yaml (excerpt)
|
|
130
|
+
channel: 2
|
|
131
|
+
tile_shape: [16, 1024, 1024]
|
|
132
|
+
overlap: 30 # cover the PSF support (decon) + the DoG's high_sigma
|
|
133
|
+
skip_empty: true
|
|
134
|
+
|
|
135
|
+
method: "custom"
|
|
136
|
+
label_name: "cilia_labels"
|
|
137
|
+
custom:
|
|
138
|
+
module: "patchworks.plugins.dog"
|
|
139
|
+
function: "segment"
|
|
140
|
+
kwargs:
|
|
141
|
+
low_sigma: 1.0
|
|
142
|
+
high_sigma: 3.0
|
|
143
|
+
threshold: 0.02
|
|
144
|
+
decon_kwargs:
|
|
145
|
+
psf: "/path/to/psf.tif"
|
|
146
|
+
dxpsf: 0.1
|
|
147
|
+
dxdata: 0.1
|
|
148
|
+
dzpsf: 0.2
|
|
149
|
+
dzdata: 0.2
|
|
150
|
+
wavelength: 525
|
|
151
|
+
na: 1.4
|
|
152
|
+
nimm: 1.515
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Run it exactly like a Cellpose config:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python -m snakemake --workflow-profile profile/slurm \
|
|
159
|
+
--configfile config/config_cilia.yaml
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Checklist specific to this config:
|
|
163
|
+
|
|
164
|
+
- **Env:** the segment job's environment needs `patchworks[dog]`
|
|
165
|
+
(`pip install "patchworks[dog]"`) on top of whatever else it uses — plain
|
|
166
|
+
`dog_label_fn` only needs scipy, but `decon_kwargs` pulls in
|
|
167
|
+
`pycudadecon`.
|
|
168
|
+
- **GPU always required:** `pycudadecon` is CUDA-only regardless of the
|
|
169
|
+
detector's own `use_gpu` flag, so `set-resources: segment:` in
|
|
170
|
+
`profile/slurm/config.yaml` must request a GPU (`slurm_extra:
|
|
171
|
+
"'--gres=gpu:1'"`) the same as for Cellpose.
|
|
172
|
+
- **`overlap`:** widen it past the PSF support, not just past `high_sigma` —
|
|
173
|
+
a thin intensity/threshold halo isn't enough once deconvolution is in the
|
|
174
|
+
loop.
|
|
175
|
+
- **`skip_empty`:** the `prepare` rule (`workflow/scripts/prepare_tiles.py`)
|
|
176
|
+
calls `estimate_empty_tiles()` before submitting any `segment` jobs,
|
|
177
|
+
regardless of `method`, so cilia/DoG runs skip background tiles exactly
|
|
178
|
+
like Cellpose runs — no extra config needed beyond `skip_empty: true`
|
|
179
|
+
(the default).
|
|
180
|
+
- Run alongside `config_cyto.yaml`/`config_nuclei.yaml` via `config/multi.yaml`
|
|
181
|
+
to also get the cilia→cell/nucleus relation — see *Relating cilia to their
|
|
182
|
+
cell*, below.
|
|
183
|
+
|
|
104
184
|
## Relating cilia to their cell
|
|
105
185
|
|
|
106
186
|
Segment the cell body with Cellpose and the cilia with `dog_label_fn` as two
|
|
@@ -136,13 +136,18 @@ image found under `scan.zarr/labels/`:
|
|
|
136
136
|
```python
|
|
137
137
|
from patchworks.plugins.napari import view_in_napari
|
|
138
138
|
|
|
139
|
-
# auto-loads scan.zarr/labels/* as Labels layers:
|
|
139
|
+
# auto-loads scan.zarr/labels/* as Labels layers, all image channels shown:
|
|
140
140
|
view_in_napari("scan.zarr")
|
|
141
141
|
|
|
142
142
|
# or point at a separate plain label store written with write_to=:
|
|
143
143
|
view_in_napari("scan.zarr", labels="labels.zarr")
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
By default every channel of the image is shown (`channel=None`), independent
|
|
147
|
+
of which one you segmented on — Cellpose might run on channel 0 while you
|
|
148
|
+
still want to see the whole multi-channel acquisition. Pass an int to view
|
|
149
|
+
just one channel instead: `view_in_napari("scan.zarr", channel=0)`.
|
|
150
|
+
|
|
146
151
|
!!! note
|
|
147
152
|
napari ships in `patchworks[all]`, or install just it with
|
|
148
153
|
`pip install "patchworks[napari]"`.
|
|
@@ -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
|
|
@@ -433,6 +446,29 @@ custom:
|
|
|
433
446
|
sigma: 1.5
|
|
434
447
|
```
|
|
435
448
|
|
|
449
|
+
### Growing labels afterwards (dilation)
|
|
450
|
+
|
|
451
|
+
To grow every label by a few pixels after segmentation — any method, not
|
|
452
|
+
just `custom` — set `dilate: N` in the config (see the tip above), or wrap
|
|
453
|
+
your function directly with
|
|
454
|
+
[`patchworks.dilate_labels`](../api/postprocess.md) when calling the API
|
|
455
|
+
yourself:
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
from patchworks import tile_process, dilate_labels
|
|
459
|
+
from patchworks.plugins.dog import dog_label_fn
|
|
460
|
+
|
|
461
|
+
fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
|
|
462
|
+
fn = dilate_labels(fn, iterations=2) # grow each label by 2 px, then run
|
|
463
|
+
result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048),
|
|
464
|
+
overlap=8, write_to="labels.zarr")
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
`dilate_labels` wraps any `(tile) -> labels` function — the same contract
|
|
468
|
+
described above — so it works with `dog_label_fn`, `cellpose_fn`, or your
|
|
469
|
+
own `segment`. It dilates each tile's labels before the halo is trimmed and
|
|
470
|
+
tiles are merged, so `overlap` must still cover the dilation amount.
|
|
471
|
+
|
|
436
472
|
### Real example: StarDist 3-D, with model caching
|
|
437
473
|
|
|
438
474
|
Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
|
|
@@ -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
|
|
@@ -267,7 +267,7 @@ def view_in_napari(
|
|
|
267
267
|
image: Union[da.Array, str, Path],
|
|
268
268
|
labels: Union[da.Array, str, Path, None] = None,
|
|
269
269
|
*,
|
|
270
|
-
channel: int | None =
|
|
270
|
+
channel: int | None = None,
|
|
271
271
|
labels_component: str = "labels",
|
|
272
272
|
image_name: str = "image",
|
|
273
273
|
labels_name: str = "labels",
|
|
@@ -290,7 +290,9 @@ def view_in_napari(
|
|
|
290
290
|
place ``tile_process`` writes them by default — each as its own Labels
|
|
291
291
|
layer. (Falls back to image-only if there are none.)
|
|
292
292
|
channel : int or None, optional
|
|
293
|
-
Channel to display from the image
|
|
293
|
+
Channel to display from the image. ``None`` (default) shows every
|
|
294
|
+
channel — the segmentation channel doesn't need to match what you
|
|
295
|
+
view. Pass an int to show just that one.
|
|
294
296
|
labels_component : str, optional
|
|
295
297
|
Array name inside a plain-zarr label store (default ``"labels"``,
|
|
296
298
|
matching ``tile_process``'s ``output_component``).
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|