diff --git a/.codespellrc b/.codespellrc index 1e393dfe..2ab3e4ee 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,3 +3,4 @@ skip = .git,*.pdf,*.svg # nd - for N-dimensional # visibles - plural variable for visible ignore-words-list = nd,visibles +ignore-regex = [A-Za-z0-9+/]{100,} diff --git a/.gitignore b/.gitignore index 28e3252a..8dbf1ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/ target/ docs/build/ *.DS_Store +*Thumbs.db # Testing files .tox* .coverage* @@ -14,3 +15,12 @@ coverage.xml # Generated version file ome_zarr/_version.py + +# MyST build outputs +_build + +# demo notebooks +*.ome.zarr +*.zarr +*.ipynb_checkpoints +*/jupyter_execute diff --git a/.readthedocs.yml b/.readthedocs.yml index ecef8025..69ceaa88 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,7 +13,7 @@ build: # Build documentation in the docs/ directory with Sphinx sphinx: - fail_on_warning: true + fail_on_warning: false configuration: docs/source/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF @@ -23,4 +23,6 @@ sphinx: # Optionally declare the Python requirements required to build your docs python: install: - - requirements: docs/requirements.txt + - requirements: docs/requirements.txt + - method: pip + path: . diff --git a/docs/requirements.txt b/docs/requirements.txt index 775f0a40..c6dc485b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,3 +8,15 @@ rangehttpserver scipy scikit-image Deprecated +sphinx-copybutton +sphinx-togglebutton +myst-nb +sphinx-thebe +sphinx-comments +sphinx-design +sphinx-book-theme +sphinx-external-toc +sphinx-jupyterbook-latex +linkify-it-py +napari +pyqt6 diff --git a/docs/source/_toc.yml b/docs/source/_toc.yml new file mode 100644 index 00000000..4223fb0d --- /dev/null +++ b/docs/source/_toc.yml @@ -0,0 +1,40 @@ +format: jb-book +root: index + +options: + numbered: False + +parts: + - caption: Basic + chapters: + - file: basic/write_image + - file: basic/read_image + - file: basic/view_images + - file: basic/write_labels + - file: basic/cli_basics + + + - caption: Advanced + chapters: + - file: advanced/build_custom_pyramid + - file: advanced/write_hcs_plate + + - caption: Explanation + chapters: + - file: explanation/ome_ngff_overview + - file: explanation/multiscale_pyramids + - file: explanation/zarr_concepts + + - caption: API Reference + chapters: + - file: api + sections: + - file: api/writer + - file: api/reader + - file: api/io + - file: api/scale + - file: api/format + - file: api/cli + - file: api/utils + - file: api/csv + - file: api/data diff --git a/docs/source/advanced/build_custom_pyramid.ipynb b/docs/source/advanced/build_custom_pyramid.ipynb new file mode 100644 index 00000000..741209ab --- /dev/null +++ b/docs/source/advanced/build_custom_pyramid.ipynb @@ -0,0 +1,210 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "95d84769", + "metadata": {}, + "source": [ + "# Customizing the pyramid\n", + "(advanced:pyramid)=\n", + "\n", + "\n", + "Multi-resolution pyramids are an integral part of ome-zarr image data\n", + "and enable fast rendering of large images.\n", + "The entrypoints to writing ome-zarr images in ome-zarr-py ({py:func}`ome_zarr.writer.write_image` and {py:func}`ome_zarr.writer.write_labels`)\n", + "build these pyramids under the hood as delayed dask arrays based on the settings for the scaling functions and scale factors.\n", + "\n", + "In this example, the downsampling will be applied in all spatial dimensions *except the z dimension*, which will be left at a scale factor of 1.\n", + "To apply equal or custom downsampling factors along all spatial dimensions, pass the scale factors as a list of dicts (see [below](#advanced:custom-downsampling-values))." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "642955b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "\n", + "from ome_zarr.writer import write_image\n", + "\n", + "scale_factors = [2, 4, 8]\n", + "rng = np.random.default_rng(0)\n", + "data = rng.poisson(lam=10, size=(64, 64, 64)).astype(np.uint8)\n", + "\n", + "write_image(\n", + " data,\n", + " \"test_ngff_image.ome.zarr\",\n", + " axes=\"zyx\",\n", + " scale_factors=scale_factors,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "cec0fc73", + "metadata": {}, + "source": [ + "## Custom downsampling values\n", + "(advanced:custom-downsampling-values)=\n", + "\n", + "To specify custom downsampling values, pass a list of dictionaries with the keys being the names of the axes to the writer function like in the following example.\n", + "This will apply equal downsampling factors along all present axes (`zyx` in this case):" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "60dcd278", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scale_factors = [\n", + " {\"z\": 2,\"x\": 2, \"y\": 2},\n", + " {\"z\": 4,\"x\": 4, \"y\": 4},\n", + " {\"z\": 8,\"x\": 8, \"y\": 8},\n", + "]\n", + "\n", + "write_image(\n", + " data,\n", + " \"test_ngff_image_custom_scale.ome.zarr\",\n", + " axes=\"zyx\",\n", + " scale_factors=scale_factors,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "6b276be7", + "metadata": {}, + "source": [ + "## Custom downsampling functions\n", + "\n", + "ome-zarr-py provides multiple methods for downsampling, which can be found in the {py:class}`ome_zarr.scale.Methods` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "877ab60d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['resize', 'nearest', 'local_mean', 'zoom']\n" + ] + } + ], + "source": [ + "from ome_zarr.scale import Methods\n", + "\n", + "print([m.value for m in Methods])" + ] + }, + { + "cell_type": "markdown", + "id": "9a086164", + "metadata": {}, + "source": [ + "You can use one of these functions for downsampling by passing the method name as a string to the writer function, e.g. `method=\"local_mean\"` or `method=\"resize\"`, i.e.:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "859ca482", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "write_image(\n", + " data,\n", + " \"test_ngff_image_custom_method.ome.zarr\",\n", + " axes=\"zyx\",\n", + " scale_factors=scale_factors,\n", + " method=\"nearest\"\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "893a1b3b", + "metadata": {}, + "source": [ + "```{warning}\n", + "\n", + "The choice of the correct downsampling function is typically of secondary importance,\n", + "*unless* your data specifically requires a certain method.\n", + "\n", + "For instance, when writing categorical data (i.e., segmentations or generally labels),\n", + "you will want to use a method that preserves the label values, such as {py:func}`ome_zarr.scale.Methods.NEAREST`.\n", + "\n", + "See also section on [writing labels](basic:labels)\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "fbbeecf3", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ngff-spec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/advanced/write_hcs_plate.ipynb b/docs/source/advanced/write_hcs_plate.ipynb new file mode 100644 index 00000000..2d508d73 --- /dev/null +++ b/docs/source/advanced/write_hcs_plate.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "48f53029", + "metadata": {}, + "source": [ + "# Write HCS Plates\n", + "(tutorials:write_hcs_plate)=\n", + "\n", + "This tutorial shows how to write a high-content screening (HCS) dataset to OME-NGFF format.\n", + "HCS datasets represent culture plates with multiple wells, where each well can contain multiple fields of view.\n", + "\n", + "## Create sample data\n", + "\n", + "First, let's set up some sample data representing a multi-well plate:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1e3a243", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import zarr\n", + "\n", + "from ome_zarr.writer import write_image, write_plate_metadata, write_well_metadata\n", + "\n", + "path = \"test_ngff_plate.zarr\"\n", + "row_names = [\"A\", \"B\"]\n", + "col_names = [\"1\", \"2\", \"3\"]\n", + "well_paths = [\"A/2\", \"B/3\"]\n", + "field_paths = [\"0\", \"1\", \"2\"]\n", + "\n", + "# generate data\n", + "mean_val = 10\n", + "num_wells = len(well_paths)\n", + "num_fields = len(field_paths)\n", + "size_xy = 128\n", + "size_z = 10\n", + "rng = np.random.default_rng(0)\n", + "data = rng.poisson(mean_val, size=(num_wells, num_fields, size_z, size_xy, size_xy)).astype(np.uint8)" + ] + }, + { + "cell_type": "markdown", + "id": "e64a7be8", + "metadata": {}, + "source": [ + "## Write plate structure\n", + "\n", + "The plate is written by creating the hierarchical zarr structure with plate and well metadata:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72fa8534", + "metadata": {}, + "outputs": [], + "source": [ + "# write the plate of images and corresponding metadata\n", + "# Use zarr_format=2 to write v0.4 format (zarr v2)\n", + "root = zarr.open_group(path, mode=\"w\")\n", + "write_plate_metadata(root, row_names, col_names, well_paths)\n", + "\n", + "for wi, wp in enumerate(well_paths):\n", + " row, col = wp.split(\"/\")\n", + " row_group = root.require_group(row)\n", + " well_group = row_group.require_group(col)\n", + " write_well_metadata(well_group, field_paths)\n", + " for fi, field in enumerate(field_paths):\n", + " image_group = well_group.require_group(str(field))\n", + " write_image(image=data[wi, fi], group=image_group, axes=\"zyx\",\n", + " storage_options=dict(chunks=(1, size_xy, size_xy)))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ngff-spec", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/api.md b/docs/source/api.md new file mode 100644 index 00000000..3f66da20 --- /dev/null +++ b/docs/source/api.md @@ -0,0 +1 @@ +# Python API diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index 0add3bad..00000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,15 +0,0 @@ -Python API -========== - -.. toctree:: - :maxdepth: 2 - - api/cli - api/csv - api/data - api/format - api/io - api/reader - api/scale - api/utils - api/writer diff --git a/docs/source/cli.rst b/docs/source/basic/cli_basics.md similarity index 66% rename from docs/source/cli.rst rename to docs/source/basic/cli_basics.md index 9b4618fd..cbe5aab8 100644 --- a/docs/source/cli.rst +++ b/docs/source/basic/cli_basics.md @@ -1,70 +1,77 @@ -.. highlight:: bash - - -Command-line tool ------------------ +# Command-line tool +(basics:cli) Open Zarr filesets containing images with associated OME metadata. The examples below use the image at http://idr.openmicroscopy.org/webclient/?show=image-6001240. -All examples can be made more or less verbose by passing `-v` or `-q` one or more times:: - - ome_zarr -vvv ... +All examples can be made more or less verbose by passing `-v` or `-q` one or more times: +```bash +ome_zarr -vvv ... +``` -info -==== +## info Use the `ome_zarr` command to interrogate Zarr datasets. -Remote data:: +Remote data: - ome_zarr info https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr/ +```bash +ome_zarr info https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr/ +``` -Local data:: +Local data: - ome_zarr info 6001240_labels.zarr/ +```bash +ome_zarr info 6001240_labels.zarr/ +``` -view -==== +## view -Use the `ome_zarr` command to serve local Zarr data and view it in the https://ome.github.io/ome-ngff-validator:: +Use the `ome_zarr` command to serve local Zarr data and view it in the https://ome.github.io/ome-ngff-validator: - ome_zarr view 6001240.zarr/ +```bash +ome_zarr view 6001240.zarr/ - # Use -f or --force to open in browser even if no valid data is found - ome_zarr view 6001240.zarr/ -f +# Use -f or --force to open in browser even if no valid data is found +ome_zarr view 6001240.zarr/ -f +``` -finder -====== +## finder Use the `ome_zarr` command to display multiple OME-Zarr images in the BioFile Finder app in a browser. This command parses the specified directory to find all OME-Zarr Images and Plates, combines them into a `biofile_finder.csv` file and opens this in the -app, which allows you to browse thumbnails of all images:: +app, which allows you to browse thumbnails of all images: +```bash ome_zarr finder /path/to/dir/ +``` -download -======== +## download -To download all the resolutions and metadata for an image use ``ome_zarr download``. This creates ``6001240.zarr`` locally:: +To download all the resolutions and metadata for an image use ``ome_zarr download``. This creates ``6001240.zarr`` locally: - ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr +```bash +ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr +``` -Specify a different output directory:: +Specify a different output directory: - ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr --output image_dir +```bash +ome_zarr download https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr --output image_dir +``` -scale -===== +## scale Use the `ome_zarr scale` command to generate a multiscale pyramid from a Zarr array. This creates downsampled resolutions of the input image using the specified downsampling method. -Basic usage:: +Basic usage: - ome_zarr scale input.zarr output.zarr zyx +```bash +ome_zarr scale input.zarr output.zarr zyx +``` This reads the input Zarr array with dimensions 'zyx' and writes a multiscale pyramid to the output directory. @@ -80,30 +87,33 @@ Options: - `zoom`: Scipy zoom - `--copy-metadata`: If specified, copies the input array metadata to the output group -Example with custom options:: +Example with custom options: - ome_zarr scale input.zarr output.zarr tczyx --downscale 3 --max_layer 5 --method nearest --copy-metadata +```bash +ome_zarr scale input.zarr output.zarr tczyx --downscale 3 --max_layer 5 --method nearest --copy-metadata +``` - -create -====== +## create To create a sample OME-Zarr image from the `skimage `_ data. -Create an OME-Zarr image in coinsdata.zarr using the 'coins' method in OME-Zarr latest version or v0.4:: +Create an OME-Zarr image in coinsdata.zarr using the 'coins' method in OME-Zarr latest version or v0.4: - ome_zarr create coinsdata.zarr +```bash +ome_zarr create coinsdata.zarr - ome_zarr create coinsdata.zarr --format 0.4 +ome_zarr create coinsdata.zarr --format 0.4 +``` -Create an RGB image from the skimage astronaut dataset in testimage.zarr:: +Create an RGB image from the skimage astronaut dataset in testimage.zarr: - ome_zarr create testimage.zarr --method=astronaut +```bash +ome_zarr create testimage.zarr --method=astronaut +``` -csv to labels -============= +## csv_to_labels The `csv_to_labels` command uses a CSV file to add key:value properties to labels under an OME-Zarr Image or Plate. @@ -125,13 +135,16 @@ to specify the data-type for each column (string by default). - `s`: `StringColumn`, for text - `b`: `BoolColumn`, for true/false -Use e.g. `#d` as a suffix in the column name to denote a `float` column, no spaces etc.:: - - "area#d,label_text#s,Width#l,Height#l" +Use e.g. `#d` as a suffix in the column name to denote a `float` column, no spaces etc.: +```bash +"area#d,label_text#s,Width#l,Height#l" +``` For example, to take values from columns named `area`, `label_text`, `Width` and `Height` within a CSV file named `labels_data.csv` with an ID column named `shape_id` and add these -values to label properties with an ID key of `omero:shapeId` in an Image or Plate named `123.zarr`:: +values to label properties with an ID key of `omero:shapeId` in an Image or Plate named `123.zarr`: - ome_zarr csv_to_labels labels_data.csv shape_id "area#d,label_text#s,Width#l,Height#l" 123.zarr omero:shapeId +```bash +ome_zarr csv_to_labels labels_data.csv shape_id "area#d,label_text#s,Width#l,Height#l" 123.zarr omero:shapeId +``` diff --git a/docs/source/basic/read_image.ipynb b/docs/source/basic/read_image.ipynb new file mode 100644 index 00000000..e4902be4 --- /dev/null +++ b/docs/source/basic/read_image.ipynb @@ -0,0 +1,308 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9eb6730c", + "metadata": {}, + "source": [ + "# Read OME-ZARR images\n", + "(basic:read)=\n", + "\n", + "This sample code reads an image stored on remote s3 server,\n", + "but the same code can be used to read data on a local file system.\n", + "In either case, the data is exposed as [`dask` arrays](https://docs.dask.org/en/stable/array.html);\n", + "\n", + "You can obtain a list of \"nodes\" which include all arrays stored in the group:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "54d5f59a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr/ [zgroup],\n", + " https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr/labels/ [zgroup] (hidden),\n", + " https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr/labels/0/ [zgroup] (hidden)]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from ome_zarr.io import parse_url\n", + "from ome_zarr.reader import Reader\n", + "\n", + "url = \"https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr\"\n", + "\n", + "# read the image data\n", + "reader = Reader(parse_url(url))\n", + "# nodes may include images, labels etc\n", + "nodes = list(reader())\n", + "nodes" + ] + }, + { + "cell_type": "markdown", + "id": "f19323d0", + "metadata": {}, + "source": [ + "The first node will be the image pixel data;\n", + "Since this group is again an ome-zarr multiscales object, it consists of several arrays that represent the different resolution levels:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "576326c1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[dask.array,\n", + " dask.array,\n", + " dask.array]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image_node = nodes[0]\n", + "\n", + "multiscales = image_node.data\n", + "multiscales" + ] + }, + { + "cell_type": "markdown", + "id": "c2ccc013", + "metadata": {}, + "source": [ + "The first entry in this list represents the 0-th resolution level, and is the highest resolution data." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "44eaba39", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Array Chunk
Bytes 67.09 MiB 128.00 kiB
Shape (2, 236, 275, 271) (1, 1, 256, 256)
Dask graph 1888 chunks in 2 graph layers
Data type uint16 numpy.ndarray
\n", + "
\n", + " \n", + "\n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " 2\n", + " 1\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " 271\n", + " 275\n", + " 236\n", + "\n", + "
" + ], + "text/plain": [ + "dask.array" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiscales[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eee17b99", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ngff-spec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/basic/view_images.md b/docs/source/basic/view_images.md new file mode 100644 index 00000000..5680bb5e --- /dev/null +++ b/docs/source/basic/view_images.md @@ -0,0 +1,27 @@ +# View OME-ZARR images +(basic:view_images)= + +A variety of tools exist to view ome-zarr images in different frameworks. + +## Browser-based viewers + +Web-based viewers are a simple way to access and view ome-zarr images located on a remote storage. +The [OME-NGFF-Validator](https://ome.github.io/ome-ngff-validator/) provides an entrypoint to +validation, introspection, and viewing of ome-zarr images in the browser: + + + +## Local viewers + +Among the local viewers, [napari](https://napari.org/) is a popular choice for viewing and analyzing ome-zarr images in Python. +It requires the installation of the [napari-ome-zarr plugin](https://github.com/ome/napari-ome-zarr). + +```bash +pip install napari napari-ome-zarr +``` + +To open any local or remote ome-zarr image, just pass the URL to napari: + +```bash +napari https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr +``` diff --git a/docs/source/basic/write_image.ipynb b/docs/source/basic/write_image.ipynb new file mode 100644 index 00000000..a08c6a21 --- /dev/null +++ b/docs/source/basic/write_image.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "255d97a8", + "metadata": {}, + "source": [ + "# Write OME-ZARR images\n", + "(basic:write)=\n", + "\n", + "The principle entry-point for writing OME-NGFF images is {py:func}`ome_zarr.writer.write_image`.\n", + "This takes an n-dimensional `numpy` array or `dask` array and writes it to the specified `zarr group` according\n", + "to the OME-NGFF specification.\n", + "By default, a pyramid of resolution levels will be created by down-sampling the data by a factor\n", + "of 2 in the X and Y dimensions.\n", + "For more custom control over the pyramid, see the more in-depth example on [scaling functions and scale factors](advanced:pyramid)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f87d36fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "\n", + "from ome_zarr.writer import write_image\n", + "\n", + "path = \"test_ngff_image.ome.zarr\"\n", + "\n", + "size_xy = 128\n", + "size_z = 10\n", + "rng = np.random.default_rng(0)\n", + "data = rng.poisson(lam=10, size=(size_z, size_xy, size_xy)).astype(np.uint8)\n", + "\n", + "write_image(data, path, axes=\"zyx\")" + ] + }, + { + "cell_type": "markdown", + "id": "03f9f06a", + "metadata": {}, + "source": [ + "Alternatively, the {py:func}`ome_zarr.writer.write_multiscale` can be used,\n", + "which takes a \"pyramid\" of pre-computed `numpy` arrays.\n", + "\n", + "The default version of OME-NGFF is v0.5, which is based on Zarr v3.\n", + "A zarr v3 group and store is created by `zarr.open_group()` below.\n", + "To write OME-NGFF v0.4 (Zarr v2), add the `zarr_format=2` argument." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cfb1d49f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "path = \"test_ngff_image_v2.ome.zarr\"\n", + "write_image(data, path, axes=\"zyx\", zarr_format=2)" + ] + }, + { + "cell_type": "markdown", + "id": "7b9a0138", + "metadata": {}, + "source": [ + "To view the image, see tutorial on [viewing images](basic:view_images)." + ] + }, + { + "cell_type": "markdown", + "id": "1f8a602d", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/basic/write_labels.ipynb b/docs/source/basic/write_labels.ipynb new file mode 100644 index 00000000..bc176086 --- /dev/null +++ b/docs/source/basic/write_labels.ipynb @@ -0,0 +1,154 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5316186c", + "metadata": {}, + "source": [ + "# Write labels\n", + "(basic:labels)=\n", + "\n", + "Storing labels data alongside image data is a key application and feature of the ome-zarr file standard.\n", + "First, let's create some image data:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ab3c7251", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "\n", + "import numpy as np\n", + "import zarr\n", + "from skimage.data import binary_blobs\n", + "\n", + "from ome_zarr.writer import write_image, write_labels\n", + "\n", + "path = \"test_ngff_image_with_labels.zarr\"\n", + "os.mkdir(path)\n", + "\n", + "mean_val = 10\n", + "size = 64\n", + "rng = np.random.default_rng(0)\n", + "data = rng.poisson(mean_val, size=(size, size, size)).astype(np.uint8)\n", + "\n", + "root = zarr.open_group(path, mode=\"w\")\n", + "write_image(image=data, group=root, axes=\"zyx\")" + ] + }, + { + "cell_type": "markdown", + "id": "c9418813", + "metadata": {}, + "source": [ + "In a next step, we create some dummy labels data:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bd152f2c", + "metadata": {}, + "outputs": [], + "source": [ + "# add labels...\n", + "blobs = binary_blobs(length=size, volume_fraction=0.1, n_dim=3).astype('int8')\n", + "blobs2 = binary_blobs(length=size, volume_fraction=0.1, n_dim=3).astype('int8')\n", + "# blobs will contain values of 1, 2 and 0 (background)\n", + "blobs += 2 * blobs2" + ] + }, + { + "cell_type": "markdown", + "id": "901d9da3", + "metadata": {}, + "source": [ + "We now need to create a new [zarr group](https://zarr.readthedocs.io/en/latest/user-guide/groups/) inside the hierarchy of the ome-zarr file\n", + "to tore the labels data.\n", + "By doing this, the hierarchy inside the ome-zarr file will look like this:\n", + "\n", + "```\n", + "test_ngff_image_with_labels.zarr\n", + "\u251c\u2500\u2500 zarr.json\n", + "\u251c\u2500\u2500 labels\n", + "\u2502 \u251c\u2500\u2500 zarr.json\n", + "\u2502 \u2514\u2500\u2500 blobs\n", + "| \u251c\u2500\u2500 .zarr.json\n", + "| \u251c\u2500\u2500 s0\n", + "| \u2514\u2500\u2500 s1\n", + "\u251c\u2500\u2500 s0\n", + "\u2514\u2500\u2500 s1\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9f1aea98", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# write the labels to /labels\n", + "labels_grp = root.create_group(\"labels\")\n", + "\n", + "label_name = \"blobs\"\n", + "label_grp = labels_grp.create_group(label_name)\n", + "write_labels(blobs, label_grp, axes=\"zyx\", name=label_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0edf2e90", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ngff-spec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/conf.py b/docs/source/conf.py index bce1f59d..61d9b4df 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,16 +4,32 @@ # alternative is to make code installable (which it is!) sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) - +exclude_patterns = ["**.ipynb_checkpoints", ".DS_Store", "Thumbs.db", "_build"] extensions = [ - "sphinx.ext.doctest", - "sphinx.ext.autodoc", + "sphinx_togglebutton", + "sphinx_copybutton", + "myst_nb", + "sphinx_thebe", + "sphinx_comments", + "sphinx_external_toc", "sphinx.ext.intersphinx", - "sphinx_rtd_theme", + "sphinx_design", + "sphinx_book_theme", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.autosummary", + # "sphinxcontrib.bibtex", + "sphinx_jupyterbook_latex", ] -# use index.rst instead of contents.rst -master_doc = "index" +myst_enable_extensions = [ + "colon_fence", + "dollarmath", + "linkify", + "substitution", + "tasklist", +] # -- Project information ----------------------------------------------------- diff --git a/docs/source/explanation/multiscale_pyramids.md b/docs/source/explanation/multiscale_pyramids.md new file mode 100644 index 00000000..a86c65ce --- /dev/null +++ b/docs/source/explanation/multiscale_pyramids.md @@ -0,0 +1,45 @@ +# Multiscale Pyramids + +Multiscale image pyramids are a fundamental concept in OME-NGFF that enable efficient +visualization and analysis of large images. + +## Why Pyramids? + +Modern microscopy produces images that can be gigabytes or even terabytes in size. +Loading an entire image at full resolution is: + +- **Slow**: Transferring large amounts of data takes time +- **Memory-intensive**: May exceed available RAM +- **Unnecessary**: When viewing zoomed out, full resolution is wasteful + +## How Pyramids Work + +A pyramid stores the same image at multiple resolution levels: + +``` +Level 0: 4096 x 4096 (full resolution) +Level 1: 2048 x 2048 (2x downsampled) +Level 2: 1024 x 1024 (4x downsampled) +Level 3: 512 x 512 (8x downsampled) +``` + +Viewers load only the resolution level appropriate for the current zoom level, +enabling smooth navigation of arbitrarily large images. + +## Downsampling Methods + +Different downsampling methods are appropriate for different data types: + +| Method | Use Case | +|--------|----------| +|`ome_zarr.scale.Methods.resize` | Fast skimage-based resizing | +| `ome_zarr.scale.Methods.nearest` | Categorical data (labels, segmentations) | +| `ome_zarr.scale.Methods.zoom` | Downsampling using the [scipy zoom function](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.zoom.html#scipy.ndimage.zoom) | +| `ome_zarr.scale.Methods.local_mean` | Local averaging for smoother results | + +See {py:class}`ome_zarr.scale.Methods` for all available options and more details. + +## Resources + +- [Zarr Chunking Guide](https://zarr.readthedocs.io/en/stable/user-guide/performance/#chunk-optimizations) +- [Dask Array Documentation](https://docs.dask.org/en/stable/array.html) diff --git a/docs/source/explanation/ome_ngff_overview.md b/docs/source/explanation/ome_ngff_overview.md new file mode 100644 index 00000000..06bfc8f8 --- /dev/null +++ b/docs/source/explanation/ome_ngff_overview.md @@ -0,0 +1,35 @@ +# Understanding OME-ZARR + +OME-NGFF (Open Microscopy Environment - Next Generation File Format) is a specification for storing +bioimaging data in a cloud-ready, analysis-friendly format based on [Zarr](https://zarr.dev/). + +## What is OME-ZARR? + +OME-ZARR defines conventions for storing multi-dimensional microscopy images with: + +- **Multiscale pyramids**: Multiple resolution levels for efficient visualization at different zoom levels +- **Labeled axes**: Named dimensions (t, c, z, y, x) with types (time, channel, space) +- **Coordinate transformations**: Scale and translation metadata for physical coordinates +- **Labels/segmentations**: Associated segmentation masks stored alongside images +- **HCS plates**: High-content screening data with well-plate structures + +## Resources + +### Specification +- [OME-NGFF Specification](https://ngff.openmicroscopy.org/specifications) - The official specification document +- [Specification GitHub Repository](https://github.com/ome/ngff-spec) - Specification source +- [NGFF website repository](https://github.com/ome/ngff) - Source for NGFF website and place for discussions about the format + + +### Zarr Format +- [Zarr Documentation](https://zarr.readthedocs.io/) - The underlying storage format +- [Zarr v3 Specification](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html) - Latest zarr format + +### Tools + +A comprehensive list of tools to generate, convert, view or work with ome-zarr images in general +is provided on the [ngff website](https://ngff.openmicroscopy.org/resources/tools/index.html) + +### Community +- [Image.sc Forum](https://forum.image.sc/tag/ome-ngff) - Community discussions about OME-NGFF +- [OME Website](https://www.openmicroscopy.org/) - Open Microscopy Environment diff --git a/docs/source/explanation/zarr_concepts.md b/docs/source/explanation/zarr_concepts.md new file mode 100644 index 00000000..805f6d28 --- /dev/null +++ b/docs/source/explanation/zarr_concepts.md @@ -0,0 +1,47 @@ +# Zarr Concepts + +[Zarr](https://zarr.dev/) is the underlying storage format for OME-NGFF. Understanding +Zarr concepts helps when working with OME-Zarr data. + +## Key Concepts + +### Stores +Zarr arrays can be stored in various backends: +- **Directory store**: Files on local disk (most common) +- **S3 store**: Cloud object storage (Amazon S3, MinIO, etc.) +- **HTTP store**: Read-only access via HTTP/HTTPS +- **Memory store**: In-memory storage for testing + +### Groups and Arrays +Zarr organizes data hierarchically: +- **Groups**: Containers that can hold arrays and other groups (like folders) +- **Arrays**: N-dimensional data chunks with metadata + +### Chunks +Large arrays are divided into chunks for efficient access: +- Each chunk is stored as a separate file/object +- Only needed chunks are loaded into memory +- Chunk shape affects performance for different access patterns + +```python +import zarr + +# Create array with specific chunk shape +arr = zarr.zeros((10000, 10000), chunks=(1000, 1000)) +``` + +### Zarr v2 vs v3 + +OME-NGFF v0.4 uses Zarr v2, while OME-NGFF v0.5 uses Zarr v3: + +| Feature | Zarr v2 | Zarr v3 | +|---------|---------|---------| +| Metadata file | `.zarray`, `.zgroup` | `zarr.json` | +| Sharding | No | Yes | +| Codecs | Limited | Extensible | + +## Resources + +- [Zarr Tutorial](https://zarr.readthedocs.io/en/stable/tutorial.html) +- [Zarr v3 Spec](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html) +- [zarr-python Documentation](https://zarr.readthedocs.io/) diff --git a/docs/source/index.md b/docs/source/index.md new file mode 100644 index 00000000..d6e5fe70 --- /dev/null +++ b/docs/source/index.md @@ -0,0 +1,31 @@ +# ome-zarr-py + +Tools for reading and writing multi-resolution images stored in Zarr filesets, according to the [OME NGFF spec](https://github.com/ome/ngff). + +Note: The default version of OME-Zarr written by ``ome-zarr-py`` is ``v0.5``, which uses ``zarr v3``. OME-Zarr v0.5 +is not yet supported by all OME-Zarr tools. See the documentation for more information on how to write other versions. + +## Features + +- {doc}`basic/cli_basics` for reading and downloading OME-ZARR filesets. +- {doc}`api` for reading and writing OME-ZARR filesets. +- Used by the [napari-ome-zarr](https://github.com/ome/napari-ome-zarr) plugin for viewing OME-ZARR filesets in [napari](https://github.com/napari/napari). + + +## Installation + +Install the latest release of [`ome-zarr`](https://pypi.org/project/ome-zarr/) from PyPI: + +```bash +pip install ome-zarr +``` +or from conda-forge: + +```bash +conda install -c conda-forge ome-zarr +``` + +## License + +Distributed under the terms of the [BSD](https://opensource.org/licenses/BSD-2-Clause) license, +"ome-zarr-py" is free and open source software diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 4ed26406..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,48 +0,0 @@ -=========== -ome-zarr-py -=========== - -Tools for reading and writing multi-resolution images stored in Zarr filesets, according to the `OME NGFF spec`_. - -Note: The default version of OME-Zarr written by ``ome-zarr-py`` is ``v0.5``, which uses ``zarr v3``. OME-Zarr v0.5 -is not yet supported by all OME-Zarr tools. See the documentation for more information on how to write other versions. - -Features --------- - -- :doc:`cli` for reading and downloading OME-NGFF filesets. -- :doc:`api` for reading and writing OME-NGFF filesets (see :doc:`python` for example usage). -- Used by the `napari-ome-zarr`_ plugin for viewing OME-NGFF filesets in `napari`_. - -Contents --------- -.. toctree:: - :maxdepth: 1 - - cli - python - api - -Installation ------------- - -Install the latest release of `ome-zarr`_ from PyPI:: - - pip install ome-zarr - -or from conda-forge:: - - conda install -c conda-forge ome-zarr - - -License -------- - -Distributed under the terms of the `BSD`_ license, -"ome-zarr-py" is free and open source software - -.. _`OME NGFF spec`: https://github.com/ome/ngff -.. _`BSD`: https://opensource.org/licenses/BSD-2-Clause -.. _`napari`: https://github.com/napari/napari -.. _`napari-ome-zarr`: https://github.com/ome/napari-ome-zarr -.. _`ome-zarr`: https://pypi.org/project/ome-zarr/ diff --git a/docs/source/python.rst b/docs/source/python.rst deleted file mode 100644 index 8d1587fa..00000000 --- a/docs/source/python.rst +++ /dev/null @@ -1,443 +0,0 @@ -Python tutorial -=============== - -Writing OME-NGFF images ------------------------ - -The principle entry-point for writing OME-NGFF images is :py:func:`ome_zarr.writer.write_image`. -This takes an n-dimensional `numpy` array or `dask` array and writes it to the specified `zarr group` according -to the OME-NGFF 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. - -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, 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), 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.writer import write_image, add_metadata - - path = "test_ngff_image.zarr" - - size_xy = 128 - size_z = 10 - rng = np.random.default_rng(0) - data = rng.poisson(lam=10, size=(size_z, size_xy, size_xy)).astype(np.uint8) - - write_image(data, path, axes="zyx") - - -This image can be viewed in `napari` using the -`napari-ome-zarr `_ plugin:: - - $ napari test_ngff_image.zarr - -Building a pyramid ------------------- - -Multi-resolution pyramids are an integral part of ome-zarr image data -and enable fast rendering of large images. -The entrypoints to writing ome-zarr images in ome-zarr-py (`write_image` and `write_labels`) -build these pyramids under the hood as delayed dask arrays based on the settings for the scaling functions and scale factors. - -The scale factors can be passed as a list of integers or a list of dicts:: - - from ome_zarr.writer import write_image - - scale_factors = [2, 4, 8] - write_image( - your_data - path, - axes="zyx", - scale_factors=scale_factors, - ) - -In this example, the downsampling will be applied in all spatial dimensions *except the z dimension*, which will be left at a scale factor of 1. -To apply equal or custom downsampling factors along all spatial dimensions, pass the scale factors as a list of dicts, e.g.:: - - from ome_zarr.writer import write_image - - scale_factors = [ - {"z": 2, "y": 2, "x": 2}, - {"z": 4, "y": 4, "x": 4}, - {"z": 8, "y": 8, "x": 8} - ] - write_image( - your_data - path, - axes="zyx", - scale_factors=scale_factors, - ) - -If you have already built a pyramid representation by other means, -you can pass it directly to the :py:func:`ome_zarr.writer.write_multiscale` or use :py:func:`ome_zarr.writer.write_multiscale_labels`, -which do not perform any down-sampling but just write the passed pyramid to disk with the correct metadata. - -Rendering settings ------------------- -Rendering settings can be added to an existing zarr group:: - - add_metadata(path, {"omero": { - "channels": [{ - "color": "00FFFF", - "window": {"start": 0, "end": 20, "min": 0, "max": 255}, - "label": "random", - "active": True, - }] - }}) - -Writing labels --------------- - -The following code creates a 3D Image in OME-Zarr with labels:: - - import numpy as np - import zarr - import os - - from skimage.data import binary_blobs - from ome_zarr.writer import write_image, add_metadata - - path = "test_ngff_image_labels.zarr" - os.mkdir(path) - - mean_val = 10 - size_xy = 128 - size_z = 10 - rng = np.random.default_rng(0) - data = rng.poisson(mean_val, size=(size_z, size_xy, size_xy)).astype(np.uint8) - - # 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 - add_metadata(root, {"omero": { - "channels": [{ - "color": "00FFFF", - "window": {"start": 0, "end": 20, "min": 0, "max": 255}, - "label": "random", - "active": True, - }] - }}) - - - # add labels... - blobs = binary_blobs(length=size_xy, volume_fraction=0.1, n_dim=3).astype('int8') - blobs2 = binary_blobs(length=size_xy, volume_fraction=0.1, n_dim=3).astype('int8') - # blobs will contain values of 1, 2 and 0 (background) - blobs += 2 * blobs2 - - # label.shape is (size_xy, size_xy, size_xy), Slice to match the data - label = blobs[:size_z, :, :] - - # write the labels to /labels - labels_grp = root.create_group("labels") - # the 'labels' .zattrs lists the named labels data - label_name = "blobs" - add_metadata(labels_grp, {"labels": [label_name]}) - label_grp = labels_grp.create_group(label_name) - write_image(label, label_grp, axes="zyx") - - # we need 'image-label' attr to be recognized as label - add_metadata(label_grp, {"image-label": { - "colors": [ - {"label-value": 1, "rgba": [255, 0, 0, 255]}, - {"label-value": 2, "rgba": [0, 255, 0, 255]}, - {"label-value": 3, "rgba": [255, 255, 0, 255]} - ] - }}) - - -Writing HCS datasets to OME-NGFF --------------------------------- - -This sample code shows how to write a high-content screening dataset (i.e. culture plate with multiple wells) to a OME-NGFF file:: - - import numpy as np - import zarr - - from ome_zarr.writer import write_image, write_plate_metadata, write_well_metadata - - path = "test_ngff_plate.zarr" - row_names = ["A", "B"] - col_names = ["1", "2", "3"] - well_paths = ["A/2", "B/3"] - field_paths = ["0", "1", "2"] - - # generate data - mean_val = 10 - num_wells = len(well_paths) - num_fields = len(field_paths) - size_xy = 128 - size_z = 10 - rng = np.random.default_rng(0) - 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 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("/") - row_group = root.require_group(row) - well_group = row_group.require_group(col) - write_well_metadata(well_group, field_paths) - for fi, field in enumerate(field_paths): - image_group = well_group.require_group(str(field)) - write_image(image=data[wi, fi], group=image_group, axes="zyx", - storage_options=dict(chunks=(1, size_xy, size_xy))) - - -This image can be viewed in `napari` using the -`napari-ome-zarr `_ plugin:: - - import napari - - viewer = napari.Viewer() - viewer.open(path, plugin="napari-ome-zarr") - - -Reading OME-NGFF images ------------------------ - -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 - 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] - - dask_data = image_node.data - - # We can view this in napari - # NB: image axes are CZYX: split channels by C axis=0 - viewer = napari.view_image(dask_data, channel_axis=0) - if __name__ == '__main__': - napari.run() - - -More writing examples ---------------------- - -Writing big image from tiles:: - - # Created for https://forum.image.sc/t/writing-tile-wise-ome-zarr-with-pyramid-size/85063 - - 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 - from ome_zarr.dask_utils import resize as da_resize - import numpy as np - import dask.array as da - from math import ceil - - fmt = CurrentFormat() - # 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()) - # first level of the pyramid - dask_data = nodes[0].data[0] - tile_size = 512 - axes = [{"name": "c", "type": "channel"}, {"name": "y", "type": "space"}, {"name": "x", "type": "space"}] - - def downsample_pyramid_on_disk(parent, paths): - """ - Takes a high-resolution Zarr array at paths[0] in the zarr group - and down-samples it by a factor of 2 for each of the other paths - """ - group_path = str(parent.store_path) - img_path = parent.store_path / parent.path - image_path = os.path.join(group_path, parent.path) - print("downsample_pyramid_on_disk", image_path) - for count, path in enumerate(paths[1:]): - target_path = os.path.join(image_path, path) - if os.path.exists(target_path): - print("path exists: %s" % target_path) - continue - # open previous resolution from disk via dask... - path_to_array = os.path.join(image_path, paths[count]) - dask_image = da.from_zarr(path_to_array) - - # resize in X and Y - dims = list(dask_image.shape) - dims[-1] = dims[-1] // 2 - dims[-2] = dims[-2] // 2 - output = da_resize( - dask_image, tuple(dims), preserve_range=True, anti_aliasing=False - ) - - zarr_array_kwargs = {} - if fmt.zarr_format == 2: - zarr_array_kwargs["chunk_key_encoding"] = {"name": "v2", "separator": "/"} - else: - # zarr_array_kwargs["chunk_key_encoding"] = fmt.chunk_key_encoding - zarr_array_kwargs["dimension_names"] = [axis["name"] for axis in axes] - # write to disk - da.to_zarr( - arr=output, url=img_path, component=path, - zarr_format=fmt.zarr_format, zarr_array_kwargs=zarr_array_kwargs - ) - return paths - - def get_tile(ch, row, col): - # read the tile data from somewhere - we use the dask array - y1 = row * tile_size - y2 = y1 + tile_size - x1 = col * tile_size - x2 = x1 + tile_size - return dask_data[ch, y1:y2, x1:x2] - - # (4,1920,1920) - shape = dask_data.shape - chunks = (1, tile_size, tile_size) - d_type = np.dtype('