tiled icon indicating copy to clipboard operation
tiled copied to clipboard

Add zarr v2 endpoints to Tiled

Open genematx opened this issue 1 year ago • 1 comments
trafficstars

This PR exposes Tiled data as a zarr collection on a set of new api endpoints, /zarr/v2/.... This allows one to use zarr clients directly with Tiled, as if it was an external filesystem accessed through fsspec.

Assuming a demo Tiled server is running on 127.0.0.1:8000 (e.g. started with tiled serve demo), one can read its contents into zarr by first specifying a file system mapper and then passing it to zarr:

import zarr
from fsspec import get_mapper

url = "http://localhost:8000/zarr/v2/"
fs_mapper = get_mapper(url)
root = zarr.open(fs_mapper, mode="r")

The resulting object is a zarr.Group, which represents the root of the Tiled catalog tree and supports (most) of the usual operations on zarr groups:

>>> print(group)
<zarr.hierarchy.Group '/' read-only>

>>> list(group.keys())
['dynamic', 'flat_array', 'high_entropy', 'low_entropy',
'nested', 'scalars', 'structured_data', 'tables']
>>> root.tree()
/
├── dynamic (3, 3) float64
 ├── flat_array (100,) float64
 ├── high_entropy (100, 100) int64
 ├── low_entropy (100, 100) int32
 ├── nested
 │   ├── cubes
 │   │   ├── tiny_cube (50, 50, 50) float64
 │   │   └── tiny_hypercube (50, 50, 50, 50, 50) float64
 │   ├── images
 │   │   ├── big_image (10000, 10000) float64
 │   │   ├── medium_image (1000, 1000) float64
 │   │   ├── small_image (300, 300) float64
 │   │   └── tiny_image (50, 50) float64
 │   └── sparse_image (100, 100) float64
 ├── scalars
 │   ├── e_arr (1,) <U7
 │   ├── fortytwo () int64
 │   ├── fsc () <U5
 │   └── pi () float64
 ├── structured_data
 │   ├── pets
 │   └── xarray_dataset
 │       ├── lat (2, 2) float64
 │       ├── lon (2, 2) float64
 │       ├── precipitation (2, 2, 3) float64
 │       ├── temperature (2, 2, 3) float64
 │       └── time (3,) datetime64[ns]
 └── tables
     ├── long_table
     │   ├── A (100000,) float64
     │   ├── B (100000,) float64
     │   └── C (100000,) float64
     ├── short_table
     │   ├── A (100,) uint8
     │   ├── B (100,) uint8
     │   └── C (100,) uint8
     └── wide_table
         ├── A (10,) float64
         ├── B (10,) float64
         ├── C (10,) float64
         ...
         ├── X (10,) float64
         ├── Y (10,) float64
         └── Z (10,) float64

NOTE: To access Tiled servers that require authentication, we can pass an api-key in the header of the HTTP requests. With fsspec, this is done by explicitly constructing an HTTPFileSystem object and mapping it to zarr:

from fsspec.implementations.http import HTTPFileSystem

headers = {"Authorization": "Apikey your-api-key-goes-here",
           "Content-Type": "application/json"}
fs = HTTPFileSystem(client_kwargs={"headers": headers})
root = zarr.open(fs.get_mapper(url), mode="r")

The native tiled datastructures are mapped to zarr as follows:

Tiled zarr
Container Group
Array Array
Sparse Array Array (dense)
Data Frame Group (of columns)
Data Frame Column Array

Addresses the Issue #562.

Checklist

  • [x] Add a Changelog entry
  • [x] Add the ticket number which this PR closes to the comment section

genematx avatar Aug 06 '24 21:08 genematx

EDIT: I have included my code from this fix here: https://github.com/davramov/tiled/tree/add-zarr-forked

Hi, I am working on serving zarr files to the standalone itkwidgets web browser visualization tool via Tiled using this PR.

I am serving the zarr via tiled using this command:

TILED_CONFIG=config.yaml TILED_ALLOW_ORIGINS="http://localhost:3000" tiled serve directory "../../data/tomo/scratch/" --public --verbose

I ran into an issue where when I pass the tiled path into the itk-vtk-viewer url, http://localhost:3000/?fileToLoad=http://127.0.0.1:8000/zarr/v2/rec20230606_152011_jong-seto_fungal-mycelia_flat-AQ_fungi2_fast, in the web app there is a 404 error:

