Skip to main content

zarrmony

PyPI version Python versions License CI

Convert any bioimage file to OME-Zarr v0.5, preserving metadata.

Zarrmony reads proprietary microscopy formats (CZI, LIF, ND2, OME-TIFF, ...) via bioio and writes them as OME-Zarr v0.5, with mean-pool pyramid generation (--downsample-method max for sparse labels) and a full audit trail of the conversion. User-supplied metadata (study/treatment/etc.) is not handled by zarrmony — it is owned by aperture-backend, which associates OME-Zarr stores to a separate metadata database.

By default (--layout auto) the writer is chosen from the reader's layout_hint: a flat reader writes one self-describing <scene>.ome.zarr store per scene under the output directory; a plate-shaped reader writes a single OME-NGFF HCS plate store at the output. The legacy bundled bioformats2raw.layout shape is opt-in via --layout bf2raw (CLI) or layout="bf2raw" (library).

Status: v0.15 in active development. API and metadata schema are not yet stable.

Install

pip install zarrmony

Readers

Zarrmony dispatches to a reader plugin per input format. They come in three tiers:

  • Built-in (bundled by default): CZI, LIF, ND2.
  • Optional extras in this repo (opt-in via pip install "zarrmony[<extra>]"): OME-TIFF via the ome-tiff extra, and ~150 vendor formats via the bioformats extra (GPL-3.0 — see below).
  • External plugins (separate PyPI distributions, entry-point registered):
    • zarrmony-phenix — Opera Phenix (wraps pyphenix.OperaPhenixReader) — pip install zarrmony-phenix
    • zarrmony-blaze — Miltenyi UltraMicroscope Blaze (MACS iQ-processed) — pip install zarrmony-blaze
    • zarrmony-snouty — Snouty single-objective light-sheet — pip install zarrmony-snouty
    • zarrmony-smartspim — LifeCanvas SmartSPIM stitched exports — pip install zarrmony-smartspim

Extras

Extra Adds When you need it
gcs gcsfs Writing output to gs:// URIs
s3 s3fs Writing output to s3:// URIs
ome-tiff bioio-ome-tiff Reading OME-TIFF input
validate ome-zarr-models Post-conversion OME-NGFF validation
bioformats bioio-bioformats Reading Bio-Formats-only vendor formats (GPL-3.0)
all All of the above except bioformats
dev pytest, ruff, pre-commit Contributing

Bio-Formats-backed vendor formats

pip install "zarrmony[bioformats]"

What it buys. Everything on the Bio-Formats supported-formats list that no permissively-licensed bioio backend covers — around 150 formats. The motivating case is Olympus/Evident cellSens VSI whole-slide data; Zeiss ZVI and Hamamatsu NDPI are in the same bucket. No zarrmony code is involved: once bioio-bioformats is installed, the built-in bioio catch-all plugin dispatches to it, and the audit record's distribution field names it. Point zarrmony at the .vsi file itself — Bio-Formats follows the .ets sidecar directory automatically. The audit records that it did: input.size_bytes is the .vsi alone (a few MB of index), and input.files is the whole set Bio-Formats read, with input.size_is_partial saying which is which. --checksum covers both — the named path under input.sha256, the whole set under input.files.sha256.

Licence. bioio-bioformats is GPL-3.0; Bio-Formats is GPL. Installing this extra puts GPL code in your environment. Zarrmony itself remains Apache-2.0 and no GPL package is in its default dependency closure — which is exactly why this extra is opt-in and why it is excluded from all. Do not add it there. See ADR-0011.

Java. Bio-Formats needs a JVM, but not one you have to install: bffile / scyjava / cjdk fetch their own JDK (~36 MiB, once) and the Bio-Formats maven artifacts on first use. No system Java, no maven, no JAVA_HOME. The first file you open is slow while that downloads; everything after is cached.

Gigapixel inputs need tiling. bioio-bioformats returns one dask chunk per plane by default. On a 141k × 168k slide that is a single 47.5 GB chunk, and the writer will try to hold it in memory to rechunk it. Pass:

zarrmony convert slide.vsi out/ --reader-kwarg dask_tiles=true

Leave tile_size off. Zarrmony plans the output geometry first and then asks the reader for tiles that fit it exactly, recording the choice in config.reader_tile_size. Pinning your own is supported and sometimes right, but a tile that does not divide the write grid makes every write split a source tile — on the reference slide that is 831,936 dask tasks against 369,600 — so the writer warns and names the tile that would have worked.

Usage

CLI

# Auto (default): dispatches on the reader's layout_hint.
#   flat readers (CZI, LIF, ND2, OME-TIFF) → per-scene stores under OUTPUT
#   plate-shaped readers (e.g. zarrmony-phenix) → a single HCS plate store at OUTPUT
zarrmony convert input.czi output_dir/

# Force per-scene (one <scene>.ome.zarr store per scene under OUTPUT).
zarrmony convert input.czi output_dir/ --layout per-scene

# Force HCS plate (one <plate>.ome.zarr store at OUTPUT). Requires a
# plate-shaped reader; flat readers raise LayoutMismatchError.
zarrmony convert phenix-acquisition/ output.ome.zarr --layout plate

