patchworks 0.2.0__tar.gz → 0.4.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.4.0/.github/workflows/lint.yml +23 -0
  2. {patchworks-0.2.0 → patchworks-0.4.0}/.github/workflows/release.yml +3 -0
  3. {patchworks-0.2.0 → patchworks-0.4.0}/PKG-INFO +70 -26
  4. {patchworks-0.2.0 → patchworks-0.4.0}/README.md +52 -25
  5. {patchworks-0.2.0 → patchworks-0.4.0}/cliff.toml +7 -4
  6. patchworks-0.4.0/docs/api/plugins/napari.md +7 -0
  7. patchworks-0.4.0/docs/api/plugins/ome_zarr.md +26 -0
  8. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/cellpose_2d.md +6 -5
  9. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/cellpose_2d.py +4 -2
  10. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/cellpose_3d.md +3 -2
  11. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/cellpose_3d.py +9 -3
  12. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/custom.md +23 -10
  13. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/custom_method.py +1 -0
  14. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/standalone_merge.md +6 -2
  15. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/stardist.md +5 -2
  16. {patchworks-0.2.0 → patchworks-0.4.0}/docs/examples/stardist_2d.py +3 -1
  17. {patchworks-0.2.0 → patchworks-0.4.0}/docs/getting_started.md +14 -10
  18. {patchworks-0.2.0 → patchworks-0.4.0}/docs/guide/gpu_distributed.md +17 -11
  19. {patchworks-0.2.0 → patchworks-0.4.0}/docs/guide/merging.md +4 -4
  20. patchworks-0.4.0/docs/guide/ome_zarr_napari.md +114 -0
  21. {patchworks-0.2.0 → patchworks-0.4.0}/docs/guide/pitfalls.md +8 -7
  22. {patchworks-0.2.0 → patchworks-0.4.0}/docs/guide/skip_empty.md +12 -7
  23. {patchworks-0.2.0 → patchworks-0.4.0}/docs/index.md +13 -2
  24. patchworks-0.4.0/mkdocs.yml +87 -0
  25. {patchworks-0.2.0 → patchworks-0.4.0}/pyproject.toml +37 -31
  26. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/__init__.py +7 -1
  27. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/_chunks.py +20 -5
  28. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/_cluster.py +5 -1
  29. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/_core.py +111 -32
  30. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/_io.py +24 -7
  31. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/_merge.py +65 -18
  32. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/_relabel.py +9 -3
  33. {patchworks-0.2.0 → patchworks-0.4.0}/src/patchworks/plugins/cellpose.py +5 -1
  34. patchworks-0.4.0/src/patchworks/plugins/napari.py +167 -0
  35. patchworks-0.4.0/src/patchworks/plugins/ome_zarr.py +459 -0
  36. {patchworks-0.2.0 → patchworks-0.4.0}/tests/test_core.py +37 -15
  37. patchworks-0.4.0/tests/test_napari.py +41 -0
  38. patchworks-0.4.0/tests/test_ome_zarr.py +95 -0
  39. patchworks-0.2.0/mkdocs.yml +0 -84
  40. {patchworks-0.2.0 → patchworks-0.4.0}/.github/workflows/docs.yml +0 -0
  41. {patchworks-0.2.0 → patchworks-0.4.0}/.gitignore +0 -0
  42. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/chunks.md +0 -0
  43. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/cluster.md +0 -0
  44. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/io.md +0 -0
  45. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/merge_tile_labels.md +0 -0
  46. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/plugins/cellpose.md +0 -0
  47. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/relabel.md +0 -0
  48. {patchworks-0.2.0 → patchworks-0.4.0}/docs/api/tile_process.md +0 -0
  49. {patchworks-0.2.0 → patchworks-0.4.0}/docs/guide/tiling.md +0 -0
  50. {patchworks-0.2.0 → patchworks-0.4.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"
@@ -22,6 +22,9 @@ jobs:
22
22
  with:
23
23
  config: cliff.toml
24
24
  args: --latest --strip header
25
+ env:
26
+ # Lets git-cliff resolve commit authors to GitHub handles/avatars.
27
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25
28
 
26
29
  - name: Create GitHub release
27
30
  uses: softprops/action-gh-release@v2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchworks
3
- Version: 0.2.0
3
+ Version: 0.4.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,25 @@ 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'
27
+ Requires-Dist: bioio-czi; extra == 'all'
28
+ Requires-Dist: bioio-lif; extra == 'all'
29
+ Requires-Dist: bioio-nd2; extra == 'all'
30
+ Requires-Dist: bioio-ome-tiff; extra == 'all'
31
+ Requires-Dist: bioio-tifffile; extra == 'all'
25
32
  Requires-Dist: nvidia-ml-py; extra == 'all'
26
33
  Requires-Dist: psutil; extra == 'all'
27
34
  Requires-Dist: scikit-image; extra == 'all'
28
35
  Requires-Dist: tqdm; extra == 'all'
36
+ Provides-Extra: bioio
37
+ Requires-Dist: bioio; extra == 'bioio'
38
+ Requires-Dist: bioio-bioformats; extra == 'bioio'
39
+ Requires-Dist: bioio-czi; extra == 'bioio'
40
+ Requires-Dist: bioio-lif; extra == 'bioio'
41
+ Requires-Dist: bioio-nd2; extra == 'bioio'
42
+ Requires-Dist: bioio-ome-tiff; extra == 'bioio'
43
+ Requires-Dist: bioio-tifffile; extra == 'bioio'
29
44
  Provides-Extra: cellpose
30
45
  Requires-Dist: cellpose>=3.0; extra == 'cellpose'
31
46
  Provides-Extra: dev
@@ -42,10 +57,17 @@ Requires-Dist: nvidia-ml-py; extra == 'gpu'
42
57
  Provides-Extra: io
43
58
  Requires-Dist: psutil; extra == 'io'
44
59
  Requires-Dist: tqdm; extra == 'io'
60
+ Provides-Extra: napari
61
+ Requires-Dist: napari[all]; extra == 'napari'
45
62
  Description-Content-Type: text/markdown
46
63
 
47
64
  # patchworks
48
65
 
66
+ [![PyPI](https://img.shields.io/pypi/v/patchworks.svg)](https://pypi.org/project/patchworks/)
67
+ [![Python versions](https://img.shields.io/pypi/pyversions/patchworks.svg)](https://pypi.org/project/patchworks/)
68
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
69
+ [![Docs](https://img.shields.io/badge/docs-imcf.one%2Fpatchworks-blue)](https://imcf.one/patchworks/)
70
+
49
71
  > Tiled processing of arbitrarily large images — any image, any function.
50
72
 
51
73
  ```
@@ -75,9 +97,15 @@ Optional extras:
75
97
  ```bash
76
98
  pip install "patchworks[gpu]" # GPU VRAM querying (nvidia-ml-py)
77
99
  pip install "patchworks[cellpose]" # Cellpose plugin
78
- pip install "patchworks[all]" # Everything
100
+ pip install "patchworks[bioio]" # convert any image format to OME-ZARR
101
+ pip install "patchworks[napari]" # interactive napari viewer plugin
102
+ pip install "patchworks[all]" # Everything (except napari GUI)
79
103
  ```
80
104
 
105
+ > `bioio` reads CZI/LIF/ND2/OME-TIFF/… The `[bioio]` extra bundles the common
106
+ > native readers (`bioio-nd2`, `bioio-ome-tiff`, `bioio-czi`, `bioio-tifffile`,
107
+ > `bioio-lif`) plus `bioio-bioformats`, the Bio-Formats catch-all reader (JVM).
108
+
81
109
  ---
82
110
 
83
111
  ## Quick start — 5 lines
@@ -85,11 +113,14 @@ pip install "patchworks[all]" # Everything
85
113
  ```python
86
114
  from patchworks import tile_process
87
115
 
116
+
88
117
  def my_fn(tile):
89
118
  from skimage.filters import threshold_otsu
90
119
  from skimage.measure import label
120
+
91
121
  return label(tile > threshold_otsu(tile)).astype("int32")
92
122
 
123
+
93
124
  result = tile_process("image.zarr", my_fn, compute=True)
94
125
  ```
95
126
 
@@ -107,10 +138,11 @@ from patchworks.plugins.cellpose import cellpose_fn
107
138
  fn = cellpose_fn("cyto3", gpu=True, diameter=30)
108
139
 
109
140
  tile_process(
110
- "image.zarr", fn,
141
+ "image.zarr",
142
+ fn,
111
143
  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
144
+ overlap=20, # gives boundary cells enough context
145
+ write_to="labels.zarr", # stream directly to disk — no RAM accumulation
114
146
  progress=True,
115
147
  )
116
148
  ```
@@ -125,15 +157,22 @@ from patchworks import tile_process
125
157
 
126
158
  model = StarDist2D.from_pretrained("2D_versatile_fluo")
127
159
 
160
+
128
161
  def stardist_fn(tile):
129
162
  img = tile[0] if tile.ndim == 3 and tile.shape[0] == 1 else tile
130
163
  norm = img.astype("float32") / (img.max() or 1)
131
164
  labels, _ = model.predict_instances(norm)
132
165
  return labels.astype("int32")[None] if tile.ndim == 3 else labels.astype("int32")
133
166
 
134
- tile_process("image.zarr", stardist_fn,
135
- tile_shape=(1, 1024, 1024), overlap=32,
136
- write_to="labels.zarr", progress=True)
167
+
168
+ tile_process(
169
+ "image.zarr",
170
+ stardist_fn,
171
+ tile_shape=(1, 1024, 1024),
172
+ overlap=32,
173
+ write_to="labels.zarr",
174
+ progress=True,
175
+ )
137
176
  ```
138
177
 
139
178
  ---
@@ -146,11 +185,13 @@ from scipy.ndimage import gaussian_filter
146
185
  from skimage.measure import label
147
186
  from patchworks import tile_process
148
187
 
188
+
149
189
  def my_custom_fn(tile: np.ndarray) -> np.ndarray:
150
190
  smoothed = gaussian_filter(tile.astype("float32"), sigma=1.5)
151
191
  binary = smoothed > smoothed.mean()
152
192
  return label(binary).astype("int32")
153
193
 
194
+
154
195
  tile_process("image.zarr", my_custom_fn, tile_shape=(1, 512, 512))
155
196
  ```
156
197
 
@@ -174,11 +215,14 @@ from patchworks import estimate_empty_tiles, tile_process
174
215
  info = estimate_empty_tiles("image.zarr", tile_shape=(120, 697, 697))
175
216
  print(f"{info['empty_fraction']:.0%} tiles are background — will be skipped")
176
217
 
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")
218
+ tile_process(
219
+ "image.zarr",
220
+ fn,
221
+ tile_shape=(120, 697, 697),
222
+ skip_empty=True,
223
+ empty_threshold=info["threshold"],
224
+ write_to="labels.zarr",
225
+ )
182
226
  ```
183
227
 
184
228
  ### Distributed cluster for GPU
@@ -190,7 +234,8 @@ client, cluster = make_local_cluster(use_gpu=True)
190
234
  try:
191
235
  tile_process("image.zarr", fn, write_to="labels.zarr", progress=True)
192
236
  finally:
193
- client.close(); cluster.close()
237
+ client.close()
238
+ cluster.close()
194
239
  ```
195
240
 
196
241
  ### Contiguous label numbering
@@ -198,9 +243,7 @@ finally:
198
243
  ```python
199
244
  # Labels are globally unique by default, but may be gappy (block-encoded IDs).
200
245
  # 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)
246
+ tile_process("image.zarr", fn, write_to="labels.zarr", sequential_labels=True)
204
247
  ```
205
248
 
206
249
  ### Use only the merge step (bring your own tiling)
@@ -215,8 +258,9 @@ from patchworks import merge_tile_labels
215
258
 
216
259
  # Your own tiling + segmentation
217
260
  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"))
261
+ labeled = image.map_blocks(
262
+ my_segment_fn, dtype="int32", meta=np.empty((0,) * image.ndim, dtype="int32")
263
+ )
220
264
 
221
265
  merged = merge_tile_labels(labeled, write_to="labels.zarr", progress=True)
222
266
  ```
@@ -257,13 +301,13 @@ tiles where the dask-image approach stalls.
257
301
 
258
302
  ## Known pitfalls (and how patchworks avoids them)
259
303
 
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 |
304
+ | Pitfall | Symptom | How patchworks handles it |
305
+ | ------------------------------ | ----------------------------------------- | ------------------------------------------------------------- |
306
+ | In-process Dask client | `FutureCancelledError: lost dependencies` | Detected at startup, raises immediately with fix instructions |
307
+ | 3-4× fn recompute during merge | Cellpose runs 3× per tile | Staging writes labels once, merge reads from disk |
308
+ | O(n²) sequential relabelling | Graph construction hangs at 1000+ tiles | Linear post-pass O(voxels) via `np.unique` + LUT |
309
+ | Wrong overlap boundary | Output shape mismatch | Always uses `boundary="none"` |
310
+ | Persisting large arrays | Worker OOM | Never persists; keeps dask graph lazy and streams |
267
311
 
268
312
  ---
269
313
 
@@ -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/… The `[bioio]` extra bundles the common
43
+ > native readers (`bioio-nd2`, `bioio-ome-tiff`, `bioio-czi`, `bioio-tifffile`,
44
+ > `bioio-lif`) plus `bioio-bioformats`, the Bio-Formats catch-all reader (JVM).
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
 
@@ -8,12 +8,11 @@ body = """
8
8
  - {% if commit.scope %}**{{ commit.scope }}**: {% endif %}{{ commit.message | split(pat="\n") | first }}
9
9
  {% endfor %}
10
10
  {% endfor %}\
11
- {% set contributors = commits | map(attribute="author.name") | unique | sort -%}
12
- {% if contributors | length > 0 %}
11
+ {% if github.contributors | length > 0 %}
13
12
  ### 👥 Contributors
14
13
 
15
- {% for name in contributors %}\
16
- - {{ name }}
14
+ {% for contributor in github.contributors | sort(attribute="username") %}\
15
+ * @{{ contributor.username }}
17
16
  {% endfor %}
18
17
  {% endif %}\
19
18
  """
@@ -47,3 +46,7 @@ commit_parsers = [
47
46
  filter_commits = true
48
47
  tag_pattern = "v[0-9].*"
49
48
  sort_commits = "oldest"
49
+
50
+ [remote.github]
51
+ owner = "imcf"
52
+ repo = "patchworks"
@@ -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