Skip to content

PR: Make *OpenImageIO* required and *ImageIO* optional. #1340

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

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,6 @@ jobs:
uv sync --all-extras --no-dev
uv run python -c "import imageio;imageio.plugins.freeimage.download()"
shell: bash
- name: Install OpenImageIO (macOs)
if: matrix.os == 'macOS-latest' && matrix.python-version == '3.13'
run: |
brew install openimageio
ln -s /opt/homebrew/Cellar/openimageio/*/lib/python*/site-packages/OpenImageIO/OpenImageIO*.so ./.venv/lib/python${{ matrix.python-version }}/site-packages/OpenImageIO.so
uv run python -c "import OpenImageIO;print(OpenImageIO.__version__)"
shell: bash
- name: Pre-Commit (All Files)
run: |
uv run pre-commit run --all-files
Expand Down
4 changes: 2 additions & 2 deletions colour/examples/io/examples_fichet2021.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import tempfile

import colour
from colour.utilities import is_openimageio_installed, message_box
from colour.utilities import is_imageio_installed, message_box

if is_openimageio_installed():
if is_imageio_installed():
ROOT_RESOURCES = os.path.join(
os.path.dirname(__file__), "..", "..", "io", "tests", "resources"
)
Expand Down
13 changes: 8 additions & 5 deletions colour/io/fichet2021.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ def from_spectral_image(path: str | PathLike) -> Specification_Fichet2021:
rf"^T\.*{PATTERN_FICHET2021}\.*{PATTERN_FICHET2021}$"
)

image_specification = ImageInput.open(path).spec()
image_input = ImageInput.open(path)
image_specification = image_input.spec()
channels = image_specification.channelnames

for i, channel in enumerate(channels):
Expand Down Expand Up @@ -405,6 +406,8 @@ def from_spectral_image(path: str | PathLike) -> Specification_Fichet2021:
for attribute in image_specification.extra_attribs
]

image_input.close()

