diff --git a/ngff_zarr/from_ngff_zarr.py b/ngff_zarr/from_ngff_zarr.py index 8ba7b72a..58de4a9e 100644 --- a/ngff_zarr/from_ngff_zarr.py +++ b/ngff_zarr/from_ngff_zarr.py @@ -73,8 +73,15 @@ def from_ngff_zarr( metadata.pop("@type", None) axes = [Axis(**axis) for axis in metadata["axes"]] + coordinateTransformations = None + if "coordinateTransformations" in metadata: + coordinateTransformations = metadata["coordinateTransformations"] metadata = Metadata( - axes=axes, datasets=datasets, name=name, version=metadata["version"] + axes=axes, + datasets=datasets, + name=name, + version=metadata["version"], + coordinateTransformations=coordinateTransformations, ) return Multiscales(images, metadata) diff --git a/ngff_zarr/to_multiscales.py b/ngff_zarr/to_multiscales.py index 8e5e970c..0113a3e3 100644 --- a/ngff_zarr/to_multiscales.py +++ b/ngff_zarr/to_multiscales.py @@ -363,6 +363,11 @@ def to_multiscales( path=path, coordinateTransformations=coordinateTransformations ) datasets.append(dataset) - metadata = Metadata(axes=axes, datasets=datasets, name=ngff_image.name) + metadata = Metadata( + axes=axes, + datasets=datasets, + name=ngff_image.name, + coordinateTransformations=None, + ) return Multiscales(images, metadata, scale_factors, method, out_chunks) diff --git a/ngff_zarr/to_ngff_zarr.py b/ngff_zarr/to_ngff_zarr.py index b2606fe7..d6a5f932 100644 --- a/ngff_zarr/to_ngff_zarr.py +++ b/ngff_zarr/to_ngff_zarr.py @@ -16,6 +16,17 @@ from .to_multiscales import to_multiscales +def _pop_metadata_optionals(metadata_dict): + for ax in metadata_dict["axes"]: + if ax["unit"] is None: + ax.pop("unit") + + if metadata_dict["coordinateTransformations"] is None: + metadata_dict.pop("coordinateTransformations") + + return metadata_dict + + def to_ngff_zarr( store: Union[MutableMapping, str, Path, BaseStore], multiscales: Multiscales, @@ -55,6 +66,7 @@ def to_ngff_zarr( """ metadata_dict = asdict(multiscales.metadata) + metadata_dict = _pop_metadata_optionals(metadata_dict) metadata_dict["@type"] = "ngff:Image" root = zarr.group(store, overwrite=overwrite, chunk_store=chunk_store) root.attrs["multiscales"] = [metadata_dict] diff --git a/ngff_zarr/zarr_metadata.py b/ngff_zarr/zarr_metadata.py index bb199007..273b4985 100644 --- a/ngff_zarr/zarr_metadata.py +++ b/ngff_zarr/zarr_metadata.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import List, Optional, Union from typing_extensions import Literal @@ -139,6 +139,11 @@ class Axis: unit: Optional[Units] = None +@dataclass +class Identity: + type: str = "identity" + + @dataclass class Scale: scale: List[float] @@ -164,6 +169,6 @@ class Dataset: class Metadata: axes: List[Axis] datasets: List[Dataset] + coordinateTransformations: Optional[List[Transform]] name: str = "image" version: str = "0.4" - coordinateTransformations: List[Transform] = field(default_factory=list) diff --git a/pyproject.toml b/pyproject.toml index f403995c..59cae9c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,7 @@ test = [ "itk-io>=5.3.0", "itk-filtering>=5.3.0", "tifffile", + "jsonschema", ] diff --git a/test/_data.py b/test/_data.py index f9ea45bf..c8d975ba 100644 --- a/test/_data.py +++ b/test/_data.py @@ -7,8 +7,8 @@ from ngff_zarr import itk_image_to_ngff_image, to_ngff_zarr from zarr.storage import DirectoryStore, MemoryStore -test_data_ipfs_cid = "bafybeidycdocaf7muhwt73m6vxfeaxfidbq7prvnhgn4tzrpxmpui5rila" -test_data_sha256 = "675a2cb532cb6fbabfd2b1e19f26f05b059ac51f0b611eed1164d8dd1a22e4cb" +test_data_ipfs_cid = "bafybeifkrfjreee5e7rfoi2ka3t2n23jlqwsrqwg4gmbyqcrc47iet27km" +test_data_sha256 = "861dd3acb8391a3ac2b10b3487c33ee74da1415ef30b9f4c37c7f8f49e802c32" test_dir = Path(__file__).resolve().parent extract_dir = "data" @@ -67,14 +67,14 @@ def store_equals(baseline_store, test_store): test_keys = set(test_store.keys()) if baseline_keys != test_keys: sys.stderr.write("test keys != baseline keys\n") - sys.stderr.write("baseline - test:", baseline_keys.difference(test_keys), "\n") - sys.stderr.write("test - baseline:", test_keys.difference(baseline_keys), "\n") + sys.stderr.write(f"baseline - test: {baseline_keys.difference(test_keys)}, \n") + sys.stderr.write(f"test - baseline: {test_keys.difference(baseline_keys)}, \n") return False for k in baseline_keys: if baseline_store[k] != test_store[k]: sys.stderr.write(f"test value != baseline value for key {k}\n") - sys.stderr.write("baseline:", baseline_store[k], "\n") - sys.stderr.write("test:", test_store[k], "\n") + sys.stderr.write(f"baseline: {baseline_store[k]}, \n") + sys.stderr.write(f"test: {test_store[k]}, \n") return False return True diff --git a/test/test_from_ngff_zarr.py b/test/test_from_ngff_zarr.py index cdc9f734..5cf62d7c 100644 --- a/test/test_from_ngff_zarr.py +++ b/test/test_from_ngff_zarr.py @@ -1,6 +1,5 @@ from dask_image import imread from ngff_zarr import ( - Methods, from_ngff_zarr, to_multiscales, to_ngff_image, @@ -11,15 +10,6 @@ from ._data import test_data_dir, verify_against_baseline -def test_gaussian_isotropic_scale_factors(input_images): - dataset_name = "cthead1" - image = input_images[dataset_name] - baseline_name = "2_4/DASK_IMAGE_GAUSSIAN.zarr" - multiscales = to_multiscales(image, [2, 4], method=Methods.DASK_IMAGE_GAUSSIAN) - # store_new_multiscales(dataset_name, f'{baseline_name}', multiscales) - verify_against_baseline(dataset_name, baseline_name, multiscales) - - def test_from_ngff_zarr(input_images): dataset_name = "lung_series" data = imread.imread(input_images[dataset_name]) diff --git a/test/test_large_serialization.py b/test/test_large_serialization.py index 79b4acbe..de0c4651 100644 --- a/test/test_large_serialization.py +++ b/test/test_large_serialization.py @@ -4,6 +4,7 @@ def test_large_image_serialization(input_images): + default_mem_target = config.memory_target config.memory_target = int(1e6) dataset_name = "lung_series" @@ -21,3 +22,5 @@ def test_large_image_serialization(input_images): test_store = MemoryStore(dimension_separator="/") to_ngff_zarr(test_store, multiscales) # verify_against_baseline(dataset_name, baseline_name, multiscales) + + config.memory_target = default_mem_target diff --git a/test/test_ngff_validation.py b/test/test_ngff_validation.py new file mode 100644 index 00000000..1ebd02d1 --- /dev/null +++ b/test/test_ngff_validation.py @@ -0,0 +1,76 @@ +import json +from typing import Dict + +import numpy as np +import urllib3 +import zarr +from jsonschema import Draft202012Validator +from ngff_zarr import Multiscales, to_multiscales, to_ngff_zarr +from referencing import Registry, Resource + +HTTP = urllib3.PoolManager() + +NGFF_URI = "https://ngff.openmicroscopy.org" + + +def load_schema(version: str = "0.4", strict: bool = False) -> Dict: + strict_str = "" + if strict: + strict_str = "strict_" + response = HTTP.request( + "GET", f"{NGFF_URI}/{version}/schemas/{strict_str}image.schema" + ) + return json.loads(response.data.decode()) + + +def check_valid_ngff(multiscale: Multiscales): + store = zarr.storage.MemoryStore(dimension_separator="/") + store = zarr.storage.DirectoryStore("/tmp/test.zarr", dimension_separator="/") + to_ngff_zarr(store, multiscale) + zarr.convenience.consolidate_metadata(store) + metadata = json.loads(store.get(".zmetadata"))["metadata"] + ngff = metadata[".zattrs"] + + image_schema = load_schema(version="0.4", strict=False) + # strict_image_schema = load_schema(version="0.4", strict=True) + registry = Registry().with_resource( + NGFF_URI, resource=Resource.from_contents(image_schema) + ) + validator = Draft202012Validator(image_schema, registry=registry) + # registry_strict = Registry().with_resource(NGFF_URI, resource=Resource.from_contents(strict_image_schema)) + # strict_validator = Draft202012Validator(strict_schema, registry=registry_strict) + + validator.validate(ngff) + # Need to add NGFF metadata property + # strict_validator.validate(ngff) + + +def test_y_x_valid_ngff(): + array = np.random.random((32, 16)) + multiscale = to_multiscales(array, [2, 4]) + + check_valid_ngff(multiscale) + + +# def test_z_y_x_valid_ngff(): +# array = np.random.random((32, 32, 16)) +# image = to_spatial_image(array) +# multiscale = to_multiscale(image, [2, 4]) + +# check_valid_ngff(multiscale) + + +# def test_z_y_x_c_valid_ngff(): +# array = np.random.random((32, 32, 16, 3)) +# image = to_spatial_image(array) +# multiscale = to_multiscale(image, [2, 4]) + +# check_valid_ngff(multiscale) + + +# def test_t_z_y_x_c_valid_ngff(): +# array = np.random.random((2, 32, 32, 16, 3)) +# image = to_spatial_image(array) +# multiscale = to_multiscale(image, [2, 4]) + +# check_valid_ngff(multiscale) diff --git a/test/test_to_ngff_zarr_dask_image.py b/test/test_to_ngff_zarr_dask_image.py index fc367da3..5662424e 100644 --- a/test/test_to_ngff_zarr_dask_image.py +++ b/test/test_to_ngff_zarr_dask_image.py @@ -15,10 +15,13 @@ def test_gaussian_isotropic_scale_factors(input_images): multiscales = to_multiscales(image, method=Methods.DASK_IMAGE_GAUSSIAN) verify_against_baseline(dataset_name, baseline_name, multiscales) + +def test_gaussian_isotropic_scale_factors_two_components(input_images): dataset_name = "brain_two_components" image = input_images[dataset_name] baseline_name = "2_4/DASK_IMAGE_GAUSSIAN.zarr" multiscales = to_multiscales(image, [2, 4], method=Methods.DASK_IMAGE_GAUSSIAN) + # store_new_multiscales(dataset_name, baseline_name, multiscales) verify_against_baseline(dataset_name, baseline_name, multiscales)