I am hacking on N5 support for zarrs and it can pretty much all be decomposed into extensions.
May be of interest to @bogovicj @mkitti @d-v-b .
There's a MVP which would allow metadata-only conversions from N5 to Zarr - i.e. owners of N5 data would just stick a zarr.json file next to every attributes.json.
The stretch goal of needing no on-disk changes would require a storage transformer, which there's not a lot of prior art for, and I'm not sure it's viable in any case (see below).
Chunk key encoding
Needs to turn the request for chunk (z,y,x) into the path "x/y/z".
Chunk grid
N5 blocks each encode their own shape, with the idea being that blocks at the end of an array where the array shape isn't an integer multiple of the block shape can encode that they are effectively truncated.
Assuming that this is the only case where this truncation happens, we could define a chunk grid which encodes this expectation.
Zarrs already does this with the regular_bounded chunk grid - I'm not sure if there are equivalents elsewhere in the ecosystem.
Note that in zarr-python v2's N5 support (and tensorstore), the trailing chunks were padded rather than truncated.
Such data would probably cause the regular_bounded chunk grid to fail.
Codec
N5 blocks comprise a header followed by encoded data, which means the N5 codec must be an array-to-bytes codec which has compression configuration inside it, rather than as part of the outermost codec chain.
N5 block data is always see comment below C order (I think; not entirely clear), despite the blocks themselves being in F order (I think) , and always bigendian. So config like
would result in a Zarr codec which, on read
- reads the header
- check for default/varlen/object modes
- determine the size of the block (should be the chunk size, or smaller if it's on the end)
- strips the header and applies a zarr codec chain for the described decompression, then bigendian byte ordering
- possibly an array-to-array codec for padding the (truncated N5) end block shape to the (padded zarr) end chunk shape, as an alternative to the
regular_bounded chunk grid above
This should be the only codec.
Storage transformer?
My current implementation is a store which wraps any other zarr store and redirects zarr.json requests to attributes.json, parses the N5 metadata response and re-encodes it as Zarr metadata.
This seems like a job for a storage transformer.
However, we can only read a storage transformer configuration from a zarr.json, and if the target data has a zarr.json, then we don't need the storage transformer (as the N5 codec and chunk key encoding would both be in that Zarr metadata).
So it's not clear how useful this would actually be.
Instead, we could just describe the steps an N5StoreWrapper would need to take and leave it up to the implementors.
I am hacking on N5 support for zarrs and it can pretty much all be decomposed into extensions.
May be of interest to @bogovicj @mkitti @d-v-b .
There's a MVP which would allow metadata-only conversions from N5 to Zarr - i.e. owners of N5 data would just stick a
zarr.jsonfile next to everyattributes.json.The stretch goal of needing no on-disk changes would require a storage transformer, which there's not a lot of prior art for, and I'm not sure it's viable in any case (see below).
Chunk key encoding
Needs to turn the request for chunk(z,y,x)into the path"x/y/z".Chunk grid
N5 blocks each encode their own shape, with the idea being that blocks at the end of an array where the array shape isn't an integer multiple of the block shape can encode that they are effectively truncated.
Assuming that this is the only case where this truncation happens, we could define a chunk grid which encodes this expectation.
Zarrs already does this with the
regular_boundedchunk grid - I'm not sure if there are equivalents elsewhere in the ecosystem.Note that in zarr-python v2's N5 support (and tensorstore), the trailing chunks were padded rather than truncated.
Such data would probably cause the
regular_boundedchunk grid to fail.Codec
N5 blocks comprise a header followed by encoded data, which means the N5 codec must be an array-to-bytes codec which has compression configuration inside it, rather than as part of the outermost codec chain.
N5 block data is always see comment below
C order (I think; not entirely clear), despite the blocks themselves being in F order (I think), and always bigendian. So config like{ "node_type": "array", ... "codecs": [ { "name": "n5", "configuration": { // N5's compression is effectively an optional bytes-to-bytes codec "bytes_to_bytes_codec": { "name": "gzip", "configuration": { "level": 6 } } } } ] }would result in a Zarr codec which, on read
regular_boundedchunk grid aboveThis should be the only codec.
Storage transformer?
My current implementation is a store which wraps any other zarr store and redirects
zarr.jsonrequests toattributes.json, parses the N5 metadata response and re-encodes it as Zarr metadata.This seems like a job for a storage transformer.
However, we can only read a storage transformer configuration from a
zarr.json, and if the target data has azarr.json, then we don't need the storage transformer (as the N5 codec and chunk key encoding would both be in that Zarr metadata).So it's not clear how useful this would actually be.
Instead, we could just describe the steps an N5StoreWrapper would need to take and leave it up to the implementors.