Skip to content

Commit

Permalink
Use AICS metadata even when GUI isn't involved
Browse files Browse the repository at this point in the history
  • Loading branch information
multimeric committed Aug 1, 2024
1 parent 2139687 commit 420898b
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 42 deletions.
34 changes: 30 additions & 4 deletions core/lls_core/models/deskew.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations
# class for initializing lattice data and setting metadata
# TODO: handle scenes
from pydantic import Field, NonNegativeFloat, validator
from pydantic import Field, NonNegativeFloat, validator, root_validator

from typing import Any, Tuple
from typing_extensions import Self, TYPE_CHECKING
Expand All @@ -12,7 +12,7 @@
from xarray import DataArray

from lls_core.models.utils import FieldAccessModel, enum_choices
from lls_core.types import image_like_to_image
from lls_core.types import image_like_to_image, is_arraylike, is_pathlike
from lls_core.utils import get_deskewed_shape

if TYPE_CHECKING:
Expand Down Expand Up @@ -164,11 +164,37 @@ def convert_pixels(cls, v: Any):
return DefinedPixelSizes(Z=v[0], Y=v[1], X=v[2])
return v

@root_validator(pre=True)
def read_image(cls, values: dict):
from aicsimageio import AICSImage
from os import fspath

img = values["input_image"]

aics: AICSImage | None = None
if is_pathlike(img):
aics = AICSImage(fspath(img))
elif isinstance(img, AICSImage):
aics = img
elif is_arraylike(img):
values["input_image"] = DataArray(img)
else:
raise ValueError("Value of input_image was neither a path, an AICSImage, or array-like.")

# If the image was convertible to AICSImage, we should use the metadata from there
if aics:
values["input_image"] = aics.xarray_dask_data
values["physical_pixel_sizes"] = aics.physical_pixel_sizes

# In all cases, input_image will be a DataArray (XArray) at this point

return values

@validator("input_image", pre=True)
def reshaping(cls, v: Any):
def reshaping(cls, v: DataArray):
# This allows a user to pass in any array-like object and have it
# converted and reshaped appropriately
array = image_like_to_image(v)
array = v
if not set(array.dims).issuperset({"X", "Y", "Z"}):
raise ValueError("The input array must at least have XYZ coordinates")
if "T" not in array.dims:
Expand Down
76 changes: 38 additions & 38 deletions core/lls_core/types.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
from typing import Union
from typing_extensions import TypeGuard, Any, TypeAlias
from dask.array.core import Array as DaskArray
# from numpy.typing import NDArray
from pyopencl.array import Array as OCLArray
import numpy as np
from numpy.typing import NDArray
from xarray import DataArray
from aicsimageio import AICSImage
from os import fspath, PathLike as OriginalPathLike

# This is a superset of os.PathLike
PathLike: TypeAlias = Union[str, bytes, OriginalPathLike]
def is_pathlike(x: Any) -> TypeGuard[PathLike]:
return isinstance(x, (str, bytes, OriginalPathLike))

ArrayLike: TypeAlias = Union[DaskArray, NDArray, OCLArray, DataArray]

def is_arraylike(arr: Any) -> TypeGuard[ArrayLike]:
return isinstance(arr, (DaskArray, np.ndarray, OCLArray, DataArray))

ImageLike: TypeAlias = Union[PathLike, AICSImage, ArrayLike]
def image_like_to_image(img: ImageLike) -> DataArray:
"""
Converts an image in one of many formats to a DataArray
"""
# First try treating it as a path
try:
img = AICSImage(fspath(img))
except TypeError:
pass
if isinstance(img, AICSImage):
return img.xarray_dask_data
else:
for required_key in ("shape", "dtype", "ndim", "__array__", "__array_ufunc__"):
if not hasattr(img, required_key):
raise ValueError(f"The provided object {img} is not array like!")
return DataArray(img)
from typing import Union
from typing_extensions import TypeGuard, Any, TypeAlias
from dask.array.core import Array as DaskArray
# from numpy.typing import NDArray
from pyopencl.array import Array as OCLArray
import numpy as np
from numpy.typing import NDArray
from xarray import DataArray
from aicsimageio import AICSImage
from os import fspath, PathLike as OriginalPathLike

# This is a superset of os.PathLike
PathLike: TypeAlias = Union[str, bytes, OriginalPathLike]
def is_pathlike(x: Any) -> TypeGuard[PathLike]:
return isinstance(x, (str, bytes, OriginalPathLike))

ArrayLike: TypeAlias = Union[DaskArray, NDArray, OCLArray, DataArray]

def is_arraylike(arr: Any) -> TypeGuard[ArrayLike]:
return isinstance(arr, (DaskArray, np.ndarray, OCLArray, DataArray))

ImageLike: TypeAlias = Union[PathLike, AICSImage, ArrayLike]
def image_like_to_image(img: ImageLike) -> DataArray:
"""
Converts an image in one of many formats to a DataArray
"""
# First try treating it as a path
try:
img = AICSImage(fspath(img))
except TypeError:
pass
if isinstance(img, AICSImage):
return img.xarray_dask_data
else:
for required_key in ("shape", "dtype", "ndim", "__array__", "__array_ufunc__"):
if not hasattr(img, required_key):
raise ValueError(f"The provided object {img} is not array like!")
return DataArray(img)
17 changes: 17 additions & 0 deletions core/tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
from lls_core.models.crop import CropParams
from lls_core.models.lattice_data import LatticeData
from lls_core.models.deskew import DeskewParams
from lls_core.models.output import OutputParams
import pytest
from pydantic import ValidationError
import tempfile
from unittest.mock import patch, PropertyMock

def test_default_save_dir(rbc_tiny: Path):
# Test that the save dir is inferred to be the input dir
Expand Down Expand Up @@ -35,3 +38,17 @@ def test_pixel_tuple_order(rbc_tiny: Path):
assert deskew.physical_pixel_sizes.X == 3.
assert deskew.physical_pixel_sizes.Y == 2.
assert deskew.physical_pixel_sizes.Z == 1.

def test_allow_trailing_slash():
with tempfile.TemporaryDirectory() as tmpdir:
output = OutputParams(
save_dir=f"{tmpdir}/"
)
assert str(output.save_dir) == tmpdir

def test_infer_czi_pixel_sizes(rbc_tiny: Path):
mock = PropertyMock()
with patch("aicsimageio.AICSImage.physical_pixel_sizes", new=mock):
DeskewParams(input_image=rbc_tiny)
# The AICSImage should be queried for the pixel sizes
assert mock.called

0 comments on commit 420898b

Please sign in to comment.