Skip to content

Getting Started

Installation

patchworks can be installed from PyPI on all operating systems, for Python ≥ 3.9.

Virtual environment (recommended)

We recommend creating a dedicated environment:

conda create -n patchworks python=3.12
conda activate patchworks
or with pixi:
pixi init patchworks && cd patchworks
pixi add python=3.12

pip install patchworks

pip install "patchworks[gpu]"
Installs nvidia-ml-py to query free GPU VRAM when auto-sizing tiles.

pip install "patchworks[cellpose]"
pip install "patchworks[all]"

The one function you need

```python
from patchworks import tile_process

result = tile_process(image, fn)
```

tile_process(image, fn) splits image into tiles, runs fn on each tile, and returns a globally consistent label array.

  • image — a dask array or a path to an OME-ZARR store
  • fn — any callable (ndarray) -> ndarray returning integer labels

Step 1: write your function

patchworks is method-agnostic. Your function receives a NumPy array (one tile) and must return an integer label array of the same shape:

```python
import numpy as np


def my_fn(tile: np.ndarray) -> np.ndarray:
    from skimage.filters import threshold_otsu
    from skimage.measure import label

    binary = tile > threshold_otsu(tile)
    return label(binary).astype("int32")
```

The function is called independently on every tile. patchworks ensures that objects spanning tile boundaries are merged into a single label.


Step 2: run it

from patchworks import tile_process

# returns a lazy dask array; labels are also written into image.zarr by
# default (image.zarr/labels/labels/, as a pyramid)
result = tile_process("image.zarr", my_fn)
print(result.shape)  # (z, y, x)
print(int(result.max().compute()))  # number of objects found
import dask.array as da
from patchworks import tile_process

arr = da.from_zarr("image.zarr")
result = tile_process(arr, my_fn)

from patchworks import tile_process

tile_process(
    "image.zarr",
    my_fn,
    write_to="labels.zarr",
    progress=True,
)
The output is written tile by tile — peak RAM is one tile, not the whole image.


Set the tile size

result = tile_process("image.zarr", my_fn, tile_shape=(1, 1024, 1024))
result = tile_process(
    "image.zarr", my_fn, tile_shape="auto", use_gpu=True
)  # sizes against GPU VRAM
from functools import partial
from patchworks import auto_tile_shape_cellpose, tile_process

tile_fn = partial(auto_tile_shape_cellpose, diameter=30, use_gpu=True)
result = tile_process("image.zarr", my_fn, tile_shape=tile_fn)

Add overlap

Methods like Cellpose and StarDist need spatial context at tile boundaries. Use overlap (in voxels) so boundary objects are fully visible:

```python
result = tile_process(
    "image.zarr",
    my_fn,
    tile_shape=(1, 2048, 2048),
    overlap=20,  # 20-voxel halo on every side
)
```

How overlap works

Each tile is expanded by overlap voxels on every side before calling fn. The halo is trimmed before merging — the final output has the original shape. Objects near boundaries have enough context to be segmented correctly.


Use Cellpose

```python
from patchworks import tile_process
from patchworks.plugins.cellpose import cellpose_fn

fn = cellpose_fn("cyto3", gpu=True, diameter=30)

tile_process(
    "image.zarr",
    fn,
    channel=0,
    tile_shape=(1, 2048, 2048),
    overlap=20,
    write_to="labels.zarr",
    progress=True,
)
```

See the Cellpose 2-D example for the full workflow.


What's next?