itkVtkViewer.js:2 
GET http://127.0.0.1:8000/zarr/v2/rec20230606_152011_jong-seto_fungal-mycelia_flat-AQ_fungi2_fast/.zattrs 404 (Not Found)

It doesn't seem like this file is getting served as part of the directory (I noticed in server/zarr.py there are routers for .zarray and .zgroup, but not .zattrs).

To solve this I added a ZarrAttrsAdapter class to tiled/adapters/zarr.py:

class ZarrAttrsAdapter:
    """
    Adapter that exposes a Zarr node's .attrs as JSON.
    """

    structure_family = "node"  # or "container" if you prefer
    specs: List[Spec] = []

    def __init__(self, node: Union[zarr.hierarchy.Group, zarr.core.Array]):
        """
        node: Zarr Group or Array whose attributes we want to serve
        """
        self._node = node

    def metadata(self) -> JSON:
        """
        Return any extra metadata. In this example, it's empty.
        """
        return {}

    def structure(self):
        """
        We have no numeric array data to describe, just JSON attributes.
        """
        return None

    def read(self) -> JSON:
        """
        Return the node's attrs as a plain dictionary.
        """
        return dict(self._node.attrs)

    def __repr__(self) -> str:
        return f"<ZarrAttrsAdapter attrs_keys={list(self._node.attrs.keys())}>"

I also added an additional router in tiled/server/zarr.py called get_zarr_attrs:

@router.get("{path:path}.zattrs", name="Zarr .zattrs metadata")
@router.get("/{path:path}/.zattrs", name="Zarr .zattrs metadata")
async def get_zarr_attrs(
    request: Request,
    entry=SecureEntry(
        scopes=["read:data", "read:metadata"],
        structure_families={
            StructureFamily.table,
            StructureFamily.container,
            StructureFamily.array,
        },
    ),
):
    """
    Return Zarr attributes metadata (.zattrs).
    If entry.metadata() (or entry.metadata) includes "zattrs", return them.
    """
    # If it's an unstructured array, we do not treat it as a group for .zattrs
    if entry.structure_family == StructureFamily.array and not isinstance(
        entry.structure().data_type, StructDtype
    ):
        raise HTTPException(status_code=HTTP_404_NOT_FOUND)

    # Attempt to retrieve .zattrs from entry.metadata
    try:
        metadata_dict = entry.metadata()  # if it's callable
    except TypeError:
        metadata_dict = entry.metadata  # if it's a property

    return Response(
        json.dumps(metadata_dict),
        status_code=200,
        media_type="application/json",
    )

With these changes, I was able to successfully load the zarr volume in the itk-vtk-viewer web app.

davramov avatar Jan 10 '25 23:01 davramov

Hi @genematx ,

Working with @dylanmcreynolds. Just checking on this, wondering if there is anything you want me to do to move this PR forward?

Thanks, David

davramov avatar Apr 17 '25 21:04 davramov

Hi, David @davramov!

Thanks for reaching out! And I am so sorry; I've completely missed your previous comment from a few months ago. Thank you for sharing your work -- it is very helpful. I think adding another route for /.zattrs makes sense, especially if it's expected by some clients (I'll try testing it with itkwidgets next time too). To be honest, this work has been stale for while, as we've been busy migrating catalogs from Mongo to SQL here, but I'm hoping to pick it up again in the next few weeks. Now, when zarr v3 is out, it seems reasonable that ensuring we can support any changes in the api would be a good next step (or maybe it could be its own PR...). Let us know if you any other ideas or thoughts.

Kind regards, Eugene.

cc: @danielballan

genematx avatar Apr 18 '25 13:04 genematx

Hi, is there something we can do to help move this along? This is an amazing feature for us.

dylanmcreynolds avatar May 27 '25 16:05 dylanmcreynolds

Hi @davramov, I'm reviving this branch with the intention to release the changes in v0.1.0 next month. Thanks again for your help, and I'm sorry it took so long for us to get back here. I've put your changes (.zattrs endpoint) into the code (apart from ZarrAttrsAdapter -- I'm not sure we need it...) and made all tests to pass. I have tried to reproduce your workflow in itkVtkViewer, but I must be doing something wrong -- I am not familiar with this viewer. I can see it sending requests to the attributes endpoint, but then nothing happens and it just displays a spinning wheel. I am going to test it with other zarr clients later today or tomorrow, but would you mind trying this branch with the most recent commits and see if it works for you, please?

genematx avatar Jul 22 '25 13:07 genematx

