Skip to content

Commit 8e1a64f

Browse files
authored
Merge pull request #413 from will-moore/ome-zarr-v0.5_writing
2 parents ba98c05 + 1a5b773 commit 8e1a64f

23 files changed

+1123
-456
lines changed

.isort.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[settings]
2-
known_third_party = dask,fsspec,numcodecs,numpy,pytest,scipy,skimage,zarr
2+
known_third_party = dask,numcodecs,numpy,pytest,scipy,skimage,zarr
33
multi_line_output = 3
44
include_trailing_comma = True
55
force_grid_wrap = 0

.readthedocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ version: 2
99
build:
1010
os: ubuntu-22.04
1111
tools:
12-
python: "3.10"
12+
python: "3.12"
1313

1414
# Build documentation in the docs/ directory with Sphinx
1515
sphinx:

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ome-zarr-py
66

77
Tools for multi-resolution images stored in Zarr filesets, according to the `OME NGFF spec`_.
88

9-
See `Readthedocs <https://ome-zarr.readthedocs.io/>`_ for usage information.
9+
See `Documentation <https://ome-zarr.readthedocs.io/>`_ for usage information.
1010

1111
Documentation
1212
-------------

docs/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
sphinx==7.1.2
1+
sphinx==8.1.3
22
sphinx-rtd-theme==3.0.2
33
fsspec
4-
zarr
4+
zarr>=v3.0.0
55
dask
66
numpy
77
scipy

docs/source/cli.rst

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ Use the `ome_zarr` command to interrogate Zarr datasets.
1919

2020
Remote data::
2121

22-
ome_zarr info https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/6001240.zarr/
22+
ome_zarr info https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr/
2323

2424
Local data::
2525

26-
ome_zarr info 6001240.zarr/
26+
ome_zarr info 6001240_labels.zarr/
2727

2828
view
2929
====
@@ -47,25 +47,27 @@ download
4747

4848
To download all the resolutions and metadata for an image use ``ome_zarr download``. This creates ``6001240.zarr`` locally::
4949

50-
ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/6001240.zarr/
50+
ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr
5151

5252
Specify a different output directory::
5353

54-
ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/6001240.zarr/ --output image_dir
54+
ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr --output image_dir
5555

5656
create
5757
======
5858

5959
To create sample OME-Zarr image from the `skimage <https://scikit-image.org/docs/stable/api/skimage.data.html>`_
6060
data.
6161

62-
Create an OME-Zarr image in coinsdata/ dir::
62+
Create an OME-Zarr image in coinsdata/ dir using 'coins' method in OME-Zarr latest version or v0.4::
6363

64-
ome_zarr create coinsdata
64+
ome_zarr create coinsdata.zarr
65+
66+
ome_zarr create coinsdata.zarr --format 0.4
6567

6668
Create an rgb image from skimage astronaut in testimage dir::
6769

68-
ome_zarr create testimage --method=astronaut
70+
ome_zarr create testimage.zarr --method=astronaut
6971

7072
csv to labels
7173
=============

docs/source/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ ome-zarr-py
44

55
Tools for reading and writing multi-resolution images stored in Zarr filesets, according to the `OME NGFF spec`_.
66

7+
NB: The default version of OME-Zarr written by ``ome-zarr-py`` is ``v0.5``, which uses ``zarr v3``. OME-Zarr v0.5
8+
is not yet supported by all OME-Zarr tools. See the documentation for more information on how to write other versions.
79

810
Features
911
--------

docs/source/python.rst

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ of 2 in the X and Y dimensions.
1313
Alternatively, the :py:func:`ome_zarr.writer.write_multiscale` can be used, which takes a
1414
"pyramid" of pre-computed `numpy` arrays.
1515

16+
The default version of OME-NGFF is v0.5, is based on Zarr v3. A zarr v3 store is created
17+
by `parse_url()` below. To write OME-NGFF v0.4 (Zarr v2), use the `fmt=FormatV04()` argument
18+
in `parse_url()`, which will create a Zarr v2 store.
19+
1620
The following code creates a 3D Image in OME-Zarr::
1721