# Bundled bioformats2raw.layout (opt-in): writes a single store at OUTPUT.
zarrmony convert input.czi output.ome.zarr --layout bf2raw

# LIF-specific: write one OME-Zarr per mosaic tile (with stage positions in
# <Plane>) instead of bioio-lif's auto-stitched 1-pixel-overlap output.
# See docs/adr/0005-lif-mosaic-write-strategy.md.
zarrmony convert mosaic.lif output_dir/ --lif-mosaic per-tile

zarrmony inspect input.czi

Library

from zarrmony import convert

# Auto (default): for a flat reader, returns {"input": ..., "stores": [...]};
# for a plate-shaped reader, returns the single plate audit dict (schema 3,
# with "fields" and a top-level "plate" block). Switch on audit["layout"].
result = convert("input.lif", "output_dir/")

# Bundled: returns the single bundle's audit dict.
audit = convert("input.lif", "output.ome.zarr", layout="bf2raw")

# HCS plate: writes one OME-NGFF plate store at OUTPUT.
audit = convert("phenix-acquisition/", "output.ome.zarr", layout="plate")

Output geometry

Zarrmony plans an output store's geometry — how many pyramid levels there are, what each level's extent is, and how each level is divided into chunks — itself, rather than delegating chunk shape to bioio-ome-zarr's memory-target heuristic. Every constant below traces to a measurement; ADR-0010 records which, and what was rejected.

What the planner does by default:

  • Chunks are world-cubic and small. Each level gets the largest power-of-two chunk whose raw size fits the 512 KiB target and whose extents are closest to cubic in micrometres, planned against that level's own voxel spacing. Near-isotropic uint16 data lands on the familiar 1,1,64,64,64; a 10:1 confocal stack (Z 5 µm, XY 0.5 µm) lands on 1,1,16,128,128 — 80 × 64 × 64 µm — instead of a voxel-cubic 64³ spanning 320 × 32 × 32 µm. T and C are never chunked, so a viewer fetching one channel at one timepoint never pays for the others.
  • The pyramid moves toward isotropy. A level halves every spatial axis whose physical spacing is within isotropy_tolerance of the finest still-halvable axis's, so the scarce axis — Z, for most volumetric light microscopy — is spent last. No axis halves below axis_floor voxels, and an axis already below it never halves: a 3-plane stack keeps its 3 planes at every level.
  • Depth is the greater of two rules. The pyramid_min_size Y/X floor, and the depth at which a level becomes a coarse level — one a viewer can decode whole and use as spatial context, meaning Z·Y·X·itemsize per timepoint and channel is at most coarse_max_bytes and the longest lateral axis is at most coarse_max_long_axis. Because depth is a max(), no conversion loses a level. A pyramid that bottoms out at the axis floor while still too large simply has no coarse level.
  • Levels above 0 are mean-pooled, uniformly. downsample_method="max" switches the whole pyramid to max-pool for sparse-label acquisitions, where mean-pooling dissolves small objects into the background.
  • The same rules apply to per-scene, bf2raw and plate output, with no Z > 1 gate and no 2D exemption: every rule is written over the axes that are present, so a 2160² plate field is planned by exactly the rule a whole-brain volume is.

Knobs

Every field lives on the frozen zarrmony.Geometry policy object, passed as convert(..., geometry=...). Most are also CLI flags on zarrmony convert.

Geometry field CLI flag Default What it sets
chunk_target_bytes --chunk-target-bytes 524288 (512 KiB) Raw (uncompressed) byte target for one chunk. Raise it for a bandwidth-bound consumer or to cut object count.
isotropy_tolerance --isotropy-tolerance 1.5 How close to the finest still-halvable axis's spacing an axis must be to halve at a level. 1.0 halves only exactly-isotropic axes; a large value halves every spatial axis every level.
axis_floor (library only) 32 Minimum voxels on any axis. On Y/X it is capped by pyramid_min_size, so an explicitly lowered depth floor is not overridden by this default.
coarse_max_bytes --coarse-max-bytes 67108864 (64 MiB) Largest decoded size, per timepoint and channel, a coarse level may have.
coarse_max_long_axis --coarse-max-long-axis 2048 Longest lateral extent, in voxels, a coarse level may have. On single-plane data this is the bound that decides coarseness — the byte bound is inert.
downsample_method --downsample-method "mean" Pooling kernel for every level above 0: mean or max.
pyramid_min_size --pyramid-min-size 256 Stop halving when the smaller of Y/X would fall below this — a floor on depth, not a cap.
chunk_shape --chunk-shape None Explicit chunk shape that bypasses the planner outright, so no byte target is consulted. --chunk-shape and --chunk-target-bytes are rejected together on the CLI.
shard_target_bytes --shard-target-bytes None (off) Raw byte target for one shard — the write unit and the storage object. Setting it turns sharding on; the bare flag resolves to 8 MiB. Must be at least chunk_target_bytes.
shard_shape --shard-shape None (off) Explicit shard shape that bypasses the shard planner. Must be a whole multiple of the chunk on every axis. --shard-shape and --shard-target-bytes are rejected together on the CLI.

