Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ jobs:
build:
name: ${{ matrix.sys.os }} ${{ matrix.python-version }}
runs-on: ${{ matrix.sys.os }}
timeout-minutes: 25
timeout-minutes: 30
strategy:
matrix:
sys:
- { os: windows-latest, shell: 'cmd /C call {0}' }
- { os: ubuntu-24.04, shell: "bash -l {0}" }
python-version: [3.9, 3.12]
python-version: [3.12]
fail-fast: false
defaults:
run:
Expand All @@ -36,12 +36,13 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Install Conda environment with Micromamba
uses: mamba-org/setup-micromamba@v2.0.2
uses: mamba-org/setup-micromamba@v1
with:
micromamba-version: latest
environment-name: test
cache-downloads: true
create-args: >-
python=${{ matrix.python-version }}
pre-commit
pytest-cov
pytest-mock
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import setuptools

zarr = ["ome-zarr>=0.9.0,<=0.10.3"]
zarr = ["ome-zarr>=0.9.0"]
openslide = ["openslide-python"]
tiff = ["tifffile==v2025.5.10", "imagecodecs"]
tiff = ["tifffile", "imagecodecs"]
cloud = ["tiledb-cloud"]

full = sorted({*zarr, *openslide, *tiff})
Expand Down
Binary file added tests/data/heLa_cells.zarr/0/c/0/0/0/0/0
Binary file not shown.
Binary file added tests/data/heLa_cells.zarr/0/c/0/1/0/0/0
Binary file not shown.
1 change: 1 addition & 0 deletions tests/data/heLa_cells.zarr/0/zarr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"chunk_grid": {"configuration": {"chunk_shape": [1, 1, 1, 2048, 2048]}, "name": "regular"}, "chunk_key_encoding": {"name": "default"}, "codecs": [{"configuration": {"chunk_shape": [1, 1, 1, 256, 256], "codecs": [{"name": "bytes"}, {"configuration": {"blocksize": 0, "clevel": 5, "cname": "zstd", "shuffle": "bitshuffle", "typesize": 1}, "name": "blosc"}], "index_codecs": [{"configuration": {"endian": "little"}, "name": "bytes"}, {"name": "crc32c"}]}, "name": "sharding_indexed"}], "data_type": "uint8", "dimension_names": ["t", "c", "z", "y", "x"], "fill_value": 0, "node_type": "array", "shape": [1, 2, 1, 512, 512], "zarr_format": 3, "attributes": {"_ome2024_ngff_challenge_stats": {"input": "", "output": "", "start": 1726085537.83182, "stop": 1726085537.839262, "read": 472566, "written": 349878, "elapsed": 0.007441997528076172, "threads": 16, "cpu_count": 8}}}
Binary file added tests/data/heLa_cells.zarr/1/c/0/0/0/0/0
Binary file not shown.
Binary file added tests/data/heLa_cells.zarr/1/c/0/1/0/0/0
Binary file not shown.
1 change: 1 addition & 0 deletions tests/data/heLa_cells.zarr/1/zarr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"chunk_grid": {"configuration": {"chunk_shape": [1, 1, 1, 2048, 2048]}, "name": "regular"}, "chunk_key_encoding": {"name": "default"}, "codecs": [{"configuration": {"chunk_shape": [1, 1, 1, 256, 256], "codecs": [{"name": "bytes"}, {"configuration": {"blocksize": 0, "clevel": 5, "cname": "zstd", "shuffle": "bitshuffle", "typesize": 1}, "name": "blosc"}], "index_codecs": [{"configuration": {"endian": "little"}, "name": "bytes"}, {"name": "crc32c"}]}, "name": "sharding_indexed"}], "data_type": "uint8", "dimension_names": ["t", "c", "z", "y", "x"], "fill_value": 0, "node_type": "array", "shape": [1, 2, 1, 256, 256], "zarr_format": 3, "attributes": {"_ome2024_ngff_challenge_stats": {"input": "", "output": "", "start": 1726085537.8454242, "stop": 1726085537.847529, "read": 117914, "written": 91878, "elapsed": 0.0021047592163085938, "threads": 16, "cpu_count": 8}}}
66 changes: 66 additions & 0 deletions tests/data/heLa_cells.zarr/ro-crate-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"@context": [
"https://w3id.org/ro/crate/1.1/context",
{
"organism_classification": "https://schema.org/taxonomicRange",
"BioChemEntity": "https://schema.org/BioChemEntity",
"channel": "https://www.openmicroscopy.org/Schemas/Documentation/Generated/OME-2016-06/ome_xsd.html#Channel",
"obo": "http://purl.obolibrary.org/obo/",
"FBcv": "http://ontobee.org/ontology/FBcv/",
"acquisiton_method": {
"@reverse": "https://schema.org/result",
"@type": "@id"
},
"biological_entity": "https://schema.org/about",
"biosample": "http://purl.obolibrary.org/obo/OBI_0002648",
"preparation_method": "https://www.wikidata.org/wiki/Property:P1537",
"specimen": "http://purl.obolibrary.org/obo/HSO_0000308"
}
],
"@graph": [
{
"@id": "./",
"@type": "Dataset",
"name": "HeLa cells",
"description": "HeLa cells showing click chemistry and immunofluorescence",
"license": "https://creativecommons.org/licenses/by/4.0/",
"resultOf": {
"@id": "#787d9fc9-658d-4c77-b225-79c449cfcd6f"
}
},
{
"@id": "ro-crate-metadata.json",
"@type": "CreativeWork",
"conformsTo": {
"@id": "https://w3id.org/ro/crate/1.1"
},
"about": {
"@id": "./"
}
},
{
"@id": "#a3bb48f9-c034-4b22-a1e6-f0dd1764f3ec",
"@type": "biosample",
"organism_classification": {
"@id": "NCBI:txid9601"
}
},
{
"@id": "#dd464d3d-96dd-41a1-ba32-92b0ab7c49c3",
"@type": "specimen",
"biosample": {
"@id": "#a3bb48f9-c034-4b22-a1e6-f0dd1764f3ec"
}
},
{
"@id": "#787d9fc9-658d-4c77-b225-79c449cfcd6f",
"@type": "image_acquisition",
"fbbi_id": {
"@id": "obo:FBbi_00000251"
},
"specimen": {
"@id": "#dd464d3d-96dd-41a1-ba32-92b0ab7c49c3"
}
}
]
}
1 change: 1 addition & 0 deletions tests/data/heLa_cells.zarr/zarr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"attributes": {"ome": {"version": "0.5", "multiscales": [{"metadata": {"method": "loci.common.image.SimpleImageScaler", "version": "Bio-Formats 6.10.1"}, "axes": [{"name": "t", "type": "time"}, {"name": "c", "type": "channel"}, {"name": "z", "type": "space"}, {"unit": "micrometer", "name": "y", "type": "space"}, {"unit": "micrometer", "name": "x", "type": "space"}], "name": "", "datasets": [{"path": "0", "coordinateTransformations": [{"scale": [1.0, 1.0, 1.0, 0.22007964065255714, 0.22007964065255714], "type": "scale"}]}, {"path": "1", "coordinateTransformations": [{"scale": [1.0, 1.0, 1.0, 0.4401592813051143, 0.4401592813051143], "type": "scale"}]}]}], "omero": {"channels": [{"color": "FF6600", "coefficient": 1, "active": true, "label": "AF555-T1", "window": {"min": 0.0, "max": 255.0, "start": 0.0, "end": 255.0}, "family": "linear", "inverted": false}, {"color": "00FF00", "coefficient": 1, "active": true, "label": "AF488-T2", "window": {"min": 0.0, "max": 255.0, "start": 0.0, "end": 255.0}, "family": "linear", "inverted": false}], "rdefs": {"defaultT": 0, "model": "color", "defaultZ": 0}}, "_creator": {"name": "ome2024-ngff-challenge", "version": "1.0.2", "notes": null}}}, "zarr_format": 3, "node_type": "group"}
107 changes: 96 additions & 11 deletions tests/integration/converters/test_ome_zarr.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import sys

