Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axes v0.4 #93

Merged
merged 16 commits into from
Jan 31, 2022
Merged
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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.0"],
install_requires=["omero-py>=5.6.0", "ome-zarr>=0.3a1"],
long_description=long_description,
keywords=["OMERO.CLI", "plugin"],
url="https://github.com/ome/omero-cli-zarr/",
Expand Down
4 changes: 3 additions & 1 deletion src/omero_zarr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from ome_zarr.format import CurrentFormat

from ._version import version as __version__

ngff_version = "0.3"
ngff_version = CurrentFormat().version

__all__ = [
"__version__",
Expand Down
17 changes: 10 additions & 7 deletions src/omero_zarr/masks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from skimage.draw import polygon as sk_polygon
from zarr.hierarchy import open_group

from .util import open_store, print_status
from . import ngff_version as VERSION
from .util import marshal_axes, marshal_transformations, open_store, print_status

# Mapping of dimension names to axes in the Zarr
DIMENSION_ORDER: Dict[str, int] = {
Expand Down Expand Up @@ -215,6 +216,7 @@ def set_image(
:param plate_path: The zarr path to the image
:return: None
"""
self.image = image
self.size_t = image.getSizeT()
self.size_c = image.getSizeC()
self.size_z = image.getSizeZ()
Expand Down Expand Up @@ -303,30 +305,31 @@ def save(self, masks: List[omero.model.Shape], name: str) -> None:
ignored_dimensions,
check_overlaps=True,
)
# For v0.3 ngff we want to reduce the number of dimensions to

axes = marshal_axes(self.image)
transformations = marshal_transformations(self.image, levels=1)

# For v0.3+ ngff we want to reduce the number of dimensions to
# match the dims of the Image.
dims_to_squeeze = []
axes = []
for dim, size in enumerate(self.image_shape):
if size == 1:
dims_to_squeeze.append(dim)
else:
axes.append("tczyx"[dim])
labels = np.squeeze(labels, axis=tuple(dims_to_squeeze))

scaler = Scaler(max_layer=input_pyramid_levels)
label_pyramid = scaler.nearest(labels)
pyramid_grp = out_labels.require_group(name)

write_multiscale(
label_pyramid, pyramid_grp, axes=axes
label_pyramid, pyramid_grp, axes=axes, transformations=transformations
) # TODO: dtype, chunks, overwite

# Specify and store metadata
image_label_colors: List[JSONDict] = []
label_properties: List[JSONDict] = []
image_label = {
"version": "0.3",
"version": VERSION,
"colors": image_label_colors,
"properties": label_properties,
"source": {"image": source_image_link},
Expand Down
87 changes: 40 additions & 47 deletions src/omero_zarr/raw_pixels.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
import numpy as np
import omero.clients # noqa
import omero.gateway # required to allow 'from omero_zarr import raw_pixels'
from ome_zarr.writer import (
write_multiscales_metadata,
write_plate_metadata,
write_well_metadata,
)
from omero.rtypes import unwrap
from skimage.transform import resize
from zarr.hierarchy import Array, Group, open_group

from . import __version__
from . import ngff_version as VERSION
from .util import open_store, print_status
from .util import marshal_axes, marshal_transformations, open_store, print_status


def image_to_zarr(image: omero.gateway.ImageWrapper, args: argparse.Namespace) -> None:
Expand All @@ -24,16 +29,15 @@ def image_to_zarr(image: omero.gateway.ImageWrapper, args: argparse.Namespace) -
print(f"Exporting to {name} ({VERSION})")
store = open_store(name)
root = open_group(store)
n_levels, axes = add_image(image, root, cache_dir=cache_dir)
add_multiscales_metadata(root, axes, n_levels)
add_image(image, root, cache_dir=cache_dir)
add_omero_metadata(root, image)
add_toplevel_metadata(root)
print("Finished.")


def add_image(
image: omero.gateway.ImageWrapper, parent: Group, cache_dir: Optional[str] = None
) -> Tuple[int, List[str]]:
) -> Tuple[int, List[Dict[str, Any]]]:
"""Adds an OMERO image pixel data as array to the given parent zarr group.
Optionally caches the pixel data in the given cache_dir directory.
Returns the number of resolution levels generated for the image.
Expand Down Expand Up @@ -79,7 +83,7 @@ def planeGen() -> np.ndarray:
longest = longest // 2
level_count += 1

return add_raw_image(
paths = add_raw_image(
planes=planes,
size_z=size_z,
size_c=size_c,
Expand All @@ -91,6 +95,15 @@ def planeGen() -> np.ndarray:
cache_file_name_func=get_cache_filename,
)

axes = marshal_axes(image)
transformations = marshal_transformations(image, len(paths))

write_multiscales_metadata(
parent, paths, axes=axes, transformations=transformations
)

return (level_count, axes)


def add_raw_image(
*,
Expand All @@ -103,7 +116,7 @@ def add_raw_image(
level_count: int,
cache_dir: Optional[str] = None,
cache_file_name_func: Callable[[int, int, int], str] = None,
) -> Tuple[int, List[str]]:
) -> List[str]:
"""Adds the raw image pixel data as array to the given parent zarr group.
Optionally caches the pixel data in the given cache_dir directory.
Returns the number of resolution levels generated for the image.
Expand All @@ -121,14 +134,8 @@ def add_raw_image(
cache_dir = ""

dims = [dim for dim in [size_t, size_c, size_z] if dim != 1]
axes = []
if size_t > 1:
axes.append("t")
if size_c > 1:
axes.append("c")
if size_z > 1:
axes.append("z")

paths: List[str] = []
field_groups: List[Array] = []
for t in range(size_t):
for c in range(size_c):
Expand All @@ -151,9 +158,11 @@ def add_raw_image(
size_x = plane.shape[1]
# If on first plane, create a new group for this resolution level
if len(field_groups) <= level:
path = str(level)
paths.append(path)
field_groups.append(
parent.create(
str(level),
path,
shape=tuple(dims + [size_y, size_x]),
chunks=tuple([1] * len(dims) + [size_y, size_x]),
dtype=d_type,
Expand All @@ -179,7 +188,8 @@ def add_raw_image(
preserve_range=True,
anti_aliasing=False,
).astype(plane.dtype)
return (level_count, axes + ["y", "x"])

return paths


def marshal_acquisition(acquisition: omero.gateway._PlateAcquisitionWrapper) -> Dict:
Expand Down Expand Up @@ -222,20 +232,14 @@ def plate_to_zarr(plate: omero.gateway._PlateWrapper, args: argparse.Namespace)

well_paths = set()

col_names = plate.getColumnLabels()
row_names = plate.getRowLabels()
col_names = [str(name) for name in plate.getColumnLabels()]
row_names = [str(name) for name in plate.getRowLabels()]

plate_metadata = {
"name": plate.name,
"rows": [{"name": str(name)} for name in row_names],
"columns": [{"name": str(name)} for name in col_names],
"version": VERSION,
}
# Add acquisitions key if at least one plate acquisition exists
acquisitions = list(plate.listPlateAcquisitions())
plate_acq = None
if acquisitions:
plate_metadata["acquisitions"] = [marshal_acquisition(x) for x in acquisitions]
root.attrs["plate"] = plate_metadata
plate_acq = [marshal_acquisition(x) for x in acquisitions]

for well in plate.listChildren():
row = plate.getRowLabels()[well.row]
Expand All @@ -256,39 +260,28 @@ def plate_to_zarr(plate: omero.gateway._PlateWrapper, args: argparse.Namespace)
row_group = root.require_group(row)
col_group = row_group.require_group(col)
field_group = col_group.require_group(field_name)
n_levels, axes = add_image(img, field_group, cache_dir=cache_dir)
add_multiscales_metadata(field_group, axes, n_levels)
add_image(img, field_group, cache_dir=cache_dir)
add_omero_metadata(field_group, img)
# Update Well metadata after each image
col_group.attrs["well"] = {"images": fields, "version": VERSION}
write_well_metadata(col_group, fields)
max_fields = max(max_fields, field + 1)
print_status(int(t0), int(time.time()), count, total)

# Update plate_metadata after each Well
plate_metadata["wells"] = [{"path": x} for x in well_paths]
plate_metadata["field_count"] = max_fields
root.attrs["plate"] = plate_metadata
write_plate_metadata(
sbesson marked this conversation as resolved.
Show resolved Hide resolved
root,
row_names,
col_names,
wells=list(well_paths),
field_count=max_fields,
acquisitions=plate_acq,
name=plate.name,
)

add_toplevel_metadata(root)
print("Finished.")


def add_multiscales_metadata(
zarr_root: Group,
axes: List[str],
resolutions: int = 1,
) -> None:

multiscales = [
{
"version": "0.3",
"datasets": [{"path": str(r)} for r in range(resolutions)],
"axes": axes,
}
]
zarr_root.attrs["multiscales"] = multiscales


def add_omero_metadata(zarr_root: Group, image: omero.gateway.ImageWrapper) -> None:

image_data = {
Expand Down
82 changes: 82 additions & 0 deletions src/omero_zarr/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import time
from typing import Dict, List

from omero.gateway import ImageWrapper
from zarr.storage import FSStore


Expand Down Expand Up @@ -33,3 +35,83 @@ def open_store(name: str) -> FSStore:
normalize_keys=False,
mode="w",
)


def marshal_pixel_sizes(image: ImageWrapper) -> Dict[str, Dict]:

pixel_sizes: Dict[str, Dict] = {}
pix_size_x = image.getPixelSizeX(units=True)
pix_size_y = image.getPixelSizeY(units=True)
pix_size_z = image.getPixelSizeZ(units=True)
# All OMERO units.lower() are valid UDUNITS-2 and therefore NGFF spec
if pix_size_x is not None:
pixel_sizes["x"] = {
"units": str(pix_size_x.getUnit()).lower(),
"value": pix_size_x.getValue(),
}
if pix_size_y is not None:
pixel_sizes["y"] = {
"units": str(pix_size_y.getUnit()).lower(),
"value": pix_size_y.getValue(),
}
if pix_size_z is not None:
pixel_sizes["z"] = {
"units": str(pix_size_z.getUnit()).lower(),
"value": pix_size_z.getValue(),
}
return pixel_sizes


def marshal_axes(image: ImageWrapper) -> List[Dict]:
# Prepare axes and transformations info...
size_c = image.getSizeC()
size_z = image.getSizeZ()
size_t = image.getSizeT()
pixel_sizes = marshal_pixel_sizes(image)

axes = []
if size_t > 1:
axes.append({"name": "t", "type": "time"})
if size_c > 1:
axes.append({"name": "c", "type": "channel"})
if size_z > 1:
axes.append({"name": "z", "type": "space"})
if pixel_sizes and "z" in pixel_sizes:
axes[-1]["units"] = pixel_sizes["z"]["units"]
# last 2 dimensions are always y and x
for dim in ("y", "x"):
axes.append({"name": dim, "type": "space"})
if pixel_sizes and dim in pixel_sizes:
axes[-1]["units"] = pixel_sizes[dim]["units"]

return axes


def marshal_transformations(
image: ImageWrapper, levels: int = 1, multiscales_zoom: float = 2.0
) -> List[List[Dict]]:

axes = marshal_axes(image)
pixel_sizes = marshal_pixel_sizes(image)

# Each path needs a transformations list...
transformations = []
zooms = {"x": 1.0, "y": 1.0, "z": 1.0}
for level in range(levels):
# {"type": "scale", "scale": [2.0, 2.0, 2.0], "axisIndices": [2, 3, 4]}
scales = []
axisIndices = []
for index, axis in enumerate(axes):
if axis["name"] in pixel_sizes:
scales.append(zooms[axis["name"]] * pixel_sizes[axis["name"]]["value"])
axisIndices.append(index)
# ...with a single 'scale' transformation each
if len(scales) > 0:
transformations.append(
[{"type": "scale", "scale": scales, "axisIndices": axisIndices}]
)
# NB we rescale X and Y for each level, but not Z
zooms["x"] = zooms["x"] * multiscales_zoom
zooms["y"] = zooms["y"] * multiscales_zoom

return transformations