return Specification_Fichet2021(
path,
components,
Expand Down Expand Up @@ -516,7 +519,9 @@ def read_spectral_image_Fichet2021(
bit_depth_specification = MAPPING_BIT_DEPTH[bit_depth]

specification = Specification_Fichet2021.from_spectral_image(path)
image = ImageInput.open(path).read_image(bit_depth_specification.openimageio)
image_input = ImageInput.open(path)
image = image_input.read_image(bit_depth_specification.openimageio)
image_input.close()

components = {}
for component, wavelengths_indexes in specification.components.items():
Expand Down Expand Up @@ -870,6 +875,4 @@ def write_spectral_image_Fichet2021(
image_buffer.specmod(), [*specification.attributes, *attributes]
)

image_buffer.write(path)

return True
return image_buffer.write(path)
32 changes: 17 additions & 15 deletions colour/io/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
as_int_array,
attest,
filter_kwargs,
is_imageio_installed,
is_openimageio_installed,
optional,
required,
Expand Down Expand Up @@ -194,7 +195,6 @@ def add_attributes_to_image_specification_OpenImageIO(
return image_specification


@required("OpenImageIO")
def image_specification_OpenImageIO(
width: int,
height: int,
Expand Down Expand Up @@ -454,6 +454,7 @@ def read_image_OpenImageIO(
return image


@required("Imageio")
def read_image_Imageio(
path: str | PathLike,
bit_depth: Literal[
Expand Down Expand Up @@ -586,14 +587,14 @@ def read_image(
dtype('float32')
"""

method = validate_method(method, tuple(READ_IMAGE_METHODS))

if method == "openimageio" and not is_openimageio_installed(): # pragma: no cover
if method.lower() == "imageio" and not is_imageio_installed(): # pragma: no cover
usage_warning(
'"OpenImageIO" related API features are not available, '
'switching to "Imageio"!'
'"Imageio" related API features are not available, '
'switching to "OpenImageIO"!'
)
method = "Imageio"
method = "openimageio"

method = validate_method(method, tuple(READ_IMAGE_METHODS))

function = READ_IMAGE_METHODS[method]

Expand Down Expand Up @@ -666,7 +667,7 @@ def write_image_OpenImageIO(

Writing an "ACES" compliant "EXR" file:

>>> if is_openimageio_installed(): # doctest: +SKIP
>>> if is_imageio_installed(): # doctest: +SKIP
... from OpenImageIO import TypeDesc
...
... chromaticities = (
Expand Down Expand Up @@ -722,13 +723,14 @@ def write_image_OpenImageIO(
image_output = ImageOutput.create(path)

image_output.open(path, image_specification)
image_output.write_image(image)
success = image_output.write_image(image)

image_output.close()

return True
return success


@required("Imageio")
def write_image_Imageio(
image: ArrayLike,
path: str | PathLike,
Expand Down Expand Up @@ -903,14 +905,14 @@ def write_image(
True
""" # noqa: D405, D407, D410, D411, D414

method = validate_method(method, tuple(WRITE_IMAGE_METHODS))

if method == "openimageio" and not is_openimageio_installed(): # pragma: no cover
if method.lower() == "imageio" and not is_imageio_installed(): # pragma: no cover
usage_warning(
'"OpenImageIO" related API features are not available, '
'"Imageio" related API features are not available, '
'switching to "Imageio"!'
)
method = "Imageio"
method = "openimageio"

method = validate_method(method, tuple(WRITE_IMAGE_METHODS))

function = WRITE_IMAGE_METHODS[method]

Expand Down
10 changes: 0 additions & 10 deletions colour/io/tests/test_fichet2021.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
match_groups_to_nm,
sds_and_msds_to_components_Fichet2021,
)
from colour.utilities import is_openimageio_installed

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
Expand Down Expand Up @@ -180,9 +179,6 @@ def test_components_to_sRGB_Fichet2021(self) -> None:
definition.
"""

if not is_openimageio_installed():
return

specification = Specification_Fichet2021(is_emissive=True)
components = sds_and_msds_to_components_Fichet2021(
SDS_ILLUMINANTS["D65"], specification
Expand Down Expand Up @@ -465,9 +461,6 @@ def test_read_spectral_image_Fichet2021(self) -> None:
definition.
"""

if not is_openimageio_installed():
return

_test_spectral_image_D65(os.path.join(ROOT_RESOURCES, "D65.exr"))

_test_spectral_image_Ohta1997(os.path.join(ROOT_RESOURCES, "Ohta1997.exr"))
Expand Down Expand Up @@ -499,9 +492,6 @@ def test_write_spectral_image_Fichet2021(self) -> None:
definition.
"""

if not is_openimageio_installed():
return

path = os.path.join(self._temporary_directory, "D65.exr")
specification = Specification_Fichet2021(is_emissive=True)
write_spectral_image_Fichet2021(
Expand Down
93 changes: 48 additions & 45 deletions colour/io/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
write_image_Imageio,
write_image_OpenImageIO,
)
from colour.utilities import attest, full, is_openimageio_installed
from colour.utilities import attest, full

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
Expand Down Expand Up @@ -60,9 +60,6 @@ def test_image_specification_OpenImageIO(self) -> None: # pragma: no cover
definition.
"""

if not is_openimageio_installed():
return

from OpenImageIO import HALF # pyright: ignore

compression = Image_Specification_Attribute("Compression", "none")
Expand Down Expand Up @@ -275,9 +272,6 @@ class TestReadImageOpenImageIO:
def test_read_image_OpenImageIO(self) -> None: # pragma: no cover
"""Test :func:`colour.io.image.read_image_OpenImageIO` definition."""

if not is_openimageio_installed():
return

image = read_image_OpenImageIO(
os.path.join(ROOT_RESOURCES, "CMS_Test_Pattern.exr"),
additional_data=False,
Expand Down Expand Up @@ -362,9 +356,6 @@ def teardown_method(self) -> None:
def test_write_image_OpenImageIO(self) -> None: # pragma: no cover
"""Test :func:`colour.io.image.write_image_OpenImageIO` definition."""

if not is_openimageio_installed():
return

from OpenImageIO import TypeDesc # pyright: ignore

path = os.path.join(self._temporary_directory, "8-bit.png")
Expand All @@ -380,27 +371,30 @@ def test_write_image_OpenImageIO(self) -> None: # pragma: no cover
np.testing.assert_equal(np.squeeze(RGB), image)

source_path = os.path.join(ROOT_RESOURCES, "Overflowing_Gradient.png")
source_image = read_image_OpenImageIO(source_path, bit_depth="uint8")
target_path = os.path.join(
self._temporary_directory, "Overflowing_Gradient.png"
)
RGB = np.arange(0, 256, 1, dtype=np.uint8)[None] * 2
write_image_OpenImageIO(RGB, target_path, bit_depth="uint8")
image = read_image_OpenImageIO(source_path, bit_depth="uint8")
np.testing.assert_equal(np.squeeze(RGB), image)
target_image = read_image_OpenImageIO(source_path, bit_depth="uint8")
np.testing.assert_equal(source_image, target_image)
np.testing.assert_equal(np.squeeze(RGB), target_image)

source_path = os.path.join(ROOT_RESOURCES, "CMS_Test_Pattern.exr")
target_path = os.path.join(self._temporary_directory, "CMS_Test_Pattern.exr")
image = read_image_OpenImageIO(
source_image = read_image_OpenImageIO(
source_path,
additional_data=False,
)
write_image_OpenImageIO(image, target_path)
image = read_image_OpenImageIO(
target_path = os.path.join(self._temporary_directory, "CMS_Test_Pattern.exr")
write_image_OpenImageIO(source_image, target_path)
target_image = read_image_OpenImageIO(
target_path,
additional_data=False,
)
assert image.shape == (1267, 1274, 3)
assert image.dtype is np.dtype("float32")
np.testing.assert_equal(source_image, target_image)
assert target_image.shape == (1267, 1274, 3)
assert target_image.dtype is np.dtype("float32")

chromaticities = (
0.73470,
Expand All @@ -419,8 +413,8 @@ def test_write_image_OpenImageIO(self) -> None: # pragma: no cover
),
Image_Specification_Attribute("compression", "none"),
]
write_image_OpenImageIO(image, target_path, attributes=write_attributes)
image, read_attributes = read_image_OpenImageIO(
write_image_OpenImageIO(target_image, target_path, attributes=write_attributes)
target_image, read_attributes = read_image_OpenImageIO(
target_path, additional_data=True
)
for write_attribute in write_attributes:
Expand Down Expand Up @@ -517,35 +511,41 @@ def test_write_image_Imageio(self) -> None:
"""Test :func:`colour.io.image.write_image_Imageio` definition."""

source_path = os.path.join(ROOT_RESOURCES, "Overflowing_Gradient.png")
source_image = read_image_Imageio(source_path, bit_depth="uint8")
target_path = os.path.join(
self._temporary_directory, "Overflowing_Gradient.png"
)
RGB = np.arange(0, 256, 1, dtype=np.uint8)[None] * 2
write_image_Imageio(RGB, target_path, bit_depth="uint8")
image = read_image_Imageio(source_path, bit_depth="uint8")
np.testing.assert_equal(np.squeeze(RGB), image)
target_image = read_image_Imageio(target_path, bit_depth="uint8")
np.testing.assert_equal(np.squeeze(RGB), target_image)
np.testing.assert_equal(source_image, target_image)

source_path = os.path.join(ROOT_RESOURCES, "CMS_Test_Pattern.exr")
target_path = os.path.join(self._temporary_directory, "CMS_Test_Pattern.exr")
image = read_image_Imageio(source_path)
write_image_Imageio(image, target_path)
image = read_image_Imageio(target_path)
assert image.shape == (1267, 1274, 3)
assert image.dtype is np.dtype("float32")

# NOTE: Those unit tests are breaking unpredictably on Linux, skipping
# for now.
# NOTE: Those unit tests are breaking on Linux, skipping for now.
if platform.system() != "Linux": # pragma: no cover
source_path = os.path.join(ROOT_RESOURCES, "CMS_Test_Pattern.exr")
source_image = read_image_Imageio(source_path)
target_path = os.path.join(
self._temporary_directory, "CMS_Test_Pattern.exr"
)
write_image_Imageio(source_image, target_path)
target_image = read_image_Imageio(target_path)
np.testing.assert_allclose(
source_image, target_image, atol=TOLERANCE_ABSOLUTE_TESTS
)
assert target_image.shape == (1267, 1274, 3)
assert target_image.dtype is np.dtype("float32")

target_path = os.path.join(self._temporary_directory, "Full_White.exr")
image = full((32, 16, 3), 1e6, dtype=np.float16)
write_image_Imageio(image, target_path)
image = read_image_Imageio(target_path)
assert np.max(image) == np.inf
target_image = full((32, 16, 3), 1e6, dtype=np.float16)
write_image_Imageio(target_image, target_path)
target_image = read_image_Imageio(target_path)
assert np.max(target_image) == np.inf

image = full((32, 16, 3), 1e6)
write_image_Imageio(image, target_path)
image = read_image_Imageio(target_path)
assert np.max(image) == 1e6
target_image = full((32, 16, 3), 1e6)
write_image_Imageio(target_image, target_path)
target_image = read_image_Imageio(target_path)
assert np.max(target_image) == 1e6


class TestReadImage:
Expand Down Expand Up @@ -582,12 +582,15 @@ def test_write_image(self) -> None:
"""Test :func:`colour.io.image.write_image` definition."""

source_path = os.path.join(ROOT_RESOURCES, "CMS_Test_Pattern.exr")
source_image = read_image(source_path)
target_path = os.path.join(self._temporary_directory, "CMS_Test_Pattern.exr")
image = read_image(source_path)
write_image(image, target_path)
image = read_image(target_path)
assert image.shape == (1267, 1274, 3)
assert image.dtype is np.dtype("float32")
write_image(source_image, target_path)
target_image = read_image(target_path)
np.testing.assert_allclose(
source_image, target_image, atol=TOLERANCE_ABSOLUTE_TESTS
)
assert target_image.shape == (1267, 1274, 3)
assert target_image.dtype is np.dtype("float32")


class TestAs3ChannelsImage:
Expand Down
6 changes: 4 additions & 2 deletions colour/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@
)
from .requirements import (
is_ctlrender_installed,
is_imageio_installed,
is_openimageio_installed,
is_matplotlib_installed,
is_networkx_installed,
is_opencolorio_installed,
is_openimageio_installed,
is_pandas_installed,
is_pydot_installed,
is_tqdm_installed,
Expand Down Expand Up @@ -181,10 +182,11 @@
]
__all__ += [
"is_ctlrender_installed",
"is_imageio_installed",
"is_openimageio_installed",
"is_matplotlib_installed",
"is_networkx_installed",
"is_opencolorio_installed",
"is_openimageio_installed",
"is_pandas_installed",
"is_pydot_installed",
"is_tqdm_installed",
Expand Down
Loading
Loading