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 6 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
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ coverage.xml
.hypothesis/
.pytest_cache/

# Unit test files
bioio/tests/resources/

# Translations
*.mo
*.pot
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Ready to contribute? Here's how to set up `bioio` for local development.

```bash
cd bioio/
just setup-dev
just install
```

4. Create a branch for local development:
Expand Down
7 changes: 1 addition & 6 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ clean:
# install with all deps
install:
pip install -e .[lint,test,docs,dev]
pre-commit install

# lint, format, and check all files
lint:
Expand All @@ -37,12 +38,6 @@ build:
just lint
just test

# install dependencies, setup pre-commit, download test resources
setup-dev:
just install
pre-commit install
python scripts/download_test_resources.py

# generate Sphinx HTML documentation
generate-docs:
rm -f docs/bioio*.rst
Expand Down
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
33 changes: 27 additions & 6 deletions bioio/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,40 @@
https://docs.pytest.org/en/latest/plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file
"""

import logging
import pathlib
import typing

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

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

log = logging.getLogger(__name__)

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


@pytest.fixture
def data_dir() -> pathlib.Path:
return pathlib.Path(__file__).parent / "data"
def sample_text_file(tmp_path: pathlib.Path) -> pathlib.Path:
example_file = tmp_path / "temp-example.txt"
example_file.write_text("just some example text here")
return example_file


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]
)
29 changes: 10 additions & 19 deletions bioio/tests/test_bio_image.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pathlib

import bioio_base as biob
import pytest

from bioio import BioImage

from .conftest import get_resource_full_path

def test_bioimage_with_text_file(sample_text_file: pathlib.Path) -> None:
with pytest.raises(biob.exceptions.UnsupportedFileFormatError):
BioImage(sample_text_file)


@pytest.mark.parametrize(
"filename",
[
pytest.param(
"example.txt",
marks=pytest.mark.xfail(raises=biob.exceptions.UnsupportedFileFormatError),
),
pytest.param(
"does-not-exist-klafjjksdafkjl.bad",
marks=pytest.mark.xfail(raises=FileNotFoundError),
),
],
)
def test_bioimage(
filename: str,
) -> None:
def test_bioimage_with_missing_file(tmp_path: pathlib.Path) -> None:
# Construct full filepath
uri = get_resource_full_path(filename)
BioImage(uri)
uri = tmp_path / "does-not-exist-klafjjksdafkjl.bad"
with pytest.raises(FileNotFoundError):
BioImage(uri)
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