The two coarse-level bounds are the defaults of the viewer this output is tuned for; they are fields rather than constants so a store can be planned for a consumer with a different budget.

# Fewer objects, same read granularity: 512 KiB chunks packed into 8 MiB
# shards. Only for consumers that read sharded zarr v3 — see below.
zarrmony convert slide.vsi output_dir/ --shard-target-bytes

# Bigger chunks: fewer objects, coarser culling. For object storage where
# listing cost matters more than round-trip latency.
zarrmony convert input.czi output_dir/ --chunk-target-bytes 2097152

# Sparse labels: keep peak intensity in the pyramid.
zarrmony convert labels.czi output_dir/ --downsample-method max
from zarrmony import Geometry, convert

audit = convert(
    "input.czi",
    "output_dir/",
    geometry=Geometry(chunk_target_bytes=2 * 1024 * 1024, downsample_method="max"),
)

chunk_shape and pyramid_min_size are also retained directly on convert() as sugar that folds into a default policy, so callers written before the policy object keep working. Passing geometry= together with either raises ValueError rather than silently picking a winner.

The resolved policy is recorded in the audit under config.geometry, and what it produced is recorded per scene / per field as level_shapes, chunk_shapes, shard_shapes and coarse_level_index — so "does this store have a level a viewer can hold whole?" is answerable from the store's own metadata.

Object count, and sharding

Small chunks trade bytes-per-object for objects. A whole-brain light-sheet store goes from 87,048 objects to ~3.2 M (~37×); a 2160² plate field goes from 4 to 39; a gigapixel slide scene reaches ~370k objects at level 0 alone. On local disk that is irrelevant. On GCS/S3 it is listing time plus per-object metadata cost, and at slide scale it is also conversion wall-clock: one such scene sustains 55 chunks/min with perfectly aligned reader tiles, which is about six days for its pyramid, against 3 h 02 m for the same scene written in 8 MiB units. Each object is 16× smaller but takes 2.8× longer to write, so the cost is graph size rather than bytes — which is why sharding fixes it and a bigger chunk only trades it for a worse viewer.

Sharding answers this without giving up read granularity, because the shard is the write unit and the chunk is the read unit. --shard-target-bytes packs whole chunks into 8 MiB storage objects: that slide scene's level 0 becomes 512² chunks inside 2048² shards, 369,600 objects down to 23,184, with each 512 KiB chunk still individually range-readable. Shards are planned by the same world-cubic rule as chunks, per level, so an isotropic volume at the defaults gets a 128 × 128 × 256 shard holding 16 chunks of 64³.

It is off by default, because it changes who can read the store. Chunks stay individually readable and every zarr-python 3 consumer is unaffected — napari-ome-zarr, dask, plain __getitem__, subsets straddling either grid, all verified byte-identical against an unsharded store. But a consumer that parses the codec chain itself sees sharding_indexed where it expects bytes and refuses the store: lucida-store accepts only [bytes] or [bytes, compressor], so a sharded store fails there with first storage codec must be 'bytes', got 'sharding_indexed'. The CLI warns whenever sharding is on. See ADR-0010 for the measurements and the reversal.

Extending zarrmony

Add support for a new bioimage format by writing a reader plugin. See Writing a zarrmony reader plugin for the Reader Protocol, matcher conventions, entry-point registration, and a worked example.

License

Apache-2.0. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zarrmony-0.15.0.tar.gz (709.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zarrmony-0.15.0-py3-none-any.whl (175.8 kB view details)

Uploaded Python 3

File details

Details for the file zarrmony-0.15.0.tar.gz.

File metadata

  • Download URL: zarrmony-0.15.0.tar.gz
  • Upload date:
  • Size: 709.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zarrmony-0.15.0.tar.gz
Algorithm Hash digest
SHA256 9e23e8b37941233eeb664e24e3b773cddb477398c9ca5f0b0f4f0b915a83f175
MD5 7465cb11b23f4bccc951754e5facbd36
BLAKE2b-256 d1736a39482b03809ee494d7587d583278806f2403ba0e9638b5ee75db998881

See more details on using hashes here.

Provenance

The following attestation bundles were made for zarrmony-0.15.0.tar.gz:

Publisher: release.yml on ferrinm/zarrmony

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zarrmony-0.15.0-py3-none-any.whl.

File metadata

  • Download URL: zarrmony-0.15.0-py3-none-any.whl
  • Upload date:
  • Size: 175.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zarrmony-0.15.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8389acbed302a8963056a24e7cb8c43b202db84f85c6c992976ad6ac881e21d3
MD5 c480fdc132c20548bcef72d633b1cb80
BLAKE2b-256 fc2e43634d1f3c89e4dc62785d4e62328a39e3126b383e4b17a6ef9967af5a34

See more details on using hashes here.

Provenance

The following attestation bundles were made for zarrmony-0.15.0-py3-none-any.whl:

Publisher: release.yml on ferrinm/zarrmony

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.15.0 This release

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.6

2 files

0.3.5

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.4

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page