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

Start v05 #110

Closed
wants to merge 4 commits into from
Closed
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 src/ome_zarr_models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class BaseGroup(GroupSpec[BaseAttrs, ArraySpec | GroupSpec], ABC): # type: igno

@property
@abstractmethod
def ome_zarr_version(self) -> Literal["0.4"]:
def ome_zarr_version(self) -> Literal["0.4", "0.5"]:
"""
Version of the OME-Zarr specification that this group corresponds to.
"""
Empty file.
16 changes: 16 additions & 0 deletions src/ome_zarr_models/v05/_bioformats2raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Literal

from pydantic import Field, JsonValue

from ome_zarr_models.base import BaseAttrs
from ome_zarr_models.v05.plate import Plate


class BioFormats2RawAttrs(BaseAttrs):
"""
A model of the attributes contained in a bioformats2raw zarr group.
"""

bioformats2raw_layout: Literal[3] = Field(..., alias="bioformats2raw.layout")
plate: Plate | None = None
series: JsonValue | None = None
26 changes: 26 additions & 0 deletions src/ome_zarr_models/v05/axes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from collections.abc import Sequence
from typing import Literal

from pydantic import JsonValue

from ome_zarr_models.base import BaseAttrs

__all__ = ["Axes", "Axis", "AxisType"]


AxisType = Literal["space", "time", "channel"]


class Axis(BaseAttrs):
"""
Model for an element of `Multiscale.axes`.

See https://ngff.openmicroscopy.org/0.5/#axes-md.
"""

name: str
type: str | None = None
unit: str | JsonValue | None = None


Axes = Sequence[Axis]
16 changes: 16 additions & 0 deletions src/ome_zarr_models/v05/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Literal

from ome_zarr_models.base import BaseGroup


class BaseGroupv05(BaseGroup):
"""
Base class for all v0.5 OME-Zarr groups.
"""

@property
def ome_zarr_version(self) -> Literal["0.5"]:
"""
OME-Zarr version.
"""
return "0.5"
133 changes: 133 additions & 0 deletions src/ome_zarr_models/v05/coordinate_transformations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
For reference, see the [coordinate transformations section of the OME-Zarr specification](https://ngff.openmicroscopy.org/0.5/#trafo-md).
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal, Self

from pydantic import Field

from ome_zarr_models.base import BaseAttrs

if TYPE_CHECKING:
from collections.abc import Iterable, Sequence

__all__ = [
"Identity",
"PathScale",
"PathTranslation",
"ScaleTransform",
"TranslationTransform",
"VectorScale",
"VectorTransform",
"VectorTranslation",
]


class Transform(BaseAttrs):
type: Literal["identity", "scale", "translation"]


class Identity(Transform):
"""
Identity transformation.

Notes
-----
Although defined in the specification, it is not allowed
to be used anywhere.
"""

type: Literal["identity"]


class VectorScale(Transform):
"""
Scale transformation parametrized by a vector of numbers.
"""

type: Literal["scale"]
scale: list[float]

@classmethod
def build(cls, data: Iterable[float]) -> Self:
"""
Create a VectorScale from an iterable of floats.
"""
return cls(type="scale", scale=list(data))

@property
def ndim(self) -> int:
"""
Number of dimensions.
"""
return len(self.scale)


class PathScale(Transform):
"""
Scale transformation parametrized by a path.
"""

type: Literal["scale"]
path: str


class VectorTranslation(Transform):
"""
Translation transformation parametrized by a vector of numbers.
"""

type: Literal["translation"] = Field(..., description="Type")
translation: list[float]

@classmethod
def build(cls, data: Iterable[float]) -> Self:
"""
Create a VectorTranslation from an iterable of floats.
"""
return cls(type="translation", translation=list(data))

@property
def ndim(self) -> int:
"""
Number of dimensions.
"""
return len(self.translation)


class PathTranslation(Transform):
"""
Translation transformation parametrized by a path.
"""

type: Literal["translation"]
translation: str


ScaleTransform = VectorScale | PathScale
TranslationTransform = VectorTranslation | PathTranslation
VectorTransform = VectorScale | VectorTranslation


def _ndim(transform: VectorTransform) -> int:
"""
Get the dimensionality of a scale or translation transform.
"""
return transform.ndim


def _build_transforms(
scale: Sequence[float], translation: Sequence[float] | None
) -> tuple[VectorScale] | tuple[VectorScale, VectorTranslation]:
"""
Create a `VectorScale` and optionally a `VectorTranslation` from a scale and a
translation parameter.
"""
vec_scale = VectorScale.build(scale)
if translation is None:
return (vec_scale,)
else:
vec_trans = VectorTranslation.build(translation)
return vec_scale, vec_trans
84 changes: 84 additions & 0 deletions src/ome_zarr_models/v05/hcs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from collections.abc import Generator
from typing import Self

from pydantic import model_validator
from pydantic_zarr.v2 import ArraySpec, GroupSpec

from ome_zarr_models.base import BaseAttrs
from ome_zarr_models.v05.base import BaseGroupv05
from ome_zarr_models.v05.plate import Plate
from ome_zarr_models.v05.well import Well

__all__ = ["HCS", "HCSAttrs"]


class HCSAttrs(BaseAttrs):
"""
HCS metadtata attributes.
"""

plate: Plate


class HCS(GroupSpec[HCSAttrs, ArraySpec | GroupSpec], BaseGroupv05): # type: ignore[misc]
"""
An OME-Zarr high-content screening (HCS) dataset representing a single plate.
"""

@model_validator(mode="after")
def _check_valid_acquisitions(self) -> Self:
"""
Check well acquisition IDs are in list of plate acquisition ids.
"""
acquisitions = self.attributes.plate.acquisitions
if acquisitions is None:
return self

valid_aq_ids = [aq.id for aq in acquisitions]

for well_i, well_group in enumerate(self.well_groups):
for image_i, well_image in enumerate(well_group.attributes.well.images):
if well_image.acquisition is None:
continue
elif well_image.acquisition not in valid_aq_ids:
msg = (
f"Acquisition ID '{well_image.acquisition} "
f"(found in well {well_i}, {image_i}) "
f"is not in list of plate acquisitions: {valid_aq_ids}"
)
raise ValueError(msg)

return self

def get_well_group(self, i: int) -> Well:
"""
Get a single well group.

Parameters
----------
i :
Index of well group.
"""
well = self.attributes.plate.wells[i]
well_path = well.path
well_path_parts = well_path.split("/")
group = self
for part in well_path_parts:
group = group.members[part]

return Well(attributes=group.attributes, members=group.members)

@property
def n_wells(self) -> int:
"""
Number of wells.
"""
return len(self.attributes.plate.wells)

@property
def well_groups(self) -> Generator[Well, None, None]:
"""
Well groups within this HCS group.
"""
for i in range(self.n_wells):
yield self.get_well_group(i)
Loading
Loading