Skip to content

Commit f29bea9

Browse files
Replace the apply method with the CLFProcessList which implements the LUTSequence base class and exposes a better API to apply the CLF transformation.
1 parent d0eee81 commit f29bea9

File tree

5 files changed

+104
-53
lines changed

5 files changed

+104
-53
lines changed

colour_clf_io/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@
103103
__change_version__ = "1"
104104
__version__ = f"{__major_version__}.{__minor_version__}.{__change_version__}"
105105

106+
try:
107+
from colour_clf_io.processing import CLFProcessList
108+
109+
__all__ += ["CLFProcessList"]
110+
except ImportError:
111+
pass
112+
106113

107114
def read_clf_from_file(path: str | Path) -> ProcessList:
108115
"""

colour_clf_io/processing.py

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
Define functionality to execute and run CLF workflows.
33
"""
44

5-
try:
6-
import colour
7-
except ImportError:
8-
raise ImportError(
5+
import importlib.util
6+
7+
if importlib.util.find_spec("colour") is None:
8+
err_msg = (
99
"Optional dependency 'colour' not found. Please install the optional "
1010
"for `processing` before accessing the processing module."
1111
)
12+
raise ImportError(err_msg)
1213

1314
from abc import abstractmethod
1415
from collections.abc import Callable
@@ -32,7 +33,7 @@
3233

3334
import colour_clf_io as clf
3435

35-
__all__ = ["apply"]
36+
__all__ = ["CLFProcessList"]
3637

3738
import numpy.typing as npt
3839
from colour.models.rgb.transfer_functions import (
@@ -833,48 +834,63 @@ def as_LUT_sequence_item( # noqa: PLR0911
833834
raise RuntimeError(message)
834835

835836

836-
def apply(
837-
process_list: clf.ProcessList,
838-
value: NDArrayFloat,
839-
normalised_values: bool = False,
840-
) -> NDArrayFloat:
837+
class CLFProcessList(LUTSequence):
838+
"""
839+
Defines a *LUT* sequence created from a `colour_clf_io.ProcessList`. Creates the
840+
nodes needed to execute the transformation described in the *Process List*.
841841
"""
842-
Apply the transformation described by the given :class:`colour_clf_io.ProcessList`
843-
to the given value.
844842

845-
Parameters
846-
----------
847-
process_list
848-
The :class:`colour_clf_io.ProcessList` instance to apply.
849-
value
850-
Input value in the form of an array. Shape and data format need to be
851-
compatible with the given `process_list`.
843+
def __init__(
844+
self,
845+
process_list: clf.ProcessList,
846+
) -> None:
847+
self.process_list = process_list
848+
lut_sequence_items = map(as_LUT_sequence_item, process_list.process_nodes)
849+
sequence = LUTSequence(*lut_sequence_items)
850+
super().__init__(*sequence)
851+
852+
def apply(self, RGB: ArrayLike, **kwargs: Any) -> NDArrayFloat:
853+
"""
854+
Apply the *LUT* sequence sequentially to given *RGB* colourspace
855+
array.
856+
857+
Parameters
858+
----------
859+
RGB
860+
*RGB* colourspace array to apply the *LUT* sequence sequentially
861+
onto.
862+
863+
Other Parameters
864+
----------------
852865
normalised_values
853-
Indicates whether the input values are normalised to the range 0..1. If
854-
this is the case, the range will be expanded to the input range expected
855-
by the given `process_list`.
866+
Argument extracted from the kwargs. Used to indicate that the
867+
values passed to the apply method are already normalised.
868+
kwargs
869+
Keywords arguments, the keys must be the class type names for which
870+
they are intended to be used with. There is no implemented way to
871+
discriminate which class instance the keyword arguments should be
872+
used with, thus if many class instances of the same type are
873+
members of the sequence, any matching keyword arguments will be
874+
used with all the class instances.
856875
857-
Returns
858-
-------
859-
:class:`NDArrayFloat`
860-
Result of applying the given :class:`colour_clf_io.ProcessList`.
876+
Returns
877+
-------
878+
:class:`numpy.ndarray`
879+
Processed *RGB* colourspace array.
861880
862-
Raises
863-
------
864-
:class:`CLFExecutionError`
865-
If the given *process_list* is invalid according to the CLF specification
866-
(e.g., missing or invalid parameters).
867-
"""
868-
if not normalised_values:
869-
value = value / process_list.process_nodes[0].in_bit_depth.scale_factor()
881+
"""
882+
RGB = as_float_array(RGB)
870883

871-
lut_sequence_items = [
872-
as_LUT_sequence_item(node) for node in process_list.process_nodes
873-
]
874-
sequence = LUTSequence(*lut_sequence_items)
875-
result = sequence.apply(value)
884+
normalised_values = kwargs.get("normalised_values", False)
885+
if not normalised_values:
886+
RGB = RGB / self.process_list.process_nodes[0].in_bit_depth.scale_factor()
876887

877-
if not normalised_values:
878-
result = result * process_list.process_nodes[-1].out_bit_depth.scale_factor()
888+
result = super().apply(RGB, **kwargs)
889+
890+
if not normalised_values:
891+
result = (
892+
result
893+
* self.process_list.process_nodes[-1].out_bit_depth.scale_factor()
894+
)
879895

880-
return result
896+
return result

colour_clf_io/tests/processing/test_common.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import numpy as np
1111

1212
import colour_clf_io as clf
13-
from colour_clf_io.processing import apply
13+
from colour_clf_io.processing import CLFProcessList
1414

1515
__all__ = [
1616
"assert_ocio_consistency",
@@ -19,6 +19,7 @@
1919
]
2020

2121
from colour.hints import NDArrayFloat
22+
from colour.io.luts import AbstractLUTSequenceOperator
2223

2324
EXAMPLE_WRAPPER = """<?xml version="1.0" ?>
2425
<ProcessList id="Example Wrapper" compCLFversion="3.0">
@@ -66,10 +67,10 @@ def ocio_output_for_file(
6667
"""Apply a color transform file to a flattened, one-dimensional list of
6768
R,G,B values.
6869
"""
69-
from PyOpenColorIO import FileTransform, GetCurrentConfig
70+
import PyOpenColorIO as ocio
7071

71-
xform = FileTransform(src=path)
72-
cpu = GetCurrentConfig().getProcessor(xform).getDefaultCPUProcessor()
72+
xform = ocio.FileTransform(src=path)
73+
cpu = ocio.GetCurrentConfig().getProcessor(xform).getDefaultCPUProcessor()
7374
result = cpu.applyRGB(rgb)
7475
# Note: depending on the input, `applyRGB` will either return the result data, or
7576
# modify the data in place. If the return value was `None` the data was modified
@@ -100,6 +101,28 @@ def result_as_array(result_text: str) -> NDArrayFloat:
100101
return np.array(result_values)
101102

102103

104+
class SimpleRange(AbstractLUTSequenceOperator):
105+
def __init__(
106+
self, in_range: tuple[float, float], out_range: tuple[float, float]
107+
) -> None:
108+
self.in_range = in_range
109+
self.out_range = out_range
110+
111+
def apply(self, x: NDArrayFloat) -> NDArrayFloat:
112+
normalised = (x - self.in_range[0]) / (self.in_range[1] - self.in_range[0])
113+
return normalised * (self.out_range[1] - self.out_range[0]) + self.out_range[0]
114+
115+
116+
class Normaliser(SimpleRange):
117+
def __init__(self, source_range: tuple[float, float]) -> None:
118+
super().__init__(source_range, (0.0, 1.0))
119+
120+
121+
class Denormaliser(SimpleRange):
122+
def __init__(self, target_range: tuple[float, float]) -> None:
123+
super().__init__((0.0, 1.0), target_range)
124+
125+
103126
def assert_ocio_consistency(
104127
value: NDArrayFloat, snippet: str, err_msg: str = "", decimals: int = 5
105128
) -> None:
@@ -110,7 +133,9 @@ def assert_ocio_consistency(
110133
if process_list is None:
111134
err = "Invalid CLF snippet."
112135
raise AssertionError(err)
113-
process_list_output = apply(process_list, value, normalised_values=True)
136+
lut_sequence = CLFProcessList(process_list)
137+
# apply_input_normalisation(process_list, lut_sequence)
138+
process_list_output = lut_sequence.apply(value, normalised_values=True)
114139
value_tuple = value[0], value[1], value[2]
115140
ocio_output = ocio_output_for_snippet(snippet, value_tuple)
116141
# Note: OCIO only accepts 16-bit floats so the precision agreement is limited.
@@ -119,17 +144,19 @@ def assert_ocio_consistency(
119144
)
120145

121146

122-
def assert_ocio_consistency_for_file(value_rgb: NDArrayFloat, clf_path: str) -> None:
147+
def assert_ocio_consistency_for_file(value: NDArrayFloat, clf_path: str) -> None:
123148
"""Assert that the colour library calculates the same output as the OCIO reference
124149
implementation for the given file.
125150
"""
126151

127-
clf_data = clf.read_clf_from_file(clf_path)
128-
if clf_data is None:
152+
process_list = clf.read_clf_from_file(clf_path)
153+
if process_list is None:
129154
err = "Invalid CLF snippet."
130155
raise AssertionError(err)
131-
process_list_output = apply(clf_data, value_rgb, normalised_values=True)
132-
ocio_output = ocio_output_for_file(clf_path, value_rgb)
156+
lut_sequence = CLFProcessList(process_list)
157+
# apply_input_normalisation(process_list, lut_sequence)
158+
process_list_output = lut_sequence.apply(value, normalised_values=True)
159+
ocio_output = ocio_output_for_file(clf_path, value)
133160
np.testing.assert_array_almost_equal(process_list_output, ocio_output)
134161

135162

colour_clf_io/tests/processing/test_helper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
__status__ = "Production"
1212

1313
import numpy as np
14+
1415
from colour_clf_io.processing import from_f16_to_uint16, from_uint16_to_f16
1516

1617

colour_clf_io/tests/processing/test_lut1d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
import numpy as np
1515
import pytest
16-
from colour_clf_io.processing import from_f16_to_uint16, from_uint16_to_f16
1716

17+
from colour_clf_io.processing import from_f16_to_uint16, from_uint16_to_f16
1818
from colour_clf_io.tests.processing.test_common import (
1919
RESOURCES_ROOT,
2020
assert_ocio_consistency,

0 commit comments

Comments
 (0)