patchworks 0.2.0__tar.gz → 0.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.
Files changed (50) hide show
  1. patchworks-0.3.0/.github/workflows/lint.yml +23 -0
  2. {patchworks-0.2.0 → patchworks-0.3.0}/PKG-INFO +60 -26
  3. {patchworks-0.2.0 → patchworks-0.3.0}/README.md +52 -25
  4. patchworks-0.3.0/docs/api/plugins/napari.md +7 -0
  5. patchworks-0.3.0/docs/api/plugins/ome_zarr.md +26 -0
  6. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/cellpose_2d.md +6 -5
  7. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/cellpose_2d.py +4 -2
  8. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/cellpose_3d.md +3 -2
  9. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/cellpose_3d.py +9 -3
  10. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/custom.md +23 -10
  11. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/custom_method.py +1 -0
  12. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/standalone_merge.md +6 -2
  13. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/stardist.md +5 -2
  14. {patchworks-0.2.0 → patchworks-0.3.0}/docs/examples/stardist_2d.py +3 -1
  15. {patchworks-0.2.0 → patchworks-0.3.0}/docs/getting_started.md +14 -10
  16. {patchworks-0.2.0 → patchworks-0.3.0}/docs/guide/gpu_distributed.md +17 -11
  17. {patchworks-0.2.0 → patchworks-0.3.0}/docs/guide/merging.md +4 -4
  18. patchworks-0.3.0/docs/guide/ome_zarr_napari.md +114 -0
  19. {patchworks-0.2.0 → patchworks-0.3.0}/docs/guide/pitfalls.md +8 -7
  20. {patchworks-0.2.0 → patchworks-0.3.0}/docs/guide/skip_empty.md +12 -7
  21. {patchworks-0.2.0 → patchworks-0.3.0}/docs/index.md +13 -2
  22. patchworks-0.3.0/mkdocs.yml +87 -0
  23. {patchworks-0.2.0 → patchworks-0.3.0}/pyproject.toml +31 -32
  24. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/__init__.py +7 -1
  25. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/_chunks.py +20 -5
  26. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/_cluster.py +5 -1
  27. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/_core.py +111 -32
  28. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/_io.py +24 -7
  29. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/_merge.py +65 -18
  30. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/_relabel.py +9 -3
  31. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/plugins/cellpose.py +5 -1
  32. patchworks-0.3.0/src/patchworks/plugins/napari.py +167 -0
  33. patchworks-0.3.0/src/patchworks/plugins/ome_zarr.py +459 -0
  34. {patchworks-0.2.0 → patchworks-0.3.0}/tests/test_core.py +37 -15
  35. patchworks-0.3.0/tests/test_napari.py +41 -0
  36. patchworks-0.3.0/tests/test_ome_zarr.py +95 -0
  37. patchworks-0.2.0/mkdocs.yml +0 -84
  38. {patchworks-0.2.0 → patchworks-0.3.0}/.github/workflows/docs.yml +0 -0
  39. {patchworks-0.2.0 → patchworks-0.3.0}/.github/workflows/release.yml +0 -0
  40. {patchworks-0.2.0 → patchworks-0.3.0}/.gitignore +0 -0
  41. {patchworks-0.2.0 → patchworks-0.3.0}/cliff.toml +0 -0
  42. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/chunks.md +0 -0
  43. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/cluster.md +0 -0
  44. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/io.md +0 -0
  45. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/merge_tile_labels.md +0 -0
  46. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/plugins/cellpose.md +0 -0
  47. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/relabel.md +0 -0
  48. {patchworks-0.2.0 → patchworks-0.3.0}/docs/api/tile_process.md +0 -0
  49. {patchworks-0.2.0 → patchworks-0.3.0}/docs/guide/tiling.md +0 -0
  50. {patchworks-0.2.0 → patchworks-0.3.0}/src/patchworks/plugins/__init__.py +0 -0
@@ -0,0 +1,23 @@
1
+ name: Lint
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ ruff:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: ruff check
16
+ uses: astral-sh/ruff-action@v3
17
+ with:
18
+ args: "check"
19
+
20
+ - name: ruff format --check
21
+ uses: astral-sh/ruff-action@v3
22
+ with:
23
+ args: "format --check"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchworks
3
- Version: 0.2.0
3
+ Version: 0.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
@@ -22,10 +22,15 @@ Requires-Dist: numpy>=1.24
22
22
  Requires-Dist: scipy>=1.9
