Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a816d3e
test_writer() doesn't use parse_url()
will-moore Aug 19, 2025
7672913
test_write_image_current() doesn't use parse_url()
will-moore Aug 19, 2025
296a234
test_write_image_dask() doesn't use parse_url()
will-moore Aug 19, 2025
d4ae7b4
test_write_image_compressed() doesn't use parse_url()
will-moore Aug 19, 2025
7adc93f
test_default_compression() doesn't use parse_url()
will-moore Aug 19, 2025
17c998a
TestMultiscalesMetadata and TestPlateMetadata don't use parse_url()
will-moore Aug 19, 2025
c309856
Remove parse_url() from all of test_writer.py
will-moore Aug 19, 2025
4865f2c
Remove usage of parse_url() for writing data in docs python.rst
will-moore Aug 22, 2025
2498034
Remove parse_url from scale.py and test_scaler.py
will-moore Aug 25, 2025
93aec37
Remove remaining parse_url where used for writing[B
will-moore Aug 25, 2025
147c76f
Remove parse_url from all python tutorial examples
will-moore Aug 25, 2025
c832da8
Fix typo (failing test)
will-moore Aug 25, 2025
5043dbe
Support write_***(data, path) instead of group
will-moore Sep 5, 2025
61d7e29
Update python.rst to show write_image(data, path)
will-moore Sep 5, 2025
f711b65
Initial addition of FormatV06
will-moore Sep 8, 2025
1b7445e
Merge remote-tracking branch 'origin/master' into parse_url_deprecation
will-moore Sep 8, 2025
28dd337
Fix tests, pass newly opened group to zarr-models for validation
will-moore Sep 9, 2025
b7581bf
Merge branch 'parse_url_deprecation' into v06_coordinateSystems
will-moore Sep 9, 2025
9a6afbc
Fix tests reading axes
will-moore Sep 9, 2025
70b6d52
Remove unused fmt.read_axes()
will-moore Sep 9, 2025
ec7b92f
Merge remote-tracking branch 'origin/master' into v06_coordinateSystems
will-moore Oct 24, 2025
6566d3c
Add support for scale=[1,0.3,0.3] to write_image() etc
will-moore Oct 28, 2025
cd92b1a
Update tests for CurrentFormat() being V06
will-moore Oct 28, 2025
e25627b
Test_writer uses write_image(...scale=scale...)
will-moore Oct 30, 2025
05cfaf4
Use deploy-preview-48--ome-ngff-validator for view
will-moore Oct 30, 2025
f657649
Fix 'input':'0' etc for v0.6 dataset transforms
will-moore Oct 30, 2025
7c29617
Use 's0', 's1' etc for dataset paths
will-moore Oct 30, 2025
f983238
Update tests to expect transform input 's0', 's1' etc
will-moore Oct 30, 2025
ce4a0df
Fix test_ome_zarr.py and test_cli.py to expect s0 paths
will-moore Oct 30, 2025
95f1c89
Fix Plate reader to not hard-code '0' as dataset path
will-moore Oct 30, 2025
511270c
Fix test_writer tests to expect ds path etc
will-moore Oct 30, 2025
37a7526
Start support for write_image(...coordinateTransformations...)
will-moore Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 27 additions & 38 deletions docs/source/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ of 2 in the X and Y dimensions.
Alternatively, the :py:func:`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, is based on Zarr v3. A zarr v3 store is created
by `parse_url()` below. To write OME-NGFF v0.4 (Zarr v2), use the `fmt=FormatV04()` argument
in `parse_url()`, which will create a Zarr v2 store.
The default version of OME-NGFF is v0.5, 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), add the `zarr_format=2` argument.

The following code creates a 3D Image in OME-Zarr::

import numpy as np
import zarr

from ome_zarr.io import parse_url
from ome_zarr.format import FormatV04
from ome_zarr.writer import write_image, add_metadata

Expand All @@ -33,11 +31,8 @@ The following code creates a 3D Image in OME-Zarr::
rng = np.random.default_rng(0)
data = rng.poisson(lam=10, size=(size_z, size_xy, size_xy)).astype(np.uint8)

# Use fmt=FormatV04() to write v0.4 format (zarr v2)
store = parse_url(path, mode="w").store
root = zarr.group(store=store)
write_image(image=data, group=root, axes="zyx",
storage_options=dict(chunks=(1, size_xy, size_xy)))
# Add fmt=FormatV04() parameter to write v0.4 format (zarr v2)
write_image(data, path, axes="zyx")


This image can be viewed in `napari` using the
Expand All @@ -49,9 +44,7 @@ Rendering settings
------------------
Rendering settings can be added to an existing zarr group::

store = parse_url(path, mode="w").store
root = zarr.group(store=store)
add_metadata(root, {"omero": {
add_metadata(path, {"omero": {
"channels": [{
"color": "00FFFF",
"window": {"start": 0, "end": 20, "min": 0, "max": 255},
Expand All @@ -71,7 +64,6 @@ The following code creates a 3D Image in OME-Zarr with labels::

from skimage.data import binary_blobs
from ome_zarr.format import FormatV04
from ome_zarr.io import parse_url
from ome_zarr.writer import write_image, add_metadata

path = "test_ngff_image_labels.zarr"
Expand All @@ -83,9 +75,8 @@ The following code creates a 3D Image in OME-Zarr with labels::
rng = np.random.default_rng(0)
data = rng.poisson(mean_val, size=(size_z, size_xy, size_xy)).astype(np.uint8)

# Use fmt=FormatV04() to write v0.4 format (zarr v2)
store = parse_url(path, mode="w").store
root = zarr.group(store=store)
# Use zarr_format=2 to write v0.4 format (zarr v2)
root = zarr.open_group(path, mode="w")
write_image(image=data, group=root, axes="zyx",
storage_options=dict(chunks=(1, size_xy, size_xy)))
# optional rendering settings
Expand Down Expand Up @@ -135,7 +126,6 @@ This sample code shows how to write a high-content screening dataset (i.e. cultu
import zarr

from ome_zarr.format import FormatV04
from ome_zarr.io import parse_url
from ome_zarr.writer import write_image, write_plate_metadata, write_well_metadata

path = "test_ngff_plate.zarr"
Expand All @@ -154,9 +144,8 @@ This sample code shows how to write a high-content screening dataset (i.e. cultu
data = rng.poisson(mean_val, size=(num_wells, num_fields, size_z, size_xy, size_xy)).astype(np.uint8)

# write the plate of images and corresponding metadata
# Use fmt=FormatV04() in parse_url() to write v0.4 format (zarr v2)
store = parse_url(path, mode="w").store
root = zarr.group(store=store)
# Use zarr_format=2 to write v0.4 format (zarr v2)
root = zarr.open_group(path, mode="w")
write_plate_metadata(root, row_names, col_names, well_paths)
for wi, wp in enumerate(well_paths):
row, col = wp.split("/")
Expand Down Expand Up @@ -185,20 +174,21 @@ This sample code reads an image stored on remote s3 server, but the same
code can be used to read data on a local file system. In either case,
the data is available as `dask` arrays::

from ome_zarr.io import parse_url
from ome_zarr.reader import Reader
from dask import array as da
import zarr
import napari

url = "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr"

# read the image data
reader = Reader(parse_url(url))
# nodes may include images, labels etc
nodes = list(reader())
# first node will be the image pixel data
image_node = nodes[0]
root = zarr.open_group(url)
zattrs = root.attrs.asdict()
# Handle v0.5+ - unwrap 'ome' namespace
if "ome" in zattrs:
zattrs = zattrs["ome"]

dask_data = image_node.data
paths = [ds["path"] for ds in zattrs["multiscales"][0]["datasets"]]
dask_data = [da.from_zarr(root[path]) for path in paths]

# We can view this in napari
# NB: image axes are CZYX: split channels by C axis=0
Expand All @@ -216,7 +206,6 @@ Writing big image from tiles::

import os
import zarr
from ome_zarr.io import parse_url
from ome_zarr.format import CurrentFormat, FormatV04
from ome_zarr.reader import Reader
from ome_zarr.writer import write_multiscales_metadata
Expand All @@ -229,10 +218,14 @@ Writing big image from tiles::
# Use fmt=FormatV04() to write v0.4 format (zarr v2)

url = "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.3/9836842.zarr"
reader = Reader(parse_url(url))
nodes = list(reader())
root = zarr.open_group(url)
zattrs = root.attrs.asdict()
# Handle v0.5+ - unwrap 'ome' namespace
if "ome" in zattrs:
zattrs = zattrs["ome"]
paths = [ds["path"] for ds in zattrs["multiscales"][0]["datasets"]]
# first level of the pyramid
dask_data = nodes[0].data[0]
dask_data = da.from_zarr(root[paths[0]])
tile_size = 512
axes = [{"name": "c", "type": "channel"}, {"name": "y", "type": "space"}, {"name": "x", "type": "space"}]

Expand Down Expand Up @@ -292,8 +285,7 @@ Writing big image from tiles::
row_count = ceil(shape[-2]/tile_size)
col_count = ceil(shape[-1]/tile_size)

store = parse_url("9836842.zarr", mode="w", fmt=fmt).store
root = zarr.group(store=store)
root = zarr.open_group("9836842.zarr", mode="w")

# create empty array at root of pyramid
zarray = root.require_array(
Expand Down Expand Up @@ -346,14 +338,11 @@ When that dask data is passed to write_image() the tiles will be loaded on the f
import zarr
from dask import delayed

from ome_zarr.io import parse_url
from ome_zarr.format import FormatV04
from ome_zarr.writer import write_image, add_metadata

zarr_name = "test_dask.zarr"
# Use fmt=FormatV04() in parse_url() to write v0.4 format (zarr v2)
store = parse_url(zarr_name, mode="w").store
root = zarr.group(store=store)
root = zarr.open_group(zarr_name, mode="w")

size_xy = 100
channel_count = 2
Expand Down
29 changes: 24 additions & 5 deletions ome_zarr/axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from typing import Any

from .format import CurrentFormat, Format
from .format import CurrentFormat, Format, format_from_version

KNOWN_AXES = {"x": "space", "y": "space", "z": "space", "c": "channel", "t": "time"}
DISCRETE_AXES = ["c", "t"]


class Axes:
Expand All @@ -18,12 +19,12 @@ def __init__(

Raises ValueError if not valid
"""
self.fmt = fmt
if axes is not None:
self.axes = self._axes_to_dicts(axes)
elif fmt.version in ("0.1", "0.2"):
# strictly 5D
self.axes = self._axes_to_dicts(["t", "c", "z", "y", "x"])
self.fmt = fmt
self.validate()

def validate(self) -> None:
Expand All @@ -45,15 +46,18 @@ def to_list(
return self._get_names()
return self.axes

@staticmethod
def _axes_to_dicts(axes: list[str] | list[dict[str, str]]) -> list[dict[str, str]]:
def _axes_to_dicts(
self, axes: list[str] | list[dict[str, str]]
) -> list[dict[str, str]]:
"""Returns a list of axis dicts with name and type"""
axes_dicts = []
for axis in axes:
if isinstance(axis, str):
axis_dict = {"name": axis}
axis_dict: dict[str, Any] = {"name": axis}
if axis in KNOWN_AXES:
axis_dict["type"] = KNOWN_AXES[axis]
if self.fmt.version.startswith("0.6"):
axis_dict["discrete"] = axis in DISCRETE_AXES
axes_dicts.append(axis_dict)
else:
axes_dicts.append(axis)
Expand Down Expand Up @@ -114,3 +118,18 @@ def _validate_03(self) -> None:
raise ValueError("4D data must have axes tzyx or czyx or tcyx")
elif val_axes != ("t", "c", "z", "y", "x"):
raise ValueError("5D data must have axes ('t', 'c', 'z', 'y', 'x')")

@staticmethod
def from_multiscales(
multiscales: dict[str, Any],
) -> list[str] | list[dict[str, str]]:
"""Create Axes from the multiscales object"""
if "version" in multiscales:
fmt = format_from_version(multiscales["version"])
axesObj = Axes(multiscales.get("axes", []), fmt=fmt)
return axesObj.to_list(fmt=fmt)
else:
# v0.5 or later - no version on multiscales, so we guess!
if "coordinateSystems" in multiscales:
return multiscales["coordinateSystems"][0].get("axes", [])
return multiscales.get("axes", [])
16 changes: 6 additions & 10 deletions ome_zarr/csv.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import csv
import os

from zarr.convenience import open as zarr_open

from .io import parse_url
import zarr

# d: DoubleColumn, for floating point numbers
# l: LongColumn, for integer numbers
Expand Down Expand Up @@ -104,12 +102,10 @@ def dict_to_zarr(
:param zarr_id: Key of label property, where value is key of props_to_add
"""

zarr = parse_url(zarr_path)
if not zarr:
raise Exception(f"No zarr found at {zarr_path}")

plate_attrs = zarr.root_attrs.get("plate", None)
multiscales = "multiscales" in zarr.root_attrs
root = zarr.open_group(zarr_path)
root_attrs = root.attrs.asdict()
plate_attrs = root_attrs.get("plate", None)
multiscales = "multiscales" in root_attrs
if plate_attrs is None and not multiscales:
raise Exception("zarr_path must be to plate.zarr or image.zarr")

Expand All @@ -124,7 +120,7 @@ def dict_to_zarr(
labels_paths = [os.path.join(zarr_path, "labels", "0")]

for path_to_labels in labels_paths:
label_group = zarr_open(path_to_labels)
label_group = zarr.open_group(path_to_labels)
attrs = label_group.attrs.asdict()
properties = attrs.get("image-label", {}).get("properties")
if properties is None:
Expand Down
Loading
Loading