Hi @davramov, I'm reviving this branch with the intention to release the changes in v0.1.0 next month. Thanks again for your help, and I'm sorry it took so long for us to get back here. I've put your changes (.zattrs endpoint) into the code (apart from ZarrAttrsAdapter -- I'm not sure we need it...) and made all tests to pass. I have tried to reproduce your workflow in itkVtkViewer, but I must be doing something wrong -- I am not familiar with this viewer. I can see it sending requests to the attributes endpoint, but then nothing happens and it just displays a spinning wheel. I am going to test it with other zarr clients later today or tomorrow, but would you mind trying this branch with the most recent commits and see if it works for you, please?

Thanks @genematx! I'll let you know when I get a chance to test this, hopefully in the next few days.

davramov avatar Jul 22 '25 22:07 davramov

TO DO:

  • [x] Revisit the need to resort to middleware, in light of get_entry being a simple function instead of a Dependency.
  • [ ] Validate this against https://kitware.github.io/itk-vtk-viewer/app/?rotate=false&fileToLoad=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0062A/6001240.zarr (remembering to set TILED_ALLOWED_ORIGINS=https://kitware.github.io/itk-vtk-viewer).

danielballan avatar Aug 06 '25 13:08 danielballan

image

joshmoore avatar Aug 06 '25 16:08 joshmoore

Apologies, @dylanmcreynolds. @danielballan and I discussed this at the 2023 SciPy and I simply love the idea of Tiled being a Zarr implementation. Kudos all around.

joshmoore avatar Aug 06 '25 17:08 joshmoore

Hahaha this brought me so much joy @joshmoore, much needed on this workday. <3 Good to see you around these parts.

danielballan avatar Aug 06 '25 20:08 danielballan

@davramov, would you mind sharing some of your data files that you serve with Tiled, please? I'm facing some uncertainty with the specific .zattrs ITK/VTK viewer expects (and I think I understand now why you needed that ZarrAttributesAdapter). We made it work with vizarr, though. You can try it by starting the Tiled server with: TILED_ALLOW_ORIGINS='["https://hms-dbmi.github.io/"]' tiled serve demo and then load https://hms-dbmi.github.io/vizarr/?source=http://127.0.0.1:8000/zarr/v2/nested/images/medium_image

genematx avatar Aug 06 '25 20:08 genematx

Hi @genematx, I have been meaning to respond to this thread all day, but keep getting sucked into meetings!

Hi @davramov, I'm reviving this branch with the intention to release the changes in v0.1.0 next month. Thanks again for your help, and I'm sorry it took so long for us to get back here. I've put your changes (.zattrs endpoint) into the code (apart from ZarrAttrsAdapter -- I'm not sure we need it...) and made all tests to pass. I have tried to reproduce your workflow in itkVtkViewer, but I must be doing something wrong -- I am not familiar with this viewer. I can see it sending requests to the attributes endpoint, but then nothing happens and it just displays a spinning wheel. I am going to test it with other zarr clients later today or tomorrow, but would you mind trying this branch with the most recent commits and see if it works for you, please?

Thanks @genematx! I'll let you know when I get a chance to test this, hopefully in the next few days.

Yesterday, I cloned the latest version of this PR to use with the itk-vtk-viewer web app. Out of the box, I did not encounter any issues with installation; Tiled registered the Zarr files as expected, and I can browse for them in the Tiled browser web page.

However, when I select a dataset to view the dataset in itk-vtk-viewer, the volume is not rendered, and interestingly, I am not getting helpful console errors on why that's the case. I have a hunch why it worked before with my ZarrAttributesAdapter I implemented, but I haven't had a chance to debug further. I'm providing logs from Tiled below (both my branch and the latest version of this PR). I am uploading the Zarr for this sand dataset to Google Drive right now, and I will share it with you via email when it's ready.

Cheers, David

Running on the beamline PC with my branch, here are the tiled logs when I load a Zarr file in the viewer app:

