diff --git a/.github/workflows/pre.yml b/.github/workflows/pre.yml index 746b81aa..335877ae 100644 --- a/.github/workflows/pre.yml +++ b/.github/workflows/pre.yml @@ -41,6 +41,7 @@ jobs: run: | python -m pip install -U pip setuptools wheel pytest python -m pip install -r requirements/requirements-dev.txt + python -m pip install . # Without -e for plugins tox -e ${{ matrix.toxenv }} --pre # If something goes wrong, we can open an issue in the repo diff --git a/.isort.cfg b/.isort.cfg index fec62009..d1afe16f 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,5 +1,5 @@ [settings] -known_third_party = dask,numcodecs,numpy,pytest,scipy,setuptools,skimage,zarr +known_third_party = dask,entrypoints,numcodecs,numpy,ome_types,pytest,scipy,setuptools,skimage,zarr multi_line_output = 3 include_trailing_comma = True force_grid_wrap = 0 diff --git a/docs/source/api.rst b/docs/source/api.rst index 34b9713a..e7ce32d5 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -5,6 +5,7 @@ API :maxdepth: 3 api/cli + api/bioformats2raw api/csv api/data api/format diff --git a/docs/source/api/bioformats2raw.rst b/docs/source/api/bioformats2raw.rst new file mode 100644 index 00000000..5d6c892c --- /dev/null +++ b/docs/source/api/bioformats2raw.rst @@ -0,0 +1,5 @@ +Bioformats2raw(``ome_zarr.bioformats2raw``) +=========================================== + +.. automodule:: ome_zarr.bioformats2raw + :members: diff --git a/docs/source/python.rst b/docs/source/python.rst index f6bbdc26..053adf04 100644 --- a/docs/source/python.rst +++ b/docs/source/python.rst @@ -109,3 +109,13 @@ the data is available as `dask` arrays:: viewer = napari.view_image(dask_data, channel_axis=0) if __name__ == '__main__': napari.run() + +Reading bioformats2raw filesets +------------------------------- + +The output from bioformats2raw encapsulates _multiple_ OME-NGFF images. +This structure has been added to the OME-NGFF specification as transitional +`"bioformats2raw.layout" `_ +metadata. To read such filesets: + +TBD diff --git a/ome_zarr/bioformats2raw.py b/ome_zarr/bioformats2raw.py new file mode 100644 index 00000000..d875ffd2 --- /dev/null +++ b/ome_zarr/bioformats2raw.py @@ -0,0 +1,133 @@ +"""Spec extension for reading bioformats2raw.layout. + +This specification detects and reads filesets which were created by +bioformats2raw and therefore can have multiple multiscale image groups +present. Each such image will be returned by the [ome_zarr.reader.Reader] +as a separate [ome_zarr.reader.Node], but metadata which has been parsed +from the OME-XML metadata associated with the specific image will be +attached. + +TBD: Example +""" + +import logging +import os +import re +import tempfile +from xml.etree import ElementTree as ET + +import ome_types + +from ome_zarr.io import ZarrLocation +from ome_zarr.reader import Node +from ome_zarr.reader import Spec as Base + +__author__ = "Open Microscopy Environment (OME)" +__copyright__ = "Open Microscopy Environment (OME)" +__license__ = "BSD-2-Clause" + +_logger = logging.getLogger(__name__) + + +class bioformats2raw(Base): + """A spec-type for reading multi-image filesets OME-XML + metadata. + """ + + @staticmethod + def matches(zarr: ZarrLocation) -> bool: + """Pass if the metadata for the zgroup contains + `{"bioformats2raw.layout": 3}`""" + layout = zarr.root_attrs.get("bioformats2raw.layout", None) + _logger.error(f"layout={layout == 3} zarr={zarr}") + return layout == 3 + + def __init__(self, node: Node) -> None: + """Load metadata from the three sources associated with this + specification: the OME zgroup metadata, the OME-XML file, and + the images zgroups themselves. + """ + super().__init__(node) + try: + # Load OME/METADATA.ome.xml + data = self._handle(node) + if data.plates: + _logger.info("Plates detected. Skipping implicit loading") + else: + # Load the OME/ zgroup metadata + ome = node.zarr.create("OME") + if ome.exists: + series_metadata = ome.zarr.root_attrs.get("series", None) + if series_metadata is not None: + node.metadata["series"] = series_metadata + + # Load each individual image + for idx, image in enumerate(data.images): + series = node.zarr.create(str(idx)) + assert series.exists(), f"{series} is missing" + _logger.info("found %s", series) + subnode = node.add(series) + if subnode: + subnode.metadata["ome-xml:index"] = idx + subnode.metadata["ome-xml:image"] = image + + node.metadata["ome-xml"] = data + + except Exception: + _logger.exception("failed to parse metadata") + + def _fix_xml(self, ns: str, elem: ET.Element) -> None: + """Correct invalid OME-XML. + + Some versions of bioformats2raw did not include a MetadataOnly + tag. + + Note: elem.insert() was not updating the object correctly. + """ + + if elem.tag == f"{ns}Pixels": + must_have = {f"{ns}BinData", f"{ns}TiffData", f"{ns}MetadataOnly"} + children = {x.tag for x in elem} + + if not any(x in children for x in must_have): + # Needs fixing + metadata_only = ET.Element(f"{ns}MetadataOnly") + + last_channel = -1 + for idx, child in enumerate(elem): + if child.tag == f"{ns}Channel": + last_channel = idx + elem.insert(last_channel + 1, metadata_only) + + elif elem.tag == f"{ns}Plane": + remove = None + for idx, child in enumerate(elem): + if child.tag == f"{ns}HashSHA1": + remove = child + if remove: + elem.remove(remove) + + def _parse_xml(self, filename: str) -> ome_types.model.OME: + """Generate [ome_types.model.OME] from OME-XML""" + # Parse the file and find the current schema + root = ET.parse(filename) + m = re.match(r"\{.*\}", root.getroot().tag) + ns = m.group(0) if m else "" + + # Update the XML to include MetadataOnly + for child in list(root.iter()): + self._fix_xml(ns, child) + fixed = ET.tostring(root.getroot()).decode() + + # Write file out for ome_types + with tempfile.NamedTemporaryFile() as t: + t.write(fixed.encode()) + t.flush() + return ome_types.from_xml(t.name) + + def _handle(self, node: Node) -> ome_types.model.OME: + """Main parsing method which looks for OME/METADATA.ome.xml""" + metadata = node.zarr.subpath("OME/METADATA.ome.xml") + _logger.info("Looking for metadata in %s", metadata) + if os.path.exists(metadata): + return self._parse_xml(metadata) diff --git a/ome_zarr/reader.py b/ome_zarr/reader.py index ccc4ea01..42307a18 100644 --- a/ome_zarr/reader.py +++ b/ome_zarr/reader.py @@ -1,4 +1,11 @@ -"""Reading logic for ome-zarr.""" +"""Reading logic for ome-zarr. + +The main class (Reader) is initialitzed with an [ome_zarr.io.ZarrLocation] +as returned by [ome_zarr.io.parse_url] and walks up and down the Zarr +hierarchy parsing each array or group into a [Node] which is aware of all +meta(data) specifications ([Spec] class) which are available in the current +runtime. +""" import logging import math @@ -6,7 +13,9 @@ from typing import Any, Dict, Iterator, List, Optional, Type, Union, cast, overload import dask.array as da +import entrypoints import numpy as np +import zarr from dask import delayed from .axes import Axes @@ -45,21 +54,49 @@ def __init__( self.post_nodes: List[Node] = [] # TODO: this should be some form of plugin infra over subclasses + found: List[Spec] = [] if Labels.matches(zarr): - self.specs.append(Labels(self)) + found.append(Labels(self)) + self.specs.append(found[-1]) if Label.matches(zarr): - self.specs.append(Label(self)) + found.append(Label(self)) + self.specs.append(found[-1]) if Multiscales.matches(zarr): - self.specs.append(Multiscales(self)) + found.append(Multiscales(self)) + self.specs.append(found[-1]) if OMERO.matches(zarr): - self.specs.append(OMERO(self)) + found.append(OMERO(self)) + self.specs.append(found[-1]) if plate_labels: - self.specs.append(PlateLabels(self)) + found.append(PlateLabels(self)) + self.specs.append(found[-1]) elif Plate.matches(zarr): - self.specs.append(Plate(self)) + found.append(Plate(self)) + self.specs.append(found[-1]) # self.add(zarr, plate_labels=True) if Well.matches(zarr): - self.specs.append(Well(self)) + found.append(Well(self)) + self.specs.append(found[-1]) + + # Load all entrypoints and give them a chance + # to claim parse the current node. + for key, value in entrypoints.get_group_named("ome_zarr.spec").items(): + cls = value.load() + if cls.matches(zarr): + found.append(cls(self)) + self.specs.append(found[-1]) + + # Anything that has not received a type at this point + # can be considered an implicit group. + if not found: + self.specs.append(Implicit(self)) + + if False: # Temporarily disable. See #174 + # Load up the hierarchy + if Leaf.matches(zarr): + self.specs.append(Leaf(self)) + else: + self.specs.append(Root(self)) @overload def first(self, spectype: Type["Well"]) -> Optional["Well"]: @@ -178,6 +215,60 @@ def lookup(self, key: str, default: Any) -> Any: return self.zarr.root_attrs.get(key, default) +class Implicit(Spec): + """ + A spec-type which simply iterates over available zgroups. + """ + + @staticmethod + def matches(zarr: ZarrLocation) -> bool: + """Always return true""" + return True + + def __init__(self, node: Node) -> None: + super().__init__(node) + + for name in zarr.group(self.zarr.store).group_keys(): + child_zarr = self.zarr.create(name) + if child_zarr.exists(): + node.add(child_zarr) + + +class Leaf(Spec): + """ + A non-root level of the Zarr hierarchy + """ + + @staticmethod + def matches(zarr: ZarrLocation) -> bool: + """Return if the parent directory is within the zarr fileset""" + + parent_zarr = zarr.create("..") + return bool(parent_zarr.exists() and (parent_zarr.zgroup or parent_zarr.zarray)) + + def __init__(self, node: Node) -> None: + super().__init__(node) + parent_zarr = node.zarr.create("..") + if parent_zarr.exists() and (parent_zarr.zgroup or parent_zarr.zarray): + node.add(parent_zarr) + + +class Root(Spec): + """ + Root of the Zarr fileset + """ + + @staticmethod + def matches(zarr: ZarrLocation) -> bool: + """Return if the parent directory is not within the zarr fileset""" + + parent_zarr = zarr.create("..") + return parent_zarr.exists() and not (parent_zarr.zgroup or parent_zarr.zarray) + + def __init__(self, node: Node) -> None: + super().__init__(node) + + class Labels(Spec): """Relatively small specification for the well-known "labels" group which only contains the name of subgroups which should be loaded as labeled images.""" diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index d5a00601..0b8be9c9 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -1,6 +1,8 @@ black cython >= 0.29.16 numpy >= 1.16.0 +entrypoints +ome-types pre-commit tox wheel diff --git a/setup.py b/setup.py index d276e58b..c33d3f26 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,8 @@ def read(fname): install_requires += (["requests"],) install_requires += (["scikit-image"],) install_requires += (["toolz"],) +install_requires += (["entrypoints"],) +install_requires += (["ome-types"],) setup( @@ -49,6 +51,7 @@ def read(fname): ], entry_points={ "console_scripts": ["ome_zarr = ome_zarr.cli:main"], + "ome_zarr.spec": ["bioformats2raw = ome_zarr.bioformats2raw:bioformats2raw"], }, tests_require=["pytest"], ) diff --git a/tests/data/bf2raw/fake-series-2.zarr/.zattrs b/tests/data/bf2raw/fake-series-2.zarr/.zattrs new file mode 100644 index 00000000..c5eadb8a --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/.zattrs @@ -0,0 +1,3 @@ +{ + "bioformats2raw.layout" : 3 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/.zgroup b/tests/data/bf2raw/fake-series-2.zarr/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/0/.zattrs b/tests/data/bf2raw/fake-series-2.zarr/0/.zattrs new file mode 100644 index 00000000..90cf50e1 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/0/.zattrs @@ -0,0 +1,38 @@ +{ + "multiscales" : [ { + "metadata" : { + "method" : "loci.common.image.SimpleImageScaler", + "version" : "Bio-Formats 6.9.1" + }, + "axes" : [ { + "name" : "t", + "type" : "time" + }, { + "name" : "c", + "type" : "channel" + }, { + "name" : "z", + "type" : "space" + }, { + "name" : "y", + "type" : "space" + }, { + "name" : "x", + "type" : "space" + } ], + "datasets" : [ { + "path" : "0", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 1.0, 1.0 ], + "type" : "scale" + } ] + }, { + "path" : "1", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 2.0, 2.0 ], + "type" : "scale" + } ] + } ], + "version" : "0.4" + } ] +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/0/.zgroup b/tests/data/bf2raw/fake-series-2.zarr/0/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/0/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/0/0/.zarray b/tests/data/bf2raw/fake-series-2.zarr/0/0/.zarray new file mode 100644 index 00000000..58baf32f --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/0/0/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 512, 512 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 512, 512 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/0/0/0/0/0/0/0 b/tests/data/bf2raw/fake-series-2.zarr/0/0/0/0/0/0/0 new file mode 100644 index 00000000..6c56e50e Binary files /dev/null and b/tests/data/bf2raw/fake-series-2.zarr/0/0/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/fake-series-2.zarr/0/1/.zarray b/tests/data/bf2raw/fake-series-2.zarr/0/1/.zarray new file mode 100644 index 00000000..fee11a32 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/0/1/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 256, 256 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 256, 256 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/0/1/0/0/0/0/0 b/tests/data/bf2raw/fake-series-2.zarr/0/1/0/0/0/0/0 new file mode 100644 index 00000000..5d0ed5ab Binary files /dev/null and b/tests/data/bf2raw/fake-series-2.zarr/0/1/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/fake-series-2.zarr/1/.zattrs b/tests/data/bf2raw/fake-series-2.zarr/1/.zattrs new file mode 100644 index 00000000..90cf50e1 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/1/.zattrs @@ -0,0 +1,38 @@ +{ + "multiscales" : [ { + "metadata" : { + "method" : "loci.common.image.SimpleImageScaler", + "version" : "Bio-Formats 6.9.1" + }, + "axes" : [ { + "name" : "t", + "type" : "time" + }, { + "name" : "c", + "type" : "channel" + }, { + "name" : "z", + "type" : "space" + }, { + "name" : "y", + "type" : "space" + }, { + "name" : "x", + "type" : "space" + } ], + "datasets" : [ { + "path" : "0", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 1.0, 1.0 ], + "type" : "scale" + } ] + }, { + "path" : "1", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 2.0, 2.0 ], + "type" : "scale" + } ] + } ], + "version" : "0.4" + } ] +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/1/.zgroup b/tests/data/bf2raw/fake-series-2.zarr/1/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/1/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/1/0/.zarray b/tests/data/bf2raw/fake-series-2.zarr/1/0/.zarray new file mode 100644 index 00000000..58baf32f --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/1/0/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 512, 512 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 512, 512 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/1/0/0/0/0/0/0 b/tests/data/bf2raw/fake-series-2.zarr/1/0/0/0/0/0/0 new file mode 100644 index 00000000..a78e024f Binary files /dev/null and b/tests/data/bf2raw/fake-series-2.zarr/1/0/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/fake-series-2.zarr/1/1/.zarray b/tests/data/bf2raw/fake-series-2.zarr/1/1/.zarray new file mode 100644 index 00000000..fee11a32 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/1/1/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 256, 256 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 256, 256 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/1/1/0/0/0/0/0 b/tests/data/bf2raw/fake-series-2.zarr/1/1/0/0/0/0/0 new file mode 100644 index 00000000..313b070c Binary files /dev/null and b/tests/data/bf2raw/fake-series-2.zarr/1/1/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/fake-series-2.zarr/OME/.zattrs b/tests/data/bf2raw/fake-series-2.zarr/OME/.zattrs new file mode 100644 index 00000000..fec53b33 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/OME/.zattrs @@ -0,0 +1,3 @@ +{ + "series" : [ "0", "1" ] +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/OME/.zgroup b/tests/data/bf2raw/fake-series-2.zarr/OME/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/OME/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/fake-series-2.zarr/OME/METADATA.ome.xml b/tests/data/bf2raw/fake-series-2.zarr/OME/METADATA.ome.xml new file mode 100644 index 00000000..6c869aec --- /dev/null +++ b/tests/data/bf2raw/fake-series-2.zarr/OME/METADATA.ome.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/.zattrs b/tests/data/bf2raw/plate-rows-2.zarr/.zattrs new file mode 100644 index 00000000..9bcb2b28 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/.zattrs @@ -0,0 +1,28 @@ +{ + "bioformats2raw.layout" : 3, + "plate" : { + "columns" : [ { + "name" : "1" + } ], + "name" : "Plate Name 0", + "wells" : [ { + "path" : "A/1", + "rowIndex" : 0, + "columnIndex" : 0 + }, { + "path" : "B/1", + "rowIndex" : 1, + "columnIndex" : 0 + } ], + "field_count" : 1, + "rows" : [ { + "name" : "A" + }, { + "name" : "B" + } ], + "acquisitions" : [ { + "id" : 0 + } ], + "version" : "0.4" + } +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/A/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/.zattrs b/tests/data/bf2raw/plate-rows-2.zarr/A/1/.zattrs new file mode 100644 index 00000000..f87f4246 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/1/.zattrs @@ -0,0 +1,8 @@ +{ + "well" : { + "images" : [ { + "path" : "0", + "acquisition" : 0 + } ] + } +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/A/1/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/1/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/.zattrs b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/.zattrs new file mode 100644 index 00000000..d1e42664 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/.zattrs @@ -0,0 +1,41 @@ +{ + "multiscales" : [ { + "metadata" : { + "method" : "loci.common.image.SimpleImageScaler", + "version" : "Bio-Formats 6.9.1" + }, + "axes" : [ { + "name" : "t", + "type" : "time" + }, { + "name" : "c", + "type" : "channel" + }, { + "unit" : "micrometer", + "name" : "z", + "type" : "space" + }, { + "unit" : "micrometer", + "name" : "y", + "type" : "space" + }, { + "unit" : "micrometer", + "name" : "x", + "type" : "space" + } ], + "datasets" : [ { + "path" : "0", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 1.0, 1.0 ], + "type" : "scale" + } ] + }, { + "path" : "1", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 2.0, 2.0 ], + "type" : "scale" + } ] + } ], + "version" : "0.4" + } ] +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/0/.zarray b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/0/.zarray new file mode 100644 index 00000000..58baf32f --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/0/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 512, 512 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 512, 512 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/0/0/0/0/0/0 b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/0/0/0/0/0/0 new file mode 100644 index 00000000..6c56e50e Binary files /dev/null and b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/0/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/1/.zarray b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/1/.zarray new file mode 100644 index 00000000..fee11a32 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/1/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 256, 256 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 256, 256 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/1/0/0/0/0/0 b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/1/0/0/0/0/0 new file mode 100644 index 00000000..5d0ed5ab Binary files /dev/null and b/tests/data/bf2raw/plate-rows-2.zarr/A/1/0/1/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/B/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/.zattrs b/tests/data/bf2raw/plate-rows-2.zarr/B/1/.zattrs new file mode 100644 index 00000000..f87f4246 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/1/.zattrs @@ -0,0 +1,8 @@ +{ + "well" : { + "images" : [ { + "path" : "0", + "acquisition" : 0 + } ] + } +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/B/1/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/1/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/.zattrs b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/.zattrs new file mode 100644 index 00000000..d1e42664 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/.zattrs @@ -0,0 +1,41 @@ +{ + "multiscales" : [ { + "metadata" : { + "method" : "loci.common.image.SimpleImageScaler", + "version" : "Bio-Formats 6.9.1" + }, + "axes" : [ { + "name" : "t", + "type" : "time" + }, { + "name" : "c", + "type" : "channel" + }, { + "unit" : "micrometer", + "name" : "z", + "type" : "space" + }, { + "unit" : "micrometer", + "name" : "y", + "type" : "space" + }, { + "unit" : "micrometer", + "name" : "x", + "type" : "space" + } ], + "datasets" : [ { + "path" : "0", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 1.0, 1.0 ], + "type" : "scale" + } ] + }, { + "path" : "1", + "coordinateTransformations" : [ { + "scale" : [ 1.0, 1.0, 1.0, 2.0, 2.0 ], + "type" : "scale" + } ] + } ], + "version" : "0.4" + } ] +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/0/.zarray b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/0/.zarray new file mode 100644 index 00000000..58baf32f --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/0/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 512, 512 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 512, 512 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/0/0/0/0/0/0 b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/0/0/0/0/0/0 new file mode 100644 index 00000000..a78e024f Binary files /dev/null and b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/0/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/1/.zarray b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/1/.zarray new file mode 100644 index 00000000..fee11a32 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/1/.zarray @@ -0,0 +1,17 @@ +{ + "chunks" : [ 1, 1, 1, 256, 256 ], + "compressor" : { + "clevel" : 5, + "blocksize" : 0, + "shuffle" : 1, + "cname" : "lz4", + "id" : "blosc" + }, + "dtype" : "|u1", + "fill_value" : 0, + "filters" : null, + "order" : "C", + "shape" : [ 1, 1, 1, 256, 256 ], + "dimension_separator" : "/", + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/1/0/0/0/0/0 b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/1/0/0/0/0/0 new file mode 100644 index 00000000..313b070c Binary files /dev/null and b/tests/data/bf2raw/plate-rows-2.zarr/B/1/0/1/0/0/0/0/0 differ diff --git a/tests/data/bf2raw/plate-rows-2.zarr/OME/.zattrs b/tests/data/bf2raw/plate-rows-2.zarr/OME/.zattrs new file mode 100644 index 00000000..db84cffa --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/OME/.zattrs @@ -0,0 +1,3 @@ +{ + "series" : [ "A/1/0", "B/1/0" ] +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/OME/.zgroup b/tests/data/bf2raw/plate-rows-2.zarr/OME/.zgroup new file mode 100644 index 00000000..aa4bf031 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/OME/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format" : 2 +} \ No newline at end of file diff --git a/tests/data/bf2raw/plate-rows-2.zarr/OME/METADATA.ome.xml b/tests/data/bf2raw/plate-rows-2.zarr/OME/METADATA.ome.xml new file mode 100644 index 00000000..4945aa05 --- /dev/null +++ b/tests/data/bf2raw/plate-rows-2.zarr/OME/METADATA.ome.xml @@ -0,0 +1 @@ +ExperimentPlate 0 of 1PlateAcquisition 0 of 1Image Description 01234567890ABCDEF1234567890ABCDEF12345678Image Description 11234567890ABCDEF1234567890ABCDEF12345678 \ No newline at end of file diff --git a/tests/test_bf2raw.py b/tests/test_bf2raw.py new file mode 100644 index 00000000..66a69e2e --- /dev/null +++ b/tests/test_bf2raw.py @@ -0,0 +1,48 @@ +import numpy as np +import pytest + +from ome_zarr.bioformats2raw import bioformats2raw +from ome_zarr.io import parse_url +from ome_zarr.reader import Multiscales, Node, Reader + +TEST_DATA = ( + ("fake-series-2.zarr", False), + ("plate-rows-2.zarr", True), +) + +class TestBf2raw: + + def assert_data(self, path, shape, plate, mode="r"): + loc = parse_url(path, mode=mode) + assert loc, f"no zarr found at {path}" + reader = Reader(loc) + nodes = list(reader()) + + assert any([ + bioformats2raw.matches(node.zarr) for node in nodes + ]), "plugin not detected" + + WHY WITH PLATE + + for node in nodes: + if bioformats2raw.matches(node.zarr): + assert "series" in node.metadata, node.metadata + elif Multiscales.matches(node.zarr): + assert node.data[0].shape == shape + assert np.max(node.data[0]) > 0 + elif "OME" in str(node.zarr): + pass # Doesn't get parsed directly + else: + raise Exception(node) + + @pytest.mark.parametrize("path,plate", TEST_DATA) + def test_read_static_data(self, request, path, plate): + shape = (1, 1, 1, 512, 512) + self.assert_data(f"{request.fspath.dirname}/data/bf2raw/{path}", shape, plate) + + @pytest.mark.parametrize("path,plate", TEST_DATA) + def test_node_static_data(self, request, path, plate): + zarr = parse_url(f"{request.fspath.dirname}/data/bf2raw/{path}") + import pdb; pdb.set_trace() + node = Node(zarr, []) + print(node.specs)