import numpy as np
import PIL.Image
import pytest
import zarr
from ome_zarr.format import FormatV04

import tiledb
from tests import assert_image_similarity, get_path, get_schema
Expand All @@ -13,6 +15,15 @@
from tiledb.bioimg.openslide import TileDBOpenSlide
from tiledb.filter import WebpFilter

try:
from ome_zarr.format import FormatV05

HAS_FORMAT_V05 = True
except ImportError:
FormatV05 = None


REQUIRED_PYTHON_v3 = (3, 12)
schemas = (get_schema(2220, 2967), get_schema(387, 463), get_schema(1280, 431))


Expand All @@ -24,11 +35,11 @@ def test_ome_zarr_converter_source_reader_exception(
tiff_path = get_path("CMU-1-Small-Region.ome.tiff")
output_reader = tmp_path / "to_tiledb_reader"

with pytest.raises(FileExistsError) as excinfo:
with pytest.raises(AssertionError) as excinfo:
OMEZarrConverter.to_tiledb(
tiff_path, str(output_reader), preserve_axes=preserve_axes
)
assert "FileExistsError" in str(excinfo)
assert "AssertionError" in str(excinfo)


@pytest.mark.parametrize("series_idx", [0, 1, 2])
Expand Down Expand Up @@ -103,7 +114,7 @@ def test_ome_zarr_converter(tmp_path, series_idx, preserve_axes):
tiledb.WebpFilter(WebpFilter.WebpInputFormat.WEBP_NONE, lossless=True),
],
)
def test_ome_zarr_converter_rountrip(
def test_ome_zarr_converter_rountrip_v2(
tmp_path, series_idx, preserve_axes, chunked, max_workers, compressor
):
input_path = get_path("CMU-1-Small-Region.ome.zarr") / str(series_idx)
Expand All @@ -116,6 +127,7 @@ def test_ome_zarr_converter_rountrip(
chunked=chunked,
max_workers=max_workers,
compressor=compressor,
reader_kwargs={"fmt": FormatV04},
)
# Store it back to NGFF Zarr
OMEZarrConverter.from_tiledb(str(tiledb_path), str(output_path))
Expand All @@ -140,16 +152,89 @@ def test_ome_zarr_converter_rountrip(

# Compare the level arrays
for i in range(len(input_group)):
# Compare the .zarray files
with open(input_path / str(i) / ".zarray") as f:
input_zarray = json.load(f)
with open(output_path / str(i) / ".zarray") as f:
output_zarray = json.load(f)
assert input_zarray == output_zarray

# TODO: Enable it once the new metadata are being assigned correctly by ome-zarr-py
# # Compare the .zarray files
# with open(input_path / str(i) / ".zarray") as f:
# input_zarray = json.load(f)
# with open(output_path / str(i) / ".zarray") as f:
# output_zarray = json.load(f)
# assert input_zarray == output_zarray

# Compare the actual data
input_array = zarr.open_array(input_path / str(i))[:]
output_array = zarr.open_array(output_path / str(i))[:]
if isinstance(compressor, tiledb.WebpFilter) and not compressor.lossless:
assert_image_similarity(
input_array.squeeze(),
output_array.squeeze(),
channel_axis=0,
min_threshold=0.87,
)
else:
np.testing.assert_array_equal(input_array, output_array)


# Condition to check if the current Python version is less than the required version
# The test is skipped if the condition is True
@pytest.mark.skipif(
sys.version_info < REQUIRED_PYTHON_v3,
reason=f"This test requires Python version {REQUIRED_PYTHON_v3[0]}.{REQUIRED_PYTHON_v3[1]} or higher.",
)
@pytest.mark.parametrize("series_idx", [0, 1, 2])
@pytest.mark.parametrize("preserve_axes", [False, True])
@pytest.mark.parametrize("chunked,max_workers", [(False, 0), (True, 0), (True, 4)])
@pytest.mark.parametrize(
"compressor",
[
tiledb.ZstdFilter(level=0)
# TODO: Enable the compression for Zarr3
# tiledb.WebpFilter(WebpFilter.WebpInputFormat.WEBP_RGB, lossless=False),
# tiledb.WebpFilter(WebpFilter.WebpInputFormat.WEBP_RGB, lossless=True),
# tiledb.WebpFilter(WebpFilter.WebpInputFormat.WEBP_NONE, lossless=True),
],
)
def test_ome_zarr_converter_rountrip_v3(
tmp_path, series_idx, preserve_axes, chunked, max_workers, compressor
):
input_path = get_path("heLa_cells.zarr")
tiledb_path = tmp_path / "to_tiledb"
output_path = tmp_path / "from_tiledb"
OMEZarrConverter.to_tiledb(
input_path,
str(tiledb_path),
preserve_axes=preserve_axes,
chunked=chunked,
max_workers=max_workers,
compressor=compressor,
reader_kwargs={"fmt": FormatV05},
)
# Store it back to NGFF Zarr
OMEZarrConverter.from_tiledb(str(tiledb_path), str(output_path))

# Same number of levels
input_group = zarr.open_group(input_path, mode="r")
tiledb_group = tiledb.Group(str(tiledb_path), mode="r")
output_group = zarr.open_group(output_path, mode="r")
assert len(input_group) == len(
tiledb_group
) # TODO: Labels are not yet ingested in TileDB
assert len(input_group) == len(output_group)

# Compare the .zattrs files

# Compare the level arrays
for i in range(len(input_group)): # TODO: -1 for labels
# TODO: Compare the .zarray files
# with open(input_path / str(i) / "zarr.json") as f:
# input_zarray = json.load(f)
# with open(output_path / str(i) / "zarr.json") as f:
# output_zarray = json.load(f)
# assert input_zarray == output_zarray # Ome-Zarr limitation in storing all the metadata

# Compare the actual data
input_array = zarr.open(input_path / str(i))[:]
output_array = zarr.open(output_path / str(i))[:]
input_array = zarr.open_array(input_path / str(i))[:]
output_array = zarr.open_array(output_path / str(i))[:]
if isinstance(compressor, tiledb.WebpFilter) and not compressor.lossless:
assert_image_similarity(
input_array.squeeze(),
Expand Down
4 changes: 3 additions & 1 deletion tiledb/bioimg/converters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ def from_tiledb(
output_config = config

slide = TileDBOpenSlide(input_path, attr=attr, config=config)
writer = cls._ImageWriterType(destination_uri, logger, **(writer_kwargs or {}))
writer = cls._ImageWriterType(
destination_uri, logger, **(writer_kwargs or {}), metadata=slide.properties
)

with slide, writer:
writer.write_group_metadata(slide.properties)
Expand Down
4 changes: 2 additions & 2 deletions tiledb/bioimg/converters/ome_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ def level_image(
raise ImportError("zarr required for reading a Tiff tile region")
if not hasattr(self, "_zarr_group"):
store = self._series.aszarr(multiscales=True)
self._zarr_group = zarr.open(store, mode="r")
return np.asarray(self._zarr_group[level][tile])
self._zarr_group = zarr.open_group(store, mode="r")
return np.asarray(self._zarr_group[str(level)][tile])

def level_metadata(self, level: int) -> Dict[str, Any]:
if level == 0:
Expand Down
Loading
Loading