tiled-1  | [a9054d3513cf584b] 172.20.0.4:48950 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/.zattrs HTTP/1.1" 200 OK
tiled-1  | [cd558578524da908] 172.20.0.4:48952 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale0/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [7e0b7e44b32f45e0] 172.20.0.4:48980 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [4f9c92f2bf9632c7] 172.20.0.4:48966 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [48e538dffdbbabac] 172.20.0.4:48960 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale2/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [f61571d24b879fc9] 172.20.0.4:48958 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale1/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [1147395087500246] 172.20.0.4:49024 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [4961584350fb75b7] 172.20.0.4:49056 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [ec32d6ac3d7c2097] 172.20.0.4:49036 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C1%2C1 HTTP/1.1" 200 OK
tiled-1  | [70824b4d8896a132] 172.20.0.4:49010 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [d4cc3333903ac0c1] 172.20.0.4:49042 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [d9fab7daa4d3903d] 172.20.0.4:48994 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [6c4bbab3b9144c4c] 172.20.0.4:49060 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [f838506296b3105c] 172.20.0.4:49064 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C1%2C1 HTTP/1.1" 200 OK
tiled-1  | [1b09915d3cb1304f] 172.20.0.4:38892 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [04086241c16360fe] 172.20.0.4:38862 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [ece15d12d03ac46a] 172.20.0.4:38920 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C1%2C2 HTTP/1.1" 200 OK
tiled-1  | [97c668b548bd9f42] 172.20.0.4:38872 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [92ba77886c676d80] 172.20.0.4:38882 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C0%2C2 HTTP/1.1" 200 OK
tiled-1  | [23cffeb44bfdaf54] 172.20.0.4:38908 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C1%2C1 HTTP/1.1" 200 OK
tiled-1  | [f5bb34fd84b12bb2] 172.20.0.4:38926 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C2%2C0 HTTP/1.1" 200 OK
tiled-1  | [623b83f4ebf7ab2b] 172.20.0.4:38932 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C2%2C1 HTTP/1.1" 200 OK
tiled-1  | [2d43d562402b2cdf] 172.20.0.4:38946 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [54515c0c690b4173] 172.20.0.4:38934 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=0%2C2%2C2 HTTP/1.1" 200 OK
tiled-1  | [a1cb5d6a05a87605] 172.20.0.4:38952 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C0%2C2 HTTP/1.1" 200 OK
tiled-1  | [5e41d4bff7ad0782] 172.20.0.4:38944 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [4315a4be85fbfe01] 172.20.0.4:38956 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [2019c1083fb074f5] 172.20.0.4:38960 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C1%2C1 HTTP/1.1" 200 OK
tiled-1  | [5174817ca2d5ac22] 172.20.0.4:38970 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C2%2C0 HTTP/1.1" 200 OK
tiled-1  | [43102f5af55a4dce] 172.20.0.4:38964 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C1%2C2 HTTP/1.1" 200 OK
tiled-1  | [13c57dcd21f486f5] 172.20.0.4:38976 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C2%2C1 HTTP/1.1" 200 OK
tiled-1  | [4fad416b99de581f] 172.20.0.4:38978 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=1%2C2%2C2 HTTP/1.1" 200 OK
tiled-1  | [2726488c2f1beae7] 172.20.0.4:38980 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [0a3a1d3c1ff0a9ed] 172.20.0.4:39000 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C0%2C2 HTTP/1.1" 200 OK
tiled-1  | [96d9de9d52fb1e6f] 172.20.0.4:38986 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [ef93e1589c473cfb] 172.20.0.4:39006 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C2%2C2 HTTP/1.1" 200 OK
tiled-1  | [184bc1a06cf4f900] 172.20.0.4:39008 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C1%2C1 HTTP/1.1" 200 OK
tiled-1  | [4cdca7b9fcfbb804] 172.20.0.4:39002 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [4f16db4d2ca91123] 172.20.0.4:39016 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C1%2C2 HTTP/1.1" 200 OK
tiled-1  | [95ae71eabf772340] 172.20.0.4:39018 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C2%2C0 HTTP/1.1" 200 OK
tiled-1  | [a306e361b1d407b7] 172.20.0.4:39026 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image?block=2%2C2%2C1 HTTP/1.1" 200 OK

Then running on on my dev computer with the latest version of the branch, I see these logs from Tiled when I access the same dataset (note the fewer number of requests):

