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

Feature/add writers #9

Merged
merged 7 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
33 changes: 2 additions & 31 deletions bioio/bio_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import xarray as xr
from ome_types import OME

from .ome_utils import generate_ome_channel_id
from .plugins import plugins_by_ext

###############################################################################
Expand Down Expand Up @@ -176,36 +177,6 @@ def determine_reader(
),
)

@staticmethod
def _generate_ome_channel_id(image_id: str, channel_id: Union[str, int]) -> str:
"""
Naively generates the standard OME channel ID using a provided ID.

Parameters
----------
image_id: str
An image id to pull the image specific index from.
See: `generate_ome_image_id` for more details.
channel_id: Union[str, int]
A string or int representing the ID for a channel.
In the context of the usage of this function, this is usually used with the
index of the channel.

Returns
-------
ome_channel_id: str
The OME standard for channel IDs.

Notes
-----
ImageIds are usually: "Image:0", "Image:1", or "Image:N",
ChannelIds are usually the combination of image index + channel index --
"Channel:0:0" for the first channel of the first image for example.
"""
# Remove the prefix 'Image:' to get just the index
image_index = image_id.replace("Image:", "")
return f"Channel:{image_index}:{channel_id}"

def __init__(
self,
image: biob.types.ImageLike,
Expand Down Expand Up @@ -349,7 +320,7 @@ def _transform_data_array_to_bioio_image_standard(
# Add channel coordinate plane because it is required in BioImage
if biob.dimensions.DimensionNames.Channel not in coords:
coords[biob.dimensions.DimensionNames.Channel] = [
BioImage._generate_ome_channel_id(
generate_ome_channel_id(
image_id=self.current_scene,
channel_id=0,
)
Expand Down
137 changes: 137 additions & 0 deletions bioio/ome_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging
import typing

import numpy as np
from ome_types.model.simple_types import PixelType

###############################################################################

log = logging.getLogger(__name__)

###############################################################################


def generate_ome_channel_id(image_id: str, channel_id: typing.Union[str, int]) -> str:
"""
Naively generates the standard OME channel ID using a provided ID.

Parameters
----------
image_id: str
An image id to pull the image specific index from.
See: `generate_ome_image_id` for more details.
channel_id: Union[str, int]
A string or int representing the ID for a channel.
In the context of the usage of this function, this is usually used with the
index of the channel.

Returns
-------
ome_channel_id: str
The OME standard for channel IDs.

Notes
-----
ImageIds are usually: "Image:0", "Image:1", or "Image:N",
ChannelIds are usually the combination of image index + channel index --
"Channel:0:0" for the first channel of the first image for example.
"""
# Remove the prefix 'Image:' to get just the index
image_index = image_id.replace("Image:", "")
return f"Channel:{image_index}:{channel_id}"


def generate_ome_image_id(image_id: typing.Union[str, int]) -> str:
"""
Naively generates the standard OME image ID using a provided ID.

Parameters
----------
image_id: Union[str, int]
A string or int representing the ID for an image.
In the context of the usage of this function, this is usually used with the
index of the scene / image.

Returns
-------
ome_image_id: str
The OME standard for image IDs.
"""
return f"Image:{image_id}"


def dtype_to_ome_type(npdtype: np.dtype) -> PixelType:
"""
Convert numpy dtype to OME PixelType

Parameters
----------
npdtype: numpy.dtype
A numpy datatype.

Returns
-------
ome_type: PixelType
One of the supported OME Pixels types

Raises
------
ValueError
No matching pixel type for provided numpy type.
"""
ometypedict = {
np.dtype(np.int8): PixelType.INT8,
np.dtype(np.int16): PixelType.INT16,
np.dtype(np.int32): PixelType.INT32,
np.dtype(np.uint8): PixelType.UINT8,
np.dtype(np.uint16): PixelType.UINT16,
np.dtype(np.uint32): PixelType.UINT32,
np.dtype(np.float32): PixelType.FLOAT,
np.dtype(np.float64): PixelType.DOUBLE,
np.dtype(np.complex64): PixelType.COMPLEXFLOAT,
np.dtype(np.complex128): PixelType.COMPLEXDOUBLE,
}
ptype = ometypedict.get(npdtype)
if ptype is None:
raise ValueError(f"Ome utils can't resolve pixel type: {npdtype.name}")
return ptype


def ome_to_numpy_dtype(ome_type: PixelType) -> np.dtype:
"""
Convert OME PixelType to numpy dtype

Parameters
----------
ome_type: PixelType
One of the supported OME Pixels types

Returns
-------
npdtype: numpy.dtype
A numpy datatype.

Raises
------
ValueError
No matching numpy type for the provided pixel type.
"""
ometypedict: typing.Dict[PixelType, np.dtype] = {
PixelType.INT8: np.dtype(np.int8),
PixelType.INT16: np.dtype(np.int16),
PixelType.INT32: np.dtype(np.int32),
PixelType.UINT8: np.dtype(np.uint8),
PixelType.UINT16: np.dtype(np.uint16),
PixelType.UINT32: np.dtype(np.uint32),
PixelType.FLOAT: np.dtype(np.float32),
PixelType.DOUBLE: np.dtype(np.float64),
PixelType.COMPLEXFLOAT: np.dtype(np.complex64),
PixelType.COMPLEXDOUBLE: np.dtype(np.complex128),
}
nptype = ometypedict.get(ome_type)
if nptype is None:
raise ValueError(f"Ome utils can't resolve pixel type: {ome_type.value}")
return nptype
57 changes: 55 additions & 2 deletions bioio/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,72 @@
https://docs.pytest.org/en/latest/plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file
"""

import logging
import pathlib
import shutil
import typing

import dask.array as da
import numpy as np
import pytest

###############################################################################

log = logging.getLogger(__name__)

###############################################################################

LOCAL_RESOURCES_DIR = pathlib.Path(__file__).parent / "resources"
LOCAL_RESOURCES_WRITE_DIR = pathlib.Path(__file__).parent / "writer_products"


def get_resource_full_path(filename: str) -> typing.Union[str, pathlib.Path]:
return LOCAL_RESOURCES_DIR / filename
def pytest_sessionstart(session: pytest.Session) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually rethinking this I think I'll change this to use tempdir. Maybe we can write out an example.txt to tempdir too and avoid having any files download for this package thus far.

"""
Called after the Session object has been created and
before performing collection and entering the run test suite loop.
"""
if LOCAL_RESOURCES_WRITE_DIR.exists():
log.warning(
f"{LOCAL_RESOURCES_WRITE_DIR.absolute} should not exist at "
"start of tests, deleting now"
)
shutil.rmtree(LOCAL_RESOURCES_WRITE_DIR)


def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
"""
Called after whole test suite run finished, right before
returning the exit status to the system.
"""
shutil.rmtree(LOCAL_RESOURCES_WRITE_DIR)


@pytest.fixture
def data_dir() -> pathlib.Path:
return pathlib.Path(__file__).parent / "data"


def get_resource_full_path(filename: str) -> typing.Union[str, pathlib.Path]:
return LOCAL_RESOURCES_DIR / filename


def get_resource_write_full_path(filename: str) -> typing.Union[str, pathlib.Path]:
LOCAL_RESOURCES_WRITE_DIR.mkdir(parents=True, exist_ok=True)
return LOCAL_RESOURCES_WRITE_DIR / filename


def np_random_from_shape(
shape: typing.Tuple[int, ...], **kwargs: typing.Any
) -> np.ndarray:
return np.random.randint(255, size=shape, **kwargs)


def da_random_from_shape(
shape: typing.Tuple[int, ...], **kwargs: typing.Any
) -> da.Array:
return da.random.randint(255, size=shape, **kwargs)


array_constructor = pytest.mark.parametrize(
"array_constructor", [np_random_from_shape, da_random_from_shape]
)
2 changes: 2 additions & 0 deletions bioio/tests/test_bio_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from .conftest import get_resource_full_path

# TODO: Write out example.txt


@pytest.mark.parametrize(
"filename",
Expand Down
2 changes: 2 additions & 0 deletions bioio/tests/writers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
Loading