1822
import numpy as np
1923
import zarr
2024

2125
from ome_zarr.io import parse_url
22-
from ome_zarr.writer import write_image
26+
from ome_zarr.format import FormatV04
27+
from ome_zarr.writer import write_image, add_metadata
2328

2429
path = "test_ngff_image.zarr"
2530

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

31-
# write the image data
36+
# Use fmt=FormatV04() to write v0.4 format (zarr v2)
3237
store = parse_url(path, mode="w").store
3338
root = zarr.group(store=store)
34-
write_image(image=data, group=root, axes="zyx", storage_options=dict(chunks=(1, size_xy, size_xy)))
39+
write_image(image=data, group=root, axes="zyx",
40+
storage_options=dict(chunks=(1, size_xy, size_xy)))
3541

3642

3743
This image can be viewed in `napari` using the
@@ -41,18 +47,18 @@ This image can be viewed in `napari` using the
4147

4248
Rendering settings
4349
------------------
44-
Render settings can be added to an existing zarr group::
50+
Rendering settings can be added to an existing zarr group::
4551

4652
store = parse_url(path, mode="w").store
4753
root = zarr.group(store=store)
48-
root.attrs["omero"] = {
54+
add_metadata(root, {"omero": {
4955
"channels": [{
5056
"color": "00FFFF",
5157
"window": {"start": 0, "end": 20, "min": 0, "max": 255},
5258
"label": "random",
5359
"active": True,
5460
}]
55-
}
61+
}})
5662

5763
Writing labels
5864
--------------
@@ -64,10 +70,11 @@ The following code creates a 3D Image in OME-Zarr with labels::
6470
import os
6571

6672
from skimage.data import binary_blobs
73+
from ome_zarr.format import FormatV04
6774
from ome_zarr.io import parse_url
68-
from ome_zarr.writer import write_image
75+
from ome_zarr.writer import write_image, add_metadata
6976

70-
path = "test_ngff_image.zarr"
77+
path = "test_ngff_image_labels.zarr"
7178
os.mkdir(path)
7279

7380
mean_val=10
@@ -76,19 +83,20 @@ The following code creates a 3D Image in OME-Zarr with labels::
7683
rng = np.random.default_rng(0)
7784
data = rng.poisson(mean_val, size=(size_z, size_xy, size_xy)).astype(np.uint8)
7885

79-
# write the image data
86+
# Use fmt=FormatV04() to write v0.4 format (zarr v2)
8087
store = parse_url(path, mode="w").store
8188
root = zarr.group(store=store)
82-
write_image(image=data, group=root, axes="zyx", storage_options=dict(chunks=(1, size_xy, size_xy)))
89+
write_image(image=data, group=root, axes="zyx",
90+
storage_options=dict(chunks=(1, size_xy, size_xy)))
8391
# optional rendering settings
84-
root.attrs["omero"] = {
92+
add_metadata(root, {"omero": {
8593
"channels": [{
8694
"color": "00FFFF",
8795
"window": {"start": 0, "end": 20, "min": 0, "max": 255},
8896
"label": "random",
8997
"active": True,
9098
}]
91-
}
99+
}})
92100

93101

94102
# add labels...
@@ -104,18 +112,19 @@ The following code creates a 3D Image in OME-Zarr with labels::
104112
labels_grp = root.create_group("labels")
105113
# the 'labels' .zattrs lists the named labels data
106114
label_name = "blobs"
107-
labels_grp.attrs["labels"] = [label_name]
115+
add_metadata(labels_grp, {"labels": [label_name]})
108116
label_grp = labels_grp.create_group(label_name)
109-
# need 'image-label' attr to be recognized as label
110-
label_grp.attrs["image-label"] = {
117+
write_image(label, label_grp, axes="zyx")
118+
119+
# we need 'image-label' attr to be recognized as label
120+
add_metadata(label_grp, {"image-label": {
111121
"colors": [
112122
{"label-value": 1, "rgba": [255, 0, 0, 255]},
113123
{"label-value": 2, "rgba": [0, 255, 0, 255]},
114124
{"label-value": 3, "rgba": [255, 255, 0, 255]}
115125
]
116-
}
126+
}})
117127

