From e7ebd35d04c6b071d3c2bbaa09d4d8410a64d7fd Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Fri, 22 Nov 2024 08:31:08 -0500 Subject: [PATCH] ENH: Add tensorstore write support --- .github/workflows/pixi-test.yml | 2 +- .github/workflows/test.yml | 11 +++- docs/installation.md | 4 ++ ngff_zarr/to_ngff_zarr.py | 92 ++++++++++++++++++++------- pixi.lock | 90 +++++++++++++++++++++++++- pyproject.toml | 3 +- test/test_to_ngff_zarr_tensorstore.py | 45 +++++++++++++ 7 files changed, 220 insertions(+), 27 deletions(-) create mode 100644 test/test_to_ngff_zarr_tensorstore.py diff --git a/.github/workflows/pixi-test.yml b/.github/workflows/pixi-test.yml index 0c5c84ee..ef5fb47f 100644 --- a/.github/workflows/pixi-test.yml +++ b/.github/workflows/pixi-test.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 5 matrix: - os: [ubuntu-22.04, windows-2022] + os: [ubuntu-22.04, macos-13] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d4d6a00e..ffbbbe22 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 5 matrix: - os: [ubuntu-22.04, windows-2022, macos-12, macos-14] + os: [ubuntu-22.04, windows-2022, macos-13] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: @@ -24,8 +24,15 @@ jobs: python -m pip install --upgrade pip python -m pip install -e ".[test,dask-image,itk,cli,validate]" + - name: Install tensorstore + if: + ${{ matrix.python-version != '3.13' && matrix.os != 'macos-12' && + matrix.os != 'windows-2022' }} + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[tensorstore]" + - name: Test with pytest - if: ${{ matrix.os != 'ubuntu-22.04' && matrix.os != 'macos-14' }} run: | pytest --junitxml=junit/test-results.xml diff --git a/docs/installation.md b/docs/installation.md index 341ee47e..6bafce90 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -30,3 +30,7 @@ Optional dependencies include: `dask-image` : Multiscale generation with `dask-image` methods. `itk` : Multiscale generation with `itk` methods. + +`tensorstore` : Support writing with `tensorstore`. + +`validate` : Support metadata validation when reading. diff --git a/ngff_zarr/to_ngff_zarr.py b/ngff_zarr/to_ngff_zarr.py index f23bf89d..5a6785c8 100644 --- a/ngff_zarr/to_ngff_zarr.py +++ b/ngff_zarr/to_ngff_zarr.py @@ -59,10 +59,32 @@ def _prep_for_to_zarr( ) +def _write_with_tensorstore(store_path: str, array, region, chunks) -> None: + """Write array using tensorstore backend""" + import tensorstore as ts + + spec = { + "driver": "zarr", + "kvstore": { + "driver": "file", + "path": store_path, + }, + "metadata": { + "chunks": chunks, + "shape": array.shape, + "dtype": array.dtype.str, + "dimension_separator": "/", + }, + } + dataset = ts.open(spec, create=True, dtype=array.dtype).result() + dataset[...] = array[region] + + def to_ngff_zarr( store: Union[MutableMapping, str, Path, BaseStore], multiscales: Multiscales, overwrite: bool = True, + use_tensorstore: bool = False, chunk_store: Optional[Union[MutableMapping, str, Path, BaseStore]] = None, progress: Optional[Union[NgffProgress, NgffProgressCallback]] = None, **kwargs, @@ -79,6 +101,9 @@ def to_ngff_zarr( :param overwrite: If True, delete any pre-existing data in `store` before creating groups. :type overwrite: bool, optional + :param use_tensorstore: If True, write array using tensorstore backend. + :type use_tensorstore: bool, optional + :param chunk_store: Separate storage for chunks. If not provided, `store` will be used for storage of both chunks and metadata. :type chunk_store: MutableMapping, str or Path, zarr.storage.BaseStore, optional @@ -89,6 +114,12 @@ def to_ngff_zarr( :param **kwargs: Passed to the zarr.creation.create() function, e.g., compression options. """ + if use_tensorstore: + if isinstance(store, (str, Path)): + store_path = str(store) + else: + raise ValueError("Tensorstore requires a path-like store") + metadata_dict = asdict(multiscales.metadata) metadata_dict = _pop_metadata_optionals(metadata_dict) metadata_dict["@type"] = "ngff:Image" @@ -262,33 +293,50 @@ def to_ngff_zarr( arr_region.chunks, meta=arr_region, ) - dask.array.to_zarr( - optimized, - zarr_array, - region=region, - component=path, - overwrite=False, - compute=True, - return_stored=False, - dimension_separator="/", - **kwargs, - ) + if use_tensorstore: + scale_path = f"{store_path}/{path}" + _write_with_tensorstore( + scale_path, + optimized, + region, + [c[0] for c in arr_region.chunks], + **kwargs, + ) + else: + dask.array.to_zarr( + optimized, + zarr_array, + region=region, + component=path, + overwrite=False, + compute=True, + return_stored=False, + dimension_separator="/", + **kwargs, + ) else: if isinstance(progress, NgffProgressCallback): progress.add_callback_task( f"[green]Writing scale {index+1} of {nscales}" ) - arr = _prep_for_to_zarr(store, arr) - dask.array.to_zarr( - arr, - store, - component=path, - overwrite=False, - compute=True, - return_stored=False, - dimension_separator="/", - **kwargs, - ) + if use_tensorstore: + scale_path = f"{store_path}/{path}" + region = tuple([slice(arr.shape[i]) for i in range(arr.ndim)]) + _write_with_tensorstore( + scale_path, arr, region, [c[0] for c in arr.chunks], **kwargs + ) + else: + arr = _prep_for_to_zarr(store, arr) + dask.array.to_zarr( + arr, + store, + component=path, + overwrite=False, + compute=True, + return_stored=False, + dimension_separator="/", + **kwargs, + ) # Minimize task graph depth if ( diff --git a/pixi.lock b/pixi.lock index cb201b90..ae7133e7 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1080,6 +1080,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4c/b4/d766586e24e7a073333c8eb8bd9275f3c6fe0569b509ae7b1699d4f00c74/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/75/d7/d5f42598dd342c3b3f6cef6b93214a38690acff91436d75350882a6e0d5e/numcodecs-0.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1104,6 +1105,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1187,6 +1189,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ed/c6/358d85e274e22d53def0c85f3cbe0933475fa3cf6922e9dca66eb25cb22f/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/00/65/12ab649988ec278e6a80a29cfcda9aea527df0045dd98da47e59ba114917/numcodecs-0.14.1.tar.gz - pypi: https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl @@ -1211,6 +1214,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl @@ -1287,6 +1291,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5f/21/e3e3af42b31e9d4543923a0ec8bb8b0f12bb9dc1a9f2f00f34d6421bd2d1/numcodecs-0.14.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl @@ -1311,6 +1316,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl @@ -1387,6 +1393,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a9/e3/2062537c65215befb3b662c6cc865836557a73fe0237fe754d3b35dec81b/numcodecs-0.14.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl @@ -1411,6 +1418,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl @@ -1490,6 +1498,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/87/30323ad2e52f56262019a4493fe5f5e71067c5561ce7e2f9c75de520f5e8/ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4b/e2/ac784ac4b6e5841e4bfb7d3e7e38497450df18eebff9465990a8ac9aecfc/numcodecs-0.14.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl @@ -1514,6 +1523,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl @@ -3729,6 +3739,74 @@ packages: url: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 requires_python: '>=3.7' +- kind: pypi + name: ml-dtypes + version: 0.5.0 + url: https://files.pythonhosted.org/packages/14/87/30323ad2e52f56262019a4493fe5f5e71067c5561ce7e2f9c75de520f5e8/ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl + sha256: cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07 + requires_dist: + - numpy>=1.21 + - numpy>=1.21.2 ; python_full_version >= '3.10' + - numpy>=1.23.3 ; python_full_version >= '3.11' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=2.1.0 ; python_full_version >= '3.13' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.9' +- kind: pypi + name: ml-dtypes + version: 0.5.0 + url: https://files.pythonhosted.org/packages/4c/b4/d766586e24e7a073333c8eb8bd9275f3c6fe0569b509ae7b1699d4f00c74/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: 54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab + requires_dist: + - numpy>=1.21 + - numpy>=1.21.2 ; python_full_version >= '3.10' + - numpy>=1.23.3 ; python_full_version >= '3.11' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=2.1.0 ; python_full_version >= '3.13' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.9' +- kind: pypi + name: ml-dtypes + version: 0.5.0 + url: https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl + sha256: d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f + requires_dist: + - numpy>=1.21 + - numpy>=1.21.2 ; python_full_version >= '3.10' + - numpy>=1.23.3 ; python_full_version >= '3.11' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=2.1.0 ; python_full_version >= '3.13' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.9' +- kind: pypi + name: ml-dtypes + version: 0.5.0 + url: https://files.pythonhosted.org/packages/ed/c6/358d85e274e22d53def0c85f3cbe0933475fa3cf6922e9dca66eb25cb22f/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl + sha256: e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599 + requires_dist: + - numpy>=1.21 + - numpy>=1.21.2 ; python_full_version >= '3.10' + - numpy>=1.23.3 ; python_full_version >= '3.11' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=2.1.0 ; python_full_version >= '3.13' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.9' - kind: pypi name: msgpack version: 1.1.0 @@ -3861,7 +3939,7 @@ packages: name: ngff-zarr version: 0.9.1 path: . - sha256: 81c96cc182a91de87121bcca24f808a7787a1ab2ffcaa78073fecbda67a24b7a + sha256: 496ef9944dd92d1a0e0aa6d7cd6df7385e48edc1cb0b4789599f25e2901db941 requires_dist: - dask[array] - importlib-resources @@ -3892,6 +3970,7 @@ packages: - sphinx>=7.4.7,<8 ; extra == 'docs' - sphinxext-opengraph>=0.9.1,<0.10 ; extra == 'docs' - itk-filtering>=5.3.0 ; extra == 'itk' + - tensorstore ; extra == 'tensorstore' - deepdiff ; extra == 'test' - pooch ; extra == 'test' - pytest>=6 ; extra == 'test' @@ -6108,6 +6187,15 @@ packages: url: https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl sha256: 80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129 requires_python: '>=3.8' +- kind: pypi + name: tensorstore + version: 0.1.68 + url: https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz + sha256: 6e13d3e3c8fb6ed67712835a343821536b38d6bdb517db554d41cebfe5947ab7 + requires_dist: + - numpy>=1.22.0 + - ml-dtypes>=0.3.1 + requires_python: '>=3.9' - kind: pypi name: tifffile version: 2024.9.20 diff --git a/pyproject.toml b/pyproject.toml index 732ddcb9..1bbace87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ cli = [ "tifffile>=2024.7.24", "imagecodecs", ] +tensorstore = ["tensorstore"] test = [ "pytest >=6", "pooch", @@ -170,7 +171,7 @@ ngff-zarr = { path = ".", editable = true } [tool.pixi.environments] default = { solve-group = "default" } -test = { features = ["test", "dask-image", "itk", "cli", "validate"], solve-group = "default" } +test = { features = ["test", "dask-image", "itk", "cli", "validate", "tensorstore"], solve-group = "default" } lint = { features = ["lint"], no-default-feature = true, solve-group = "default" } docs = { features = ["docs"], no-default-feature = true, solve-group = "default" } data = { features = ["data"], no-default-feature = true, solve-group = "default" } diff --git a/test/test_to_ngff_zarr_tensorstore.py b/test/test_to_ngff_zarr_tensorstore.py new file mode 100644 index 00000000..b41e6ed0 --- /dev/null +++ b/test/test_to_ngff_zarr_tensorstore.py @@ -0,0 +1,45 @@ +import tempfile + +import pytest + +from ngff_zarr import Methods, to_multiscales, to_ngff_zarr, from_ngff_zarr + +from ._data import verify_against_baseline + + +def test_gaussian_isotropic_scale_factors(input_images): + pytest.importorskip("tensorstore") + + dataset_name = "cthead1" + image = input_images[dataset_name] + baseline_name = "2_4/ITKWASM_GAUSSIAN.zarr" + multiscales = to_multiscales(image, [2, 4], method=Methods.ITKWASM_GAUSSIAN) + with tempfile.TemporaryDirectory() as tmpdir: + to_ngff_zarr(tmpdir, multiscales, use_tensorstore=True) + multiscales = from_ngff_zarr(tmpdir) + verify_against_baseline(dataset_name, baseline_name, multiscales) + + baseline_name = "auto/ITKWASM_GAUSSIAN.zarr" + multiscales = to_multiscales(image, method=Methods.ITKWASM_GAUSSIAN) + with tempfile.TemporaryDirectory() as tmpdir: + to_ngff_zarr(tmpdir, multiscales, use_tensorstore=True) + multiscales = from_ngff_zarr(tmpdir) + verify_against_baseline(dataset_name, baseline_name, multiscales) + + dataset_name = "cthead1" + image = input_images[dataset_name] + baseline_name = "2_3/ITKWASM_GAUSSIAN.zarr" + multiscales = to_multiscales(image, [2, 3], method=Methods.ITKWASM_GAUSSIAN) + with tempfile.TemporaryDirectory() as tmpdir: + to_ngff_zarr(tmpdir, multiscales, use_tensorstore=True) + multiscales = from_ngff_zarr(tmpdir) + verify_against_baseline(dataset_name, baseline_name, multiscales) + + dataset_name = "MR-head" + image = input_images[dataset_name] + baseline_name = "2_3_4/ITKWASM_GAUSSIAN.zarr" + multiscales = to_multiscales(image, [2, 3, 4], method=Methods.ITKWASM_GAUSSIAN) + with tempfile.TemporaryDirectory() as tmpdir: + to_ngff_zarr(tmpdir, multiscales, use_tensorstore=True) + multiscales = from_ngff_zarr(tmpdir) + verify_against_baseline(dataset_name, baseline_name, multiscales)