tiled-1  | [158f2cbaded942fd] 172.20.0.6:44650 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/.zattrs HTTP/1.1" 200 OK
tiled-1  | [f61c241872dc2a9a] 172.20.0.6:44666 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale0/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [5426f2138bd395af] 172.20.0.6:44686 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [d67a683e0e73d2dc] 172.20.0.6:44684 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale3/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [c8489d5108509d42] 172.20.0.6:44674 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale1/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [cfe33ca156047cca] 172.20.0.6:44678 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale2/image/.zarray HTTP/1.1" 200 OK
tiled-1  | [97e2ed48ce11f2a7] 172.20.0.6:44712 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [2fc604eeb0b522c2] 172.20.0.6:44714 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [2909f43ab26dc753] 172.20.0.6:44694 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [5b8b0f4d35545529] 172.20.0.6:44702 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C0%2C1 HTTP/1.1" 200 OK
tiled-1  | [b56f59ac829ef07f] 172.20.0.6:44698 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C0%2C0 HTTP/1.1" 200 OK
tiled-1  | [5343190e60ea529d] 172.20.0.6:44706 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=0%2C1%2C1 HTTP/1.1" 200 OK
tiled-1  | [1533afd240fbd3a8] 172.20.0.6:44726 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C1%2C0 HTTP/1.1" 200 OK
tiled-1  | [cbdcd18bbdd7f6c9] 172.20.0.6:40464 (unset) - "GET /zarr/v2/rec20240425_104614_nist-sand-30-100_27keV_z8mm_n2625/scale4/image?block=1%2C1%2C1 HTTP/1.1" 200 OK

davramov avatar Aug 06 '25 21:08 davramov

Just a heads up that the viewers you've mentioned are largely if not solely OME-Zarr viewers rather than just Zarr viewers. You can test their expectations under https://ome.github.io/ome-ngff-validator

For abstract/generic Zarr confirmation, you may need another tool or strategy.

joshmoore avatar Aug 06 '25 21:08 joshmoore

That is very helpful; thank you, @davramov! Thank you for the link, @joshmoore! That's what I suspected; we may need to mimic OME-Zarr attributes to make those viewers work with Tiled. Having a validation tool is great!

genematx avatar Aug 06 '25 22:08 genematx

Hi @genematx, I have been meaning to respond to this thread all day, but keep getting sucked into meetings!

Hi @davramov, I'm reviving this branch with the intention to release the changes in v0.1.0 next month. Thanks again for your help, and I'm sorry it took so long for us to get back here. I've put your changes (.zattrs endpoint) into the code (apart from ZarrAttrsAdapter -- I'm not sure we need it...) and made all tests to pass. I have tried to reproduce your workflow in itkVtkViewer, but I must be doing something wrong -- I am not familiar with this viewer. I can see it sending requests to the attributes endpoint, but then nothing happens and it just displays a spinning wheel. I am going to test it with other zarr clients later today or tomorrow, but would you mind trying this branch with the most recent commits and see if it works for you, please?

Thanks @genematx! I'll let you know when I get a chance to test this, hopefully in the next few days.

Yesterday, I cloned the latest version of this PR to use with the itk-vtk-viewer web app. Out of the box, I did not encounter any issues with installation; Tiled registered the Zarr files as expected, and I can browse for them in the Tiled browser web page.

However, when I select a dataset to view the dataset in itk-vtk-viewer, the volume is not rendered, and interestingly, I am not getting helpful console errors on why that's the case. I have a hunch why it worked before with my ZarrAttributesAdapter I implemented, but I haven't had a chance to debug further. I'm providing logs from Tiled below (both my branch and the latest version of this PR). I am uploading the Zarr for this sand dataset to Google Drive right now, and I will share it with you via email when it's ready.

Cheers, David

Running on the beamline PC with my branch, here are the tiled logs when I load a Zarr file in the viewer app:

I think the issue was with the zarr codec. Looks like ITK/VTK only accepts lz4, but notzstd. It might be worth exploring an option to specify the codec in the request, since we encode the data ourselves in Tiled and can use any codec.

Screenshot 2025-08-07 at 11 33 54 AM

Thanks again, @davramov for the data. They were really helpful!

genematx avatar Aug 07 '25 15:08 genematx

Nice! I suppose we could consult the Accept-Encoding header, and default to lz4 if not set. I'm not sure though whether any Zarr clients currently engage that header, or whether Zarr might have a preferred way to do this. Probably worth splitting this into a separate issue and PR.

danielballan avatar Aug 07 '25 15:08 danielballan

I had some issues when trying to use lz4 with zarr v3 (hence the change to zstd), but I haven't explored other options yet. A separate PR -- that's what I thought too!

genematx avatar Aug 07 '25 15:08 genematx

cc: @thewtex in case he has thoughts

joshmoore avatar Aug 07 '25 17:08 joshmoore

I am surprised itk-vtk-viewer did not work with lz4.

Regardless, amazing work, all! :clap: :sparkler:

thewtex avatar Aug 07 '25 17:08 thewtex

Yay! 🎉 I'll give it a try ASAP.

joshmoore avatar Aug 08 '25 19:08 joshmoore