23
23
  Requires-Dist: zarr>=2.14
24
24
  Provides-Extra: all
25
+ Requires-Dist: bioio; extra == 'all'
26
+ Requires-Dist: bioio-bioformats; extra == 'all'
25
27
  Requires-Dist: nvidia-ml-py; extra == 'all'
26
28
  Requires-Dist: psutil; extra == 'all'
27
29
  Requires-Dist: scikit-image; extra == 'all'
28
30
  Requires-Dist: tqdm; extra == 'all'
31
+ Provides-Extra: bioio
32
+ Requires-Dist: bioio; extra == 'bioio'
33
+ Requires-Dist: bioio-bioformats; extra == 'bioio'
29
34
  Provides-Extra: cellpose
30
35
  Requires-Dist: cellpose>=3.0; extra == 'cellpose'
31
36
  Provides-Extra: dev
@@ -42,10 +47,17 @@ Requires-Dist: nvidia-ml-py; extra == 'gpu'
42
47
  Provides-Extra: io
43
48
  Requires-Dist: psutil; extra == 'io'
44
49
  Requires-Dist: tqdm; extra == 'io'
50
+ Provides-Extra: napari
51
+ Requires-Dist: napari[all]; extra == 'napari'
45
52
  Description-Content-Type: text/markdown
46
53
 
47
54
  # patchworks
48
55
 
