Write OME-ZARR images

Writing ome-zarr images is primarily exposed through the ome_zarr.classes.image.OMEZarrImage and ome_zarr.classes.image.OMEZarrMultiscales classes, which provide a high-level API for creating and manipulating OME-ZARR images and pyramids.

import numpy as np

from ome_zarr import OMEZarrImage, OMEZarrMultiscale

Let’s first create some random data to write:

path = "test_ngff.ome.zarr"

# create some random data to write
size_xy = 128
size_z = 10
rng = np.random.default_rng(0)
data = rng.poisson(lam=10, size=(2, size_z, size_xy, size_xy)).astype(np.uint8)

We then create an OMEZarrImage from our data, where we can specify some basic metadata for the image data, such as the types of axes (czyx) and their scales and units. The OMEZarrMultiscale class creation then builds a multiscale pyramid of dask arrays by downsampling as specified by the scale_factors parameter. You can use this class to pass how viewers should render the image by specifying optional parameters such as channel_names, channel_colors and contrast_limits. As a last step, we write the multiscale image to disk using the to_ome_zarr method, which will create a valid OME-ZARR file that can be read by any OME-ZARR compatible viewer.

image = OMEZarrImage(
  data=data,
  axes=["c", "z", "y", "x"],
  scale={"c": 1.0, "z": 0.5, "y": 0.1, "x": 0.1},
  axes_units={"z": "micrometer", "y": "micrometer", "x": "micrometer"},
)

multiscales = OMEZarrMultiscale(
    image=image,
    scale_factors=(2, 4, 8),
    method="resize",
    channel_names=["DAPI", "GFP"],  # optional
    channel_colors=["00FFFF", "FF00FF"],  # optional
    contrast_limits=[(0, 255), (0, 255)]  # optional
    )
multiscales.to_ome_zarr(path)
[]
multiscales.images
[OMEZarrImage(data=dask.array<array, shape=(2, 10, 128, 128), dtype=uint8, chunksize=(2, 10, 128, 128), chunktype=numpy.ndarray>, axes=['c', 'z', 'y', 'x'], scale={'c': 1.0, 'z': 0.5, 'y': 0.1, 'x': 0.1}, axes_units={'z': 'micrometer', 'y': 'micrometer', 'x': 'micrometer'}, name='image'),
 OMEZarrImage(data=dask.array<resize_block, shape=(2, 10, 64, 64), dtype=uint8, chunksize=(2, 10, 64, 64), chunktype=numpy.ndarray>, axes=['c', 'z', 'y', 'x'], scale={'c': 1.0, 'z': 0.5, 'y': 0.2, 'x': 0.2}, axes_units={'z': 'micrometer', 'y': 'micrometer', 'x': 'micrometer'}, name='image'),
 OMEZarrImage(data=dask.array<resize_block, shape=(2, 10, 32, 32), dtype=uint8, chunksize=(2, 10, 32, 32), chunktype=numpy.ndarray>, axes=['c', 'z', 'y', 'x'], scale={'c': 1.0, 'z': 0.5, 'y': 0.4, 'x': 0.4}, axes_units={'z': 'micrometer', 'y': 'micrometer', 'x': 'micrometer'}, name='image'),
 OMEZarrImage(data=dask.array<resize_block, shape=(2, 10, 16, 16), dtype=uint8, chunksize=(2, 10, 16, 16), chunktype=numpy.ndarray>, axes=['c', 'z', 'y', 'x'], scale={'c': 1.0, 'z': 0.5, 'y': 0.8, 'x': 0.8}, axes_units={'z': 'micrometer', 'y': 'micrometer', 'x': 'micrometer'}, name='image')]

API alternative: Direct write

Besides the above-described class-based approach, another principle entry-point for writing OME-ZARR images is using the ome_zarr.writer.write_image() function. This takes an n-dimensional numpy array or dask array and writes it to the specified zarr group according to the OME-ZARR specification. By default, a pyramid of resolution levels will be created by down-sampling the data by a factor of 2 in the X and Y dimensions. For more custom control over the pyramid, see the more in-depth example on scaling functions and scale factors.

from ome_zarr.writer import write_image

path = "test_ngff_image.ome.zarr"
write_image(data, path, axes="czyx")
[]

Alternatively, the ome_zarr.writer.write_multiscale() can be used, which takes a “pyramid” of pre-computed numpy arrays.

The default version of OME-NGFF is v0.5, which is based on Zarr v3. A zarr v3 group and store is created by zarr.open_group() below. To write OME-NGFF v0.4 (Zarr v2), pass the fmt=FormatV04() argument.

from ome_zarr.format import FormatV04

path = "test_ngff_image_v2.ome.zarr"
write_image(data, path, axes="zyx", fmt=FormatV04()) 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[6], line 4
      1 from ome_zarr.format import FormatV04
      2 
      3 path = "test_ngff_image_v2.ome.zarr"
----> 4 write_image(data, path, axes="zyx", fmt=FormatV04())

File ~/checkouts/readthedocs.org/user_builds/ome-zarr/envs/stable/lib/python3.12/site-packages/ome_zarr/writer.py:685, in write_image(image, group, scale_factors, name, method, scaler, fmt, axes, coordinate_transformations, storage_options, compute, scale, axes_units, **metadata)
    680 if type(fmt) in (FormatV01, FormatV02, FormatV03):
    681     raise DeprecationWarning(
    682         f"Writing ome-zarr v{fmt.version} is deprecated and has been removed in version 0.15.0."
    683     )
--> 685 axes = _get_valid_axes(len(image.shape), axes, axes_units=axes_units, fmt=fmt)
    686 dims = _extract_dims_from_axes(axes)
    688 if scale is None:

File ~/checkouts/readthedocs.org/user_builds/ome-zarr/envs/stable/lib/python3.12/site-packages/ome_zarr/writer.py:61, in _get_valid_axes(ndim, axes, axes_units, fmt)
     58     axes = list(axes)
     60 if ndim is not None and len(axes) != ndim:
---> 61     raise ValueError(
     62         f"axes length ({len(axes)}) must match number of dimensions ({ndim})"
     63     )
     65 # validates on init
     66 axes_obj = Axes(axes, axes_units, fmt)

ValueError: axes length (3) must match number of dimensions (4)

To view the image, see tutorial on viewing images.