diff --git a/.isort.cfg b/.isort.cfg index 0e8f21a..10ca857 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,2 +1,2 @@ [settings] -known_third_party = numpy,ome_zarr,omero,omero_zarr,setuptools,skimage,zarr +known_third_party = numpy,ome_types,ome_zarr,omero,omero_zarr,setuptools,skimage,zarr diff --git a/setup.py b/setup.py index 47f6352..47888e5 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def get_long_description() -> str: author="The Open Microscopy Team", author_email="", python_requires=">=3", - install_requires=["omero-py>=5.6.0", "ome-zarr>=0.5.0"], + install_requires=["omero-py>=5.6.0", "ome-zarr>=0.5.0", "ome-types"], long_description=long_description, keywords=["OMERO.CLI", "plugin"], url="https://github.com/ome/omero-cli-zarr/", diff --git a/src/omero_zarr/cli.py b/src/omero_zarr/cli.py index a94500f..1302902 100644 --- a/src/omero_zarr/cli.py +++ b/src/omero_zarr/cli.py @@ -7,7 +7,7 @@ from omero.cli import CLI, BaseControl, Parser, ProxyStringType from omero.gateway import BlitzGateway, BlitzObjectWrapper -from omero.model import ImageI, PlateI +from omero.model import FilesetI, ImageI, PlateI from zarr.hierarchy import open_group from zarr.storage import FSStore @@ -20,6 +20,7 @@ from .raw_pixels import ( add_omero_metadata, add_toplevel_metadata, + fileset_to_zarr, image_to_zarr, plate_to_zarr, ) @@ -323,6 +324,9 @@ def export(self, args: argparse.Namespace) -> None: elif isinstance(args.object, PlateI): plate = self._lookup(self.gateway, "Plate", args.object.id) plate_to_zarr(plate, args) + elif isinstance(args.object, FilesetI): + fileset = self._lookup(self.gateway, "Fileset", args.object.id) + fileset_to_zarr(fileset, args) def _lookup( self, gateway: BlitzGateway, otype: str, oid: int diff --git a/src/omero_zarr/raw_pixels.py b/src/omero_zarr/raw_pixels.py index b1bcc7b..4d98e6d 100644 --- a/src/omero_zarr/raw_pixels.py +++ b/src/omero_zarr/raw_pixels.py @@ -18,14 +18,74 @@ from . import __version__ from . import ngff_version as VERSION -from .util import marshal_axes, marshal_transformations, open_store, print_status +from .util import ( + get_minimum_image_ome_xml, + marshal_axes, + marshal_transformations, + open_store, + print_status, +) -def image_to_zarr(image: omero.gateway.ImageWrapper, args: argparse.Namespace) -> None: +def fileset_to_zarr( + fileset: omero.gateway.FilesetWrapper, args: argparse.Namespace +) -> None: + """Exports a FileSet according to bioformats2raw.layout""" + images = list(fileset.copyImages()) + images_to_zarr(images, args) + + +def images_to_zarr( + images: List[omero.gateway.ImageWrapper], + args: argparse.Namespace, + target_dir: str = None, +) -> None: + """Exports images in bioformats2raw.layout, each under path '0', '1' etc.""" + if target_dir is None: + target_dir = args.output if args.output else "zarr_export" + dir_index = 1 + dir_name = target_dir + while os.path.exists(dir_name): + dir_name = f"{target_dir}({dir_index})" + dir_index += 1 + os.mkdir(dir_name) + + # write bioformats2raw.layout + zattrs = os.path.join(dir_name, ".zattrs") + with open(zattrs, "w") as zfile: + zfile.write( + """{ + "bioformats2raw.layout" : 3 +}""" + ) + # write OME/METADATA.ome.xml + ome_dir = os.path.join(dir_name, "OME") + xml_path = os.path.join(ome_dir, "METADATA.ome.xml") + os.mkdir(ome_dir) + images = list(images) # in case it's a generator + ome_xml = get_minimum_image_ome_xml(images) + with open(xml_path, "w") as xml_file: + xml_file.write(ome_xml) + + # write images + for index, image in enumerate(images): + target = os.path.join(dir_name, str(index)) + image_to_zarr(image, args, target) + + +def image_to_zarr( + image: omero.gateway.ImageWrapper, args: argparse.Namespace, target: str = None +) -> None: + """ + Export an image to zarr + @param target: Optional path/to/image.zarr. Default is args.output/ID.zarr + """ target_dir = args.output cache_dir = target_dir if args.cache_numpy else None - - name = os.path.join(target_dir, "%s.zarr" % image.id) + if target is None: + name = os.path.join(target_dir, "%s.zarr" % image.id) + else: + name = target print(f"Exporting to {name} ({VERSION})") store = open_store(name) root = open_group(store) diff --git a/src/omero_zarr/util.py b/src/omero_zarr/util.py index d4d00cb..7492c62 100644 --- a/src/omero_zarr/util.py +++ b/src/omero_zarr/util.py @@ -1,6 +1,9 @@ import time from typing import Dict, List +from ome_types import to_xml +from ome_types.model import OME, Image, Pixels +from ome_types.model.simple_types import ImageID, PixelsID, PixelType from omero.gateway import ImageWrapper from zarr.storage import FSStore @@ -112,3 +115,30 @@ def marshal_transformations( zooms["y"] = zooms["y"] * multiscales_zoom return transformations + + +def get_minimum_image_ome_xml(images: List[ImageWrapper]) -> str: + """Generates minimal OME.xml for""" + + ome = OME() + + for image in images: + pixels_id = image.getPixelsId() + pix = image.getPrimaryPixels() + pixels_type = image.getPrimaryPixels().getPixelsType().getValue() + ptype = PixelType(pixels_type) + pixels = Pixels( + id=PixelsID("Pixels:%s" % pixels_id), + dimension_order=pix.getDimensionOrder().getValue(), + size_c=image.getSizeC(), + size_t=image.getSizeT(), + size_z=image.getSizeZ(), + size_x=image.getSizeX(), + size_y=image.getSizeY(), + type=ptype, + metadata_only=True, + ) + img = Image(id=ImageID("Image:%s" % image.id), pixels=pixels, name=image.name) + ome.images.append(img) + + return to_xml(ome)