Repository navigation
Zarr I/O PR 4: native Zarr loading #7336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
f8aaa3e
Start the PR 4 plan: header, constraints, scope and findings
trexfeathers 2149b95
Add the PR 4 plan's file structure and interface design
trexfeathers 81ef978
Add PR 4 plan tasks 1-3: the dependency, the interface, the variable
trexfeathers 714930a
Add PR 4 plan tasks 4-5: the Zarr group, and the shared contract
trexfeathers c379be7
Add PR 4 plan tasks 6-7: CF decoding, and the lazy read
trexfeathers cb7f407
Add PR 4 plan tasks 8-10: spans, CFReader, and the Zarr loader
trexfeathers 00bbb11
Add PR 4 plan tasks 11-12: format detection and the load chain
trexfeathers dfb58e9
Finish the PR 4 plan: integration, docs, the pull request, verification
trexfeathers d365da6
Self-review the PR 4 plan: fix the pointers, close the gaps
trexfeathers adbb712
Correct the PR 4 plan against the files that are actually there
trexfeathers adfd266
Point the PR 4 plan's baseline at the merged spec
trexfeathers 31ebd55
Base the PR 4 plan on zarr-io-pr-3, with a fork preview first
trexfeathers 54d5ac6
Add zarr as an optional dependency, and the package that uses it
trexfeathers ec0e5a0
Split the Zarr store-builder tests into their own module
trexfeathers 66a544d
Add format and encoding to the CF dataset interface
trexfeathers 3bc38ca
Keep close and context-manager coverage in the read half of the contract
trexfeathers eac443e
Reflow CFDataset.format's docstring under the 88-character line limit
trexfeathers 06b8c5b
Present a Zarr array through the CF dataset interface
trexfeathers 77b1d3f
Present a Zarr group through the CF dataset interface
trexfeathers 2b496b3
Assert variables keep an omitted dimension name in their own dimensions
trexfeathers d07f024
Implement the CF decoding Zarr does not do for itself
trexfeathers 1e163e4
Stop an out-of-range missing-value attribute crashing the load
trexfeathers 790f7a9
Read Zarr data lazily, aligned to the store's own chunks
trexfeathers 41d77fe
Prove the Zarr read uses the cache, and stop a warning that cried wolf
trexfeathers 0b7656b
Ask the shared CFDataset read contract of the Zarr store
trexfeathers eddf084
Match CF dimensions on (name, size), not on the name alone
trexfeathers e234da0
Let CFReader read any already-open CF dataset
trexfeathers 8fc1dd4
Build cubes from a Zarr store
trexfeathers c2612d2
Teach the format agent to recognise a Zarr store
trexfeathers f056297
Skip the NetCDF-detection test when iris-test-data is absent
trexfeathers 4618014
Let the load chain reach a target that is not a file
trexfeathers 4b63e0e
Update the known-formats snapshot for the registered Zarr spec
trexfeathers 60088c3
Restore load_files's public contract, and guard the dispatch split
trexfeathers e209e64
Load realistic Zarr stores end to end, both versions
trexfeathers d4edf6c
Withhold netCDF-c's _NCProperties stamp, and add bounds to the Zarr f…
trexfeathers d2b1bf6
Document Zarr loading
trexfeathers 750d1bf
Fix review findings on the Zarr documentation page
trexfeathers ffad8ba
Record PR 4 in the spec, and what implementation changed
trexfeathers e7fda90
Give both Zarr load routes the same clear ImportError
trexfeathers 6a583d2
Fix the shared Zarr fixture's standing warning, and warn when a versi…
trexfeathers f340757
Narrow the version 2 fill-value warning to a storage fill value of zero
trexfeathers d2f28a1
Updated lock files.
trexfeathers 3090ead
Decode every sentinel in a vector missing_value
trexfeathers d1d3ac8
Size Zarr chunks on the decoded dtype, not the stored one
trexfeathers 55c5dfc
Carry the (name, size) rule through coordinate attachment
trexfeathers 09fb1a5
Rule the Zarr store element out when a file handle is supplied
trexfeathers 92118c9
Record the review findings in the spec
trexfeathers 3a164af
Size as_dask() chunks on the decoded dtype too
trexfeathers 17aad90
Trim the Zarr docs to what a user acts on
trexfeathers 5a86836
Assemble the Zarr dimension mapping when the store is opened
trexfeathers 242e2fd
Action the local review points on the Zarr reader
trexfeathers 29b2986
Share the URI categoriser, and name the Zarr versions read
trexfeathers 0b7f097
Drop chunk alignment; warn for both backends instead
trexfeathers 5899048
Merge brownfield into PR 4, after PR 5 landed
trexfeathers d9027dc
Action four more review points
trexfeathers 5688b24
Load UGRID meshes from Zarr, and action the rest of PR 4's review
trexfeathers 93a647f
Say why a netCDF-named module names Zarr
trexfeathers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| .. explanation:: Zarr I/O Handling in Iris | ||
| :tags: topic_load_save | ||
|
|
||
| Read about how Iris loads Zarr stores. | ||
|
|
||
| .. include:: ../../common_links.inc | ||
|
|
||
| .. _zarr_io: | ||
|
|
||
| ========================= | ||
| Zarr I/O Handling in Iris | ||
| ========================= | ||
|
|
||
| .. readingtime:: | ||
|
|
||
| `Zarr <https://zarr.dev/>`_ stores an array as many independently compressed | ||
| chunks under a prefix, rather than as one file. Iris reads Zarr version 2 and | ||
| version 3 stores natively, through | ||
| `zarr-python <https://zarr.readthedocs.io/>`_. | ||
|
|
||
| ``zarr`` is an **optional dependency**, and Iris requires at least version | ||
| 3.0.8. Install it to load Zarr stores:: | ||
|
|
||
| conda install -c conda-forge "zarr>=3.0.8" | ||
|
|
||
| Iris without it installs and runs normally, raising an ``ImportError`` that | ||
| names the package and its minimum required version if you ask it to load a | ||
| store. | ||
|
|
||
| .. _zarr_io_loading: | ||
|
|
||
| Loading | ||
| ------- | ||
|
|
||
| A store is a directory, or a URL, so pass it where you would pass a file:: | ||
|
|
||
| cubes = iris.load("/data/forecast.zarr") | ||
|
|
||
| Iris recognises a local store by the metadata file at its root -- ``zarr.json`` | ||
| for version 3, ``.zgroup`` for version 2 -- not by the ``.zarr`` suffix, so a | ||
| directory that merely has the suffix is not mistaken for one. | ||
|
|
||
| A store holding UGRID mesh data loads as a mesh cube, the same as the | ||
| equivalent netCDF file would. :func:`iris.mesh.load_meshes` reads a store | ||
| too. | ||
|
|
||
| Saving | ||
| ------ | ||
|
|
||
| Iris does not save to a native Zarr store. ``iris.save(cube, "out.zarr")`` | ||
| raises ``ValueError: Cannot save; no saver``, naming neither Zarr nor an | ||
| alternative. Save to netCDF instead, or write Zarr through netCDF-c's NCZarr | ||
| driver, covered below in :ref:`zarr_io_nczarr`. | ||
|
|
||
| A cube loaded from Zarr may carry an attribute netCDF cannot hold, which | ||
| fails the save. See :ref:`zarr_io_attributes`. | ||
|
|
||
| Remote stores | ||
| ------------- | ||
|
|
||
| A URL with one of the ``s3``, ``gs``, ``az`` or ``abfs`` schemes, ending in | ||
| ``.zarr``, is passed to zarr-python unaltered:: | ||
|
|
||
| cubes = iris.load("s3://noaa-gfs-bdp-pds/forecast.zarr") | ||
|
|
||
| Iris adds no store library of its own and handles no credentials. Install the | ||
| one your scheme needs -- ``s3fs``, ``gcsfs``, ``adlfs`` -- and configure it as | ||
| that package documents. zarr-python raises an ``ImportError`` naming the | ||
| package if it is missing. | ||
|
|
||
| Groups | ||
| ------ | ||
|
|
||
| A Zarr store may hold several groups, and they need not describe the same | ||
| thing. Iris loads **one** group per call, like xarray, rather than merging | ||
| them: ``iris.load`` reads the root group, and a store whose root holds no data | ||
| variables raises a message naming the groups that do. | ||
|
|
||
| To read another group, call the Zarr loader directly:: | ||
|
|
||
| from iris.cube import CubeList | ||
| import iris.fileformats.zarr | ||
|
|
||
| cubes = CubeList( | ||
| iris.fileformats.zarr.load_cubes( | ||
| "/data/sentinel.zarr", group="measurements/reflectance/r10m" | ||
| ) | ||
| ) | ||
|
|
||
| Dimension names | ||
| --------------- | ||
|
|
||
| A netCDF dimension has a name, one length, and a meaning shared across the | ||
| whole file. A Zarr dimension name promises less. An array need not name its | ||
| dimensions at all, and the same name in two arrays guarantees nothing -- not | ||
| even the same length. Both are permitted by the specification. | ||
|
|
||
| So Iris treats two arrays as sharing a dimension only when they name it | ||
| identically **and** it is the same length in both. Anything else loads as an | ||
| **anonymous** dimension, which is an ordinary Iris cube dimension with no | ||
| coordinate on it. That covers both a dimension the store never named, and a | ||
| name the store used at two different lengths. | ||
|
|
||
| A coordinate left with nothing to attach to is not discarded. It goes to | ||
| :data:`iris.loading.LOAD_PROBLEMS`, and the load warns that not every object | ||
| was parsed. | ||
|
|
||
| Version 2 stores have no dimension-name field. They carry names in an | ||
| ``_ARRAY_DIMENSIONS`` attribute instead -- the convention xarray established | ||
| -- which Iris reads, so dimensions are named on both versions. The attribute | ||
| describes the store rather than the data, so it does not itself reach | ||
| ``cube.attributes``. | ||
|
|
||
| Missing data | ||
| ------------ | ||
|
|
||
| Zarr returns a plain array and applies no masking, so Iris does it, from the | ||
| CF attributes: ``_FillValue`` and ``missing_value`` for sentinels, | ||
| ``valid_min``/``valid_max``/``valid_range`` for a range, and | ||
| ``scale_factor``/``add_offset`` for packed data. | ||
|
|
||
| A Zarr array also carries a ``fill_value`` in its metadata. That says what | ||
| unwritten storage reads back as, not which values are missing, and | ||
| zarr-python defaults it to zero. Iris reads it as missing data only on a | ||
| version 2 store whose array declares no CF attribute -- which is what version | ||
| 2 writers meant by it, and what xarray does. | ||
|
|
||
| That one case can mask a genuine zero, so Iris warns when it happens, naming | ||
| the variable. Setting a ``_FillValue`` attribute on the array prevents both | ||
| the masking and the warning. | ||
|
|
||
| Chunking | ||
| -------- | ||
|
|
||
| Iris chooses Dask chunks from the store's own chunking, as it does for | ||
| netCDF, and :data:`~iris.fileformats.cf.loader.CHUNK_CONTROL` works the same | ||
| way for both formats. | ||
|
|
||
| A Zarr chunk is the atomic unit of storage: reading any part of one fetches | ||
| and decompresses the whole object, so a Dask chunk smaller than a store chunk | ||
| costs a re-read per division. Iris does not adjust its chunking to avoid | ||
| that, any more than it does for netCDF, but it warns when a store chunk is | ||
| larger than Dask's target chunk size -- where the cost starts to be worth | ||
| your attention. Use ``CHUNK_CONTROL`` to choose the chunking yourself. | ||
|
|
||
| A **sharded** array is chunked on its inner chunks, not its shards. Zarr's | ||
| sharding codec is indexed, so a reader fetches only the inner chunks it | ||
| needs. | ||
|
|
||
| zarr-python fetches chunks concurrently, and Dask's worker count multiplies | ||
| with that concurrency. If a remote store rate-limits you, lower either Dask's | ||
| worker count or zarr-python's own ``zarr.config`` setting | ||
| ``async.concurrency``. | ||
|
|
||
| .. _zarr_io_attributes: | ||
|
|
||
| Attributes | ||
| ---------- | ||
|
|
||
| A Zarr attribute value may be any JSON literal, including many things CF | ||
| cannot express, such as multidimensional arrays, dictionaries or lists of | ||
| lists. Iris carries such a value onto the cube unchanged and does not | ||
| interpret it. See :issue:`7288` for the wider question of how the CF | ||
| attribute model should map onto JSON. | ||
|
|
||
| .. note:: | ||
|
|
||
| A cube carrying such an attribute **cannot be saved as netCDF**, because | ||
| netCDF attribute values are scalars, strings and one-dimensional arrays. | ||
| The netCDF library raises ``TypeError: illegal data type for attribute | ||
| b'stac_discovery'``, which does not name the cube. Delete the attribute | ||
| before saving:: | ||
|
|
||
| del cube.attributes["stac_discovery"] | ||
|
|
||
| See :issue:`7329`. | ||
|
|
||
| .. _zarr_io_nczarr: | ||
|
|
||
| NCZarr | ||
| ------ | ||
|
|
||
| Iris also reads and writes Zarr through netCDF-c's NCZarr driver, reached by | ||
| way of a URL fragment:: | ||
|
|
||
| iris.load("file:///data/forecast.zarr#mode=nczarr,file") | ||
|
|
||
| NCZarr implements Zarr version 2, and writes scalar variables as length-one | ||
| arrays. The native reader described above handles both versions, and is | ||
| what a plain path or URL -- one with no ``#mode=`` fragment -- reaches. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.