56
+ [![PyPI](https://img.shields.io/pypi/v/patchworks.svg)](https://pypi.org/project/patchworks/)
57
+ [![Python versions](https://img.shields.io/pypi/pyversions/patchworks.svg)](https://pypi.org/project/patchworks/)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
+ [![Docs](https://img.shields.io/badge/docs-imcf.one%2Fpatchworks-blue)](https://imcf.one/patchworks/)
60
+
49
61
  > Tiled processing of arbitrarily large images — any image, any function.
50
62
 
51
63
  ```
@@ -75,9 +87,15 @@ Optional extras:
75
87
  ```bash
76
88
  pip install "patchworks[gpu]" # GPU VRAM querying (nvidia-ml-py)
77
89
  pip install "patchworks[cellpose]" # Cellpose plugin
78
- pip install "patchworks[all]" # Everything
90
+ pip install "patchworks[bioio]" # convert any image format to OME-ZARR
91
+ pip install "patchworks[napari]" # interactive napari viewer plugin
92
+ pip install "patchworks[all]" # Everything (except napari GUI)
79
93
  ```
80
94
 
95
+ > `bioio` reads CZI/LIF/ND2/OME-TIFF/… — it ships with `bioio-bioformats`, the
96
+ > Bio-Formats catch-all reader (needs a JVM). Add faster native readers where
97
+ > you want them (e.g. `bioio-ome-tiff`, `bioio-czi`, `bioio-lif`, `bioio-nd2`).
98
+
81
99
  ---
82
100
 
83
101
  ## Quick start — 5 lines
@@ -85,11 +103,14 @@ pip install "patchworks[all]" # Everything
85
103
  ```python
86
104
  from patchworks import tile_process
87
105
 
106
+
88
107
  def my_fn(tile):
89
108
  from skimage.filters import threshold_otsu
90
109
  from skimage.measure import label
110
+
91
111
  return label(tile > threshold_otsu(tile)).astype("int32")
92
112
 
113
+
93
114
  result = tile_process("image.zarr", my_fn, compute=True)
94
115
  ```
95
116
 
@@ -107,10 +128,11 @@ from patchworks.plugins.cellpose import cellpose_fn
107
128
  fn = cellpose_fn("cyto3", gpu=True, diameter=30)
108
129
 
109
130
  tile_process(
110
- "image.zarr", fn,
131
+ "image.zarr",
132
+ fn,
111
133
  tile_shape=(1, 2048, 2048), # one z-slice per tile
112
- overlap=20, # gives boundary cells enough context
113
- write_to="labels.zarr", # stream directly to disk — no RAM accumulation
134
+ overlap=20, # gives boundary cells enough context
135
+ write_to="labels.zarr", # stream directly to disk — no RAM accumulation
114
136
  progress=True,
115
137
  )
116
138
  ```
@@ -125,15 +147,22 @@ from patchworks import tile_process
125
147
 
126
148
  model = StarDist2D.from_pretrained("2D_versatile_fluo")
127
149
 
150
+
128
151
  def stardist_fn(tile):
129
152
  img = tile[0] if tile.ndim == 3 and tile.shape[0] == 1 else tile
130
153
  norm = img.astype("float32") / (img.max() or 1)
131
154
  labels, _ = model.predict_instances(norm)
132
155
  return labels.astype("int32")[None] if tile.ndim == 3 else labels.astype("int32")
133
156
 
134
- tile_process("image.zarr", stardist_fn,
135
- tile_shape=(1, 1024, 1024), overlap=32,
136
- write_to="labels.zarr", progress=True)
157
+
158
+ tile_process(
159
+ "image.zarr",
160
+ stardist_fn,
161
+ tile_shape=(1, 1024, 1024),
162
+ overlap=32,
163
+ write_to="labels.zarr",
164
+ progress=True,
165
+ )
137
166
  ```
138
167
 
139
168
  ---
@@ -146,11 +175,13 @@ from scipy.ndimage import gaussian_filter
146
175
  from skimage.measure import label
147
176
  from patchworks import tile_process
148
177
 
178
+
149
179
  def my_custom_fn(tile: np.ndarray) -> np.ndarray:
150
180
  smoothed = gaussian_filter(tile.astype("float32"), sigma=1.5)
151
181
  binary = smoothed > smoothed.mean()
152
182
  return label(binary).astype("int32")
153
183
 
184
+
154
185
  tile_process("image.zarr", my_custom_fn, tile_shape=(1, 512, 512))
155
186
  ```
156
187
 
@@ -174,11 +205,14 @@ from patchworks import estimate_empty_tiles, tile_process
174
205
  info = estimate_empty_tiles("image.zarr", tile_shape=(120, 697, 697))
175
206
  print(f"{info['empty_fraction']:.0%} tiles are background — will be skipped")
176
207
 
177
- tile_process("image.zarr", fn,
178
- tile_shape=(120, 697, 697),
179
- skip_empty=True,
180
- empty_threshold=info["threshold"],
181
- write_to="labels.zarr")
208
+ tile_process(
209
+ "image.zarr",
210
+ fn,
211
+ tile_shape=(120, 697, 697),
212
+ skip_empty=True,
213
+ empty_threshold=info["threshold"],
214
+ write_to="labels.zarr",
215
+ )
182
216
  ```
183
217
 
184
218
  ### Distributed cluster for GPU
@@ -190,7 +224,8 @@ client, cluster = make_local_cluster(use_gpu=True)
190
224
  try:
191
225
  tile_process("image.zarr", fn, write_to="labels.zarr", progress=True)
192
226
  finally:
193
- client.close(); cluster.close()
227
+ client.close()
228
+ cluster.close()
194
229
  ```
195
230
 
196
231
  ### Contiguous label numbering
@@ -198,9 +233,7 @@ finally:
198
233
  ```python
199
234
  # Labels are globally unique by default, but may be gappy (block-encoded IDs).
200
235
  # sequential_labels=True does a linear relabel O(voxels) — not O(n_tiles²).
201
- tile_process("image.zarr", fn,
202
- write_to="labels.zarr",
203
- sequential_labels=True)
236
+ tile_process("image.zarr", fn, write_to="labels.zarr", sequential_labels=True)
204
237
  ```
205
238
 
206
239
  ### Use only the merge step (bring your own tiling)
@@ -215,8 +248,9 @@ from patchworks import merge_tile_labels
215
248
 
216
249
  # Your own tiling + segmentation
217
250
  image = da.from_zarr("image.zarr").rechunk((1, 1024, 1024))
218
- labeled = image.map_blocks(my_segment_fn, dtype="int32",
219
- meta=np.empty((0,) * image.ndim, dtype="int32"))
251
+ labeled = image.map_blocks(
252
+ my_segment_fn, dtype="int32", meta=np.empty((0,) * image.ndim, dtype="int32")
253
+ )
220
254
 
221
255
  merged = merge_tile_labels(labeled, write_to="labels.zarr", progress=True)
222
256
  ```
@@ -257,13 +291,13 @@ tiles where the dask-image approach stalls.
257
291
 
258
292
  ## Known pitfalls (and how patchworks avoids them)
259
293
 
260
- | Pitfall | Symptom | How patchworks handles it |
261
- |---|---|---|
262
- | In-process Dask client | `FutureCancelledError: lost dependencies` | Detected at startup, raises immediately with fix instructions |
263
- | 3-4× fn recompute during merge | Cellpose runs 3× per tile | Staging writes labels once, merge reads from disk |
264
- | O(n²) sequential relabelling | Graph construction hangs at 1000+ tiles | Linear post-pass O(voxels) via `np.unique` + LUT |
265
- | Wrong overlap boundary | Output shape mismatch | Always uses `boundary="none"` |
266
- | Persisting large arrays | Worker OOM | Never persists; keeps dask graph lazy and streams |
294
+ | Pitfall | Symptom | How patchworks handles it |
295
+ | ------------------------------ | ----------------------------------------- | ------------------------------------------------------------- |
296
+ | In-process Dask client | `FutureCancelledError: lost dependencies` | Detected at startup, raises immediately with fix instructions |
297
+ | 3-4× fn recompute during merge | Cellpose runs 3× per tile | Staging writes labels once, merge reads from disk |
298
+ | O(n²) sequential relabelling | Graph construction hangs at 1000+ tiles | Linear post-pass O(voxels) via `np.unique` + LUT |
299
+ | Wrong overlap boundary | Output shape mismatch | Always uses `boundary="none"` |
300
+ | Persisting large arrays | Worker OOM | Never persists; keeps dask graph lazy and streams |
267
301
 
268
302
  ---
269
303
 
@@ -1,5 +1,10 @@
1
1
  # patchworks
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/patchworks.svg)](https://pypi.org/project/patchworks/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/patchworks.svg)](https://pypi.org/project/patchworks/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Docs](https://img.shields.io/badge/docs-imcf.one%2Fpatchworks-blue)](https://imcf.one/patchworks/)
7
+
3
8
  > Tiled processing of arbitrarily large images — any image, any function.
4
9
 
5
10
  ```
@@ -29,9 +34,15 @@ Optional extras:
29
34
  ```bash
30
35
  pip install "patchworks[gpu]" # GPU VRAM querying (nvidia-ml-py)
31
36
  pip install "patchworks[cellpose]" # Cellpose plugin
32
- pip install "patchworks[all]" # Everything
37
+ pip install "patchworks[bioio]" # convert any image format to OME-ZARR
38
+ pip install "patchworks[napari]" # interactive napari viewer plugin
39
+ pip install "patchworks[all]" # Everything (except napari GUI)
33
40
  ```
34
41
 
42
+ > `bioio` reads CZI/LIF/ND2/OME-TIFF/… — it ships with `bioio-bioformats`, the
43
+ > Bio-Formats catch-all reader (needs a JVM). Add faster native readers where
44
+ > you want them (e.g. `bioio-ome-tiff`, `bioio-czi`, `bioio-lif`, `bioio-nd2`).
45
+
35
46
  ---
36
47
 
37
48
  ## Quick start — 5 lines
@@ -39,11 +50,14 @@ pip install "patchworks[all]" # Everything
39
50
  ```python
40
51
  from patchworks import tile_process
41
52
 
53
+
42
54
  def my_fn(tile):
43
55
  from skimage.filters import threshold_otsu
44
56
  from skimage.measure import label
57
+
45
58
  return label(tile > threshold_otsu(tile)).astype("int32")
46
59
 
60
+
47
61
  result = tile_process("image.zarr", my_fn, compute=True)
48
62
  ```
49
63
 
@@ -61,10 +75,11 @@ from patchworks.plugins.cellpose import cellpose_fn
61
75
  fn = cellpose_fn("cyto3", gpu=True, diameter=30)
62
76
 
63
77
  tile_process(
64
- "image.zarr", fn,
78
+ "image.zarr",
79
+ fn,
65
80
  tile_shape=(1, 2048, 2048), # one z-slice per tile
66
- overlap=20, # gives boundary cells enough context
67
- write_to="labels.zarr", # stream directly to disk — no RAM accumulation
81
+ overlap=20, # gives boundary cells enough context
82
+ write_to="labels.zarr", # stream directly to disk — no RAM accumulation
68
83
  progress=True,
69
84
  )
70
85
  ```
@@ -79,15 +94,22 @@ from patchworks import tile_process
79
94
 
80
95
  model = StarDist2D.from_pretrained("2D_versatile_fluo")
81
96
 
97
+
82
98
  def stardist_fn(tile):
83
99
  img = tile[0] if tile.ndim == 3 and tile.shape[0] == 1 else tile
84
100
  norm = img.astype("float32") / (img.max() or 1)
85
101
  labels, _ = model.predict_instances(norm)
86
102
  return labels.astype("int32")[None] if tile.ndim == 3 else labels.astype("int32")
87
103
 
88
- tile_process("image.zarr", stardist_fn,
89
- tile_shape=(1, 1024, 1024), overlap=32,
90
- write_to="labels.zarr", progress=True)
104
+
105
+ tile_process(
106
+ "image.zarr",
107
+ stardist_fn,
108
+ tile_shape=(1, 1024, 1024),
109
+ overlap=32,
110
+ write_to="labels.zarr",
111
+ progress=True,
112
+ )
91
113
  ```
92
114
 
93
115
  ---
@@ -100,11 +122,13 @@ from scipy.ndimage import gaussian_filter
100
122
  from skimage.measure import label
101
123
  from patchworks import tile_process
102
124
 
125
+
103
126
  def my_custom_fn(tile: np.ndarray) -> np.ndarray:
104
127
  smoothed = gaussian_filter(tile.astype("float32"), sigma=1.5)
105
128
  binary = smoothed > smoothed.mean()
106
129
  return label(binary).astype("int32")
107
130
 
131
+
108
132
  tile_process("image.zarr", my_custom_fn, tile_shape=(1, 512, 512))
109
133
  ```
110
134
 
@@ -128,11 +152,14 @@ from patchworks import estimate_empty_tiles, tile_process
128
152
  info = estimate_empty_tiles("image.zarr", tile_shape=(120, 697, 697))
129
153
  print(f"{info['empty_fraction']:.0%} tiles are background — will be skipped")
130
154
 
131
- tile_process("image.zarr", fn,
132
- tile_shape=(120, 697, 697),
133
- skip_empty=True,
134
- empty_threshold=info["threshold"],
135
- write_to="labels.zarr")
155
+ tile_process(
156
+ "image.zarr",
157
+ fn,
158
+ tile_shape=(120, 697, 697),
159
+ skip_empty=True,
160
+ empty_threshold=info["threshold"],
161
+ write_to="labels.zarr",
162
+ )
136
163
  ```
137
164
 
138
165
  ### Distributed cluster for GPU
@@ -144,7 +171,8 @@ client, cluster = make_local_cluster(use_gpu=True)
144
171
  try:
145
172
  tile_process("image.zarr", fn, write_to="labels.zarr", progress=True)
146
173
  finally:
147
- client.close(); cluster.close()
174
+ client.close()
175
+ cluster.close()
148
176
  ```
149
177
 
150
178
  ### Contiguous label numbering
@@ -152,9 +180,7 @@ finally:
152
180
  ```python
153
181
  # Labels are globally unique by default, but may be gappy (block-encoded IDs).
154
182
  # sequential_labels=True does a linear relabel O(voxels) — not O(n_tiles²).
155
- tile_process("image.zarr", fn,
156
- write_to="labels.zarr",
157
- sequential_labels=True)
183
+ tile_process("image.zarr", fn, write_to="labels.zarr", sequential_labels=True)
158
184
  ```
159
185
 
160
186
  ### Use only the merge step (bring your own tiling)
@@ -169,8 +195,9 @@ from patchworks import merge_tile_labels
169
195
 
170
196
  # Your own tiling + segmentation
171
197
  image = da.from_zarr("image.zarr").rechunk((1, 1024, 1024))
172
- labeled = image.map_blocks(my_segment_fn, dtype="int32",
173
- meta=np.empty((0,) * image.ndim, dtype="int32"))
198
+ labeled = image.map_blocks(
199
+ my_segment_fn, dtype="int32", meta=np.empty((0,) * image.ndim, dtype="int32")
200
+ )
174
201
 
175
202
  merged = merge_tile_labels(labeled, write_to="labels.zarr", progress=True)
176
203
  ```
@@ -211,13 +238,13 @@ tiles where the dask-image approach stalls.
211
238
 
212
239
  ## Known pitfalls (and how patchworks avoids them)
213
240
 
214
- | Pitfall | Symptom | How patchworks handles it |
215
- |---|---|---|
216
- | In-process Dask client | `FutureCancelledError: lost dependencies` | Detected at startup, raises immediately with fix instructions |
217
- | 3-4× fn recompute during merge | Cellpose runs 3× per tile | Staging writes labels once, merge reads from disk |
218
- | O(n²) sequential relabelling | Graph construction hangs at 1000+ tiles | Linear post-pass O(voxels) via `np.unique` + LUT |
219
- | Wrong overlap boundary | Output shape mismatch | Always uses `boundary="none"` |
220
- | Persisting large arrays | Worker OOM | Never persists; keeps dask graph lazy and streams |
241
+ | Pitfall | Symptom | How patchworks handles it |
242
+ | ------------------------------ | ----------------------------------------- | ------------------------------------------------------------- |
243
+ | In-process Dask client | `FutureCancelledError: lost dependencies` | Detected at startup, raises immediately with fix instructions |
244
+ | 3-4× fn recompute during merge | Cellpose runs 3× per tile | Staging writes labels once, merge reads from disk |
245
+ | O(n²) sequential relabelling | Graph construction hangs at 1000+ tiles | Linear post-pass O(voxels) via `np.unique` + LUT |
246
+ | Wrong overlap boundary | Output shape mismatch | Always uses `boundary="none"` |
247
+ | Persisting large arrays | Worker OOM | Never persists; keeps dask graph lazy and streams |
221
248
 
222
249
  ---
223
250
 
@@ -0,0 +1,7 @@
1
+ # napari viewer plugin
2
+
3
+ Open an OME-ZARR image and overlay the labels produced by
4
+ [`tile_process`](../tile_process.md) as a napari *Labels* layer in a single
5
+ call. Requires the optional `napari` extra (`pip install "patchworks[napari]"`).
6
+
7
+ ::: patchworks.plugins.napari.view_in_napari
@@ -0,0 +1,26 @@
1
+ # OME-ZARR conversion plugin
2
+
3
+ Write any array or image file to a pyramidal OME-ZARR store, add resolution
4
+ levels to an existing store, or store a label image inside an OME-ZARR under
5
+ the NGFF `labels/` group. Uses only the core dependencies for arrays and
6
+ `.zarr` inputs; reading other file formats needs the optional `bioio` extra
7
+ (`pip install "patchworks[bioio]"`).
8
+
9
+ Pyramids downsample **X and Y only** — `Z` (and channel/time) are kept at full
10
+ resolution, matching anisotropic microscopy stacks.
11
+
12
+ ## to_ome_zarr
13
+
14
+ ::: patchworks.plugins.ome_zarr.to_ome_zarr
15
+
16
+ ## add_pyramid
17
+
18
+ ::: patchworks.plugins.ome_zarr.add_pyramid
19
+
20
+ ## write_labels
21
+
22
+ ::: patchworks.plugins.ome_zarr.write_labels
23
+
24
+ ## register_labels
25
+
26
+ ::: patchworks.plugins.ome_zarr.register_labels
@@ -18,8 +18,8 @@ from patchworks.plugins.cellpose import cellpose_fn
18
18
 
19
19
  IMAGE = "image.zarr"
20
20
  OUTPUT = "labels.zarr"
21
- CHANNEL = 0 # channel to segment
22
- DIAMETER = 30 # expected cell diameter in pixels
21
+ CHANNEL = 0 # channel to segment
22
+ DIAMETER = 30 # expected cell diameter in pixels
23
23
 
24
24
  # 1. Create the Cellpose function
25
25
  fn = cellpose_fn("cyto3", gpu=True, diameter=DIAMETER)
@@ -31,10 +31,11 @@ print(f"{info['empty_fraction']:.0%} of slices are background")
31
31
  # 3. Run
32
32
  tile_fn = partial(auto_tile_shape_cellpose, diameter=DIAMETER, use_gpu=True)
33
33
  tile_process(
34
- IMAGE, fn,
34
+ IMAGE,
35
+ fn,
35
36
  channel=CHANNEL,
36
- tile_shape=tile_fn, # auto-sized for GPU VRAM
37
- overlap=20, # 20-voxel halo for boundary cells
37
+ tile_shape=tile_fn, # auto-sized for GPU VRAM
38
+ overlap=20, # 20-voxel halo for boundary cells
38
39
  skip_empty=True,
39
40
  empty_threshold=info["threshold"],
40
41
  write_to=OUTPUT,
@@ -3,6 +3,7 @@
3
3
  Each z-slice is processed independently. Tiles overlap by 20 voxels so cells
4
4
  near tile boundaries are fully visible to Cellpose.
5
5
  """
6
+
6
7
  from functools import partial
7
8
 
8
9
  from patchworks import auto_tile_shape_cellpose, tile_process
@@ -19,11 +20,12 @@ fn = cellpose_fn("cyto3", gpu=True, diameter=DIAMETER)
19
20
  tile_fn = partial(auto_tile_shape_cellpose, diameter=DIAMETER, use_gpu=True)
20
21
 
21
22
  tile_process(
22
- IMAGE, fn,
23
+ IMAGE,
24
+ fn,
23
25
  channel=CHANNEL,
24
26
  tile_shape=tile_fn,
25
27
  overlap=20,
26
- skip_empty=True, # skip background slices
28
+ skip_empty=True, # skip background slices
27
29
  write_to=OUTPUT,
28
30
  progress=True,
29
31
  )
@@ -18,7 +18,7 @@ from patchworks.plugins.cellpose import cellpose_fn
18
18
  IMAGE = "image.zarr"
19
19
  OUTPUT = "labels_3d.zarr"
20
20
  CHANNEL = 0
21
- DIAMETER = 20 # pixels
21
+ DIAMETER = 20 # pixels
22
22
  ANISOTROPY = 3.0 # z_spacing / xy_spacing
23
23
 
24
24
  fn = cellpose_fn(
@@ -45,7 +45,8 @@ print("Dashboard:", client.dashboard_link)
45
45
 
46
46
  try:
47
47
  tile_process(
48
- IMAGE, fn,
48
+ IMAGE,
49
+ fn,
49
50
  channel=CHANNEL,
50
51
  tile_shape=tile_fn,
51
52
  overlap=10,
@@ -3,15 +3,20 @@
3
3
  Each tile contains the full z extent. Cellpose do_3D=True runs segmentation on
4
4
  xy, xz, and yz planes and takes a 3-D consensus.
5
5
  """
6
+
6
7
  from functools import partial
7
8
 
8
- from patchworks import auto_tile_shape_cellpose, make_local_cluster, tile_process
9
+ from patchworks import (
10
+ auto_tile_shape_cellpose,
11
+ make_local_cluster,
12
+ tile_process,
13
+ )
9
14
  from patchworks.plugins.cellpose import cellpose_fn
10
15
 
11
16
  IMAGE = "image.zarr"
12
17
  OUTPUT = "labels_3d.zarr"
13
18
  CHANNEL = 0
14
- DIAMETER = 20 # pixels
19
+ DIAMETER = 20 # pixels
15
20
  ANISOTROPY = 3.0 # z-spacing / xy-spacing
16
21
 
17
22
  fn = cellpose_fn(
@@ -35,7 +40,8 @@ print("Dashboard:", client.dashboard_link)
35
40
 
36
41
  try:
37
42
  tile_process(
38
- IMAGE, fn,
43
+ IMAGE,
44
+ fn,
39
45
  channel=CHANNEL,
40
46
  tile_shape=tile_fn,
41
47
  overlap=10,
@@ -11,10 +11,12 @@ from skimage.filters import threshold_otsu
11
11
  from skimage.measure import label
12
12
  from patchworks import tile_process
13
13
 
14
+
14
15
  def threshold_fn(tile: np.ndarray) -> np.ndarray:
15
16
  thr = threshold_otsu(tile)
16
17
  return label(tile > thr).astype("int32")
17
18
 
19
+
18
20
  result = tile_process("image.zarr", threshold_fn, compute=True)
19
21
  ```
20
22
 
@@ -27,17 +29,22 @@ from skimage.morphology import remove_small_objects
27
29
  from skimage.measure import label
28
30
  from patchworks import tile_process
29
31
 
32
+
30
33
  def smooth_and_label(tile: np.ndarray) -> np.ndarray:
31
34
  smoothed = gaussian_filter(tile.astype("float32"), sigma=1.5)
32
35
  binary = smoothed > smoothed.mean()
33
36
  cleaned = remove_small_objects(binary, min_size=100)
34
37
  return label(cleaned).astype("int32")
35
38
 
36
- tile_process("image.zarr", smooth_and_label,
37
- tile_shape=(1, 512, 512),
38
- overlap=16,
39
- write_to="labels.zarr",
40
- progress=True)
39
+
40
+ tile_process(
41
+ "image.zarr",
42
+ smooth_and_label,
43
+ tile_shape=(1, 512, 512),
44
+ overlap=16,
45
+ write_to="labels.zarr",
46
+ progress=True,
47
+ )
41
48
  ```
42
49
 
43
50
  ## PyTorch model
@@ -51,6 +58,7 @@ from patchworks import tile_process
51
58
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
52
59
  model = MySegmentationModel().to(device).eval()
53
60
 
61
+
54
62
  @torch.no_grad()
55
63
  def torch_fn(tile: np.ndarray) -> np.ndarray:
56
64
  t = torch.from_numpy(tile.astype("float32")).unsqueeze(0).unsqueeze(0).to(device)
@@ -58,11 +66,15 @@ def torch_fn(tile: np.ndarray) -> np.ndarray:
58
66
  pred = logits.argmax(1).squeeze(0).cpu().numpy()
59
67
  return pred.astype("int32")
60
68
 
61
- tile_process("image.zarr", torch_fn,
62
- tile_shape=(1, 512, 512),
63
- use_gpu=True,
64
- write_to="labels.zarr",
65
- progress=True)
69
+
70
+ tile_process(
71
+ "image.zarr",
72
+ torch_fn,
73
+ tile_shape=(1, 512, 512),
74
+ use_gpu=True,
75
+ write_to="labels.zarr",
76
+ progress=True,
77
+ )
66
78
  ```
67
79
 
68
80
  ## Any array input, not just zarr
@@ -79,6 +91,7 @@ result = tile_process(arr, my_fn, compute=True)
79
91
  # From tifffile
80
92
  import tifffile
81
93
  import dask.array as da
94
+
82
95
  arr = da.from_array(tifffile.imread("image.tif", aszarr=True))
83
96
  result = tile_process(arr, my_fn, compute=True)
84
97
  ```
@@ -3,6 +3,7 @@
3
3
  patchworks doesn't care what's inside fn. Here's a more elaborate example
4
4
  using scipy + skimage with preprocessing.
5
5
  """
6
+
6
7
  import numpy as np
7
8
  from scipy.ndimage import gaussian_filter
8
9
  from skimage.filters import threshold_otsu
@@ -13,12 +13,15 @@ from patchworks import merge_tile_labels
13
13
  # Your own tiling + segmentation
14
14
  image = da.from_zarr("image.zarr").rechunk((1, 1024, 1024))
15
15
 
16
+
16
17
  def my_fn(tile: np.ndarray) -> np.ndarray:
17
18
  ... # your segmentation code
18
19
  return labels.astype("int32")
19
20
 
21
+
20
22
  labeled = image.map_blocks(
21
- my_fn, dtype="int32",
23
+ my_fn,
24
+ dtype="int32",
22
25
  meta=np.empty((0,) * image.ndim, dtype="int32"),
23
26
  )
24
27
 
@@ -37,7 +40,7 @@ merged = merge_tile_labels(
37
40
  "my_staged_labels.zarr",
38
41
  input_component="raw_labels", # component name inside the zarr
39
42
  write_to="merged.zarr",
40
- sequential_labels=True, # renumber to 1..N
43
+ sequential_labels=True, # renumber to 1..N
41
44
  )
42
45
  ```
43
46
 
@@ -64,5 +67,6 @@ use `merge_tile_labels`:
64
67
  labeled = your_pipeline(da.from_zarr("image.zarr")) # dask.array.Array
65
68
 
66
69
  from patchworks import merge_tile_labels
70
+
67
71
  merged = merge_tile_labels(labeled, write_to="final.zarr")
68
72
  ```
@@ -33,10 +33,11 @@ def stardist_fn(tile: np.ndarray) -> np.ndarray:
33
33
 
34
34
 
35
35
  tile_process(
36
- IMAGE, stardist_fn,
36
+ IMAGE,
37
+ stardist_fn,
37
38
  channel=0,
38
39
  tile_shape=(1, 1024, 1024),
39
- overlap=32, # StarDist receptive field is larger than Cellpose
40
+ overlap=32, # StarDist receptive field is larger than Cellpose
40
41
  write_to=OUTPUT,
41
42
  progress=True,
42
43
  )
@@ -51,10 +52,12 @@ tile_process(
51
52
  ```python
52
53
  from functools import lru_cache
53
54
 
55
+
54
56
  @lru_cache(maxsize=1)
55
57
  def _get_model():
56
58
  return StarDist2D.from_pretrained("2D_versatile_fluo")
57
59
 
60
+
58
61
  def stardist_fn(tile):
59
62
  model = _get_model()
60
63
  ...