Support Reading and Writing of Pyramidal Formats
Use Case
Please provide a use case to help us understand your request in context
Many OME-Tiffs and CZI files are pyramidal where LODs have different array shapes. It would be great to support the reading of these one shape (scene) at a time and the writing of these through iterable of arrays.
Solution
Please describe your ideal solution
tifffile very recently added support for both reading and writing of pyramidal tiffs, pull in these changes and wrap in our API and Reader and future OmeTiffWriter.
Note, these will likely not be supported until after current institute high priority projects are done and after aicsimageio==4.0.0 release.
Waiting for test file data, pyramids of CZI and TIFF
Adopt the levels API for reading:
img = AICSImage("my-pyramid.czi")
img.levels # returns tuple of levels
img.current_level # returns current level, default / starts at level 0 (full resolution)
img.set_level # update current level
Undecided how to adopt for Writing.
Handle known bug of multi-resolution (multi-level) OME TIFFs selecting the wrong metadata due to "Images" being created correctly for the total number of scenes in the file, but they don't use the matching IDs for the correct scenes / resolutions.
Tracking ome-types metadata bug over on ome-types: https://github.com/tlambert03/ome-types/issues/71
Going to work with Zarr devs to see if the chunked read issue on multi-resolution / pyramid is a:
- dask
from_zarrimplementation issue - tifffile
ZarrTiffStorespec implementation issue - or a Zarr "the spec is still undetermined" issue
Reasoning here is that dask from_zarr is assuming there will be a key of just ./zarray on the MutableMapping object and tifffile aszarr supports multi-resolution zarr by creating keys with names: 0/.zarray, 1/.zarray, etc. for the different resolution levels. This causes chunk read time issues when they key is requested and a ValueError occurs.
Minimum reproduction:
In [1]: import dask.array as da
In [2]: from tifffile import TiffFile
In [3]: t = TiffFile("aicsimageio/tests/resources/variable_scene_shape_first_scene_pyramid.ome.tiff")
In [4]: da.from_zarr(t.series[0].levels[0].aszarr())
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-4-d757ba4b5cab> in <module>
----> 1 t_as_d = da.from_zarr(t.series[0].levels[0].aszarr())
~/miniconda3/envs/aicsimageio/lib/python3.9/site-packages/dask/array/core.py in from_zarr(url, component, storage_options, chunks, name, inline_array, **kwargs)
3159 else:
3160 mapper = url
-> 3161 z = zarr.Array(mapper, read_only=True, path=component, **kwargs)
3162 chunks = chunks if chunks is not None else z.chunks
3163 if name is None:
~/miniconda3/envs/aicsimageio/lib/python3.9/site-packages/zarr/core.py in __init__(self, store, path, read_only, chunk_store, synchronizer, cache_metadata, cache_attrs)
121
122 # initialize metadata
--> 123 self._load_metadata()
124
125 # initialize attributes
~/miniconda3/envs/aicsimageio/lib/python3.9/site-packages/zarr/core.py in _load_metadata(self)
138 """(Re)load metadata from store."""
139 if self._synchronizer is None:
--> 140 self._load_metadata_nosync()
141 else:
142 mkey = self._key_prefix + array_meta_key
~/miniconda3/envs/aicsimageio/lib/python3.9/site-packages/zarr/core.py in _load_metadata_nosync(self)
147 try:
148 mkey = self._key_prefix + array_meta_key
--> 149 meta_bytes = self._store[mkey]
150 except KeyError:
151 raise ArrayNotFoundError(self._path)
~/active/cell/tifffile/tifffile/tifffile.py in __getitem__(self, key)
7945 if key in self._store:
7946 return self._store[key]
-> 7947 return self._getitem(key)
7948
7949 def _getitem(self, key):
~/active/cell/tifffile/tifffile/tifffile.py in _getitem(self, key)
8104 def _getitem(self, key):
8105 """Return chunk from file."""
-> 8106 keyframe, page, chunkindex, offset, bytecount = self._parse_key(key)
8107
8108 if self._chunkmode:
~/active/cell/tifffile/tifffile/tifffile.py in _parse_key(self, key)
8144 # multiscales
8145 print(key)
-> 8146 level, key = key.split('/')
8147 series = self._data[int(level)]
8148 else:
ValueError: not enough values to unpack (expected 2, got 1)
When I manually printed the key that dask was trying to get from the MutableMapping it was just .zarray.
In [4]: da.from_zarr(t.series[0].levels[0].aszarr())
<snip>
ValueError: not enough values to unpack (expected 2, got 1)
To access the first level of the first multi-resolution series as zarr array, not group, use t.series[0].aszarr(level=0). This is currently necessary because t.series[0].levels[0] is the same as t.series[0].
Thanks @cgohlke. Patched in the current open PR to default reading level 0 of pyramid files for now.
@JacksonMaxfield Reading of pyramid levels is not available in aicsimageio 4.0.3, is it?
The CZI files I provided for #274 can be used as pyramid examples, if that's useful
https://drive.google.com/drive/folders/1XukPEpCfIvibTT_6992sCgXeszxg-hUK?usp=sharing
Is there a way to open a CZI file with aicsimageio and save it to a TIFF or some other format with the pyramid?
Is there a way to open a CZI file with aicsimageio and save it to a TIFF or some other format with the pyramid?
Replying to myself, I think maybe with pyvips.Image.tiffsave(), where it's possible to select an output with a pyramid. But here, it seems that what we'd do is load the largest level with aicsimageio, and then re-create a pyramid with pyvips, rather than copying over the already existing pyramid.
@rcasero 4.0.3 does not support multi-scale / pyramid level selection. This issue is slated for 4.1.
You can use bioformats or pyvips but I am not an expert in either of them to give you the best guidance there.
@JacksonMaxfield Thanks. I made it work with pyvips.Image.tiffsave
im_vips.tiffsave(tif_filename, tile=True, pyramid=True, resunit='cm',
xres=float(1e-3/xres), yres=float(1e-3/yres), bigtiff=True)
https://github.com/rcasero/cytometer/blob/a3f9abf792f24dba771d9940fb49c9bde25efc26/cytometer/data.py#L1618
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.