118-
write_image(label, label_grp, axes="zyx")
119128

120129
Writing HCS datasets to OME-NGFF
121130
--------------------------------
@@ -125,6 +134,7 @@ This sample code shows how to write a high-content screening dataset (i.e. cultu
125134
import numpy as np
126135
import zarr
127136

137+
from ome_zarr.format import FormatV04
128138
from ome_zarr.io import parse_url
129139
from ome_zarr.writer import write_image, write_plate_metadata, write_well_metadata
130140

@@ -144,6 +154,7 @@ This sample code shows how to write a high-content screening dataset (i.e. cultu
144154
data = rng.poisson(mean_val, size=(num_wells, num_fields, size_z, size_xy, size_xy)).astype(np.uint8)
145155

146156
# write the plate of images and corresponding metadata
157+
# Use fmt=FormatV04() in parse_url() to write v0.4 format (zarr v2)
147158
store = parse_url(path, mode="w").store
148159
root = zarr.group(store=store)
149160
write_plate_metadata(root, row_names, col_names, well_paths)
@@ -154,7 +165,8 @@ This sample code shows how to write a high-content screening dataset (i.e. cultu
154165
write_well_metadata(well_group, field_paths)
155166
for fi, field in enumerate(field_paths):
156167
image_group = well_group.require_group(str(field))
157-
write_image(image=data[wi, fi], group=image_group, axes="zyx", storage_options=dict(chunks=(1, size_xy, size_xy)))
168+
write_image(image=data[wi, fi], group=image_group, axes="zyx",
169+
storage_options=dict(chunks=(1, size_xy, size_xy)))
158170

159171

160172
This image can be viewed in `napari` using the
@@ -177,11 +189,9 @@ the data is available as `dask` arrays::
177189
from ome_zarr.reader import Reader
178190
import napari
179191

180-
url = "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0062A/6001240.zarr"
192+
url = "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr"
181193

182194
# read the image data
183-
store = parse_url(url, mode="r").store
184-
185195
reader = Reader(parse_url(url))
186196
# nodes may include images, labels etc
187197
nodes = list(reader())
@@ -207,26 +217,32 @@ Writing big image from tiles::
207217
import os
208218
import zarr
209219
from ome_zarr.io import parse_url
220+
from ome_zarr.format import CurrentFormat, FormatV04
210221
from ome_zarr.reader import Reader
211222
from ome_zarr.writer import write_multiscales_metadata
212223
from ome_zarr.dask_utils import resize as da_resize
213224
import numpy as np
214225
import dask.array as da
215226
from math import ceil
216227

228+
fmt = CurrentFormat()
229+
# Use fmt=FormatV04() to write v0.4 format (zarr v2)
230+
217231
url = "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.3/9836842.zarr"
218232
reader = Reader(parse_url(url))
219233
nodes = list(reader())
220234
# first level of the pyramid
221235
dask_data = nodes[0].data[0]
222236
tile_size = 512
237+
axes = [{"name": "c", "type": "channel"}, {"name": "y", "type": "space"}, {"name": "x", "type": "space"}]
223238

224239
def downsample_pyramid_on_disk(parent, paths):
225240
"""
226241
Takes a high-resolution Zarr array at paths[0] in the zarr group
227242
and down-samples it by a factor of 2 for each of the other paths
228243
"""
229-
group_path = parent.store.path
244+
group_path = str(parent.store_path)
245+
img_path = parent.store_path / parent.path
230246
image_path = os.path.join(group_path, parent.path)
231247
print("downsample_pyramid_on_disk", image_path)
232248
for count, path in enumerate(paths[1:]):
@@ -246,10 +262,16 @@ Writing big image from tiles::
246262
dask_image, tuple(dims), preserve_range=True, anti_aliasing=False
247263
)
248264

265+
options = {}
266+
if fmt.zarr_format == 2:
267+
options["dimension_separator"] = "/"
268+
else:
269+
options["chunk_key_encoding"] = fmt.chunk_key_encoding
270+
options["dimension_names"] = [axis["name"] for axis in axes]
249271
# write to disk
250272
da.to_zarr(
251-
arr=output, url=image_path, component=path,
252-
dimension_separator=parent._store._dimension_separator,
273+
arr=output, url=img_path, component=path,
274+
zarr_format=fmt.zarr_format, **options
253275
)
254276
return paths
255277

@@ -270,16 +292,18 @@ Writing big image from tiles::
270292
row_count = ceil(shape[-2]/tile_size)
271293
col_count = ceil(shape[-1]/tile_size)
272294

273-
store = parse_url("9836842.zarr", mode="w").store
295+
store = parse_url("9836842.zarr", mode="w", fmt=fmt).store
274296
root = zarr.group(store=store)
275297

276298
# create empty array at root of pyramid
277-
zarray = root.require_dataset(
299+
zarray = root.require_array(
278300
"0",
279301
shape=shape,
280302
exact=True,
281303
chunks=chunks,
282304
dtype=d_type,
305+
chunk_key_encoding=fmt.chunk_key_encoding,
306+
dimension_names=[axis["name"] for axis in axes], # omit for v0.4
283307
)
284308

285309
print("row_count", row_count, "col_count", col_count)
@@ -296,7 +320,6 @@ Writing big image from tiles::
296320
zarray[ch_index, y1:y2, x1:x2] = tile
297321

298322
paths = ["0", "1", "2"]
299-
axes = [{"name": "c", "type": "channel"}, {"name": "y", "type": "space"}, {"name": "x", "type": "space"}]
300323

301324
# We have "0" array. This downsamples (in X and Y dims only) to create "1" and "2"
302325
downsample_pyramid_on_disk(root, paths)
@@ -313,7 +336,8 @@ Writing big image from tiles::
313336
write_multiscales_metadata(root, datasets, axes=axes)
314337

315338

316-
Using dask to fetch::
339+
Using dask to fetch. Here concatenate lazy "delayed" source of tiles into a full image.
340+
When that dask data is passed to write_image() the tiles will be loaded on the fly::
317341

318342
# Created for https://forum.image.sc/t/writing-tile-wise-ome-zarr-with-pyramid-size/85063
319343

@@ -323,9 +347,11 @@ Using dask to fetch::
323347
from dask import delayed
324348

325349
from ome_zarr.io import parse_url
326-
from ome_zarr.writer import write_image, write_multiscales_metadata
350+
from ome_zarr.format import FormatV04
351+
from ome_zarr.writer import write_image, add_metadata
327352

328353
zarr_name = "test_dask.zarr"
354+
# Use fmt=FormatV04() in parse_url() to write v0.4 format (zarr v2)
329355
store = parse_url(zarr_name, mode="w").store
330356
root = zarr.group(store=store)
331357

@@ -374,7 +400,7 @@ Using dask to fetch::
374400
# This will create a downsampled 'multiscales' pyramid
375401
write_image(dask_data, root, axes="czyx")
376402

377-
root.attrs["omero"] = {
403+
add_metadata(root, {"omero": {
378404
"channels": [
379405
{
380406
"color": "FF0000",
@@ -389,7 +415,7 @@ Using dask to fetch::
389415
"active": True,
390416
},
391417
]
392-
}
418+
}})
393419

394420
print("Created image. Open with...")
395421
print(f"ome_zarr view {zarr_name}")

0 commit comments

Comments
 (0)