Coverage for io/luts/tests/test_sequence.py: 100%
88 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""Define the unit tests for the :mod:`colour.io.luts.sequence` module."""
3from __future__ import annotations
5import textwrap
6import typing
8import numpy as np
10from colour.constants import TOLERANCE_ABSOLUTE_TESTS
12if typing.TYPE_CHECKING:
13 from colour.hints import Any, ArrayLike, NDArrayFloat
15from colour.io.luts import (
16 LUT1D,
17 LUT3D,
18 AbstractLUTSequenceOperator,
19 LUT3x1D,
20 LUTSequence,
21)
22from colour.models import gamma_function
23from colour.utilities import as_float_array, tstack
25__author__ = "Colour Developers"
26__copyright__ = "Copyright 2013 Colour Developers"
27__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
28__maintainer__ = "Colour Developers"
29__email__ = "colour-developers@colour-science.org"
30__status__ = "Production"
32__all__ = [
33 "TestLUTSequence",
34]
37class TestLUTSequence:
38 """
39 Define :class:`colour.io.luts.sequence.LUTSequence` class unit tests
40 methods.
41 """
43 def setup_method(self) -> None:
44 """Initialise the common tests attributes."""
46 self._LUT_1 = LUT1D(LUT1D.linear_table(16) + 0.125, "Nemo 1D")
47 self._LUT_2 = LUT3D(LUT3D.linear_table(16) ** (1 / 2.2), "Nemo 3D")
48 self._LUT_3 = LUT3x1D(LUT3x1D.linear_table(16) * 0.750, "Nemo 3x1D")
49 self._LUT_sequence = LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3)
51 samples = np.linspace(0, 1, 5)
53 self._RGB = tstack([samples, samples, samples])
55 def test_required_attributes(self) -> None:
56 """Test the presence of required attributes."""
58 required_attributes = ("sequence",)
60 for attribute in required_attributes:
61 assert attribute in dir(LUTSequence)
63 def test_required_methods(self) -> None:
64 """Test the presence of required methods."""
66 required_methods = (
67 "__init__",
68 "__getitem__",
69 "__setitem__",
70 "__delitem__",
71 "__len__",
72 "__str__",
73 "__repr__",
74 "__eq__",
75 "__ne__",
76 "insert",
77 "apply",
78 "copy",
79 )
81 for method in required_methods:
82 assert method in dir(LUTSequence)
84 def test_sequence(self) -> None:
85 """Test :class:`colour.io.luts.sequence.LUTSequence.sequence` property."""
87 sequence = [self._LUT_1, self._LUT_2, self._LUT_3]
88 LUT_sequence = LUTSequence()
89 LUT_sequence.sequence = sequence
90 assert self._LUT_sequence.sequence == sequence
92 def test__init__(self) -> None:
93 """Test :class:`colour.io.luts.sequence.LUTSequence.__init__` method."""
95 assert LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3) == self._LUT_sequence
97 def test__getitem__(self) -> None:
98 """Test :class:`colour.io.luts.sequence.LUTSequence.__getitem__` method."""
100 assert self._LUT_sequence[0] == self._LUT_1
101 assert self._LUT_sequence[1] == self._LUT_2
102 assert self._LUT_sequence[2] == self._LUT_3
104 def test__setitem__(self) -> None:
105 """Test :class:`colour.io.luts.sequence.LUTSequence.__setitem__` method."""
107 LUT_sequence = self._LUT_sequence.copy()
108 LUT_sequence[0] = self._LUT_3
109 LUT_sequence[1] = self._LUT_1
110 LUT_sequence[2] = self._LUT_2
112 assert LUT_sequence[1] == self._LUT_1
113 assert LUT_sequence[2] == self._LUT_2
114 assert LUT_sequence[0] == self._LUT_3
116 def test__delitem__(self) -> None:
117 """Test :class:`colour.io.luts.sequence.LUTSequence.__delitem__` method."""
119 LUT_sequence = self._LUT_sequence.copy()
121 del LUT_sequence[0]
122 del LUT_sequence[0]
124 assert LUT_sequence[0] == self._LUT_3
126 def test__len__(self) -> None:
127 """Test :class:`colour.io.luts.sequence.LUTSequence.__len__` method."""
129 assert len(self._LUT_sequence) == 3
131 def test__str__(self) -> None:
132 """Test :class:`colour.io.luts.sequence.LUTSequence.__str__` method."""
134 assert str(self._LUT_sequence) == (
135 textwrap.dedent(
136 """
137 LUT Sequence
138 ------------
140 Overview
142 LUT1D --> LUT3D --> LUT3x1D
144 Operations
146 LUT1D - Nemo 1D
147 ---------------
149 Dimensions : 1
150 Domain : [ 0. 1.]
151 Size : (16,)
153 LUT3D - Nemo 3D
154 ---------------
156 Dimensions : 3
157 Domain : [[ 0. 0. 0.]
158 [ 1. 1. 1.]]
159 Size : (16, 16, 16, 3)
161 LUT3x1D - Nemo 3x1D
162 -------------------
164 Dimensions : 2
165 Domain : [[ 0. 0. 0.]
166 [ 1. 1. 1.]]
167 Size : (16, 3)
168 """
169 ).strip()
170 )
172 def test__repr__(self) -> None:
173 """Test :class:`colour.io.luts.sequence.LUTSequence.__repr__` method."""
175 LUT_sequence = self._LUT_sequence.copy()
176 LUT_sequence[1].table = LUT3D.linear_table(5)
178 assert repr(LUT_sequence) == (
179 textwrap.dedent(
180 """
181LUTSequence(
182 LUT1D([ 0.125 , 0.19166667, 0.25833333, 0.325 , 0.39166667,
183 0.45833333, 0.525 , 0.59166667, 0.65833333, 0.725 ,
184 0.79166667, 0.85833333, 0.925 , 0.99166667, 1.05833333,
185 1.125 ],
186 'Nemo 1D',
187 [ 0., 1.],
188 16),
189 LUT3D([[[[ 0. , 0. , 0. ],
190 [ 0. , 0. , 0.25],
191 [ 0. , 0. , 0.5 ],
192 [ 0. , 0. , 0.75],
193 [ 0. , 0. , 1. ]],
195 [[ 0. , 0.25, 0. ],
196 [ 0. , 0.25, 0.25],
197 [ 0. , 0.25, 0.5 ],
198 [ 0. , 0.25, 0.75],
199 [ 0. , 0.25, 1. ]],
201 [[ 0. , 0.5 , 0. ],
202 [ 0. , 0.5 , 0.25],
203 [ 0. , 0.5 , 0.5 ],
204 [ 0. , 0.5 , 0.75],
205 [ 0. , 0.5 , 1. ]],
207 [[ 0. , 0.75, 0. ],
208 [ 0. , 0.75, 0.25],
209 [ 0. , 0.75, 0.5 ],
210 [ 0. , 0.75, 0.75],
211 [ 0. , 0.75, 1. ]],
213 [[ 0. , 1. , 0. ],
214 [ 0. , 1. , 0.25],
215 [ 0. , 1. , 0.5 ],
216 [ 0. , 1. , 0.75],
217 [ 0. , 1. , 1. ]]],
219 [[[ 0.25, 0. , 0. ],
220 [ 0.25, 0. , 0.25],
221 [ 0.25, 0. , 0.5 ],
222 [ 0.25, 0. , 0.75],
223 [ 0.25, 0. , 1. ]],
225 [[ 0.25, 0.25, 0. ],
226 [ 0.25, 0.25, 0.25],
227 [ 0.25, 0.25, 0.5 ],
228 [ 0.25, 0.25, 0.75],
229 [ 0.25, 0.25, 1. ]],
231 [[ 0.25, 0.5 , 0. ],
232 [ 0.25, 0.5 , 0.25],
233 [ 0.25, 0.5 , 0.5 ],
234 [ 0.25, 0.5 , 0.75],
235 [ 0.25, 0.5 , 1. ]],
237 [[ 0.25, 0.75, 0. ],
238 [ 0.25, 0.75, 0.25],
239 [ 0.25, 0.75, 0.5 ],
240 [ 0.25, 0.75, 0.75],
241 [ 0.25, 0.75, 1. ]],
243 [[ 0.25, 1. , 0. ],
244 [ 0.25, 1. , 0.25],
245 [ 0.25, 1. , 0.5 ],
246 [ 0.25, 1. , 0.75],
247 [ 0.25, 1. , 1. ]]],
249 [[[ 0.5 , 0. , 0. ],
250 [ 0.5 , 0. , 0.25],
251 [ 0.5 , 0. , 0.5 ],
252 [ 0.5 , 0. , 0.75],
253 [ 0.5 , 0. , 1. ]],
255 [[ 0.5 , 0.25, 0. ],
256 [ 0.5 , 0.25, 0.25],
257 [ 0.5 , 0.25, 0.5 ],
258 [ 0.5 , 0.25, 0.75],
259 [ 0.5 , 0.25, 1. ]],
261 [[ 0.5 , 0.5 , 0. ],
262 [ 0.5 , 0.5 , 0.25],
263 [ 0.5 , 0.5 , 0.5 ],
264 [ 0.5 , 0.5 , 0.75],
265 [ 0.5 , 0.5 , 1. ]],
267 [[ 0.5 , 0.75, 0. ],
268 [ 0.5 , 0.75, 0.25],
269 [ 0.5 , 0.75, 0.5 ],
270 [ 0.5 , 0.75, 0.75],
271 [ 0.5 , 0.75, 1. ]],
273 [[ 0.5 , 1. , 0. ],
274 [ 0.5 , 1. , 0.25],
275 [ 0.5 , 1. , 0.5 ],
276 [ 0.5 , 1. , 0.75],
277 [ 0.5 , 1. , 1. ]]],
279 [[[ 0.75, 0. , 0. ],
280 [ 0.75, 0. , 0.25],
281 [ 0.75, 0. , 0.5 ],
282 [ 0.75, 0. , 0.75],
283 [ 0.75, 0. , 1. ]],
285 [[ 0.75, 0.25, 0. ],
286 [ 0.75, 0.25, 0.25],
287 [ 0.75, 0.25, 0.5 ],
288 [ 0.75, 0.25, 0.75],
289 [ 0.75, 0.25, 1. ]],
291 [[ 0.75, 0.5 , 0. ],
292 [ 0.75, 0.5 , 0.25],
293 [ 0.75, 0.5 , 0.5 ],
294 [ 0.75, 0.5 , 0.75],
295 [ 0.75, 0.5 , 1. ]],
297 [[ 0.75, 0.75, 0. ],
298 [ 0.75, 0.75, 0.25],
299 [ 0.75, 0.75, 0.5 ],
300 [ 0.75, 0.75, 0.75],
301 [ 0.75, 0.75, 1. ]],
303 [[ 0.75, 1. , 0. ],
304 [ 0.75, 1. , 0.25],
305 [ 0.75, 1. , 0.5 ],
306 [ 0.75, 1. , 0.75],
307 [ 0.75, 1. , 1. ]]],
309 [[[ 1. , 0. , 0. ],
310 [ 1. , 0. , 0.25],
311 [ 1. , 0. , 0.5 ],
312 [ 1. , 0. , 0.75],
313 [ 1. , 0. , 1. ]],
315 [[ 1. , 0.25, 0. ],
316 [ 1. , 0.25, 0.25],
317 [ 1. , 0.25, 0.5 ],
318 [ 1. , 0.25, 0.75],
319 [ 1. , 0.25, 1. ]],
321 [[ 1. , 0.5 , 0. ],
322 [ 1. , 0.5 , 0.25],
323 [ 1. , 0.5 , 0.5 ],
324 [ 1. , 0.5 , 0.75],
325 [ 1. , 0.5 , 1. ]],
327 [[ 1. , 0.75, 0. ],
328 [ 1. , 0.75, 0.25],
329 [ 1. , 0.75, 0.5 ],
330 [ 1. , 0.75, 0.75],
331 [ 1. , 0.75, 1. ]],
333 [[ 1. , 1. , 0. ],
334 [ 1. , 1. , 0.25],
335 [ 1. , 1. , 0.5 ],
336 [ 1. , 1. , 0.75],
337 [ 1. , 1. , 1. ]]]],
338 'Nemo 3D',
339 [[ 0., 0., 0.],
340 [ 1., 1., 1.]],
341 5),
342 LUT3x1D([[ 0. , 0. , 0. ],
343 [ 0.05, 0.05, 0.05],
344 [ 0.1 , 0.1 , 0.1 ],
345 [ 0.15, 0.15, 0.15],
346 [ 0.2 , 0.2 , 0.2 ],
347 [ 0.25, 0.25, 0.25],
348 [ 0.3 , 0.3 , 0.3 ],
349 [ 0.35, 0.35, 0.35],
350 [ 0.4 , 0.4 , 0.4 ],
351 [ 0.45, 0.45, 0.45],
352 [ 0.5 , 0.5 , 0.5 ],
353 [ 0.55, 0.55, 0.55],
354 [ 0.6 , 0.6 , 0.6 ],
355 [ 0.65, 0.65, 0.65],
356 [ 0.7 , 0.7 , 0.7 ],
357 [ 0.75, 0.75, 0.75]],
358 'Nemo 3x1D',
359 [[ 0., 0., 0.],
360 [ 1., 1., 1.]],
361 16)
362)
363""".strip()
364 )
365 )
367 def test__eq__(self) -> None:
368 """Test :class:`colour.io.luts.sequence.LUTSequence.__eq__` method."""
370 LUT_sequence_1 = LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3)
371 LUT_sequence_2 = LUTSequence(self._LUT_1, self._LUT_2)
373 assert self._LUT_sequence == LUT_sequence_1
375 assert self._LUT_sequence != self._LUT_sequence[0]
377 assert LUT_sequence_1 != LUT_sequence_2
379 def test__neq__(self) -> None:
380 """Test :class:`colour.io.luts.sequence.LUTSequence.__neq__` method."""
382 assert self._LUT_sequence != LUTSequence(
383 self._LUT_1, self._LUT_2.copy() * 0.75, self._LUT_3
384 )
386 def test_insert(self) -> None:
387 """Test :class:`colour.io.luts.sequence.LUTSequence.insert` method."""
389 LUT_sequence = self._LUT_sequence.copy()
391 LUT_sequence.insert(1, self._LUT_2.copy())
393 assert LUT_sequence == LUTSequence(
394 self._LUT_1,
395 self._LUT_2,
396 self._LUT_2,
397 self._LUT_3,
398 )
400 def test_apply(self) -> None:
401 """Test :class:`colour.io.luts.sequence.LUTSequence.apply` method."""
403 class GammaOperator(AbstractLUTSequenceOperator):
404 """
405 Gamma operator for unit tests.
407 Parameters
408 ----------
409 gamma
410 Gamma value.
411 """
413 def __init__(self, gamma: ArrayLike = 1) -> None:
414 self._gamma = as_float_array(gamma)
416 def apply(
417 self,
418 RGB: ArrayLike,
419 *args: Any, # noqa: ARG002
420 **kwargs: Any,
421 ) -> NDArrayFloat:
422 """
423 Apply the *LUT* sequence operator to the specified *RGB* colourspace
424 array.
426 Parameters
427 ----------
428 RGB
429 *RGB* colourspace array to apply the *LUT* sequence
430 operator onto.
432 Returns
433 -------
434 :class:`numpy.ndarray`
435 Processed *RGB* colourspace array.
436 """
438 direction = kwargs.get("direction", "Forward")
440 gamma = self._gamma if direction == "Forward" else 1.0 / self._gamma
442 return as_float_array(gamma_function(RGB, gamma))
444 LUT_sequence = self._LUT_sequence.copy()
445 LUT_sequence.insert(1, GammaOperator(1 / 2.2))
446 samples = np.linspace(0, 1, 5)
447 RGB = tstack([samples, samples, samples])
449 np.testing.assert_allclose(
450 LUT_sequence.apply(RGB, GammaOperator={"direction": "Inverse"}),
451 np.array(
452 [
453 [0.03386629, 0.03386629, 0.03386629],
454 [0.27852298, 0.27852298, 0.27852298],
455 [0.46830881, 0.46830881, 0.46830881],
456 [0.65615595, 0.65615595, 0.65615595],
457 [0.75000000, 0.75000000, 0.75000000],
458 ]
459 ),
460 atol=TOLERANCE_ABSOLUTE_TESTS,
461 )