From f909476d791751fc15610122b9e12fc5a24e6c8a Mon Sep 17 00:00:00 2001 From: tjdcs Date: Fri, 21 Jun 2024 10:36:10 +0800 Subject: [PATCH] Add rgb_colorspace equivalence and tests --- colour/models/rgb/rgb_colourspace.py | 58 +++++++++++++++ .../models/rgb/tests/test_rgb_colourspace.py | 74 +++++++++++++++++++ colour/models/rgb/transfer_functions/gamma.py | 20 +++++ .../transfer_functions/tests/test_gamma.py | 10 +++ 4 files changed, 162 insertions(+) diff --git a/colour/models/rgb/rgb_colourspace.py b/colour/models/rgb/rgb_colourspace.py index 56756ed29..1137196c7 100644 --- a/colour/models/rgb/rgb_colourspace.py +++ b/colour/models/rgb/rgb_colourspace.py @@ -26,6 +26,7 @@ from __future__ import annotations from copy import deepcopy +from functools import partial import numpy as np @@ -277,6 +278,63 @@ def __init__( self._use_derived_matrix_XYZ_to_RGB: bool = False self.use_derived_matrix_XYZ_to_RGB = use_derived_matrix_XYZ_to_RGB + def __eq__(self, other: object) -> bool: + """Return weather or not two RGB spaces are equivalent and would produce + the same results with XYZ_to_RGB and visa-versa. Can detect and compare + instances of `partial` for the cctf properties. + + Parameters + ---------- + other : object + + Returns + ------- + bool + """ + if not isinstance(other, RGB_Colourspace): + return False + + cctf_decoding_eq: bool + if isinstance(self.cctf_decoding, partial) and isinstance( + other.cctf_decoding, partial + ): + cctf_decoding_eq = np.all( + ( + self.cctf_decoding.func == other.cctf_decoding.func, + np.all(self.cctf_decoding.args == other.cctf_decoding.args), + np.all(self.cctf_decoding.keywords == other.cctf_decoding.keywords), + ) + ) + else: + cctf_decoding_eq = self.cctf_decoding == other.cctf_decoding + + cctf_encoding_eq: bool + if isinstance(self.cctf_encoding, partial) and isinstance( + other.cctf_encoding, partial + ): + cctf_encoding_eq = np.all( + ( + self.cctf_encoding.func == other.cctf_encoding.func, + np.all(self.cctf_encoding.args == other.cctf_encoding.args), + np.all(self.cctf_encoding.keywords == other.cctf_encoding.keywords), + ) + ) + else: + cctf_encoding_eq = self.cctf_encoding == other.cctf_encoding + + return np.all( + ( + self.name == other.name, + np.all(self.primaries == other.primaries), + np.all(self.whitepoint == self.whitepoint), + self.whitepoint_name == self.whitepoint_name, + np.all(self.matrix_RGB_to_XYZ == other.matrix_RGB_to_XYZ), + np.all(self.matrix_XYZ_to_RGB == other.matrix_XYZ_to_RGB), + cctf_decoding_eq, + cctf_encoding_eq, + ) + ) + @property def name(self) -> str: """ diff --git a/colour/models/rgb/tests/test_rgb_colourspace.py b/colour/models/rgb/tests/test_rgb_colourspace.py index d0cfc1d09..c689b3859 100644 --- a/colour/models/rgb/tests/test_rgb_colourspace.py +++ b/colour/models/rgb/tests/test_rgb_colourspace.py @@ -4,6 +4,7 @@ import re import textwrap +from functools import partial from itertools import product import numpy as np @@ -24,6 +25,7 @@ matrix_RGB_to_RGB, normalised_primary_matrix, ) +from colour.models.rgb.transfer_functions.gamma import gamma_function from colour.utilities import domain_range_scale, ignore_numpy_errors __author__ = "Colour Developers" @@ -66,6 +68,77 @@ def setup_method(self): linear_function, ) + class TestRGBSpace__eq__: + def setup_method(self): + pass + # Some pytest possible issue requires this since the encapsulating + # class has a setup_method. + + @staticmethod + def get_two_rgb_spaces(): + p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) + whitepoint = np.array([0.32168, 0.33767]) + matrix_RGB_to_XYZ = np.identity(3) + matrix_XYZ_to_RGB = np.identity(3) + + s1 = RGB_Colourspace( + "RGB Colourspace", + p, + whitepoint, + "ACES", + matrix_RGB_to_XYZ, + matrix_XYZ_to_RGB, + linear_function, + linear_function, + ) + + s2 = RGB_Colourspace( + "RGB Colourspace", + p, + whitepoint, + "ACES", + matrix_RGB_to_XYZ, + matrix_XYZ_to_RGB, + linear_function, + linear_function, + ) + + return s1, s2 + + def test_simple_eq(self): + s1, s2 = self.get_two_rgb_spaces() + + assert s1 is not s2 + assert s1 == s2 + + # Even if one space uses derived, if they return the same matrix + # they are equivalent + s2.use_derived_matrix_RGB_to_XYZ = True + s1.matrix_RGB_to_XYZ = s2.matrix_RGB_to_XYZ + s1.matrix_XYZ_to_RGB = s2.matrix_XYZ_to_RGB + + assert s1 == s2 + + def test_partial_cctf(self): + s1, s2 = self.get_two_rgb_spaces() + + s1.cctf_decoding = partial(gamma_function, exponent=2) + s1.cctf_encoding = partial(gamma_function, exponent=1 / 2) + assert s1 != s2 + + s2.cctf_encoding = partial(gamma_function, exponent=1 / 2) + assert s1 != s2 + + s2.cctf_decoding = partial(gamma_function, exponent=1.5) + assert s1 != s2 + + s2.cctf_decoding = partial(gamma_function, 2) + # Should be equal, but because one partial uses kwargs it is an error + assert s1 != s2 + + s2.cctf_decoding = partial(gamma_function, exponent=2) + assert s1 == s2 + def test_required_attributes(self): """Test the presence of required attributes.""" @@ -92,6 +165,7 @@ def test_required_methods(self): "__init__", "__str__", "__repr__", + "__eq__", "use_derived_transformation_matrices", "chromatically_adapt", "copy", diff --git a/colour/models/rgb/transfer_functions/gamma.py b/colour/models/rgb/transfer_functions/gamma.py index 153ddf632..087a9d4dc 100644 --- a/colour/models/rgb/transfer_functions/gamma.py +++ b/colour/models/rgb/transfer_functions/gamma.py @@ -101,6 +101,26 @@ def __call__(self, a: ArrayLike): negative_number_handling=self.negative_number_handling, ) + def __eq__(self, other: object) -> bool: + """Return if two gamma functions have the same parameters and therefore + produce the same results + + Parameters + ---------- + other : object + + Returns + ------- + bool + """ + if not isinstance(other, GammaFunction): + return False + + return ( + self.exponent == other.exponent + and self.negative_number_handling == other.negative_number_handling + ) + def gamma_function( a: ArrayLike, diff --git a/colour/models/rgb/transfer_functions/tests/test_gamma.py b/colour/models/rgb/transfer_functions/tests/test_gamma.py index 41c4d2245..4d4a92ce9 100644 --- a/colour/models/rgb/transfer_functions/tests/test_gamma.py +++ b/colour/models/rgb/transfer_functions/tests/test_gamma.py @@ -204,6 +204,16 @@ def test_n_dimensional_gamma_function(self): GammaFunction(2.2, "Clamp")(a), a_p, atol=TOLERANCE_ABSOLUTE_TESTS ) + def test__eq__(self): + g1 = GammaFunction(exponent=3, negative_number_handling="Clip") + g2 = GammaFunction(exponent=3, negative_number_handling="Clip") + g3 = GammaFunction(exponent=4, negative_number_handling="Clip") + g4 = GammaFunction(exponent=3, negative_number_handling="Mirror") + + assert g1 == g2 + assert g1 != g3 + assert g1 != g4 + @ignore_numpy_errors def test_nan_gamma_function(self): """