Skip to content

Commit 00db787

Browse files
authored
Merge pull request #20 from imcf/feat/ome-zarr-sharding
feat: OME-ZARR sharding, default progress bars, numpydoc sweep
2 parents a30f0ea + d73f0ab commit 00db787

20 files changed

Lines changed: 956 additions & 90 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
> Tiled processing of arbitrarily large images — any image, any function.
99
10-
```
10+
```text
1111
┌──────┬──────┬──────┐ fn(tile) → labels ┌──────┬──────┬──────┐
1212
│ tile │ tile │ tile │ ─────────────────────► │ 1 │ 2 │ 3 │
1313
├──────┼──────┼──────┤ ├──────┼──────┼──────┤
@@ -295,6 +295,7 @@ Full docs, guides and tutorials: **<https://imcf.one/patchworks/>**
295295
- dask[array], numpy, zarr, scipy
296296

297297
Optional:
298+
298299
- `psutil` — accurate RAM sizing for `tile_shape="auto"`
299300
- `nvidia-ml-py` — accurate GPU VRAM sizing
300301
- `tqdm` — progress bars

docs/examples/stardist.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ tile_process(
4747
Load the model **outside** the `fn` closure. If you load it inside,
4848
it will be re-initialised (and potentially re-downloaded) once per tile.
4949

50-
For distributed execution, use `functools.partial` with a cached model:
50+
For distributed execution, use `functools.partial` with a cached model:
5151

52-
```python
53-
from functools import lru_cache
52+
```python
53+
from functools import lru_cache
5454

5555

56-
@lru_cache(maxsize=1)
57-
def _get_model():
58-
return StarDist2D.from_pretrained("2D_versatile_fluo")
56+
@lru_cache(maxsize=1)
57+
def _get_model():
58+
return StarDist2D.from_pretrained("2D_versatile_fluo")
5959

6060

61-
def stardist_fn(tile):
62-
model = _get_model()
63-
...
64-
```
61+
def stardist_fn(tile):
62+
model = _get_model()
63+
...
64+
```

docs/getting_started.md

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ patchworks can be installed from PyPI on all operating systems, for Python ≥ 3
4646

4747
## The one function you need
4848

49-
```python
50-
from patchworks import tile_process
49+
```python
50+
from patchworks import tile_process
5151

52-
result = tile_process(image, fn)
53-
```
52+
result = tile_process(image, fn)
53+
```
5454

5555
`tile_process(image, fn)` splits `image` into tiles, runs `fn` on each tile,
5656
and returns a globally consistent label array.
@@ -65,17 +65,17 @@ and returns a globally consistent label array.
6565
patchworks is method-agnostic. Your function receives a NumPy array (one tile)
6666
and must return an integer label array of the same shape:
6767

68-
```python
69-
import numpy as np
68+
```python
69+
import numpy as np
7070

7171

72-
def my_fn(tile: np.ndarray) -> np.ndarray:
73-
from skimage.filters import threshold_otsu
74-
from skimage.measure import label
72+
def my_fn(tile: np.ndarray) -> np.ndarray:
73+
from skimage.filters import threshold_otsu
74+
from skimage.measure import label
7575

76-
binary = tile > threshold_otsu(tile)
77-
return label(binary).astype("int32")
78-
```
76+
binary = tile > threshold_otsu(tile)
77+
return label(binary).astype("int32")
78+
```
7979

8080
The function is called independently on every tile. patchworks ensures that
8181
objects spanning tile boundaries are merged into a single label.
@@ -155,14 +155,14 @@ objects spanning tile boundaries are merged into a single label.
155155
Methods like Cellpose and StarDist need spatial context at tile boundaries.
156156
Use `overlap` (in voxels) so boundary objects are fully visible:
157157

158-
```python
159-
result = tile_process(
160-
"image.zarr",
161-
my_fn,
162-
tile_shape=(1, 2048, 2048),
163-
overlap=20, # 20-voxel halo on every side
164-
)
165-
```
158+
```python
159+
result = tile_process(
160+
"image.zarr",
161+
my_fn,
162+
tile_shape=(1, 2048, 2048),
163+
overlap=20, # 20-voxel halo on every side
164+
)
165+
```
166166

167167
!!! info "How overlap works"
168168
Each tile is expanded by `overlap` voxels on every side before calling `fn`.
@@ -173,22 +173,22 @@ result = tile_process(
173173

174174
## Use Cellpose
175175

176-
```python
177-
from patchworks import tile_process
178-
from patchworks.plugins.cellpose import cellpose_fn
179-
180-
fn = cellpose_fn("cyto3", gpu=True, diameter=30)
181-
182-
tile_process(
183-
"image.zarr",
184-
fn,
185-
channel=0,
186-
tile_shape=(1, 2048, 2048),
187-
overlap=20,
188-
write_to="labels.zarr",
189-
progress=True,
190-
)
191-
```
176+
```python
177+
from patchworks import tile_process
178+
from patchworks.plugins.cellpose import cellpose_fn
179+
180+
fn = cellpose_fn("cyto3", gpu=True, diameter=30)
181+
182+
tile_process(
183+
"image.zarr",
184+
fn,
185+
channel=0,
186+
tile_shape=(1, 2048, 2048),
187+
overlap=20,
188+
write_to="labels.zarr",
189+
progress=True,
190+
)
191+
```
192192

193193
See the [Cellpose 2-D example](examples/cellpose_2d.md) for the full workflow.
194194

docs/guide/gpu_distributed.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ in the same process as the kernel. When your segmentation function holds the
6060
Python GIL (every PyTorch/CUDA `eval` does), the worker thread can't send
6161
heartbeats. The scheduler declares it dead, and the merge fails:
6262

63-
```
63+
```python
6464
FutureCancelledError: lost dependencies
6565
```
6666

docs/guide/merging.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ even though it's the same cell.
99

1010
patchworks solves this with a zarr-native merge algorithm:
1111

12-
```
12+
```text
1313
Tile A labels: Tile B labels: After merge:
1414
┌────────────┐ ┌────────────┐ ┌──────────────────────┐
1515
│ 3 1 2 │ │ 1 4 2 │ │ 3 1 2 │ 501 5 502│
@@ -32,7 +32,7 @@ Each tile's labels are written to a temporary zarr once. This is critical:
3232
without staging, any downstream operation that reads the label array re-runs
3333
your segmentation function. The merge internally reads labels multiple times.
3434

35-
```
35+
```text
3636
tile_process calls fn once per tile → staged zarr
3737
3838
merge reads from staged zarr (no fn calls)

docs/guide/ome_zarr_napari.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,28 @@ and streaming the downsampled result out through dask with bounded chunks. The
7676
graph never chains level-on-level and no whole plane/volume is held in RAM, so
7777
terabyte images convert in bounded memory.
7878

79+
### Sharding (fewer files)
80+
81+
A big array becomes tens of thousands of tiny chunk files, which strain
82+
filesystems and object stores. Sharding packs many chunks into one **shard**
83+
file (zarr v3), cutting the file count ~100×:
84+
85+
```python
86+
to_ome_zarr("scan.ims", "scan.zarr", shard=True) # auto ~512 MB shards
87+
to_ome_zarr("scan.ims", "scan.zarr", shard=(1, 16, 2048, 2048)) # explicit
88+
```
89+
90+
Default is `shard=False` for maximum reader compatibility — sharding is
91+
zarr-v3-only, so older tools may not read it (your zarr/napari stack does).
92+
A sharded write holds ~one shard per worker in RAM, so very large shards cost
93+
memory.
94+
95+
### Progress
96+
97+
All write steps show a dask progress bar **by default** (`progress=True`), so
98+
you can see how long a conversion will take. Pass `progress=False` to silence
99+
it.
100+
79101
!!! note "Install the readers you need"
80102
`pip install "patchworks[bioio]"` pulls `bioio` plus the `bioio-bioformats`
81103
catch-all reader (needs a JVM). For speed, add native readers for your

docs/guide/pitfalls.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ single-GPU runs — patchworks pins it to 1 thread automatically).
3030

3131
patchworks detects in-process clients at startup and raises immediately:
3232

33-
```
33+
```python
3434
RuntimeError: Active Dask client uses an in-process worker (processes=False).
3535
This breaks the label merge when fn holds the GIL. Use a process-based
3636
cluster instead:

docs/guide/skip_empty.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,6 @@ tile_process(
8888
After a `tile_process` run with `skip_empty=True`, the log reports exactly
8989
how many tiles ran your function:
9090

91-
```
91+
```text
9292
INFO patchworks._core: skip_empty: 486/2200 tiles ran fn, 1714 skipped (max<=412.0)
9393
```

docs/guide/tiling.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ peak RAM during segmentation is approximately one tile's worth of data.
1313
## Choosing a tile size
1414

1515
The right tile size depends on:
16+
1617
- Your available RAM (or GPU VRAM)
1718
- The minimum context your segmentation method needs (objects should fit fully
1819
inside a tile, or you need overlap)
@@ -62,7 +63,7 @@ Methods that need spatial context (Cellpose, StarDist, U-Net) produce wrong
6263
results near tile edges: objects at the boundary are cut off. Overlap fixes this
6364
by expanding each tile by `overlap` voxels on every side.
6465

65-
```
66+
```text
6667
No overlap: With overlap=20:
6768
┌──────────┐ ┌──────────────────┐
6869
│ │ │ ░░░░░░░░░░░░░░ │
@@ -86,4 +87,4 @@ No overlap: With overlap=20:
8687
automatically clips the depth per axis, so z-tiles of size 1 (typical in
8788
2-D Cellpose mode) get `depth=0` in z even if you pass `overlap=20`.
8889

89-
Axes that are too small for the requested overlap simply get a smaller halo.
90+
Axes that are too small for the requested overlap simply get a smaller halo.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**Tiled processing of arbitrarily large images — any image, any function.**
44

5-
```
5+
```text
66
┌──────┬──────┬──────┐ ┌──────┬──────┬──────┐
77
│ │ │ │ fn(tile) → IDs │ 1 │ 2 │ 3 │
88
│ │ │ │ ───────────────► │ │ │ │

0 commit comments

Comments
 (0)