patchworks 1.3.1__tar.gz → 1.3.3__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.3.1 → patchworks-1.3.3}/PKG-INFO +1 -1
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/dog.md +3 -3
- patchworks-1.3.3/docs/guide/custom_segmentation.md +163 -0
- patchworks-1.3.3/docs/guide/label_relations.md +44 -0
- patchworks-1.3.3/docs/guide/measurements.md +48 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/ome_zarr_napari.md +1 -1
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/snakemake.md +27 -223
- {patchworks-1.3.1 → patchworks-1.3.3}/mkdocs.yml +4 -1
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/plugins/dog.py +1 -1
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/config/config.yaml +2 -1
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/config/config_cilia.yaml +2 -1
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/scripts/_pw.py +5 -2
- {patchworks-1.3.1 → patchworks-1.3.3}/.github/workflows/docs.yml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/.github/workflows/lint.yml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/.github/workflows/release.yml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/.gitignore +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/.markdownlint-cli2.yaml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/LICENSE +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/README.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/cliff.toml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/chunks.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/cluster.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/io.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/plugins/dog.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/plugins/napari.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/postprocess.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/relabel.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/api/tile_process.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/assets/logo.png +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/custom.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/custom_method.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/dog.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/standalone_merge.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/stardist.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/examples/stardist_2d.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/getting_started.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/merging.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/performance.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/pitfalls.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/skip_empty.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/guide/tiling.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/docs/index.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/pyproject.toml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/__init__.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_chunks.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_cluster.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_core.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_distributed.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_io.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_merge.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_postprocess.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_relabel.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/_relations.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_core.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_distributed.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_dog.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_napari.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_ome_zarr.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_postprocess.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/tests/test_relations.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/README.md +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/Snakefile +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/config/multi.yaml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/pixi.toml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/rules/common.smk +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/rules/convert.smk +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/rules/merge.smk +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/rules/segment.smk +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/scripts/convert.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/scripts/merge.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/workflow/scripts/run_multi.py +0 -0
- {patchworks-1.3.1 → patchworks-1.3.3}/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.
|
|
3
|
+
Version: 1.3.3
|
|
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
|
|
@@ -97,12 +97,12 @@ tile_process(IMAGE, fn, tile_shape=(1, 1024, 1024), overlap=8, write_to=OUTPUT)
|
|
|
97
97
|
|
|
98
98
|
On the cluster, set `dilate: 2` in the YAML config instead — it applies to
|
|
99
99
|
`method: "custom"` (this plugin) the same way it does for `cellpose`/
|
|
100
|
-
`threshold`, see [Growing labels
|
|
100
|
+
`threshold`, see [Growing labels afterwards](../guide/custom_segmentation.md#growing-labels-afterwards-dilation).
|
|
101
101
|
|
|
102
102
|
## Using it in the Snakemake workflow
|
|
103
103
|
|
|
104
104
|
No dedicated wiring needed — `patchworks.plugins.dog` exposes a `segment(tile, **kwargs)`
|
|
105
|
-
adapter for the documented [`"custom"` method](../guide/
|
|
105
|
+
adapter for the documented [`"custom"` method](../guide/custom_segmentation.md):
|
|
106
106
|
|
|
107
107
|
```yaml
|
|
108
108
|
method: "custom"
|
|
@@ -185,6 +185,6 @@ Checklist specific to this config:
|
|
|
185
185
|
|
|
186
186
|
Segment the cell body with Cellpose and the cilia with `dog_label_fn` as two
|
|
187
187
|
separate `tile_process` runs (same image, same `tile_shape`), then use
|
|
188
|
-
[`label_relations`](../guide/
|
|
188
|
+
[`label_relations`](../guide/label_relations.md)
|
|
189
189
|
to map each cilium to the cell it belongs to — see
|
|
190
190
|
`workflow/config/multi.yaml` for the same thing wired up as a cluster job.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Custom segmentation function
|
|
2
|
+
|
|
3
|
+
Not using Cellpose? Run **your own** per-tile function — no need to edit the
|
|
4
|
+
package. You write one function; patchworks handles everything around it
|
|
5
|
+
(tiling, halos, skipping empty tiles, the zarr-native merge, global
|
|
6
|
+
relabelling, resume, logs).
|
|
7
|
+
|
|
8
|
+
This applies the same whether you call the API directly (`tile_process`) or
|
|
9
|
+
run via the [Snakemake cluster workflow](snakemake.md) — see [Wiring it into
|
|
10
|
+
the cluster workflow](#wiring-it-into-the-cluster-workflow) below for the
|
|
11
|
+
config side.
|
|
12
|
+
|
|
13
|
+
## The contract
|
|
14
|
+
|
|
15
|
+
Your function is called **once per tile**:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
labels = segment(tile) # plus any kwargs you configure
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| | What you get / must return |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| **Input `tile`** | A NumPy array of **one** tile, with the overlap halo already included. The channel and pyramid level are already selected, so it is purely spatial: `(z, y, x)` for a 3-D run, `(y, x)` for 2-D. Dtype is the image's (e.g. `uint16`). |
|
|
24
|
+
| **Return** | An integer **label** array (not a boolean mask), **same shape** as `tile`. `0` = background; each object a distinct positive integer. |
|
|
25
|
+
| **Labels** | Only need to be unique **within the tile**. Don't try to make them globally unique — the merge step stitches objects across tile borders and renumbers everything to a contiguous `1..N` (`sequential_labels: true`). |
|
|
26
|
+
| **Shape** | Must match the input exactly — patchworks trims the halo off your output, so a wrong shape is an error. Don't crop or resize inside the function. |
|
|
27
|
+
|
|
28
|
+
That is the whole interface. Anything that turns an image tile into a label
|
|
29
|
+
image works: classic image processing, StarDist, a trained model, an external
|
|
30
|
+
binary you shell out to, …
|
|
31
|
+
|
|
32
|
+
## Minimal example (no GPU, no deps beyond scikit-image)
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# my_seg.py
|
|
36
|
+
import numpy as np
|
|
37
|
+
from skimage.measure import label
|
|
38
|
+
|
|
39
|
+
def segment(tile: np.ndarray, sigma: float = 2.0) -> np.ndarray:
|
|
40
|
+
"""Threshold + connected components. Returns int32 labels (0 = bg)."""
|
|
41
|
+
from skimage.filters import gaussian, threshold_otsu
|
|
42
|
+
|
|
43
|
+
smooth = gaussian(tile, sigma=sigma, preserve_range=True)
|
|
44
|
+
thr = threshold_otsu(smooth) if smooth.max() > smooth.min() else np.inf
|
|
45
|
+
return label(smooth > thr).astype("int32")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from patchworks import tile_process
|
|
50
|
+
from my_seg import segment
|
|
51
|
+
|
|
52
|
+
tile_process("image.zarr", segment, write_to="labels.zarr")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Growing labels afterwards (dilation)
|
|
56
|
+
|
|
57
|
+
To grow every label by a few pixels after segmentation, wrap your function
|
|
58
|
+
with [`patchworks.dilate_labels`](../api/postprocess.md):
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from patchworks import tile_process, dilate_labels
|
|
62
|
+
from patchworks.plugins.dog import dog_label_fn
|
|
63
|
+
|
|
64
|
+
fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
|
|
65
|
+
fn = dilate_labels(fn, iterations=2) # grow each label by 2 px, then run
|
|
66
|
+
result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048),
|
|
67
|
+
overlap=8, write_to="labels.zarr")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`dilate_labels` wraps any `(tile) -> labels` function — the same contract
|
|
71
|
+
above — so it works with `dog_label_fn`, `cellpose_fn`, or your own
|
|
72
|
+
`segment`. It dilates each tile's labels before the halo is trimmed and
|
|
73
|
+
tiles are merged, so `overlap` must still cover the dilation amount. On the
|
|
74
|
+
cluster, set `dilate: N` in the config instead — see [Configure the
|
|
75
|
+
run](snakemake.md#3-configure-the-run).
|
|
76
|
+
|
|
77
|
+
By default the dilation itself runs on CPU (scipy), independent of whatever
|
|
78
|
+
backend `fn` used — pass `use_gpu=True` to dilate via cupy instead:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
fn = dilate_labels(fn, iterations=2, use_gpu=True) # needs cupy installed
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On the cluster, this is the `dilate_gpu: true` config key.
|
|
85
|
+
|
|
86
|
+
## Real example: StarDist 3-D, with model caching
|
|
87
|
+
|
|
88
|
+
Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
|
|
89
|
+
own process so this matters less, but for local runs one process segments many
|
|
90
|
+
tiles — cache the model at module level (or with `functools.lru_cache`):
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# stardist_seg.py
|
|
94
|
+
import numpy as np
|
|
95
|
+
|
|
96
|
+
_MODEL = None
|
|
97
|
+
|
|
98
|
+
def _model():
|
|
99
|
+
global _MODEL
|
|
100
|
+
if _MODEL is None: # loaded once per worker process
|
|
101
|
+
from stardist.models import StarDist3D
|
|
102
|
+
_MODEL = StarDist3D.from_pretrained("3D_demo")
|
|
103
|
+
return _MODEL
|
|
104
|
+
|
|
105
|
+
def segment(tile: np.ndarray, prob_thresh: float = 0.5) -> np.ndarray:
|
|
106
|
+
from csbdeep.utils import normalize
|
|
107
|
+
|
|
108
|
+
labels, _ = _model().predict_instances(
|
|
109
|
+
normalize(tile), prob_thresh=prob_thresh
|
|
110
|
+
)
|
|
111
|
+
return labels.astype("int32")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Using a GPU? Just let your framework see it — nothing extra needed.
|
|
115
|
+
|
|
116
|
+
## Test it before you submit
|
|
117
|
+
|
|
118
|
+
Run your function on one real tile first — it catches shape/dtype bugs in
|
|
119
|
+
seconds instead of after a queue wait (or a long local run). Output must be
|
|
120
|
+
integer, same shape, `0` for background:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from patchworks import load_ome_zarr
|
|
124
|
+
from my_seg import segment
|
|
125
|
+
|
|
126
|
+
img = load_ome_zarr("results/image.zarr", channel=0, level=0)
|
|
127
|
+
tile = img[:, :512, :512].compute() # a small spatial block
|
|
128
|
+
out = segment(tile)
|
|
129
|
+
|
|
130
|
+
assert out.shape == tile.shape, (out.shape, tile.shape)
|
|
131
|
+
assert out.dtype.kind in "iu" # integer labels, not a float mask
|
|
132
|
+
print("objects in tile:", int(out.max()))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Wiring it into the cluster workflow
|
|
136
|
+
|
|
137
|
+
Point the config at your module and function:
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
method: "custom"
|
|
141
|
+
label_name: "my_labels"
|
|
142
|
+
custom:
|
|
143
|
+
module: "my_seg" # import name
|
|
144
|
+
function: "segment" # default is "segment"
|
|
145
|
+
kwargs: # optional — forwarded as segment(tile, **kwargs)
|
|
146
|
+
sigma: 1.5
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Same for the StarDist example above:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
method: "custom"
|
|
153
|
+
label_name: "stardist"
|
|
154
|
+
custom:
|
|
155
|
+
module: "stardist_seg"
|
|
156
|
+
function: "segment"
|
|
157
|
+
kwargs:
|
|
158
|
+
prob_thresh: 0.5
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
For getting the module importable on a compute node, dependency/GPU
|
|
162
|
+
checklist, and troubleshooting, see [Custom functions on the
|
|
163
|
+
cluster](snakemake.md#custom-segmentation-function) in the cluster guide.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Relating labels across segmentations
|
|
2
|
+
|
|
3
|
+
Once you have two segmentations of the same image (e.g. nuclei inside cells),
|
|
4
|
+
`label_relations()` maps each label in one to the label it overlaps most in
|
|
5
|
+
the other — by streaming both arrays chunk by chunk, so it scales to
|
|
6
|
+
hundreds of thousands of objects without loading anything fully into RAM.
|
|
7
|
+
|
|
8
|
+
Both label arrays must share the exact same chunk layout — same
|
|
9
|
+
`tile_shape`/pyramid `level` when they were produced.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import dask.array as da
|
|
13
|
+
from patchworks import label_relations
|
|
14
|
+
|
|
15
|
+
nuclei = da.from_zarr("results/image.zarr", component="labels/nuclei_labels/0")
|
|
16
|
+
cells = da.from_zarr("results/image.zarr", component="labels/cyto_labels/0")
|
|
17
|
+
|
|
18
|
+
table = label_relations(nuclei, cells)
|
|
19
|
+
table[2]
|
|
20
|
+
# {'match': 3, 'overlap_voxels': 4821, 'overlap_fraction': 0.94}
|
|
21
|
+
# -> nucleus 2 belongs to cell 3, 94% of its voxels fall inside it
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`table` only contains matched `a` labels (nuclei with at least one
|
|
25
|
+
overlapping voxel in `cells`) — unmatched labels and full per-`b` coverage
|
|
26
|
+
need a bit more bookkeeping (the [cluster workflow's `run_multi.py`
|
|
27
|
+
script](snakemake.md#one-command-multiple-segmentations--relations) does
|
|
28
|
+
this for you and writes it as a two-sheet workbook).
|
|
29
|
+
|
|
30
|
+
Save it as a table yourself:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import csv
|
|
34
|
+
|
|
35
|
+
with open("nuclei_to_cell.csv", "w", newline="") as f:
|
|
36
|
+
w = csv.writer(f)
|
|
37
|
+
w.writerow(["nucleus_id", "cell_id", "overlap_voxels", "overlap_fraction"])
|
|
38
|
+
for nucleus_id, m in table.items():
|
|
39
|
+
w.writerow([nucleus_id, m["match"], m["overlap_voxels"], m["overlap_fraction"]])
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
On the cluster, producing the two label stores in the first place is a
|
|
43
|
+
matter of running the workflow twice against the same `work_dir` — see
|
|
44
|
+
[Running two segmentations](snakemake.md#running-two-segmentations-eg-nuclei--cytoplasm).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Measurements (fast, whole-volume regionprops)
|
|
2
|
+
|
|
3
|
+
`skimage.measure.regionprops` needs the full labelled + intensity array in
|
|
4
|
+
RAM — fine for one tile, not for a hundred-thousand-object OME-ZARR.
|
|
5
|
+
|
|
6
|
+
## Interactively, in napari
|
|
7
|
+
|
|
8
|
+
[napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)
|
|
9
|
+
is built for this — its "Measure" dock widget computes area/centroid/intensity
|
|
10
|
+
stats directly off a Labels layer's dask/zarr-backed array, out-of-core, and
|
|
11
|
+
scales with chunk count rather than object count. It's the best fit for
|
|
12
|
+
measuring *every* object in a store this size, not just a cropped region —
|
|
13
|
+
see [View image + labels in napari](ome_zarr_napari.md#view-image--labels-in-napari).
|
|
14
|
+
Bundled in `patchworks[napari]`.
|
|
15
|
+
|
|
16
|
+
For interactively inspecting individual cells by clicking in the viewer (not
|
|
17
|
+
all objects at once), the
|
|
18
|
+
[napari-skimage-regionprops](https://github.com/haesleinhuepf/napari-skimage-regionprops)
|
|
19
|
+
plugin's table widget also works well — point it at a cropped region rather
|
|
20
|
+
than the full volume, since it loads its input fully into memory.
|
|
21
|
+
|
|
22
|
+
## Headless / scripted
|
|
23
|
+
|
|
24
|
+
Use [`dask-image`](https://image.dask.org)'s `ndmeasure`, which computes
|
|
25
|
+
directly on the dask/zarr-backed arrays, chunk-parallel, without
|
|
26
|
+
materializing the volume:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install dask-image
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import dask.array as da
|
|
34
|
+
from dask_image.ndmeasure import area, center_of_mass, mean, standard_deviation
|
|
35
|
+
|
|
36
|
+
labels = da.from_zarr("results/image.zarr", component="labels/cyto_labels/0")
|
|
37
|
+
image = da.from_zarr("results/image.zarr", component="0")[0] # channel 0, level 0
|
|
38
|
+
|
|
39
|
+
ids = da.unique(labels[labels > 0]).compute()
|
|
40
|
+
areas = area(image, labels, ids).compute() # voxel counts
|
|
41
|
+
means = mean(image, labels, ids).compute() # mean intensity
|
|
42
|
+
stds = standard_deviation(image, labels, ids).compute()
|
|
43
|
+
centroids = center_of_mass(image, labels, ids).compute() # voxel coords (z, y, x)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Multiply `areas` by the voxel's physical volume and `centroids` by the pixel
|
|
47
|
+
size (both read straight from the OME-ZARR's own `multiscales` metadata) to
|
|
48
|
+
get µm-scale measurements.
|
|
@@ -159,7 +159,7 @@ just one channel instead: `view_in_napari("scan.zarr", channel=0)`.
|
|
|
159
159
|
out-of-core straight off the Labels layer's backing dask/zarr array, so
|
|
160
160
|
it scales to the same huge label images `tile_process` writes, unlike
|
|
161
161
|
plain `skimage.measure.regionprops`. Bundled in `patchworks[napari]`. See
|
|
162
|
-
[Measurements](
|
|
162
|
+
[Measurements](measurements.md)
|
|
163
163
|
for the non-interactive/headless equivalent.
|
|
164
164
|
|
|
165
165
|
## End-to-end
|
|
@@ -65,6 +65,7 @@ empty_threshold: null # null → Otsu
|
|
|
65
65
|
method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU), "custom"
|
|
66
66
|
label_name: "cellpose" # name under image.zarr/labels/
|
|
67
67
|
dilate: 0 # optional: pixels to grow labels by, any method
|
|
68
|
+
dilate_gpu: false # dilate via cupy instead of scipy (needs a GPU)
|
|
68
69
|
cellpose:
|
|
69
70
|
model: "cyto3"
|
|
70
71
|
diameter: 30
|
|
@@ -80,15 +81,16 @@ sequential_labels: true # renumber labels to a contiguous 1..N
|
|
|
80
81
|
|
|
81
82
|
!!! tip "Growing labels after segmentation"
|
|
82
83
|
`dilate: N` grows every label by `N` pixels once segmentation finishes,
|
|
83
|
-
regardless of `method` (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
regardless of `method`. `0` (default) disables it. Runs on CPU (scipy)
|
|
85
|
+
by default; set `dilate_gpu: true` to dilate via cupy instead — that
|
|
86
|
+
needs `cupy` installed in the segment job's environment (matching your
|
|
87
|
+
CUDA version, e.g. `pip install cupy-cuda12x`) and a GPU allocated for
|
|
88
|
+
that job (`set-resources: segment:` in `profile/slurm/config.yaml`,
|
|
89
|
+
same as for a GPU `method`). It's independent of whatever `method`
|
|
90
|
+
itself runs on — you can dilate on GPU even with `method: "threshold"`
|
|
91
|
+
(CPU), or on CPU even with `method: "cellpose"` (GPU). See [Growing
|
|
92
|
+
labels afterwards](custom_segmentation.md#growing-labels-afterwards-dilation)
|
|
93
|
+
for how it works and the equivalent direct-API call.
|
|
92
94
|
|
|
93
95
|
!!! tip "Tile size vs runtime"
|
|
94
96
|
`tile_shape: "auto"` sizes each tile to your GPU's VRAM. Smaller tiles =
|
|
@@ -266,39 +268,11 @@ results/image.zarr/labels/cyto_labels/
|
|
|
266
268
|
Different segmentations of the same image can use different `channel` and
|
|
267
269
|
`cellpose:` settings freely, but keep `tile_shape`/`level` the same across
|
|
268
270
|
configs — the label arrays then share the exact same chunk layout, which
|
|
269
|
-
`label_relations()`
|
|
271
|
+
[`label_relations()`](label_relations.md) requires.
|
|
270
272
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
`label_relations()` maps each label in one to the label it overlaps most in
|
|
275
|
-
the other — by streaming both arrays chunk by chunk, so it scales to
|
|
276
|
-
hundreds of thousands of objects without loading anything fully into RAM:
|
|
277
|
-
|
|
278
|
-
```python
|
|
279
|
-
import dask.array as da
|
|
280
|
-
from patchworks import label_relations
|
|
281
|
-
|
|
282
|
-
nuclei = da.from_zarr("results/image.zarr", component="labels/nuclei_labels/0")
|
|
283
|
-
cells = da.from_zarr("results/image.zarr", component="labels/cyto_labels/0")
|
|
284
|
-
|
|
285
|
-
table = label_relations(nuclei, cells)
|
|
286
|
-
table[2]
|
|
287
|
-
# {'match': 3, 'overlap_voxels': 4821, 'overlap_fraction': 0.94}
|
|
288
|
-
# -> nucleus 2 belongs to cell 3, 94% of its voxels fall inside it
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Save it as a table:
|
|
292
|
-
|
|
293
|
-
```python
|
|
294
|
-
import csv
|
|
295
|
-
|
|
296
|
-
with open("nuclei_to_cell.csv", "w", newline="") as f:
|
|
297
|
-
w = csv.writer(f)
|
|
298
|
-
w.writerow(["nucleus_id", "cell_id", "overlap_voxels", "overlap_fraction"])
|
|
299
|
-
for nucleus_id, m in table.items():
|
|
300
|
-
w.writerow([nucleus_id, m["match"], m["overlap_voxels"], m["overlap_fraction"]])
|
|
301
|
-
```
|
|
273
|
+
See [Relating labels across segmentations](label_relations.md) for what
|
|
274
|
+
`label_relations()` returns and how to save it yourself — the cluster
|
|
275
|
+
workflow's own automation is below.
|
|
302
276
|
|
|
303
277
|
### One command: multiple segmentations + relations
|
|
304
278
|
|
|
@@ -338,9 +312,9 @@ extra) with two sheets:
|
|
|
338
312
|
| `<a>` | every non-background `a` label, **including unmatched ones** | `<a>_id`, `<b>_id` (blank if unmatched), `overlap_voxels`, `overlap_fraction` (0 if unmatched) |
|
|
339
313
|
| `<b>` | every non-background `b` label, **including ones with zero matches** | `<b>_id`, `<a>_count`, `total_overlap_voxels` |
|
|
340
314
|
|
|
341
|
-
Unlike calling `label_relations()` directly (which
|
|
342
|
-
|
|
343
|
-
segmentations, so counts (e.g. "how many nuclei have no matching cell",
|
|
315
|
+
Unlike calling [`label_relations()`](label_relations.md) directly (which
|
|
316
|
+
only returns matched `a` labels), the workbook always covers every object in
|
|
317
|
+
both segmentations, so counts (e.g. "how many nuclei have no matching cell",
|
|
344
318
|
"how many cells have zero cilia") aren't silently dropped.
|
|
345
319
|
|
|
346
320
|
Both lists are ordinary lists, so 3+ segmentations work the same way — add
|
|
@@ -358,189 +332,19 @@ cyto_labels` and `cilia_labels -> nuclei_labels`) so you can use whichever
|
|
|
358
332
|
fits a given dataset. See `config/config_cilia.yaml`. Its deconvolution step
|
|
359
333
|
needs `pip install "patchworks[dog]"` in the segment jobs' environment.
|
|
360
334
|
|
|
361
|
-
## Measurements
|
|
362
|
-
|
|
363
|
-
`skimage.measure.regionprops` needs the full labelled + intensity array in
|
|
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]`.
|
|
335
|
+
## Measurements
|
|
374
336
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
pip install dask-image
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
```python
|
|
384
|
-
import dask.array as da
|
|
385
|
-
from dask_image.ndmeasure import area, center_of_mass, mean, standard_deviation
|
|
386
|
-
|
|
387
|
-
labels = da.from_zarr("results/image.zarr", component="labels/cyto_labels/0")
|
|
388
|
-
image = da.from_zarr("results/image.zarr", component="0")[0] # channel 0, level 0
|
|
389
|
-
|
|
390
|
-
ids = da.unique(labels[labels > 0]).compute()
|
|
391
|
-
areas = area(image, labels, ids).compute() # voxel counts
|
|
392
|
-
means = mean(image, labels, ids).compute() # mean intensity
|
|
393
|
-
stds = standard_deviation(image, labels, ids).compute()
|
|
394
|
-
centroids = center_of_mass(image, labels, ids).compute() # voxel coords (z, y, x)
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
Multiply `areas` by the voxel's physical volume and `centroids` by the pixel
|
|
398
|
-
size (both read straight from the OME-ZARR's own `multiscales` metadata) to
|
|
399
|
-
get µm-scale measurements.
|
|
400
|
-
|
|
401
|
-
For interactively inspecting individual cells by clicking in the viewer
|
|
402
|
-
(not all objects at once), the
|
|
403
|
-
[napari-skimage-regionprops](https://github.com/haesleinhuepf/napari-skimage-regionprops)
|
|
404
|
-
plugin's table widget works well — point it at a cropped region rather than
|
|
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.
|
|
337
|
+
See [Measurements](measurements.md) for computing area/centroid/intensity
|
|
338
|
+
stats on a whole label store, interactively in napari or headless/scripted —
|
|
339
|
+
`skimage.measure.regionprops` alone doesn't scale to a store this size.
|
|
409
340
|
|
|
410
341
|
## Custom segmentation function
|
|
411
342
|
|
|
412
|
-
Not using Cellpose?
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
### The contract
|
|
418
|
-
|
|
419
|
-
Your function is called **once per tile**:
|
|
420
|
-
|
|
421
|
-
```python
|
|
422
|
-
labels = segment(tile) # plus any kwargs you configure
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
| | What you get / must return |
|
|
426
|
-
| --- | --- |
|
|
427
|
-
| **Input `tile`** | A NumPy array of **one** tile, with the overlap halo already included. The channel and pyramid level from the config are already selected, so it is purely spatial: `(z, y, x)` for a 3-D run, `(y, x)` for 2-D. Dtype is the image's (e.g. `uint16`). |
|
|
428
|
-
| **Return** | An integer **label** array (not a boolean mask), **same shape** as `tile`. `0` = background; each object a distinct positive integer. |
|
|
429
|
-
| **Labels** | Only need to be unique **within the tile**. Don't try to make them globally unique — the merge step stitches objects across tile borders and renumbers everything to a contiguous `1..N` (`sequential_labels: true`). |
|
|
430
|
-
| **Shape** | Must match the input exactly — patchworks trims the halo off your output, so a wrong shape is an error. Don't crop or resize inside the function. |
|
|
431
|
-
|
|
432
|
-
That is the whole interface. Anything that turns an image tile into a label
|
|
433
|
-
image works: classic image processing, StarDist, a trained model, an external
|
|
434
|
-
binary you shell out to, …
|
|
435
|
-
|
|
436
|
-
### Minimal example (no GPU, no deps beyond scikit-image)
|
|
437
|
-
|
|
438
|
-
```python
|
|
439
|
-
# my_seg.py
|
|
440
|
-
import numpy as np
|
|
441
|
-
from skimage.measure import label
|
|
442
|
-
|
|
443
|
-
def segment(tile: np.ndarray, sigma: float = 2.0) -> np.ndarray:
|
|
444
|
-
"""Threshold + connected components. Returns int32 labels (0 = bg)."""
|
|
445
|
-
from skimage.filters import gaussian, threshold_otsu
|
|
446
|
-
|
|
447
|
-
smooth = gaussian(tile, sigma=sigma, preserve_range=True)
|
|
448
|
-
thr = threshold_otsu(smooth) if smooth.max() > smooth.min() else np.inf
|
|
449
|
-
return label(smooth > thr).astype("int32")
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
```yaml
|
|
453
|
-
method: "custom"
|
|
454
|
-
label_name: "my_labels"
|
|
455
|
-
custom:
|
|
456
|
-
module: "my_seg" # import name (see "Make it importable")
|
|
457
|
-
function: "segment" # default is "segment"
|
|
458
|
-
kwargs: # optional — forwarded as segment(tile, **kwargs)
|
|
459
|
-
sigma: 1.5
|
|
460
|
-
```
|
|
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
|
-
|
|
485
|
-
### Real example: StarDist 3-D, with model caching
|
|
486
|
-
|
|
487
|
-
Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
|
|
488
|
-
own process so this matters less, but for local runs one process segments many
|
|
489
|
-
tiles — cache the model at module level (or with `functools.lru_cache`):
|
|
490
|
-
|
|
491
|
-
```python
|
|
492
|
-
# stardist_seg.py
|
|
493
|
-
import numpy as np
|
|
494
|
-
|
|
495
|
-
_MODEL = None
|
|
496
|
-
|
|
497
|
-
def _model():
|
|
498
|
-
global _MODEL
|
|
499
|
-
if _MODEL is None: # loaded once per worker process
|
|
500
|
-
from stardist.models import StarDist3D
|
|
501
|
-
_MODEL = StarDist3D.from_pretrained("3D_demo")
|
|
502
|
-
return _MODEL
|
|
503
|
-
|
|
504
|
-
def segment(tile: np.ndarray, prob_thresh: float = 0.5) -> np.ndarray:
|
|
505
|
-
from csbdeep.utils import normalize
|
|
506
|
-
|
|
507
|
-
labels, _ = _model().predict_instances(
|
|
508
|
-
normalize(tile), prob_thresh=prob_thresh
|
|
509
|
-
)
|
|
510
|
-
return labels.astype("int32")
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
```yaml
|
|
514
|
-
method: "custom"
|
|
515
|
-
label_name: "stardist"
|
|
516
|
-
custom:
|
|
517
|
-
module: "stardist_seg"
|
|
518
|
-
function: "segment"
|
|
519
|
-
kwargs:
|
|
520
|
-
prob_thresh: 0.5
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
Using a GPU? The segment jobs already hold one (the `gres: "gpu:1"` request),
|
|
524
|
-
so just let your framework see it — nothing extra in the config.
|
|
525
|
-
|
|
526
|
-
### Test it before you submit
|
|
527
|
-
|
|
528
|
-
Run your function on one real tile first — it catches shape/dtype bugs in
|
|
529
|
-
seconds instead of after a queue wait. Output must be integer, same shape, `0`
|
|
530
|
-
for background:
|
|
531
|
-
|
|
532
|
-
```python
|
|
533
|
-
from patchworks import load_ome_zarr
|
|
534
|
-
from my_seg import segment
|
|
535
|
-
|
|
536
|
-
img = load_ome_zarr("results/image.zarr", channel=0, level=0)
|
|
537
|
-
tile = img[:, :512, :512].compute() # a small spatial block
|
|
538
|
-
out = segment(tile)
|
|
539
|
-
|
|
540
|
-
assert out.shape == tile.shape, (out.shape, tile.shape)
|
|
541
|
-
assert out.dtype.kind in "iu" # integer labels, not a float mask
|
|
542
|
-
print("objects in tile:", int(out.max()))
|
|
543
|
-
```
|
|
343
|
+
Not using Cellpose? See [Custom segmentation function](custom_segmentation.md)
|
|
344
|
+
for the function contract, examples (including label dilation), and how to
|
|
345
|
+
test it before submitting. The rest of this section covers what's specific to
|
|
346
|
+
running it **on the cluster**: getting the module importable and the
|
|
347
|
+
checklist below.
|
|
544
348
|
|
|
545
349
|
### Make it importable on the cluster
|
|
546
350
|
|
|
@@ -41,8 +41,11 @@ nav:
|
|
|
41
41
|
- Empty tile skipping: guide/skip_empty.md
|
|
42
42
|
- GPU & distributed: guide/gpu_distributed.md
|
|
43
43
|
- Performance & memory: guide/performance.md
|
|
44
|
-
-
|
|
44
|
+
- Custom segmentation function: guide/custom_segmentation.md
|
|
45
|
+
- Relating labels across segmentations: guide/label_relations.md
|
|
46
|
+
- Measurements: guide/measurements.md
|
|
45
47
|
- OME-ZARR & napari: guide/ome_zarr_napari.md
|
|
48
|
+
- Cluster workflow (Snakemake): guide/snakemake.md
|
|
46
49
|
- Pitfalls: guide/pitfalls.md
|
|
47
50
|
- Examples:
|
|
48
51
|
- Cellpose 2-D: examples/cellpose_2d.md
|
|
@@ -25,7 +25,7 @@ With deconvolution first (widen ``overlap`` to cover the PSF support):
|
|
|
25
25
|
>>> result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048), overlap=32)
|
|
26
26
|
|
|
27
27
|
For the Snakemake workflow's ``method: "custom"`` (see
|
|
28
|
-
docs/guide/
|
|
28
|
+
docs/guide/custom_segmentation.md), use the
|
|
29
29
|
:func:`segment` adapter instead of the factory directly:
|
|
30
30
|
|
|
31
31
|
>>> # custom: {module: "patchworks.plugins.dog", function: "segment",
|
|
@@ -30,7 +30,8 @@ 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
|
|
33
|
+
# dilate: 2 # optional: pixels to grow labels by after segmentation, any method
|
|
34
|
+
# dilate_gpu: true # optional: dilate via cupy instead of scipy, needs a GPU
|
|
34
35
|
# Also namespaces this run's intermediate files under work_dir/<label_name>/,
|
|
35
36
|
# so a second segmentation (different label_name, e.g. nuclei vs cytoplasm)
|
|
36
37
|
# can safely target the same work_dir — see docs/guide/snakemake.md
|
|
@@ -25,7 +25,8 @@ skip_empty: true
|
|
|
25
25
|
empty_threshold: null
|
|
26
26
|
|
|
27
27
|
method: "custom"
|
|
28
|
-
# dilate: 2
|
|
28
|
+
# dilate: 2 # optional: pixels to grow labels by after segmentation
|
|
29
|
+
# dilate_gpu: true # optional: dilate via cupy instead of scipy, needs a GPU
|
|
29
30
|
label_name: "cilia_labels"
|
|
30
31
|
custom:
|
|
31
32
|
module: "patchworks.plugins.dog"
|
|
@@ -136,7 +136,10 @@ def build_fn(cfg):
|
|
|
136
136
|
function (``cfg["custom"] = {module, function, kwargs}``). Optional
|
|
137
137
|
``cfg["dilate"]``: int, pixels to grow labels by after segmentation
|
|
138
138
|
(via ``patchworks.dilate_labels``), applied regardless of ``method``.
|
|
139
|
-
Omitted/0 disables dilation.
|
|
139
|
+
Omitted/0 disables dilation. ``cfg["dilate_gpu"]``: bool, dilate via
|
|
140
|
+
cupyx instead of scipy (default ``False``); only takes effect when
|
|
141
|
+
``dilate`` is set, and needs a GPU allocated for the segment job
|
|
142
|
+
(independent of whether ``method`` itself uses one).
|
|
140
143
|
|
|
141
144
|
Returns
|
|
142
145
|
-------
|
|
@@ -149,7 +152,7 @@ def build_fn(cfg):
|
|
|
149
152
|
if dilate:
|
|
150
153
|
from patchworks import dilate_labels
|
|
151
154
|
|
|
152
|
-
fn = dilate_labels(fn, iterations=dilate)
|
|
155
|
+
fn = dilate_labels(fn, iterations=dilate, use_gpu=cfg.get("dilate_gpu", False))
|
|
153
156
|
|
|
154
157
|
return fn
|
|
155
158
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|